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