xref: /original-bsd/usr.bin/ul/ul.c (revision 3ca00c4d)
1 /*	@(#)ul.c	4.4	05/26/83	*/
2 static char SccsId[] =	"@(#)ul.c	4.4 (Berkeley) 05/26/83";
3 
4 #include <stdio.h>
5 
6 #define	IESC	'\033'
7 #define	SO	'\016'
8 #define	SI	'\017'
9 #define	HFWD	'9'
10 #define	HREV	'8'
11 #define	FREV	'7'
12 #define	MAXBUF	512
13 
14 #define	NORMAL	000
15 #define	ALTSET	001	/* Reverse */
16 #define	SUPERSC	002	/* Dim */
17 #define	SUBSC	004	/* Dim | Ul */
18 #define	UNDERL	010	/* Ul */
19 #define	BOLD	020	/* Bold */
20 
21 int	must_use_uc, must_overstrike;
22 char	*CURS_UP, *CURS_RIGHT, *CURS_LEFT,
23 	*ENTER_STANDOUT, *EXIT_STANDOUT, *ENTER_UNDERLINE, *EXIT_UNDERLINE,
24 	*ENTER_DIM, *ENTER_BOLD, *ENTER_REVERSE, *UNDER_CHAR, *EXIT_ATTRIBUTES;
25 
26 struct	CHAR	{
27 	char	c_mode;
28 	char	c_char;
29 } ;
30 
31 char	buf[BUFSIZ];
32 
33 struct	CHAR	obuf[MAXBUF];
34 int	col, maxcol;
35 int	mode;
36 int	halfpos;
37 int	upln;
38 int	iflag;
39 
40 main(argc, argv)
41 	int argc;
42 	char **argv;
43 {
44 	int c;
45 	char *cp, *termtype;
46 	FILE *f;
47 	char termcap[1024];
48 	char *getenv();
49 	extern int optind;
50 	extern char *optarg;
51 
52 	termtype = getenv("TERM");
53 	if (termtype == NULL || (argv[0][0] == 'c' && !isatty(1)))
54 		termtype = "lpr";
55 	while ((c=getopt(argc, argv, "it:T:")) != EOF)
56 		switch(c) {
57 
58 		case 't':
59 		case 'T': /* for nroff compatibility */
60 				termtype = optarg;
61 			break;
62 		case 'i':
63 			iflag = 1;
64 			break;
65 
66 		default:
67 			fprintf(stderr,
68 				"Usage: %s [ -i ] [ -tTerm ] file...\n",
69 				argv[0]);
70 			exit(1);
71 		}
72 
73 	switch(tgetent(termcap, termtype)) {
74 
75 	case 1:
76 		break;
77 
78 	default:
79 		fprintf(stderr,"trouble reading termcap");
80 		/* fall through to ... */
81 
82 	case 0:
83 		/* No such terminal type - assume dumb */
84 		strcpy(termcap, "dumb:os:col#80:cr=^M:sf=^J:am:");
85 		break;
86 	}
87 	initcap();
88 	if (    (tgetflag("os") && ENTER_BOLD==NULL ) ||
89 		(tgetflag("ul") && ENTER_UNDERLINE==NULL && UNDER_CHAR==NULL))
90 			must_overstrike = 1;
91 	setbuf(stdout, buf);
92 	initbuf();
93 	if (optind == argc)
94 		filter(stdin);
95 	else for (; optind<argc; optind++) {
96 		f = fopen(argv[optind],"r");
97 		if (f == NULL) {
98 			perror(argv[optind]);
99 			exit(1);
100 		} else
101 			filter(f);
102 	}
103 	exit(0);
104 }
105 
106 filter(f)
107 FILE *f;
108 {
109 	register c;
110 
111 	while((c = getc(f)) != EOF) switch(c) {
112 
113 	case '\b':
114 		if (col > 0)
115 			col--;
116 		continue;
117 
118 	case '\t':
119 		col = (col+8) & ~07;
120 		if (col > maxcol)
121 			maxcol = col;
122 		continue;
123 
124 	case '\r':
125 		col = 0;
126 		continue;
127 
128 	case SO:
129 		mode |= ALTSET;
130 		continue;
131 
132 	case SI:
133 		mode &= ~ALTSET;
134 		continue;
135 
136 	case IESC:
137 		switch (c = getc(f)) {
138 
139 		case HREV:
140 			if (halfpos == 0) {
141 				mode |= SUPERSC;
142 				halfpos--;
143 			} else if (halfpos > 0) {
144 				mode &= ~SUBSC;
145 				halfpos--;
146 			} else {
147 				halfpos = 0;
148 				reverse();
149 			}
150 			continue;
151 
152 		case HFWD:
153 			if (halfpos == 0) {
154 				mode |= SUBSC;
155 				halfpos++;
156 			} else if (halfpos < 0) {
157 				mode &= ~SUPERSC;
158 				halfpos++;
159 			} else {
160 				halfpos = 0;
161 				fwd();
162 			}
163 			continue;
164 
165 		case FREV:
166 			reverse();
167 			continue;
168 
169 		default:
170 			fprintf(stderr,
171 				"Unknown escape sequence in input: %o, %o\n",
172 				IESC, c);
173 			exit(1);
174 		}
175 		continue;
176 
177 	case '_':
178 		if (obuf[col].c_char)
179 			obuf[col].c_mode |= UNDERL | mode;
180 		else
181 			obuf[col].c_char = '_';
182 	case ' ':
183 		col++;
184 		if (col > maxcol)
185 			maxcol = col;
186 		continue;
187 
188 	case '\n':
189 		flushln();
190 		continue;
191 
192 	default:
193 		if (c < ' ')	/* non printing */
194 			continue;
195 		if (obuf[col].c_char == '\0') {
196 			obuf[col].c_char = c;
197 			obuf[col].c_mode = mode;
198 		} else if (obuf[col].c_char == '_') {
199 			obuf[col].c_char = c;
200 			obuf[col].c_mode |= UNDERL|mode;
201 		} else if (obuf[col].c_char == c)
202 			obuf[col].c_mode |= BOLD|mode;
203 		else {
204 			obuf[col].c_mode = c;
205 			obuf[col].c_mode = mode;
206 		}
207 		col++;
208 		if (col > maxcol)
209 			maxcol = col;
210 		continue;
211 	}
212 	if (maxcol)
213 		flushln();
214 }
215 
216 flushln()
217 {
218 	register lastmode;
219 	register i;
220 	int hadmodes = 0;
221 
222 	lastmode = NORMAL;
223 	for (i=0; i<maxcol; i++) {
224 		if (obuf[i].c_mode != lastmode) {
225 			hadmodes++;
226 			setmode(obuf[i].c_mode);
227 			lastmode = obuf[i].c_mode;
228 		}
229 		if (obuf[i].c_char == '\0') {
230 			if (upln) {
231 				puts(CURS_RIGHT);
232 			} else
233 				outc(' ');
234 		} else
235 			outc(obuf[i].c_char);
236 	}
237 	if (lastmode != NORMAL) {
238 		setmode(0);
239 	}
240 	if (must_overstrike && hadmodes)
241 		overstrike();
242 	putchar('\n');
243 	if (iflag && hadmodes)
244 		iattr();
245 	fflush(stdout);
246 	if (upln)
247 		upln--;
248 	initbuf();
249 }
250 
251 /*
252  * For terminals that can overstrike, overstrike underlines and bolds.
253  * We don't do anything with halfline ups and downs, or Greek.
254  */
255 overstrike()
256 {
257 	register int i;
258 	char lbuf[256];
259 	register char *cp = lbuf;
260 	int hadbold=0;
261 
262 	/* Set up overstrike buffer */
263 	for (i=0; i<maxcol; i++)
264 		switch (obuf[i].c_mode) {
265 		case NORMAL:
266 		default:
267 			*cp++ = ' ';
268 			break;
269 		case UNDERL:
270 			*cp++ = '_';
271 			break;
272 		case BOLD:
273 			*cp++ = obuf[i].c_char;
274 			hadbold=1;
275 			break;
276 		}
277 	putchar('\r');
278 	for (*cp=' '; *cp==' '; cp--)
279 		*cp = 0;
280 	for (cp=lbuf; *cp; cp++)
281 		putchar(*cp);
282 	if (hadbold) {
283 		putchar('\r');
284 		for (cp=lbuf; *cp; cp++)
285 			putchar(*cp=='_' ? ' ' : *cp);
286 		putchar('\r');
287 		for (cp=lbuf; *cp; cp++)
288 			putchar(*cp=='_' ? ' ' : *cp);
289 	}
290 }
291 
292 iattr()
293 {
294 	register int i;
295 	char lbuf[256];
296 	register char *cp = lbuf;
297 
298 	for (i=0; i<maxcol; i++)
299 		switch (obuf[i].c_mode) {
300 		case NORMAL:	*cp++ = ' '; break;
301 		case ALTSET:	*cp++ = 'g'; break;
302 		case SUPERSC:	*cp++ = '^'; break;
303 		case SUBSC:	*cp++ = 'v'; break;
304 		case UNDERL:	*cp++ = '_'; break;
305 		case BOLD:	*cp++ = '!'; break;
306 		default:	*cp++ = 'X'; break;
307 		}
308 	for (*cp=' '; *cp==' '; cp--)
309 		*cp = 0;
310 	for (cp=lbuf; *cp; cp++)
311 		putchar(*cp);
312 	putchar('\n');
313 }
314 
315 initbuf()
316 {
317 	register i;
318 
319 	for (i=0; i<MAXBUF; i++) {
320 		obuf[i].c_char = '\0';
321 		obuf[i].c_mode = NORMAL;
322 	}
323 	col = 0;
324 	maxcol = 0;
325 	mode &= ALTSET;
326 }
327 
328 fwd()
329 {
330 	register oldcol, oldmax;
331 
332 	oldcol = col;
333 	oldmax = maxcol;
334 	flushln();
335 	col = oldcol;
336 	maxcol = oldmax;
337 }
338 
339 reverse()
340 {
341 	upln++;
342 	fwd();
343 	puts(CURS_UP);
344 	puts(CURS_UP);
345 	upln++;
346 }
347 
348 initcap()
349 {
350 	static char tcapbuf[512];
351 	char *termtype;
352 	char *bp = tcapbuf;
353 	char *getenv(), *tgetstr();
354 
355 	/* This nonsense attempts to work with both old and new termcap */
356 	CURS_UP =		tgetstr("up", &bp);
357 	CURS_RIGHT =		tgetstr("ri", &bp);
358 	if (CURS_RIGHT == NULL)
359 		CURS_RIGHT =	tgetstr("nd", &bp);
360 	CURS_LEFT =		tgetstr("le", &bp);
361 	if (CURS_LEFT == NULL)
362 		CURS_LEFT =	tgetstr("bc", &bp);
363 	if (CURS_LEFT == NULL && tgetflag("bs"))
364 		CURS_LEFT =	"\b";
365 
366 	ENTER_STANDOUT =	tgetstr("so", &bp);
367 	EXIT_STANDOUT =		tgetstr("se", &bp);
368 	ENTER_UNDERLINE =	tgetstr("us", &bp);
369 	EXIT_UNDERLINE =	tgetstr("ue", &bp);
370 	ENTER_DIM =		tgetstr("mh", &bp);
371 	ENTER_BOLD =		tgetstr("md", &bp);
372 	ENTER_REVERSE =		tgetstr("mr", &bp);
373 	EXIT_ATTRIBUTES =	tgetstr("me", &bp);
374 
375 	if (!ENTER_BOLD && ENTER_REVERSE)
376 		ENTER_BOLD = ENTER_REVERSE;
377 	if (!ENTER_BOLD && ENTER_STANDOUT)
378 		ENTER_BOLD = ENTER_STANDOUT;
379 	if (!ENTER_UNDERLINE && ENTER_STANDOUT) {
380 		ENTER_UNDERLINE = ENTER_STANDOUT;
381 		EXIT_UNDERLINE = EXIT_STANDOUT;
382 	}
383 	if (!ENTER_DIM && ENTER_STANDOUT)
384 		ENTER_DIM = ENTER_STANDOUT;
385 	if (!ENTER_REVERSE && ENTER_STANDOUT)
386 		ENTER_REVERSE = ENTER_STANDOUT;
387 	if (!EXIT_ATTRIBUTES && EXIT_STANDOUT)
388 		EXIT_ATTRIBUTES = EXIT_STANDOUT;
389 
390 	/*
391 	 * Note that we use REVERSE for the alternate character set,
392 	 * not the as/ae capabilities.  This is because we are modelling
393 	 * the model 37 teletype (since that's what nroff outputs) and
394 	 * the typical as/ae is more of a graphics set, not the greek
395 	 * letters the 37 has.
396 	 */
397 
398 #ifdef notdef
399 printf("so %s se %s us %s ue %s me %s\n",
400 	ENTER_STANDOUT, EXIT_STANDOUT, ENTER_UNDERLINE,
401 	EXIT_UNDERLINE, EXIT_ATTRIBUTES);
402 #endif
403 	UNDER_CHAR =		tgetstr("uc", &bp);
404 	must_use_uc = (UNDER_CHAR && !ENTER_UNDERLINE);
405 }
406 
407 outchar(c)
408 char c;
409 {
410 	putchar(c&0177);
411 }
412 
413 puts(str)
414 char *str;
415 {
416 	if (str)
417 		tputs(str, 1, outchar);
418 }
419 
420 static curmode = 0;
421 outc(c)
422 char c;
423 {
424 	putchar(c);
425 	if (must_use_uc &&  (curmode&UNDERL)) {
426 		puts(CURS_LEFT);
427 		puts(UNDER_CHAR);
428 	}
429 }
430 
431 setmode(newmode)
432 int newmode;
433 {
434 	if (!iflag)
435 	{
436 		if (curmode != NORMAL && newmode != NORMAL)
437 			setmode(NORMAL);
438 		switch (newmode) {
439 		case NORMAL:
440 			switch(curmode) {
441 			case NORMAL:
442 				break;
443 			case UNDERL:
444 				puts(EXIT_UNDERLINE);
445 				break;
446 			default:
447 				/* This includes standout */
448 				puts(EXIT_ATTRIBUTES);
449 				break;
450 			}
451 			break;
452 		case ALTSET:
453 			puts(ENTER_REVERSE);
454 			break;
455 		case SUPERSC:
456 			/*
457 			 * This only works on a few terminals.
458 			 * It should be fixed.
459 			 */
460 			puts(ENTER_UNDERLINE);
461 			puts(ENTER_DIM);
462 			break;
463 		case SUBSC:
464 			puts(ENTER_DIM);
465 			break;
466 		case UNDERL:
467 			puts(ENTER_UNDERLINE);
468 			break;
469 		case BOLD:
470 			puts(ENTER_BOLD);
471 			break;
472 		default:
473 			/*
474 			 * We should have some provision here for multiple modes
475 			 * on at once.  This will have to come later.
476 			 */
477 			puts(ENTER_STANDOUT);
478 			break;
479 		}
480 	}
481 	curmode = newmode;
482 }
483 /*	@(#)getopt.c	3.2	*/
484 #define	ERR(s, c)	if(opterr){\
485 	fputs(argv[0], stderr);\
486 	fputs(s, stderr);\
487 	fputc(c, stderr);\
488 	fputc('\n', stderr);}
489 
490 int	opterr = 1;
491 int	optind = 1;
492 char	*optarg;
493 char	*index();
494 
495 int
496 getopt (argc, argv, opts)
497 	char **argv, *opts;
498 {
499 	static int sp = 1;
500 	char c;
501 	char *cp;
502 
503 	if (sp == 1)
504 		if (optind >= argc ||
505 		   argv[optind][0] != '-' || argv[optind][1] == '\0')
506 			return EOF;
507 		else if (strcmp(argv[optind], "--") == NULL) {
508 			optind++;
509 			return EOF;
510 		}
511 		else if (strcmp(argv[optind], "-?") == NULL) {
512 			optind++;
513 			return '?';
514 		}
515 	c = argv[optind][sp];
516 	if (c == ':' || (cp=index(opts, c)) == NULL) {
517 		ERR (": illegal option -- ", c);
518 		if (argv[optind][++sp] == '\0') {
519 			optind++;
520 			sp = 1;
521 		}
522 		return '?';
523 	}
524 	if (*++cp == ':') {
525 		if (argv[optind][2] != '\0')
526 			optarg = &argv[optind++][sp+1];
527 		else if (++optind >= argc) {
528 			ERR (": option requires an argument -- ", c);
529 			sp = 1;
530 			return '?';
531 		} else
532 			optarg = argv[optind++];
533 		sp = 1;
534 	}
535 	else if (argv[optind][++sp] == '\0') {
536 		sp = 1;
537 		optind++;
538 	}
539 	return c;
540 }
541