[comp.sources.amiga] MRPrint

ain@j.cc.purdue.edu (Pat-bob White) (10/15/88)

Submitted by:	mrr@amanpt1.zone1.com (Mark Rinfret)
Summary:	A "pr"-like printer utility
Poster Boy:	Rob Tillotson	(akl@j.cc.purdue.edu)
Archive Name:	sources/amiga/volume5/mrprint.d.Z binaries/amiga/volume8/mrprint.d.Z
Tested
 
NOTES:
     This is a nice little printer utility similar to "pr".  It takes a
bunch of files, paginates them, expands tabs, etc., and prints them.  It
also has the ability to skip binary files, so that you can print an entire
directory and have it skip executables and object files.
     MRPrint requires the ARP Library, version 1.1 or later.





 
 
 
========================================
 
#	This is a shell archive.
#	Remove everything above and including the cut line.
#	Then run the rest of the file through sh.
#----cut here-----cut here-----cut here-----cut here----#
#!/bin/sh
# shar:	Shell Archiver
#	Run the following text with /bin/sh to create:
#	MRPrint.DOC
# This archive created: Wed Oct 12 13:29:41 1988
# By:	Rob Tillotson (Bored Students Anonymous)
cat << \SHAR_EOF > MRPrint.DOC

MRPrint - 	A detabbing print utility for the Amiga

Author:		Mark Rinfret

Requires the ARP library, V1.1 or later.

---------------------------------------------------

MRPRint prints Amiga text files to the printer device or standard
output.  MRPrint provides several processing options which afford
the user some flexibility in determining the output format.  Perhaps
the most important feature is tab expansion.  MRPrint expands embedded
tabs to blanks, according to the user's tab spacing specification 
(default=4).  MRPrint also will optionally generate page headers, line
numbers and new margins.  Input lines that are too long for the current
margin settings will be split across multiple lines, maintaining correct
pagination.  A form feed character detected at the beginning of an input 
line will be recognized and cause a page eject.

MRPrint is invoked from the CLI with Un*x style command line parameters.  
Wildcarding of file names is supported, allowing either AmigaDOS or Un*x
style wildcard specifications.  All parameters, including the file name(s)
to be printed are optional.  In the case of formatting options, defaults
are used if no option is explicity given.  All options must precede file 
names on the command line.  To see a list of options, invoke MRPrint with:

		MRPrint -?

Of special note is the fact that MRPrint will automatically detect and
reject files which have binary (non-printable) content.  You can therefore
invoke MRPrint in the following form

	MRPrint *

to print all files in the current directory without having to worry about 
object, program data, or directory files being printed.  


If no file names are passed on the command line, MRPrint will put up a
file name requester.  Multiple files may be printed in this fashion,
terminating MRPrint by pressing the CANCEL gadget on the requester.


The current default values for MRPrint are:

	left margin:		5
	right margin:		85
	tab spaces:			4
	headers:			enabled
	line numbers:		disabled

Of course, having the source (nice rhyme, eh?), you can change these so
that you don't have to constantly type in your favorite options.  One
thing - be sure that your margin settings in preferences don't interfere
with MRPrint's behavior.  It expects that the left margin is set to 1 and
the right margin is at least as wide as what you specify.  I usually
set my preferences to 1, 255. Of course, if you're ambitious, you'll add
the code to get the Preferences settings and adapt.  

There are lots of things that can be done to improve this program. A 
'copies' option would be nice as well as options to support letter quality, 
draft, characters per inch, etc.  The Amiga supports these things in a fairly
nice, generic way if you have the right printer driver. I hope you find this
to be a useful addition to your tool box.  By the way, I love ARP.  It's
a good example of the way things should be done.  My hat is off to Charlie
Heath and his crew for developing such a fine product and making it freely
available to the public.

											Mark Rinfret
											mark@amanpt1.zone1.com
											work: 401-849-9930 x301
											home: 401-846-7639


SHAR_EOF
#	End of shell archive
exit 0

ain@j.cc.purdue.edu (Pat-bob White) (10/15/88)

Submitted by:	mrr@amanpt1.zone1.com (Mark Rinfret)
Summary:	A "pr"-like printer utility
Poster Boy:	Rob Tillotson	(akl@j.cc.purdue.edu)
Archive Name:	sources/amiga/volume5/mrprint.s.Z
Tested
 
NOTES:
     This is a nice little printer utility similar to "pr".  It takes a
bunch of files, paginates them, expands tabs, etc., and prints them.  It
also has the ability to skip binary files, so that you can print an entire
directory and have it skip executables and object files.
     MRPrint requires the ARP Library, version 1.1 or later.





 
 
 
========================================
 
#	This is a shell archive.
#	Remove everything above and including the cut line.
#	Then run the rest of the file through sh.
#----cut here-----cut here-----cut here-----cut here----#
#!/bin/sh
# shar:	Shell Archiver
#	Run the following text with /bin/sh to create:
#	Makefile
#	MRPrint.c
# This archive created: Wed Oct 12 13:30:03 1988
# By:	Rob Tillotson (Bored Students Anonymous)
cat << \SHAR_EOF > Makefile
# Manx V3.6 makefile for MRPrint.

CFLAGS = -n

mrprint: mrprint.o
		ln -g -o MRPrint MRPrint.o -larp -lc

mrprint.uue: MRPrint
	uuencode MRPrint MRPrint >mrprint.uue

shar:	MRPrint.uue
		makekit -n MRPrint.shar MRPrint.DOC MRPrint.c Makefile mrprint.uue

zoo:	MRPrint
		zoo a MRPrint ReadMe MRPrint MRPrint.DOC MRPrint MRPrint.c Makefile \
		sys:libs/arp.library
SHAR_EOF
cat << \SHAR_EOF > MRPrint.c
/* :ts=4 */
/*
   MRPrint:  	detabbing text file printer for the Amiga
   Author:		Mark Rinfret (Usenet: mrr@amanpt1; Bix: markr)

	  I am offering this to the Amiga user community without restrictions.
	  If you make improvements, please re-release with source.	Enjoy!


   This program will print text files containing embedded tabs and
   form feeds.  Though the default tab setting is 4, the user may
   override this to some other value as necessary.  MRPrint will also
   optionally output a page header containing the filename, current
   date and time, line number and page number.  MRPrint supports variable
   margins and will enforce them.  Line numbers will be printed if
   requested.  Note that by default, MRPrint prints to PRT:.  If you wish
   to redirect output, be sure to use the "-s" option.

	  Usage:  pr [-l] [-n#] [-t#] [-h] [file1] file2] ...
	  options:
			-h		do not print a page header
			-l		print with line numbers
			-L# 	set left margin to #
			-n# 	print # lines per page 
			-R# 	set right margin to #
			-s		print to standard output
			-t# 	set tab to # spaces (default 4)

	  Handles ARP wildcarding.

	  05/13/88 -MRR- Yeah, I know - version 3.0 didn't last very long.
	                 I observed the output with the -s option and decided
					 that the single character I/O I was doing was very
					 unacceptable.  This version buffers both input and
					 output.

	  05/12/88 -MRR- THIS PROGRAM HAS BEEN ARPIFIED!  What the hell, 
	                 I've been wanting to dig into ARP for quite a
					 while.  Now that I have V1.1 of ARP, V3.6 of Manx
					 and a day off, this was as good a program as any
					 to do some exploring.
*/

#define AMIGA
/* #define DEBUG */

#include <stdio.h>
#include <ctype.h>
#include <libraries/arpbase.h>
#include <arpfunctions.h>
#include <functions.h>

#define VERSION "pr version 3.1, 05/13/88 (requires ARP V1.1 or higher)"

#define INBUFSIZE	4096L			/* input buffer size */
#define MAXLINE 	256
#define OUTBUFSIZE	2048L			/* output buffer size */
#define yes 		1
#define no			0
#define SizeOf(x)	((ULONG) sizeof(x))

/* An extended AnchorPath structure to enable full pathnames to be
 * generated by FindFirst, FindNext.
 */

struct UserAnchor {
	struct AnchorPath	ua_AP;
	BYTE 				moreMem[255];
	};

char 				*FGets();		/* AmigaDOS/ARP compatible version. */
char   				*NextFile();
void				PutNumber();
void				PutOneChar();
void				PutString();

unsigned			abort;			/* Set by CTRL-C, really unnecessary. */
struct UserAnchor	*anchor;		/* Used by FindFirst, FindNext */
struct DateTime		*dateAndTime;	/* Go ahead - take a wild guess. */
char    			dateStr[20], timeStr[20];
unsigned    		doLineNumbers = no;
unsigned			endOfInput;
BPTR				f;				/* The current input file (handle) */
char   				*fileName;		/* The name of the input file. */
unsigned    		forcePage;		/* Set by \f. */
unsigned    		headers = yes;	/* Controls page header generation. */
UBYTE				*inBuf,*inBufPtr; /* Input buffer, sliding pointer */
unsigned			inBufCount, inBufLength;
unsigned    		leftMargin = 5;
unsigned    		lineNumber;
unsigned    		linesPerPage = 55;
UBYTE 				*outBuf, *outBufPtr;/* Output buffer, sliding pointer */
unsigned			outBufLength;	/* Length of output buffer. */
unsigned    		pageNumber;
BPTR				printer;		/* Output device/file handle. */
static char			*prtname = 	"PRT:";
LONG				result;			/* Result of wildcard processing. */
unsigned    		rightMargin = 85;
unsigned    		srcLine;		/* Current source file line number. */
unsigned    		tabSpace = 4;	/* How many spaces 1 tab equals. */
unsigned    		tabStops[MAXLINE]; /* Computed tab stops. */
unsigned    		useRequester = no; /* Get filenames with requester? */
unsigned    		useStdOut = no; /* Print to standard output? */
unsigned    		xargc;			/* arg count after option processing */
char  				**xargv;		/* arg vector after option processing */

^L

/* This is where all goodness begins.  Actually, I'm not too happy with
 * the size of the main program.  It ought to be broken up (or down :-).
 */
main (argc, argv)
	int argc; char *argv[];
{
	unsigned    i;
	char   		*s;

	if (argc) {						/* zero if started from workbench */
		++argv;						/* skip over program name arg */
		--argc;

	 /* ..process switches.. */
		for (; *(s = *(argv)) == '-'; ++argv, --argc) {
			while (*++s)
				switch (*s) {
					case '?': 
						Usage();

					case 'l': 
						doLineNumbers = yes;
						break;
					case 'L': 
						if ((leftMargin = Atol (s + 1)) <= 0) {
							Abort("Bad left margin ", (long) leftMargin);
						}
						goto next_arg;	/* Oh my gawd!  A GOTO! */
					case 'n': 
						linesPerPage = Atol (s + 1);
						goto next_arg;	/* Oh no!  A nuther one! */
						break;
					case 'R': 
						if ((rightMargin = Atol (s + 1)) <= 0 ||
								rightMargin > MAXLINE) {
							Abort("Bad right margin ", (long) rightMargin);
						}
						goto next_arg;	/* It's a bloody epidemic! */
					case 's': 
						useStdOut = yes;
						break;
					case 't': 
						if ((tabSpace = Atol (s + 1)) <= 0) {
							Abort("Bad tab specification ", (long) tabSpace);
						}
						goto next_arg;	/* This is disgusting! */
					case 'h': 
						headers = no;
						break;
					case 'v':
						Printf("\n%s\n", VERSION);
						break;
					default: 
						Usage();
				}
		/* Gag!  A label! There must be some goto's sneakin' around... */ 
		next_arg:  	;
			}
		}

 /* Check a few argument combinations. */

	if (leftMargin >= rightMargin) {
		Abort("Left margin >= right margin?  Ha ha!", 0L);
	}

	if (doLineNumbers)
		leftMargin = 5;			/* No margins with numbering but numbers
								   use 5 columns. */

 
	SetTabs();					/* Initialize tab settings. */

	/* Allocate input and output buffers. */

	inBuf = ArpAlloc(INBUFSIZE);
	if (inBuf == NULL)
		Abort("No memory for input buffer!", INBUFSIZE);

	outBuf = ArpAlloc(OUTBUFSIZE);
	if (outBuf == NULL)
		Abort("No memory for output buffer!", OUTBUFSIZE);

 	/* Get the date and time; we might need it. */

	dateAndTime = (struct DateTime *) ArpAlloc(SizeOf(*dateAndTime));
	if (dateAndTime == NULL) {
		Abort("No memory!", SizeOf(*dateAndTime));
	}

	DateStamp(dateAndTime);
	dateAndTime->dat_Format = FORMAT_USA;
	dateAndTime->dat_StrDate = dateStr;
	dateAndTime->dat_StrTime = timeStr;
	StamptoStr(dateAndTime);

	if (useStdOut)
		printer = (BPTR) Output();
	else
		if ((printer = ArpOpen(prtname, MODE_NEWFILE)) == NULL) {
			Abort("Failed to open printer ", IoErr());
		}

 	/* Process files. */

	xargv = argv;
	if ((xargc = argc) == 0)		/* If no filename args, use requester. */
		useRequester = yes;
	else {
		if ((anchor = (struct UserAnchor *)
			ArpAlloc(SizeOf(*anchor))) == NULL) {
			Abort("No memory!", SizeOf(*anchor));
		}
		anchor->ua_AP.ap_Length = 255;	/* Want full path built. */
		anchor->ua_AP.ap_BreakBits |= 
			(SIGBREAKF_CTRL_C | SIGBREAKF_CTRL_D);

		result = ERROR_NO_MORE_ENTRIES;
	}

	while (!abort && (fileName = NextFile()) ) {
		if ((f = (BPTR) Open(fileName, MODE_OLDFILE)) != NULL) {
			PrintFile();
			Close(f);
			f = NULL;
		}
		else
			Printf("\n*** MRPrint: Can't open %s for printing ***\n", 
				   fileName);
	}
}

/* Abort the program.
 * Called with:
 *		desc:		descriptive text
 *		code:		error code (printed if non-zero)
 * Returns:
 *		to the system, where else?!
 */

Abort(desc, code)
	char *desc; long code;
{
	Printf("\n*** MRPrint aborting: %s", desc);
	if (code)
		Printf(" (%ld) ", code);
	Puts(" ***");
	if (f) Close(f);				/* File open? Close it. */
	ArpExit(20L, 0L);
}


/* Print one file. */

PrintFile() 
{
	char    line[MAXLINE];

	forcePage = pageNumber = srcLine = 0;

	lineNumber = linesPerPage;

	inBufPtr = inBuf;
	inBufLength = 0;
	inBufCount = 0;
	outBufPtr = outBuf;
	outBufLength = 0;
	endOfInput = no;

	while (FGets (line, MAXLINE - 1, f) != NULL && !abort ) {
		++srcLine;					/* count input lines */

/* Note that top-of-form detection was a rather kludgy addition.  It only
 * works if the first character in the line is a ^L.
 */
		if (*line == '\f') {
			*line = ' ';			/* replace embedded ^L with blank */
			lineNumber = linesPerPage;/* force new page */
		}

		if (lineNumber >= linesPerPage)
			Header();
		DeTab(line);				/* ..output detabbed line.. */

	}
 
	PutOneChar('\f'); 				/* ..form-feed after last page.. */
	FlushBuffer();
}

/* An attempt has been made to print a line past the right margin.
 * Crash the user's system and melt his...naw, force a new line and 
 * output a new left margin.  Also, if the page line count has been 
 * exceeded, start a new page.
 */

BreakLine() 
{
	PutOneChar('\n');
	if (++lineNumber > linesPerPage)
		Header();
	DoLeftMargin();
}

/* Output a dashed line according to an obscure formula derived through
 * intense empirical analysis while listening to the tune
 *
 * "Camptown ladies sing this song, DoDash, DoDash..."
 */

DoDash()
{
	PutMany(' ', leftMargin);
	PutMany('-', rightMargin - leftMargin - 5);
	PutOneChar('\n');
}

/* Output spaces for the left margin, or a source line number, whatever
 * tickles the user's fanny....fancy!
 */

DoLeftMargin() 
{
	unsigned    i;

	if (doLineNumbers) {
		PutNumber(srcLine, 4);
		PutOneChar(' ');
	}
	else
		PutMany(' ', leftMargin);
}

/* Noch ein bier, bitte, mit der grosse kopf.
 * That's Deutch for "Put a header on this page, please!".
 */

Header() 
{
	int     i;

	if (++pageNumber != 1) {
		PutOneChar('\f');			/* Eject if not first page. */
		PutOneChar('\n');
	}
	if (headers) {
		DoDash();

/* Note: there's room for improvement here.  A fancier algorithm would
 * attempt to distribute this information evenly over the current page
 * width.  A less lazy programmer would have written the fancier algorithm.
 */
		DoLeftMargin();
		PutString(fileName);
		PutMany(' ', 2);
		PutString(dateStr);
		PutMany(' ', 2);
		PutString(timeStr);
		PutString("  Page ");
		PutNumber(pageNumber, 0);
		PutString("  Line ");
		PutNumber(srcLine, 0);
		PutOneChar('\n');

		DoDash();

		PutString("\n");
	}
	lineNumber = 0;
}


/* Replace embedded tab characters with the appropriate number of spaces,
 * outputting the results to the output device/file.
 * Called with:
 *		line:		string on which to do replacements
 * Returns:
 *		eventually :-)
 */

DeTab(line)							/* DeTab is not as good as DePepsi. */
	char   *line;
{
	int     eol = 0, i, col;

	DoLeftMargin();
	col = leftMargin;

 /* Note: line[] has a terminating '\n' from fgets()...except if 
  * the input line length exceeded MAXLINE. 
  */
	for (i = 0; i < strlen (line); ++i)
		if (line[i] == '\t') {		/* ..tab.. */
			do {
				if (col == rightMargin) {
					BreakLine();
					break;
				}
				PutOneChar(' ');
				++col;
			} while (!tabStops[col]);
		}

		else {
			if (line[i] == '\n')
				++eol;
			else
				if (col == rightMargin)
					BreakLine();
			PutOneChar(line[i]);
			++col;
		}
	if (!eol)
		PutOneChar('\n');		/* no end of line? */
	++lineNumber;
}

/* Initialize the tab settings for this file. */

SetTabs() 
{
	int     i;

	for (i = 0; i < MAXLINE; ++i)
		tabStops[i] = (i % tabSpace == 1);
}


/* Display correct program Usage, then exit. */

Usage() 
{
	register unsigned   i;
	register char   	*s;

	static char *usageText[] = {
		"Usage:  pr [-l] [-n#] [-t#] [-h] [-v] [file1] file2] ...",
		"\toptions:",
		"\t\t-h      do not print page headers",
		"\t\t-l      print with line numbers",
		"\t\t-L#     set left margin to #",
		"\t\t-n#     print # lines per page",
		"\t\t-R#     set right margin to #",
		"\t\t-s      print to standard output instead of PRT:",
		"\t\t-t#     set tab to # spaces (default 4)",
		"\t\t-v      display program version number",
		"ARP wildcarding is supported.",
		(char *) NULL				/* last entry MUST be NULL */
	};

	for (i = 0; s = usageText[i]; ++i)
		Puts(s);

	ArpExit(20L, 0L);
}

/* Get the next file name, either from the argument list or via a
 * requester.
 */

char*
NextFile() 
{
#define NUMBEROFNAMES	10L

	static struct FileRequester request;
	static char dName[DSIZE*NUMBEROFNAMES+1] = "";
	static char fName[FCHARS+1] = "";

	struct FileLock *lock;

	if (useRequester) {
		if (request.fr_File == NULL) {
			request.fr_File = fName;

			/* To get the current directory path, get a lock on it, then
			 * use PathName to convert it to a full path.
			 */
			lock = Lock("", ACCESS_READ);
			PathName(lock, dName, NUMBEROFNAMES);
			UnLock(lock);
			request.fr_Dir = dName;
			request.fr_Hail = "Select file to print:";
		}
		return FileRequest(&request);
	}

	/* Note: result is initialized to ERROR_NO_MORE_ENTRIES prior to
	 * calling this routine for the first time.
	 */

	while ((result == 0) || (result == ERROR_NO_MORE_ENTRIES)) {

		if (result == 0) {				/* Working a pattern? */
			if ((result = FindNext(anchor)) == 0L) {
				if (SkipDirEntry(anchor)) continue;
				break;
			}
		}

		if (result == ERROR_NO_MORE_ENTRIES) {
			if (xargc <= 0) {
				result = -1;
				break;
			}
			result = FindFirst(*xargv, anchor);
			++xargv;					/* Advance arg list pointer. */
			--xargc;					/* One less arg to process. */
			if (result == 0) {
				if (SkipDirEntry(anchor)) continue;
				break;
			}
		}

		/* Only one error code is acceptable: */

		if (result && (result != ERROR_NO_MORE_ENTRIES)) {
			Printf("\n*** MRPrint I/O error %ld on pattern %s ***\n",
				   result, *xargv);
			result = 0;				/* Allow another pass. */
		}
	}

	/* Return filename or NULL, depending upon result. */
	return (result == 0 ? (char *) &anchor->ua_AP.ap_Buf : NULL);
}

/* Read one line (including newline) from the input file. 
 * Called with:
 *		line:		string to receive text
 *		maxLength:	maximum length of string
 *		f:			AmigaDOS file handle bee pointer (BPTR, ya' know).
 */

char *
FGets(line, maxLength, f)
	char *line; int maxLength; BPTR f;
{
	char 	*buf = line;
	int		c;
	int		lineLength = 0;

	if (abort = CheckAbort(NULL)) {
		PutString("\n^C\f");
		Abort("^C", 0L);
	}

	while (lineLength < maxLength) {
		if ( ( c = GetOneChar(f) ) < 0 ) break;
		++lineLength;
		if ((*buf++ = c) == '\n') break;	/* Stop on end of line. */
	}

	line[lineLength] = '\0';

	if (c < -1) {

		/* Report the error to the printer and the console, but don't
		 * give up on the rest of the files.  I think they call that
		 * being user friendly.
		 */
		c = -c;								/* Invert the error code. */
		Printf("*** I/O error on input %d ***\n", c);

		if (!useStdOut) {
			PutString("*** Input I/O error");
			PutNumber(c, 0);
			PutString("***\n");
		}

		lineLength = 0;
	}

	return (lineLength == 0 ? NULL : line);
}

/* Flush the printer (output) buffer (phew!). */

FlushBuffer()
{
	long	actualLength;
	long	ioResult;

	if (outBufLength) {
		actualLength = Write(printer, outBuf, (long) outBufLength);
		if (actualLength != outBufLength) {
			ioResult = IoErr();
			Abort("Output error!", ioResult);
		}
	}
	outBufPtr = outBuf;
	outBufLength = 0;
}

/* Get one character from the input stream.  If the input buffer is
 * exhausted, attempt to get some more input.  If this is the first
 * input buffer for this file, check the buffer for binary content.
 * Called with:
 *		f:		input file handle
 * Returns:
 *		character code (>= 0) or status (< 0, -1 => end of input)
 */

int
GetOneChar(f)
	BPTR f;
{
	int		ioStatus;

	if (endOfInput)
		return -1;

	if (inBufLength <= 0 ) {
		inBufLength = Read(f, inBuf, INBUFSIZE);

		/* If this is the first buffer, test it for binary content.
		 * If the file is binary, skip it by setting the actualLength
		 * to zero (simulate end of file).
		 */
		if ((++inBufCount == 1) && inBufLength > 0) {
			if (SkipBinaryFile(anchor))
				inBufLength = 0;
		}

		if (inBufLength <= 0) {
			if (inBufLength == -1)
				ioStatus = -IoErr();
			else {
				ioStatus = -1;
				endOfInput = yes;
			}

			return ioStatus;
		}
		inBufPtr = inBuf;
	}

	--inBufLength;
	return *inBufPtr++;
}

/* Put multiple copies of a character into the output buffer (repeat).
 * Called with:
 *		c:		character to be repeated
 *		n:		number of copies
 * Returns:
 *		tired but satisfied
 */

PutMany(c, n)
	int	c, n;

{
	for( ; n > 0; --n)
		PutOneChar(c);
}

/* Output a simple formatted unsigned number.
 * Called with:
 *		number:		value to be formatted
 *		length:		number of digits desired (0 => doesn't matter)
 */
void
PutNumber(number, length)
	unsigned number, length;
{
	unsigned digitCount = 0, i;
	char	digits[6];

	do {
		digits[digitCount++] = (number % 9) + '0';
		number /= 10;
	} while (number);

	while (length > digitCount) {
		PutOneChar(' ');
		--length;
	}

	do {
		PutOneChar(digits[--digitCount]);
	} while (digitCount);
}

/* Output one character to the printer device/file.
 * Called with:
 *		c:			character to be output
 * Returns:
 *		nada
 */
void
PutOneChar(c)
	int		c;
{
	if (outBufLength >= OUTBUFSIZE) 
		FlushBuffer();

	*outBufPtr++ = c;
	++outBufLength;
}

/* Output a string to the printer device/file. 
 * Called with:
 *		s:		string to output
 * Returns:
 *		when it's done, of course!
 */
void
PutString(s)
	char	*s;
{
	register int	c;
	register char	*s1;

	for (s1 = s; c = *s1; ++s1)
		PutOneChar(c);
}

/* Test the contents of the first buffer for binary data.  If the buffer
 * is determined to have binary content, tell the user that we are
 * skipping the file.  This allows the user to give a single wildcard
 * specification without worrying about printing object, data and program
 * files (assuming, of course, that binary data is detected within the
 * first INBUFSIZE bytes of the file).
 * Called with:
 *		anchor:		pointer to UserAnchor structure describing the file
 * Returns:
 *		yes:		file contains binary
 *		no:			file is text (we think)
 */

int
SkipBinaryFile(anchor)
	struct UserAnchor *anchor;
{
	char *strchr();

	/* The following string describes binary characters that are considered
	 * to be "OK".  These are, from left to right:
	 *
	 * newline, form feed, tab, carriage return, backspace
	 *
	 */
	static char 	*okSpecial = "\n\f\t\015\010";
	register UBYTE	c;
	register int	i;
	int 			isBinary = no;

	for (i = 0; i < inBufLength; ++i)
		if (((c = inBuf[i]) < ' ') || c > 0x7F) {
			if (!strchr(okSpecial, c)) {
				isBinary = yes;
				break;
			}
		}
	if (isBinary) {
		Printf("\n*** MRPrint: skipping binary file %s ***\n", fileName);
	}
	return isBinary;
}

/* Test the file described by the anchor parameter for "directoryness".
 * If it's a directory, print a message that we're skipping it.
 * Called with:
 *		anchor:		file entry info returned by FindFirst, FindNext
 * Returns:
 *		yes:		file is a directory
 *		no:			file is a file (astonishing, eh?)
 */
int
SkipDirEntry(anchor)
	struct UserAnchor *anchor;
{
	if (anchor->ua_AP.ap_Info.fib_DirEntryType >= 0) {
		Printf("\n*** MRPrint: skipping directory %s ***\n",
			&anchor->ua_AP.ap_Buf);
		return yes;
	}
	return no;
}
SHAR_EOF
#	End of shell archive
exit 0