pr.c (11219B)
1 #include <u.h> 2 #include <libc.h> 3 #include <bio.h> 4 #include <ctype.h> 5 6 /* 7 * PR command (print files in pages and columns, with headings) 8 * 2+head+2+page[56]+5 9 */ 10 11 #define err pr_err 12 #define ISPRINT(c) ((c) >= ' ') 13 #define ESC '\033' 14 #define LENGTH 66 15 #define LINEW 72 16 #define NUMW 5 17 #define MARGIN 10 18 #define DEFTAB 8 19 #define NFILES 10 20 #define HEAD "%12.12s %4.4s %s Page %d\n\n\n", date+4, date+24, head, Page 21 #define TOLOWER(c) (isupper(c) ? tolower(c) : c) /* ouch! */ 22 #define cerror(S) fprint(2, "pr: %s", S) 23 #define STDINNAME() nulls 24 #define TTY "/dev/cons", 0 25 #define PROMPT() fprint(2, "\a") /* BEL */ 26 #define TABS(N,C) if((N = intopt(argv, &C)) < 0) N = DEFTAB 27 #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" */ 31 32 typedef struct Fils Fils; 33 typedef struct Colp* Colp; 34 typedef struct Err Err; 35 36 struct Fils 37 { 38 Biobuf* f_f; 39 char* f_name; 40 long f_nextc; 41 }; 42 struct Colp 43 { 44 Rune* c_ptr; 45 Rune* c_ptr0; 46 long c_lno; 47 }; 48 struct Err 49 { 50 Err* e_nextp; 51 char* e_mess; 52 }; 53 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; 94 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); 111 112 /* 113 * return date file was last modified 114 */ 115 #define getdate prgetdate 116 117 char* 118 getdate(void) 119 { 120 static char *now = 0; 121 static Dir *sbuf; 122 ulong mtime; 123 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 } 139 140 char* 141 ffiler(char *s) 142 { 143 return smprint("can't open %s\n", s); 144 } 145 146 void 147 main(int argc, char *argv[]) 148 { 149 Fils fstr[NFILES]; 150 int nfdone = 0; 151 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 } 170 171 int 172 findopt(int argc, char *argv[]) 173 { 174 char **eargv = argv; 175 int eargc = 0, c; 176 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 } else 188 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 else 223 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 } 282 283 int 284 intopt(char *argv[], int *optp) 285 { 286 int c; 287 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 } 295 296 int 297 pr(char *name) 298 { 299 char *date = 0, *head = 0; 300 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 else 336 while(Line < Len) 337 put('\n'); 338 } 339 C = '\0'; 340 return 1; 341 } 342 343 void 344 putpage(void) 345 { 346 int colno; 347 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 else 367 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 else 395 while(Line < Len) 396 put('\n'); 397 } 398 399 void 400 nexbuf(void) 401 { 402 Rune *s = Buffer; 403 Colp p = Colpts; 404 int j, c, bline = 0; 405 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 } 440 441 /* 442 * line balancing for last page 443 */ 444 void 445 balance(int bline) 446 { 447 Rune *s = Buffer; 448 Colp p = Colpts; 449 int colno = 0, j, c, l; 450 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 } 464 465 int 466 get(int colno) 467 { 468 static int peekc = 0; 469 Colp p; 470 Fils *q; 471 long c; 472 473 if(peekc) { 474 peekc = 0; 475 c = Etabc; 476 } else 477 if(Buffer) { 478 p = &Colpts[colno]; 479 if(p->c_ptr >= (p+1)->c_ptr0) 480 c = -1; 481 else 482 if((c = *p->c_ptr) != -1) 483 p->c_ptr++; 484 } else 485 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 } else 491 q->f_nextc = Bgetrune(q->f_f); 492 if(Etabn != 0 && c == Etabc) { 493 Inpos++; 494 peekc = ETABS; 495 c = ' '; 496 } else 497 if(ISPRINT(c)) 498 Inpos++; 499 else 500 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 } 516 517 void 518 put(long c) 519 { 520 int move; 521 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 } 564 565 void 566 putspace(void) 567 { 568 int nc; 569 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 } 578 579 int 580 atoix(char **p) 581 { 582 int n = 0, c; 583 584 while(isdigit(c = *++*p)) 585 n = 10*n + c - '0'; 586 (*p)--; 587 return n; 588 } 589 590 /* 591 * Defer message about failure to open file to prevent messing up 592 * 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; 599 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 } else 607 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 } 624 625 void* 626 getspace(ulong n) 627 { 628 void *t; 629 630 if((t = malloc(n)) == 0) 631 die("out of space"); 632 return t; 633 } 634 635 void 636 die(char *s) 637 { 638 error++; 639 errprint(); 640 cerror(s); 641 Bputc(&bout, '\n'); 642 exits("error"); 643 } 644 645 /* 646 void 647 onintr(void) 648 { 649 error++; 650 errprint(); 651 exits("error"); 652 } 653 /**/ 654 655 /* 656 * print accumulated error reports 657 */ 658 void 659 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 }