]> git.lizzy.rs Git - plan9front.git/blob - sys/src/cmd/pr.c
2aff9ab433d91e3d777fc19250bd1b3b57935507
[plan9front.git] / sys / src / cmd / pr.c
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*
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 }