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