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 ISPRINT(c) ((c) >= ' ')12 #define ESC '\033'13 #define LENGTH 6614 #define LINEW 7215 #define NUMW 516 #define MARGIN 1017 #define DEFTAB 818 #define NFILES 1019 #define HEAD "%12.12s %4.4s %s Page %d\n\n\n", date+4, date+24, head, Page20 #define TOLOWER(c) (isupper(c) ? tolower(c) : c) /* ouch! */21 #define cerror(S) fprint(2, "pr: %s", S)22 #define STDINNAME() nulls23 #define TTY "/dev/cons", 024 #define PROMPT() fprint(2, "\a") /* BEL */25 #define TABS(N,C) if((N = intopt(argv, &C)) < 0) N = DEFTAB26 #define ETABS (Inpos % Etabn)27 #define ITABS (Itabn > 0 && Nspace > 1 && Nspace >= (nc = Itabn - Outpos % Itabn))28 #define NSEPC '\t'29 #define EMPTY 14 /* length of " -- empty file" */31 typedef struct Fils Fils;32 typedef struct Colp* Colp;33 typedef struct Err Err;35 struct Fils36 {37 Biobuf* f_f;38 char* f_name;39 long f_nextc;40 };41 struct Colp42 {43 Rune* c_ptr;44 Rune* c_ptr0;45 long c_lno;46 };47 struct Err48 {49 Err* e_nextp;50 char* e_mess;51 };53 int Balance = 0;54 Biobuf bout;55 Rune* Bufend;56 Rune* Buffer = 0;57 int C = '\0';58 Colp Colpts;59 int Colw;60 int Dblspace = 1;61 Err* err = 0;62 int error = 0;63 int Etabc = '\t';64 int Etabn = 0;65 Fils* Files;66 int Formfeed = 0;67 int Fpage = 1;68 char* Head = 0;69 int Inpos;70 int Itabc = '\t';71 int Itabn = 0;72 Err* Lasterr = (Err*)&err;73 int Lcolpos;74 int Len = LENGTH;75 int Line;76 int Linew = 0;77 long Lnumb = 0;78 int Margin = MARGIN;79 int Multi = 0;80 int Ncols = 1;81 int Nfiles = 0;82 int Nsepc = NSEPC;83 int Nspace;84 char nulls[] = "";85 int Numw;86 int Offset = 0;87 int Outpos;88 int Padodd;89 int Page;90 int Pcolpos;91 int Plength;92 int Sepc = 0;94 extern int atoix(char**);95 extern void balance(int);96 extern void die(char*);97 extern void errprint(void);98 extern char* ffiler(char*);99 extern int findopt(int, char**);100 extern int get(int);101 extern void* getspace(ulong);102 extern int intopt(char**, int*);103 extern void main(int, char**);104 extern Biobuf* mustopen(char*, Fils*);105 extern void nexbuf(void);106 extern int pr(char*);107 extern void put(long);108 extern void putpage(void);109 extern void putspace(void);111 /*112 * return date file was last modified113 */114 #define getdate prgetdate116 char*117 getdate(void)118 {119 static char *now = 0;120 static Dir *sbuf;121 ulong mtime;123 if(Nfiles > 1 || Files->f_name == nulls) {124 if(now == 0) {125 mtime = time(0);126 now = ctime(mtime);127 }128 return now;129 }130 mtime = 0;131 sbuf = dirstat(Files->f_name);132 if(sbuf){133 mtime = sbuf->mtime;134 free(sbuf);135 }136 return ctime(mtime);137 }139 char*140 ffiler(char *s)141 {142 return smprint("can't open %s\n", s);143 }145 void146 main(int argc, char *argv[])147 {148 Fils fstr[NFILES];149 int nfdone = 0;151 Binit(&bout, 1, OWRITE);152 Files = fstr;153 for(argc = findopt(argc, argv); argc > 0; --argc, ++argv)154 if(Multi == 'm') {155 if(Nfiles >= NFILES - 1)156 die("too many files");157 if(mustopen(*argv, &Files[Nfiles++]) == 0)158 nfdone++; /* suppress printing */159 } else {160 if(pr(*argv))161 Bterm(Files->f_f);162 nfdone++;163 }164 if(!nfdone) /* no files named, use stdin */165 pr(nulls); /* on GCOS, use current file, if any */166 errprint(); /* print accumulated error reports */167 exits(error? "error": 0);168 }170 int171 findopt(int argc, char *argv[])172 {173 char **eargv = argv;174 int eargc = 0, c;176 while(--argc > 0) {177 switch(c = **++argv) {178 case '-':179 if((c = *++*argv) == '\0')180 break;181 case '+':182 do {183 if(isdigit(c)) {184 --*argv;185 Ncols = atoix(argv);186 } else187 switch(c = TOLOWER(c)) {188 case '+':189 if((Fpage = atoix(argv)) < 1)190 Fpage = 1;191 continue;192 case 'd':193 Dblspace = 2;194 continue;195 case 'e':196 TABS(Etabn, Etabc);197 continue;198 case 'f':199 Formfeed++;200 continue;201 case 'h':202 if(--argc > 0)203 Head = argv[1];204 continue;205 case 'i':206 TABS(Itabn, Itabc);207 continue;208 case 'l':209 Len = atoix(argv);210 continue;211 case 'a':212 case 'm':213 Multi = c;214 continue;215 case 'o':216 Offset = atoix(argv);217 continue;218 case 's':219 if((Sepc = (*argv)[1]) != '\0')220 ++*argv;221 else222 Sepc = '\t';223 continue;224 case 't':225 Margin = 0;226 continue;227 case 'w':228 Linew = atoix(argv);229 continue;230 case 'n':231 Lnumb++;232 if((Numw = intopt(argv, &Nsepc)) <= 0)233 Numw = NUMW;234 case 'b':235 Balance = 1;236 continue;237 case 'p':238 Padodd = 1;239 continue;240 default:241 die("bad option");242 }243 } while((c = *++*argv) != '\0');244 if(Head == argv[1])245 argv++;246 continue;247 }248 *eargv++ = *argv;249 eargc++;250 }251 if(Len == 0)252 Len = LENGTH;253 if(Len <= Margin)254 Margin = 0;255 Plength = Len - Margin/2;256 if(Multi == 'm')257 Ncols = eargc;258 switch(Ncols) {259 case 0:260 Ncols = 1;261 case 1:262 break;263 default:264 if(Etabn == 0) /* respect explicit tab specification */265 Etabn = DEFTAB;266 }267 if(Linew == 0)268 Linew = Ncols != 1 && Sepc == 0? LINEW: 512;269 if(Lnumb)270 Linew -= Multi == 'm'? Numw: Numw*Ncols;271 if((Colw = (Linew - Ncols + 1)/Ncols) < 1)272 die("width too small");273 if(Ncols != 1 && Multi == 0) {274 ulong buflen = ((ulong)(Plength/Dblspace + 1))*(Linew+1)*sizeof(char);275 Buffer = getspace(buflen*sizeof(*Buffer));276 Bufend = &Buffer[buflen];277 Colpts = getspace((Ncols+1)*sizeof(*Colpts));278 }279 return eargc;280 }282 int283 intopt(char *argv[], int *optp)284 {285 int c;287 if((c = (*argv)[1]) != '\0' && !isdigit(c)) {288 *optp = c;289 (*argv)++;290 }291 c = atoix(argv);292 return c != 0? c: -1;293 }295 int296 pr(char *name)297 {298 char *date = 0, *head = 0;300 if(Multi != 'm' && mustopen(name, &Files[0]) == 0)301 return 0;302 if(Buffer)303 Bungetc(Files->f_f);304 if(Lnumb)305 Lnumb = 1;306 for(Page = 0;; putpage()) {307 if(C == -1)308 break;309 if(Buffer)310 nexbuf();311 Inpos = 0;312 if(get(0) == -1)313 break;314 Bflush(&bout);315 Page++;316 if(Page >= Fpage) {317 if(Margin == 0)318 continue;319 if(date == 0)320 date = getdate();321 if(head == 0)322 head = Head != 0 ? Head :323 Nfiles < 2? Files->f_name: nulls;324 Bprint(&bout, "\n\n");325 Nspace = Offset;326 putspace();327 Bprint(&bout, HEAD);328 }329 }330 if(Padodd && (Page&1) == 1) {331 Line = 0;332 if(Formfeed)333 put('\f');334 else335 while(Line < Len)336 put('\n');337 }338 C = '\0';339 return 1;340 }342 void343 putpage(void)344 {345 int colno;347 for(Line = Margin/2;; get(0)) {348 for(Nspace = Offset, colno = 0, Outpos = 0; C != '\f';) {349 if(Lnumb && C != -1 && (colno == 0 || Multi == 'a')) {350 if(Page >= Fpage) {351 putspace();352 Bprint(&bout, "%*ld", Numw, Buffer?353 Colpts[colno].c_lno++: Lnumb);354 Outpos += Numw;355 put(Nsepc);356 }357 Lnumb++;358 }359 for(Lcolpos=0, Pcolpos=0; C!='\n' && C!='\f' && C!=-1; get(colno))360 put(C);361 if(C==-1 || ++colno==Ncols || C=='\n' && get(colno)==-1)362 break;363 if(Sepc)364 put(Sepc);365 else366 if((Nspace += Colw - Lcolpos + 1) < 1)367 Nspace = 1;368 }369 /*370 if(C == -1) {371 if(Margin != 0)372 break;373 if(colno != 0)374 put('\n');375 return;376 }377 */378 if(C == -1 && colno == 0) {379 if(Margin != 0)380 break;381 return;382 }383 if(C == '\f')384 break;385 put('\n');386 if(Dblspace == 2 && Line < Plength)387 put('\n');388 if(Line >= Plength)389 break;390 }391 if(Formfeed)392 put('\f');393 else394 while(Line < Len)395 put('\n');396 }398 void399 nexbuf(void)400 {401 Rune *s = Buffer;402 Colp p = Colpts;403 int j, c, bline = 0;405 for(;;) {406 p->c_ptr0 = p->c_ptr = s;407 if(p == &Colpts[Ncols])408 return;409 (p++)->c_lno = Lnumb + bline;410 for(j = (Len - Margin)/Dblspace; --j >= 0; bline++)411 for(Inpos = 0;;) {412 if((c = Bgetrune(Files->f_f)) == -1) {413 for(*s = -1; p <= &Colpts[Ncols]; p++)414 p->c_ptr0 = p->c_ptr = s;415 if(Balance)416 balance(bline);417 return;418 }419 if(ISPRINT(c))420 Inpos++;421 if(Inpos <= Colw || c == '\n') {422 *s = c;423 if(++s >= Bufend)424 die("page-buffer overflow");425 }426 if(c == '\n')427 break;428 switch(c) {429 case '\b':430 if(Inpos == 0)431 s--;432 case ESC:433 if(Inpos > 0)434 Inpos--;435 }436 }437 }438 }440 /*441 * line balancing for last page442 */443 void444 balance(int bline)445 {446 Rune *s = Buffer;447 Colp p = Colpts;448 int colno = 0, j, c, l;450 c = bline % Ncols;451 l = (bline + Ncols - 1)/Ncols;452 bline = 0;453 do {454 for(j = 0; j < l; ++j)455 while(*s++ != '\n')456 ;457 (++p)->c_lno = Lnumb + (bline += l);458 p->c_ptr0 = p->c_ptr = s;459 if(++colno == c)460 l--;461 } while(colno < Ncols - 1);462 }464 int465 get(int colno)466 {467 static int peekc = 0;468 Colp p;469 Fils *q;470 long c;472 if(peekc) {473 peekc = 0;474 c = Etabc;475 } else476 if(Buffer) {477 p = &Colpts[colno];478 if(p->c_ptr >= (p+1)->c_ptr0)479 c = -1;480 else481 if((c = *p->c_ptr) != -1)482 p->c_ptr++;483 } else484 if((c = (q = &Files[Multi == 'a'? 0: colno])->f_nextc) == -1) {485 for(q = &Files[Nfiles]; --q >= Files && q->f_nextc == -1;)486 ;487 if(q >= Files)488 c = '\n';489 } else490 q->f_nextc = Bgetrune(q->f_f);491 if(Etabn != 0 && c == Etabc) {492 Inpos++;493 peekc = ETABS;494 c = ' ';495 } else496 if(ISPRINT(c))497 Inpos++;498 else499 switch(c) {500 case '\b':501 case ESC:502 if(Inpos > 0)503 Inpos--;504 break;505 case '\f':506 if(Ncols == 1)507 break;508 c = '\n';509 case '\n':510 case '\r':511 Inpos = 0;512 }513 return C = c;514 }516 void517 put(long c)518 {519 int move;521 switch(c) {522 case ' ':523 Nspace++;524 Lcolpos++;525 return;526 case '\b':527 if(Lcolpos == 0)528 return;529 if(Nspace > 0) {530 Nspace--;531 Lcolpos--;532 return;533 }534 if(Lcolpos > Pcolpos) {535 Lcolpos--;536 return;537 }538 case ESC:539 move = -1;540 break;541 case '\n':542 Line++;543 case '\r':544 case '\f':545 Pcolpos = 0;546 Lcolpos = 0;547 Nspace = 0;548 Outpos = 0;549 default:550 move = (ISPRINT(c) != 0);551 }552 if(Page < Fpage)553 return;554 if(Lcolpos > 0 || move > 0)555 Lcolpos += move;556 if(Lcolpos <= Colw) {557 putspace();558 Bputrune(&bout, c);559 Pcolpos = Lcolpos;560 Outpos += move;561 }562 }564 void565 putspace(void)566 {567 int nc;569 for(; Nspace > 0; Outpos += nc, Nspace -= nc)570 if(ITABS)571 Bputc(&bout, Itabc);572 else {573 nc = 1;574 Bputc(&bout, ' ');575 }576 }578 int579 atoix(char **p)580 {581 int n = 0, c;583 while(isdigit(c = *++*p))584 n = 10*n + c - '0';585 (*p)--;586 return n;587 }589 /*590 * Defer message about failure to open file to prevent messing up591 * alignment of page with tear perforations or form markers.592 * Treat empty file as special case and report as diagnostic.593 */594 Biobuf*595 mustopen(char *s, Fils *f)596 {597 char *tmp;599 if(*s == '\0') {600 f->f_name = STDINNAME();601 f->f_f = malloc(sizeof(Biobuf));602 if(f->f_f == 0)603 cerror("no memory");604 Binit(f->f_f, 0, OREAD);605 } else606 if((f->f_f = Bopen(f->f_name = s, OREAD)) == 0) {607 tmp = ffiler(f->f_name);608 s = strcpy((char*)getspace(strlen(tmp) + 1), tmp);609 free(tmp);610 }611 if(f->f_f != 0) {612 if((f->f_nextc = Bgetrune(f->f_f)) >= 0 || Multi == 'm')613 return f->f_f;614 sprint(s = (char*)getspace(strlen(f->f_name) + 1 + EMPTY),615 "%s -- empty file\n", f->f_name);616 Bterm(f->f_f);617 }618 error = 1;619 cerror(s);620 fprint(2, "\n");621 return 0;622 }624 void*625 getspace(ulong n)626 {627 void *t;629 if((t = malloc(n)) == 0)630 die("out of space");631 return t;632 }634 void635 die(char *s)636 {637 error++;638 errprint();639 cerror(s);640 Bputc(&bout, '\n');641 exits("error");642 }644 /*645 void646 onintr(void)647 {648 error++;649 errprint();650 exits("error");651 }652 /**/654 /*655 * print accumulated error reports656 */657 void658 errprint(void)659 {660 Bflush(&bout);661 for(; err != 0; err = err->e_nextp) {662 cerror(err->e_mess);663 fprint(2, "\n");664 }665 }