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