ref: bb2060028e616df560628cf6f1e6e5d196d50833
dir: /sys/src/cmd/pr.c/
#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 ISPRINT(c) ((c) >= ' ') #define ESC '\033' #define LENGTH 66 #define LINEW 72 #define NUMW 5 #define MARGIN 10 #define DEFTAB 8 #define NFILES 20 #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 */ 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); Blethal(&bout, nil); 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); Blethal(f->f_f, nil); } 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) { Blethal(f->f_f, nil); 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"); } }