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