|  | #include <u.h> | 
|  | #include <libc.h> | 
|  | #include <bio.h> | 
|  | #include <ctype.h> | 
|  |  | 
|  | /* | 
|  | *	PR command (print files in pages and columns, with headings) | 
|  | *	2+head+2+page[56]+5 | 
|  | */ | 
|  |  | 
|  | #define err		pr_err | 
|  | #define	ISPRINT(c)	((c) >= ' ') | 
|  | #define ESC		'\033' | 
|  | #define LENGTH		66 | 
|  | #define LINEW		72 | 
|  | #define NUMW		5 | 
|  | #define MARGIN		10 | 
|  | #define DEFTAB		8 | 
|  | #define NFILES		10 | 
|  | #define HEAD		"%12.12s %4.4s  %s Page %d\n\n\n", date+4, date+24, head, Page | 
|  | #define TOLOWER(c)	(isupper(c) ? tolower(c) : c)	/* ouch! */ | 
|  | #define cerror(S)	fprint(2, "pr: %s", S) | 
|  | #define STDINNAME()	nulls | 
|  | #define TTY		"/dev/cons", 0 | 
|  | #define PROMPT()	fprint(2, "\a") /* BEL */ | 
|  | #define TABS(N,C)	if((N = intopt(argv, &C)) < 0) N = DEFTAB | 
|  | #define ETABS		(Inpos % Etabn) | 
|  | #define ITABS		(Itabn > 0 && Nspace > 1 && Nspace >= (nc = Itabn - Outpos % Itabn)) | 
|  | #define NSEPC		'\t' | 
|  | #define EMPTY		14	/* length of " -- empty file" */ | 
|  |  | 
|  | typedef	struct	Fils	Fils; | 
|  | typedef	struct	Colp*	Colp; | 
|  | typedef	struct	Err	Err; | 
|  |  | 
|  | struct	Fils | 
|  | { | 
|  | Biobuf*	f_f; | 
|  | char*	f_name; | 
|  | long	f_nextc; | 
|  | }; | 
|  | struct	Colp | 
|  | { | 
|  | Rune*	c_ptr; | 
|  | Rune*	c_ptr0; | 
|  | long	c_lno; | 
|  | }; | 
|  | struct	Err | 
|  | { | 
|  | Err*	e_nextp; | 
|  | char*	e_mess; | 
|  | }; | 
|  |  | 
|  | int	Balance = 0; | 
|  | Biobuf	bout; | 
|  | Rune*	Bufend; | 
|  | Rune*	Buffer = 0; | 
|  | int	C = '\0'; | 
|  | Colp	Colpts; | 
|  | int	Colw; | 
|  | int	Dblspace = 1; | 
|  | Err*	err = 0; | 
|  | int	error = 0; | 
|  | int	Etabc = '\t'; | 
|  | int	Etabn = 0; | 
|  | Fils*	Files; | 
|  | int	Formfeed = 0; | 
|  | int	Fpage = 1; | 
|  | char*	Head = 0; | 
|  | int	Inpos; | 
|  | int	Itabc = '\t'; | 
|  | int	Itabn = 0; | 
|  | Err*	Lasterr = (Err*)&err; | 
|  | int	Lcolpos; | 
|  | int	Len = LENGTH; | 
|  | int	Line; | 
|  | int	Linew = 0; | 
|  | long	Lnumb = 0; | 
|  | int	Margin = MARGIN; | 
|  | int	Multi = 0; | 
|  | int	Ncols = 1; | 
|  | int	Nfiles = 0; | 
|  | int	Nsepc = NSEPC; | 
|  | int	Nspace; | 
|  | char	nulls[] = ""; | 
|  | int	Numw; | 
|  | int	Offset = 0; | 
|  | int	Outpos; | 
|  | int	Padodd; | 
|  | int	Page; | 
|  | int	Pcolpos; | 
|  | int	Plength; | 
|  | int	Sepc = 0; | 
|  |  | 
|  | extern	int	atoix(char**); | 
|  | extern	void	balance(int); | 
|  | extern	void	die(char*); | 
|  | extern	void	errprint(void); | 
|  | extern	char*	ffiler(char*); | 
|  | extern	int	findopt(int, char**); | 
|  | extern	int	get(int); | 
|  | extern	void*	getspace(ulong); | 
|  | extern	int	intopt(char**, int*); | 
|  | extern	void	main(int, char**); | 
|  | extern	Biobuf*	mustopen(char*, Fils*); | 
|  | extern	void	nexbuf(void); | 
|  | extern	int	pr(char*); | 
|  | extern	void	put(long); | 
|  | extern	void	putpage(void); | 
|  | extern	void	putspace(void); | 
|  |  | 
|  | /* | 
|  | * return date file was last modified | 
|  | */ | 
|  | #define getdate prgetdate | 
|  |  | 
|  | char* | 
|  | getdate(void) | 
|  | { | 
|  | static char *now = 0; | 
|  | static Dir *sbuf; | 
|  | ulong mtime; | 
|  |  | 
|  | if(Nfiles > 1 || Files->f_name == nulls) { | 
|  | if(now == 0) { | 
|  | mtime = time(0); | 
|  | now = ctime(mtime); | 
|  | } | 
|  | return now; | 
|  | } | 
|  | mtime = 0; | 
|  | sbuf = dirstat(Files->f_name); | 
|  | if(sbuf){ | 
|  | mtime = sbuf->mtime; | 
|  | free(sbuf); | 
|  | } | 
|  | return ctime(mtime); | 
|  | } | 
|  |  | 
|  | char* | 
|  | ffiler(char *s) | 
|  | { | 
|  | return smprint("can't open %s\n", s); | 
|  | } | 
|  |  | 
|  | void | 
|  | main(int argc, char *argv[]) | 
|  | { | 
|  | Fils fstr[NFILES]; | 
|  | int nfdone = 0; | 
|  |  | 
|  | Binit(&bout, 1, OWRITE); | 
|  | Files = fstr; | 
|  | for(argc = findopt(argc, argv); argc > 0; --argc, ++argv) | 
|  | if(Multi == 'm') { | 
|  | if(Nfiles >= NFILES - 1) | 
|  | die("too many files"); | 
|  | if(mustopen(*argv, &Files[Nfiles++]) == 0) | 
|  | nfdone++; /* suppress printing */ | 
|  | } else { | 
|  | if(pr(*argv)) | 
|  | Bterm(Files->f_f); | 
|  | nfdone++; | 
|  | } | 
|  | if(!nfdone)			/* no files named, use stdin */ | 
|  | pr(nulls);		/* on GCOS, use current file, if any */ | 
|  | errprint();			/* print accumulated error reports */ | 
|  | exits(error? "error": 0); | 
|  | } | 
|  |  | 
|  | int | 
|  | findopt(int argc, char *argv[]) | 
|  | { | 
|  | char **eargv = argv; | 
|  | int eargc = 0, c; | 
|  |  | 
|  | while(--argc > 0) { | 
|  | switch(c = **++argv) { | 
|  | case '-': | 
|  | if((c = *++*argv) == '\0') | 
|  | break; | 
|  | case '+': | 
|  | do { | 
|  | if(isdigit(c)) { | 
|  | --*argv; | 
|  | Ncols = atoix(argv); | 
|  | } else | 
|  | switch(c = TOLOWER(c)) { | 
|  | case '+': | 
|  | if((Fpage = atoix(argv)) < 1) | 
|  | Fpage = 1; | 
|  | continue; | 
|  | case 'd': | 
|  | Dblspace = 2; | 
|  | continue; | 
|  | case 'e': | 
|  | TABS(Etabn, Etabc); | 
|  | continue; | 
|  | case 'f': | 
|  | Formfeed++; | 
|  | continue; | 
|  | case 'h': | 
|  | if(--argc > 0) | 
|  | Head = argv[1]; | 
|  | continue; | 
|  | case 'i': | 
|  | TABS(Itabn, Itabc); | 
|  | continue; | 
|  | case 'l': | 
|  | Len = atoix(argv); | 
|  | continue; | 
|  | case 'a': | 
|  | case 'm': | 
|  | Multi = c; | 
|  | continue; | 
|  | case 'o': | 
|  | Offset = atoix(argv); | 
|  | continue; | 
|  | case 's': | 
|  | if((Sepc = (*argv)[1]) != '\0') | 
|  | ++*argv; | 
|  | else | 
|  | Sepc = '\t'; | 
|  | continue; | 
|  | case 't': | 
|  | Margin = 0; | 
|  | continue; | 
|  | case 'w': | 
|  | Linew = atoix(argv); | 
|  | continue; | 
|  | case 'n': | 
|  | Lnumb++; | 
|  | if((Numw = intopt(argv, &Nsepc)) <= 0) | 
|  | Numw = NUMW; | 
|  | case 'b': | 
|  | Balance = 1; | 
|  | continue; | 
|  | case 'p': | 
|  | Padodd = 1; | 
|  | continue; | 
|  | default: | 
|  | die("bad option"); | 
|  | } | 
|  | } while((c = *++*argv) != '\0'); | 
|  | if(Head == argv[1]) | 
|  | argv++; | 
|  | continue; | 
|  | } | 
|  | *eargv++ = *argv; | 
|  | eargc++; | 
|  | } | 
|  | if(Len == 0) | 
|  | Len = LENGTH; | 
|  | if(Len <= Margin) | 
|  | Margin = 0; | 
|  | Plength = Len - Margin/2; | 
|  | if(Multi == 'm') | 
|  | Ncols = eargc; | 
|  | switch(Ncols) { | 
|  | case 0: | 
|  | Ncols = 1; | 
|  | case 1: | 
|  | break; | 
|  | default: | 
|  | if(Etabn == 0)		/* respect explicit tab specification */ | 
|  | Etabn = DEFTAB; | 
|  | } | 
|  | if(Linew == 0) | 
|  | Linew = Ncols != 1 && Sepc == 0? LINEW: 512; | 
|  | if(Lnumb) | 
|  | Linew -= Multi == 'm'? Numw: Numw*Ncols; | 
|  | if((Colw = (Linew - Ncols + 1)/Ncols) < 1) | 
|  | die("width too small"); | 
|  | if(Ncols != 1 && Multi == 0) { | 
|  | ulong buflen = ((ulong)(Plength/Dblspace + 1))*(Linew+1)*sizeof(char); | 
|  | Buffer = getspace(buflen*sizeof(*Buffer)); | 
|  | Bufend = &Buffer[buflen]; | 
|  | Colpts = getspace((Ncols+1)*sizeof(*Colpts)); | 
|  | } | 
|  | return eargc; | 
|  | } | 
|  |  | 
|  | int | 
|  | intopt(char *argv[], int *optp) | 
|  | { | 
|  | int c; | 
|  |  | 
|  | if((c = (*argv)[1]) != '\0' && !isdigit(c)) { | 
|  | *optp = c; | 
|  | (*argv)++; | 
|  | } | 
|  | c = atoix(argv); | 
|  | return c != 0? c: -1; | 
|  | } | 
|  |  | 
|  | int | 
|  | pr(char *name) | 
|  | { | 
|  | char *date = 0, *head = 0; | 
|  |  | 
|  | if(Multi != 'm' && mustopen(name, &Files[0]) == 0) | 
|  | return 0; | 
|  | if(Buffer) | 
|  | Bungetc(Files->f_f); | 
|  | if(Lnumb) | 
|  | Lnumb = 1; | 
|  | for(Page = 0;; putpage()) { | 
|  | if(C == -1) | 
|  | break; | 
|  | if(Buffer) | 
|  | nexbuf(); | 
|  | Inpos = 0; | 
|  | if(get(0) == -1) | 
|  | break; | 
|  | Bflush(&bout); | 
|  | Page++; | 
|  | if(Page >= Fpage) { | 
|  | if(Margin == 0) | 
|  | continue; | 
|  | if(date == 0) | 
|  | date = getdate(); | 
|  | if(head == 0) | 
|  | head = Head != 0 ? Head : | 
|  | Nfiles < 2? Files->f_name: nulls; | 
|  | Bprint(&bout, "\n\n"); | 
|  | Nspace = Offset; | 
|  | putspace(); | 
|  | Bprint(&bout, HEAD); | 
|  | } | 
|  | } | 
|  | if(Padodd && (Page&1) == 1) { | 
|  | Line = 0; | 
|  | if(Formfeed) | 
|  | put('\f'); | 
|  | else | 
|  | while(Line < Len) | 
|  | put('\n'); | 
|  | } | 
|  | C = '\0'; | 
|  | return 1; | 
|  | } | 
|  |  | 
|  | void | 
|  | putpage(void) | 
|  | { | 
|  | int colno; | 
|  |  | 
|  | for(Line = Margin/2;; get(0)) { | 
|  | for(Nspace = Offset, colno = 0, Outpos = 0; C != '\f';) { | 
|  | if(Lnumb && C != -1 && (colno == 0 || Multi == 'a')) { | 
|  | if(Page >= Fpage) { | 
|  | putspace(); | 
|  | Bprint(&bout, "%*ld", Numw, Buffer? | 
|  | Colpts[colno].c_lno++: Lnumb); | 
|  | Outpos += Numw; | 
|  | put(Nsepc); | 
|  | } | 
|  | Lnumb++; | 
|  | } | 
|  | for(Lcolpos=0, Pcolpos=0; C!='\n' && C!='\f' && C!=-1; get(colno)) | 
|  | put(C); | 
|  | if(C==-1 || ++colno==Ncols || C=='\n' && get(colno)==-1) | 
|  | break; | 
|  | if(Sepc) | 
|  | put(Sepc); | 
|  | else | 
|  | if((Nspace += Colw - Lcolpos + 1) < 1) | 
|  | Nspace = 1; | 
|  | } | 
|  | /* | 
|  | if(C == -1) { | 
|  | if(Margin != 0) | 
|  | break; | 
|  | if(colno != 0) | 
|  | put('\n'); | 
|  | return; | 
|  | } | 
|  | */ | 
|  | if(C == -1 && colno == 0) { | 
|  | if(Margin != 0) | 
|  | break; | 
|  | return; | 
|  | } | 
|  | if(C == '\f') | 
|  | break; | 
|  | put('\n'); | 
|  | if(Dblspace == 2 && Line < Plength) | 
|  | put('\n'); | 
|  | if(Line >= Plength) | 
|  | break; | 
|  | } | 
|  | if(Formfeed) | 
|  | put('\f'); | 
|  | else | 
|  | while(Line < Len) | 
|  | put('\n'); | 
|  | } | 
|  |  | 
|  | void | 
|  | nexbuf(void) | 
|  | { | 
|  | Rune *s = Buffer; | 
|  | Colp p = Colpts; | 
|  | int j, c, bline = 0; | 
|  |  | 
|  | for(;;) { | 
|  | p->c_ptr0 = p->c_ptr = s; | 
|  | if(p == &Colpts[Ncols]) | 
|  | return; | 
|  | (p++)->c_lno = Lnumb + bline; | 
|  | for(j = (Len - Margin)/Dblspace; --j >= 0; bline++) | 
|  | for(Inpos = 0;;) { | 
|  | if((c = Bgetrune(Files->f_f)) == -1) { | 
|  | for(*s = -1; p <= &Colpts[Ncols]; p++) | 
|  | p->c_ptr0 = p->c_ptr = s; | 
|  | if(Balance) | 
|  | balance(bline); | 
|  | return; | 
|  | } | 
|  | if(ISPRINT(c)) | 
|  | Inpos++; | 
|  | if(Inpos <= Colw || c == '\n') { | 
|  | *s = c; | 
|  | if(++s >= Bufend) | 
|  | die("page-buffer overflow"); | 
|  | } | 
|  | if(c == '\n') | 
|  | break; | 
|  | switch(c) { | 
|  | case '\b': | 
|  | if(Inpos == 0) | 
|  | s--; | 
|  | case ESC: | 
|  | if(Inpos > 0) | 
|  | Inpos--; | 
|  | } | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | /* | 
|  | * line balancing for last page | 
|  | */ | 
|  | void | 
|  | balance(int bline) | 
|  | { | 
|  | Rune *s = Buffer; | 
|  | Colp p = Colpts; | 
|  | int colno = 0, j, c, l; | 
|  |  | 
|  | c = bline % Ncols; | 
|  | l = (bline + Ncols - 1)/Ncols; | 
|  | bline = 0; | 
|  | do { | 
|  | for(j = 0; j < l; ++j) | 
|  | while(*s++ != '\n') | 
|  | ; | 
|  | (++p)->c_lno = Lnumb + (bline += l); | 
|  | p->c_ptr0 = p->c_ptr = s; | 
|  | if(++colno == c) | 
|  | l--; | 
|  | } while(colno < Ncols - 1); | 
|  | } | 
|  |  | 
|  | int | 
|  | get(int colno) | 
|  | { | 
|  | static int peekc = 0; | 
|  | Colp p; | 
|  | Fils *q; | 
|  | long c; | 
|  |  | 
|  | if(peekc) { | 
|  | peekc = 0; | 
|  | c = Etabc; | 
|  | } else | 
|  | if(Buffer) { | 
|  | p = &Colpts[colno]; | 
|  | if(p->c_ptr >= (p+1)->c_ptr0) | 
|  | c = -1; | 
|  | else | 
|  | if((c = *p->c_ptr) != -1) | 
|  | p->c_ptr++; | 
|  | } else | 
|  | if((c = (q = &Files[Multi == 'a'? 0: colno])->f_nextc) == -1) { | 
|  | for(q = &Files[Nfiles]; --q >= Files && q->f_nextc == -1;) | 
|  | ; | 
|  | if(q >= Files) | 
|  | c = '\n'; | 
|  | } else | 
|  | q->f_nextc = Bgetrune(q->f_f); | 
|  | if(Etabn != 0 && c == Etabc) { | 
|  | Inpos++; | 
|  | peekc = ETABS; | 
|  | c = ' '; | 
|  | } else | 
|  | if(ISPRINT(c)) | 
|  | Inpos++; | 
|  | else | 
|  | switch(c) { | 
|  | case '\b': | 
|  | case ESC: | 
|  | if(Inpos > 0) | 
|  | Inpos--; | 
|  | break; | 
|  | case '\f': | 
|  | if(Ncols == 1) | 
|  | break; | 
|  | c = '\n'; | 
|  | case '\n': | 
|  | case '\r': | 
|  | Inpos = 0; | 
|  | } | 
|  | return C = c; | 
|  | } | 
|  |  | 
|  | void | 
|  | put(long c) | 
|  | { | 
|  | int move; | 
|  |  | 
|  | switch(c) { | 
|  | case ' ': | 
|  | Nspace++; | 
|  | Lcolpos++; | 
|  | return; | 
|  | case '\b': | 
|  | if(Lcolpos == 0) | 
|  | return; | 
|  | if(Nspace > 0) { | 
|  | Nspace--; | 
|  | Lcolpos--; | 
|  | return; | 
|  | } | 
|  | if(Lcolpos > Pcolpos) { | 
|  | Lcolpos--; | 
|  | return; | 
|  | } | 
|  | case ESC: | 
|  | move = -1; | 
|  | break; | 
|  | case '\n': | 
|  | Line++; | 
|  | case '\r': | 
|  | case '\f': | 
|  | Pcolpos = 0; | 
|  | Lcolpos = 0; | 
|  | Nspace = 0; | 
|  | Outpos = 0; | 
|  | default: | 
|  | move = (ISPRINT(c) != 0); | 
|  | } | 
|  | if(Page < Fpage) | 
|  | return; | 
|  | if(Lcolpos > 0 || move > 0) | 
|  | Lcolpos += move; | 
|  | if(Lcolpos <= Colw) { | 
|  | putspace(); | 
|  | Bputrune(&bout, c); | 
|  | Pcolpos = Lcolpos; | 
|  | Outpos += move; | 
|  | } | 
|  | } | 
|  |  | 
|  | void | 
|  | putspace(void) | 
|  | { | 
|  | int nc; | 
|  |  | 
|  | for(; Nspace > 0; Outpos += nc, Nspace -= nc) | 
|  | if(ITABS) | 
|  | Bputc(&bout, Itabc); | 
|  | else { | 
|  | nc = 1; | 
|  | Bputc(&bout, ' '); | 
|  | } | 
|  | } | 
|  |  | 
|  | int | 
|  | atoix(char **p) | 
|  | { | 
|  | int n = 0, c; | 
|  |  | 
|  | while(isdigit(c = *++*p)) | 
|  | n = 10*n + c - '0'; | 
|  | (*p)--; | 
|  | return n; | 
|  | } | 
|  |  | 
|  | /* | 
|  | * Defer message about failure to open file to prevent messing up | 
|  | * alignment of page with tear perforations or form markers. | 
|  | * Treat empty file as special case and report as diagnostic. | 
|  | */ | 
|  | Biobuf* | 
|  | mustopen(char *s, Fils *f) | 
|  | { | 
|  | char *tmp; | 
|  |  | 
|  | if(*s == '\0') { | 
|  | f->f_name = STDINNAME(); | 
|  | f->f_f = malloc(sizeof(Biobuf)); | 
|  | if(f->f_f == 0) | 
|  | cerror("no memory"); | 
|  | Binit(f->f_f, 0, OREAD); | 
|  | } else | 
|  | if((f->f_f = Bopen(f->f_name = s, OREAD)) == 0) { | 
|  | tmp = ffiler(f->f_name); | 
|  | s = strcpy((char*)getspace(strlen(tmp) + 1), tmp); | 
|  | free(tmp); | 
|  | } | 
|  | if(f->f_f != 0) { | 
|  | if((f->f_nextc = Bgetrune(f->f_f)) >= 0 || Multi == 'm') | 
|  | return f->f_f; | 
|  | sprint(s = (char*)getspace(strlen(f->f_name) + 1 + EMPTY), | 
|  | "%s -- empty file\n", f->f_name); | 
|  | Bterm(f->f_f); | 
|  | } | 
|  | error = 1; | 
|  | cerror(s); | 
|  | fprint(2, "\n"); | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | void* | 
|  | getspace(ulong n) | 
|  | { | 
|  | void *t; | 
|  |  | 
|  | if((t = malloc(n)) == 0) | 
|  | die("out of space"); | 
|  | return t; | 
|  | } | 
|  |  | 
|  | void | 
|  | die(char *s) | 
|  | { | 
|  | error++; | 
|  | errprint(); | 
|  | cerror(s); | 
|  | Bputc(&bout, '\n'); | 
|  | exits("error"); | 
|  | } | 
|  |  | 
|  | /* | 
|  | void | 
|  | onintr(void) | 
|  | { | 
|  | error++; | 
|  | errprint(); | 
|  | exits("error"); | 
|  | } | 
|  | /**/ | 
|  |  | 
|  | /* | 
|  | * print accumulated error reports | 
|  | */ | 
|  | void | 
|  | errprint(void) | 
|  | { | 
|  | Bflush(&bout); | 
|  | for(; err != 0; err = err->e_nextp) { | 
|  | cerror(err->e_mess); | 
|  | fprint(2, "\n"); | 
|  | } | 
|  | } |