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