xref: /plan9/sys/src/cmd/pr.c (revision 208510e168b9c00c3c6969f56b807dc03575167d)
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