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 20
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*
getdate(void)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*
ffiler(char * s)138 ffiler(char *s)
139 {
140 return smprint("can't open %s\n", s);
141 }
142
143 void
main(int argc,char * argv[])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
findopt(int argc,char * argv[])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
intopt(char * argv[],int * optp)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
pr(char * name)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
putpage(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
nexbuf(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
balance(int bline)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
get(int colno)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
put(long c)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
putspace(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
atoix(char ** p)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*
mustopen(char * s,Fils * f)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*
getspace(ulong n)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
die(char * s)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
errprint(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