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