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]+5
9 */
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" */
32 typedef struct Fils Fils;
33 typedef struct Colp* Colp;
34 typedef struct Err Err;
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 };
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;
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);
112 /*
113 * return date file was last modified
114 */
115 #define getdate prgetdate
117 char*
118 getdate(void)
120 static char *now = 0;
121 static Dir *sbuf;
122 ulong mtime;
124 if(Nfiles > 1 || Files->f_name == nulls) {
125 if(now == 0) {
126 mtime = time(0);
127 now = ctime(mtime);
129 return now;
131 mtime = 0;
132 sbuf = dirstat(Files->f_name);
133 if(sbuf){
134 mtime = sbuf->mtime;
135 free(sbuf);
137 return ctime(mtime);
140 char*
141 ffiler(char *s)
143 return smprint("can't open %s\n", s);
146 void
147 main(int argc, char *argv[])
149 Fils fstr[NFILES];
150 int nfdone = 0;
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++;
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);
171 int
172 findopt(int argc, char *argv[])
174 char **eargv = argv;
175 int eargc = 0, c;
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");
244 } while((c = *++*argv) != '\0');
245 if(Head == argv[1])
246 argv++;
247 continue;
249 *eargv++ = *argv;
250 eargc++;
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;
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));
280 return eargc;
283 int
284 intopt(char *argv[], int *optp)
286 int c;
288 if((c = (*argv)[1]) != '\0' && !isdigit(c)) {
289 *optp = c;
290 (*argv)++;
292 c = atoix(argv);
293 return c != 0? c: -1;
296 int
297 pr(char *name)
299 char *date = 0, *head = 0;
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);
331 if(Padodd && (Page&1) == 1) {
332 Line = 0;
333 if(Formfeed)
334 put('\f');
335 else
336 while(Line < Len)
337 put('\n');
339 C = '\0';
340 return 1;
343 void
344 putpage(void)
346 int colno;
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);
358 Lnumb++;
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;
370 /*
371 if(C == -1) {
372 if(Margin != 0)
373 break;
374 if(colno != 0)
375 put('\n');
376 return;
378 */
379 if(C == -1 && colno == 0) {
380 if(Margin != 0)
381 break;
382 return;
384 if(C == '\f')
385 break;
386 put('\n');
387 if(Dblspace == 2 && Line < Plength)
388 put('\n');
389 if(Line >= Plength)
390 break;
392 if(Formfeed)
393 put('\f');
394 else
395 while(Line < Len)
396 put('\n');
399 void
400 nexbuf(void)
402 Rune *s = Buffer;
403 Colp p = Colpts;
404 int j, c, bline = 0;
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;
420 if(ISPRINT(c))
421 Inpos++;
422 if(Inpos <= Colw || c == '\n') {
423 *s = c;
424 if(++s >= Bufend)
425 die("page-buffer overflow");
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--;
441 /*
442 * line balancing for last page
443 */
444 void
445 balance(int bline)
447 Rune *s = Buffer;
448 Colp p = Colpts;
449 int colno = 0, j, c, l;
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')
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);
465 int
466 get(int colno)
468 static int peekc = 0;
469 Colp p;
470 Fils *q;
471 long c;
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;)
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;
514 return C = c;
517 void
518 put(long c)
520 int move;
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;
535 if(Lcolpos > Pcolpos) {
536 Lcolpos--;
537 return;
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);
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;
565 void
566 putspace(void)
568 int nc;
570 for(; Nspace > 0; Outpos += nc, Nspace -= nc)
571 if(ITABS)
572 Bputc(&bout, Itabc);
573 else {
574 nc = 1;
575 Bputc(&bout, ' ');
579 int
580 atoix(char **p)
582 int n = 0, c;
584 while(isdigit(c = *++*p))
585 n = 10*n + c - '0';
586 (*p)--;
587 return n;
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)
598 char *tmp;
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);
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);
619 error = 1;
620 cerror(s);
621 fprint(2, "\n");
622 return 0;
625 void*
626 getspace(ulong n)
628 void *t;
630 if((t = malloc(n)) == 0)
631 die("out of space");
632 return t;
635 void
636 die(char *s)
638 error++;
639 errprint();
640 cerror(s);
641 Bputc(&bout, '\n');
642 exits("error");
645 /*
646 void
647 onintr(void)
649 error++;
650 errprint();
651 exits("error");
653 /**/
655 /*
656 * print accumulated error reports
657 */
658 void
659 errprint(void)
661 Bflush(&bout);
662 for(; err != 0; err = err->e_nextp) {
663 cerror(err->e_mess);
664 fprint(2, "\n");