Blob
1 #include <u.h>2 #include <libc.h>3 #include <bio.h>4 #include <ctype.h>6 /*7 * PR command (print files in pages and columns, with headings)8 * 2+head+2+page[56]+59 */11 #define err pr_err12 #define ISPRINT(c) ((c) >= ' ')13 #define ESC '\033'14 #define LENGTH 6615 #define LINEW 7216 #define NUMW 517 #define MARGIN 1018 #define DEFTAB 819 #define NFILES 1020 #define HEAD "%12.12s %4.4s %s Page %d\n\n\n", date+4, date+24, head, Page21 #define TOLOWER(c) (isupper(c) ? tolower(c) : c) /* ouch! */22 #define cerror(S) fprint(2, "pr: %s", S)23 #define STDINNAME() nulls24 #define TTY "/dev/cons", 025 #define PROMPT() fprint(2, "\a") /* BEL */26 #define TABS(N,C) if((N = intopt(argv, &C)) < 0) N = DEFTAB27 #define ETABS (Inpos % Etabn)28 #define ITABS (Itabn > 0 && Nspace > 1 && Nspace >= (nc = Itabn - Outpos % Itabn))29 #define NSEPC '\t'30 #define EMPTY 14 /* length of " -- empty file" */32 typedef struct Fils Fils;33 typedef struct Colp* Colp;34 typedef struct Err Err;36 struct Fils37 {38 Biobuf* f_f;39 char* f_name;40 long f_nextc;41 };42 struct Colp43 {44 Rune* c_ptr;45 Rune* c_ptr0;46 long c_lno;47 };48 struct Err49 {50 Err* e_nextp;51 char* e_mess;52 };54 int Balance = 0;55 Biobuf bout;56 Rune* Bufend;57 Rune* Buffer = 0;58 int C = '\0';59 Colp Colpts;60 int Colw;61 int Dblspace = 1;62 Err* err = 0;63 int error = 0;64 int Etabc = '\t';65 int Etabn = 0;66 Fils* Files;67 int Formfeed = 0;68 int Fpage = 1;69 char* Head = 0;70 int Inpos;71 int Itabc = '\t';72 int Itabn = 0;73 Err* Lasterr = (Err*)&err;74 int Lcolpos;75 int Len = LENGTH;76 int Line;77 int Linew = 0;78 long Lnumb = 0;79 int Margin = MARGIN;80 int Multi = 0;81 int Ncols = 1;82 int Nfiles = 0;83 int Nsepc = NSEPC;84 int Nspace;85 char nulls[] = "";86 int Numw;87 int Offset = 0;88 int Outpos;89 int Padodd;90 int Page;91 int Pcolpos;92 int Plength;93 int Sepc = 0;95 extern int atoix(char**);96 extern void balance(int);97 extern void die(char*);98 extern void errprint(void);99 extern char* ffiler(char*);100 extern int findopt(int, char**);101 extern int get(int);102 extern void* getspace(ulong);103 extern int intopt(char**, int*);104 extern void main(int, char**);105 extern Biobuf* mustopen(char*, Fils*);106 extern void nexbuf(void);107 extern int pr(char*);108 extern void put(long);109 extern void putpage(void);110 extern void putspace(void);112 /*113 * return date file was last modified114 */115 #define getdate prgetdate117 char*118 getdate(void)119 {120 static char *now = 0;121 static Dir *sbuf;122 ulong mtime;124 if(Nfiles > 1 || Files->f_name == nulls) {125 if(now == 0) {126 mtime = time(0);127 now = ctime(mtime);128 }129 return now;130 }131 mtime = 0;132 sbuf = dirstat(Files->f_name);133 if(sbuf){134 mtime = sbuf->mtime;135 free(sbuf);136 }137 return ctime(mtime);138 }140 char*141 ffiler(char *s)142 {143 return smprint("can't open %s\n", s);144 }146 void147 main(int argc, char *argv[])148 {149 Fils fstr[NFILES];150 int nfdone = 0;152 Binit(&bout, 1, OWRITE);153 Files = fstr;154 for(argc = findopt(argc, argv); argc > 0; --argc, ++argv)155 if(Multi == 'm') {156 if(Nfiles >= NFILES - 1)157 die("too many files");158 if(mustopen(*argv, &Files[Nfiles++]) == 0)159 nfdone++; /* suppress printing */160 } else {161 if(pr(*argv))162 Bterm(Files->f_f);163 nfdone++;164 }165 if(!nfdone) /* no files named, use stdin */166 pr(nulls); /* on GCOS, use current file, if any */167 errprint(); /* print accumulated error reports */168 exits(error? "error": 0);169 }171 int172 findopt(int argc, char *argv[])173 {174 char **eargv = argv;175 int eargc = 0, c;177 while(--argc > 0) {178 switch(c = **++argv) {179 case '-':180 if((c = *++*argv) == '\0')181 break;182 case '+':183 do {184 if(isdigit(c)) {185 --*argv;186 Ncols = atoix(argv);187 } else188 switch(c = TOLOWER(c)) {189 case '+':190 if((Fpage = atoix(argv)) < 1)191 Fpage = 1;192 continue;193 case 'd':194 Dblspace = 2;195 continue;196 case 'e':197 TABS(Etabn, Etabc);198 continue;199 case 'f':200 Formfeed++;201 continue;202 case 'h':203 if(--argc > 0)204 Head = argv[1];205 continue;206 case 'i':207 TABS(Itabn, Itabc);208 continue;209 case 'l':210 Len = atoix(argv);211 continue;212 case 'a':213 case 'm':214 Multi = c;215 continue;216 case 'o':217 Offset = atoix(argv);218 continue;219 case 's':220 if((Sepc = (*argv)[1]) != '\0')221 ++*argv;222 else223 Sepc = '\t';224 continue;225 case 't':226 Margin = 0;227 continue;228 case 'w':229 Linew = atoix(argv);230 continue;231 case 'n':232 Lnumb++;233 if((Numw = intopt(argv, &Nsepc)) <= 0)234 Numw = NUMW;235 case 'b':236 Balance = 1;237 continue;238 case 'p':239 Padodd = 1;240 continue;241 default:242 die("bad option");243 }244 } while((c = *++*argv) != '\0');245 if(Head == argv[1])246 argv++;247 continue;248 }249 *eargv++ = *argv;250 eargc++;251 }252 if(Len == 0)253 Len = LENGTH;254 if(Len <= Margin)255 Margin = 0;256 Plength = Len - Margin/2;257 if(Multi == 'm')258 Ncols = eargc;259 switch(Ncols) {260 case 0:261 Ncols = 1;262 case 1:263 break;264 default:265 if(Etabn == 0) /* respect explicit tab specification */266 Etabn = DEFTAB;267 }268 if(Linew == 0)269 Linew = Ncols != 1 && Sepc == 0? LINEW: 512;270 if(Lnumb)271 Linew -= Multi == 'm'? Numw: Numw*Ncols;272 if((Colw = (Linew - Ncols + 1)/Ncols) < 1)273 die("width too small");274 if(Ncols != 1 && Multi == 0) {275 ulong buflen = ((ulong)(Plength/Dblspace + 1))*(Linew+1)*sizeof(char);276 Buffer = getspace(buflen*sizeof(*Buffer));277 Bufend = &Buffer[buflen];278 Colpts = getspace((Ncols+1)*sizeof(*Colpts));279 }280 return eargc;281 }283 int284 intopt(char *argv[], int *optp)285 {286 int c;288 if((c = (*argv)[1]) != '\0' && !isdigit(c)) {289 *optp = c;290 (*argv)++;291 }292 c = atoix(argv);293 return c != 0? c: -1;294 }296 int297 pr(char *name)298 {299 char *date = 0, *head = 0;301 if(Multi != 'm' && mustopen(name, &Files[0]) == 0)302 return 0;303 if(Buffer)304 Bungetc(Files->f_f);305 if(Lnumb)306 Lnumb = 1;307 for(Page = 0;; putpage()) {308 if(C == -1)309 break;310 if(Buffer)311 nexbuf();312 Inpos = 0;313 if(get(0) == -1)314 break;315 Bflush(&bout);316 Page++;317 if(Page >= Fpage) {318 if(Margin == 0)319 continue;320 if(date == 0)321 date = getdate();322 if(head == 0)323 head = Head != 0 ? Head :324 Nfiles < 2? Files->f_name: nulls;325 Bprint(&bout, "\n\n");326 Nspace = Offset;327 putspace();328 Bprint(&bout, HEAD);329 }330 }331 if(Padodd && (Page&1) == 1) {332 Line = 0;333 if(Formfeed)334 put('\f');335 else336 while(Line < Len)337 put('\n');338 }339 C = '\0';340 return 1;341 }343 void344 putpage(void)345 {346 int colno;348 for(Line = Margin/2;; get(0)) {349 for(Nspace = Offset, colno = 0, Outpos = 0; C != '\f';) {350 if(Lnumb && C != -1 && (colno == 0 || Multi == 'a')) {351 if(Page >= Fpage) {352 putspace();353 Bprint(&bout, "%*ld", Numw, Buffer?354 Colpts[colno].c_lno++: Lnumb);355 Outpos += Numw;356 put(Nsepc);357 }358 Lnumb++;359 }360 for(Lcolpos=0, Pcolpos=0; C!='\n' && C!='\f' && C!=-1; get(colno))361 put(C);362 if(C==-1 || ++colno==Ncols || C=='\n' && get(colno)==-1)363 break;364 if(Sepc)365 put(Sepc);366 else367 if((Nspace += Colw - Lcolpos + 1) < 1)368 Nspace = 1;369 }370 /*371 if(C == -1) {372 if(Margin != 0)373 break;374 if(colno != 0)375 put('\n');376 return;377 }378 */379 if(C == -1 && colno == 0) {380 if(Margin != 0)381 break;382 return;383 }384 if(C == '\f')385 break;386 put('\n');387 if(Dblspace == 2 && Line < Plength)388 put('\n');389 if(Line >= Plength)390 break;391 }392 if(Formfeed)393 put('\f');394 else395 while(Line < Len)396 put('\n');397 }399 void400 nexbuf(void)401 {402 Rune *s = Buffer;403 Colp p = Colpts;404 int j, c, bline = 0;406 for(;;) {407 p->c_ptr0 = p->c_ptr = s;408 if(p == &Colpts[Ncols])409 return;410 (p++)->c_lno = Lnumb + bline;411 for(j = (Len - Margin)/Dblspace; --j >= 0; bline++)412 for(Inpos = 0;;) {413 if((c = Bgetrune(Files->f_f)) == -1) {414 for(*s = -1; p <= &Colpts[Ncols]; p++)415 p->c_ptr0 = p->c_ptr = s;416 if(Balance)417 balance(bline);418 return;419 }420 if(ISPRINT(c))421 Inpos++;422 if(Inpos <= Colw || c == '\n') {423 *s = c;424 if(++s >= Bufend)425 die("page-buffer overflow");426 }427 if(c == '\n')428 break;429 switch(c) {430 case '\b':431 if(Inpos == 0)432 s--;433 case ESC:434 if(Inpos > 0)435 Inpos--;436 }437 }438 }439 }441 /*442 * line balancing for last page443 */444 void445 balance(int bline)446 {447 Rune *s = Buffer;448 Colp p = Colpts;449 int colno = 0, j, c, l;451 c = bline % Ncols;452 l = (bline + Ncols - 1)/Ncols;453 bline = 0;454 do {455 for(j = 0; j < l; ++j)456 while(*s++ != '\n')457 ;458 (++p)->c_lno = Lnumb + (bline += l);459 p->c_ptr0 = p->c_ptr = s;460 if(++colno == c)461 l--;462 } while(colno < Ncols - 1);463 }465 int466 get(int colno)467 {468 static int peekc = 0;469 Colp p;470 Fils *q;471 long c;473 if(peekc) {474 peekc = 0;475 c = Etabc;476 } else477 if(Buffer) {478 p = &Colpts[colno];479 if(p->c_ptr >= (p+1)->c_ptr0)480 c = -1;481 else482 if((c = *p->c_ptr) != -1)483 p->c_ptr++;484 } else485 if((c = (q = &Files[Multi == 'a'? 0: colno])->f_nextc) == -1) {486 for(q = &Files[Nfiles]; --q >= Files && q->f_nextc == -1;)487 ;488 if(q >= Files)489 c = '\n';490 } else491 q->f_nextc = Bgetrune(q->f_f);492 if(Etabn != 0 && c == Etabc) {493 Inpos++;494 peekc = ETABS;495 c = ' ';496 } else497 if(ISPRINT(c))498 Inpos++;499 else500 switch(c) {501 case '\b':502 case ESC:503 if(Inpos > 0)504 Inpos--;505 break;506 case '\f':507 if(Ncols == 1)508 break;509 c = '\n';510 case '\n':511 case '\r':512 Inpos = 0;513 }514 return C = c;515 }517 void518 put(long c)519 {520 int move;522 switch(c) {523 case ' ':524 Nspace++;525 Lcolpos++;526 return;527 case '\b':528 if(Lcolpos == 0)529 return;530 if(Nspace > 0) {531 Nspace--;532 Lcolpos--;533 return;534 }535 if(Lcolpos > Pcolpos) {536 Lcolpos--;537 return;538 }539 case ESC:540 move = -1;541 break;542 case '\n':543 Line++;544 case '\r':545 case '\f':546 Pcolpos = 0;547 Lcolpos = 0;548 Nspace = 0;549 Outpos = 0;550 default:551 move = (ISPRINT(c) != 0);552 }553 if(Page < Fpage)554 return;555 if(Lcolpos > 0 || move > 0)556 Lcolpos += move;557 if(Lcolpos <= Colw) {558 putspace();559 Bputrune(&bout, c);560 Pcolpos = Lcolpos;561 Outpos += move;562 }563 }565 void566 putspace(void)567 {568 int nc;570 for(; Nspace > 0; Outpos += nc, Nspace -= nc)571 if(ITABS)572 Bputc(&bout, Itabc);573 else {574 nc = 1;575 Bputc(&bout, ' ');576 }577 }579 int580 atoix(char **p)581 {582 int n = 0, c;584 while(isdigit(c = *++*p))585 n = 10*n + c - '0';586 (*p)--;587 return n;588 }590 /*591 * Defer message about failure to open file to prevent messing up592 * alignment of page with tear perforations or form markers.593 * Treat empty file as special case and report as diagnostic.594 */595 Biobuf*596 mustopen(char *s, Fils *f)597 {598 char *tmp;600 if(*s == '\0') {601 f->f_name = STDINNAME();602 f->f_f = malloc(sizeof(Biobuf));603 if(f->f_f == 0)604 cerror("no memory");605 Binit(f->f_f, 0, OREAD);606 } else607 if((f->f_f = Bopen(f->f_name = s, OREAD)) == 0) {608 tmp = ffiler(f->f_name);609 s = strcpy((char*)getspace(strlen(tmp) + 1), tmp);610 free(tmp);611 }612 if(f->f_f != 0) {613 if((f->f_nextc = Bgetrune(f->f_f)) >= 0 || Multi == 'm')614 return f->f_f;615 sprint(s = (char*)getspace(strlen(f->f_name) + 1 + EMPTY),616 "%s -- empty file\n", f->f_name);617 Bterm(f->f_f);618 }619 error = 1;620 cerror(s);621 fprint(2, "\n");622 return 0;623 }625 void*626 getspace(ulong n)627 {628 void *t;630 if((t = malloc(n)) == 0)631 die("out of space");632 return t;633 }635 void636 die(char *s)637 {638 error++;639 errprint();640 cerror(s);641 Bputc(&bout, '\n');642 exits("error");643 }645 /*646 void647 onintr(void)648 {649 error++;650 errprint();651 exits("error");652 }653 /**/655 /*656 * print accumulated error reports657 */658 void659 errprint(void)660 {661 Bflush(&bout);662 for(; err != 0; err = err->e_nextp) {663 cerror(err->e_mess);664 fprint(2, "\n");665 }666 }