plan9port

[fork] Plan 9 from user space
git clone git://src.adamsgaard.dk/plan9port # fast
git clone https://src.adamsgaard.dk/plan9port.git # slow
Log | Files | Refs | README | LICENSE Back to index

pr.c (11219B)


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