home
***
CD-ROM
|
disk
|
FTP
|
other
***
search
/
The Fred Fish Collection 1.5
/
ffcollection-1-5-1992-11.iso
/
ff_disks
/
200-299
/
ff279.lzh
/
MRPrint
/
MRPrint.c
< prev
next >
Wrap
C/C++ Source or Header
|
1989-11-20
|
23KB
|
829 lines
/* :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;
}