home *** CD-ROM | disk | FTP | other *** search
- /* :ts=4 */
- /*
- * MRPrint: detabbing text file printer for the Amiga
- * Author: Mark Rinfret (Usenet: mrr@amanpt1.ZONE1.COM; 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]...filen]
- * 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.
- *
- * 08/30/89 -MRR- V3.4: Fixed bug in line numbering.
- *
- * 11/12/88 -MRR- Changed default margins to 1, 80, lines per page to 62.
- *
- * 08/26/88 -MRR- When MRPrint detected a binary file, it printed a blank page
- * to "commemorate" the event. Ugh!
- *
- * 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.4, 08/29/89 (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 = 1;
- unsigned lineNumber;
- unsigned linesPerPage = 60;
- 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 = 80;
- 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 */
-
-
-
-
- /*
- * 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.. */
-
- }
-
- if (srcLine) { /* We printed something? */
- 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 algorithm 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);
- }
-
- /*
- * Print a header.
- */
-
- 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.
- */
- PutString(" "); /* Don't call 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] == 0x08) { /* backspace? */
- if (col > 1) {
- PutOneChar(line[i]);
- --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 % 10) + '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, ESCape
- *
- */
- static char *okSpecial = "\n\f\t\015\010\033";
- 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;
- }
-