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