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