]> git.ipfire.org Git - thirdparty/util-linux.git/blob - text-utils/ul.c
textual: spell and encode the name of Arkadiusz Miśkiewicz correctly
[thirdparty/util-linux.git] / text-utils / ul.c
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. All advertising materials mentioning features or use of this software
14 * must display the following acknowledgement:
15 * This product includes software developed by the University of
16 * California, Berkeley and its contributors.
17 * 4. Neither the name of the University nor the names of its contributors
18 * may be used to endorse or promote products derived from this software
19 * without specific prior written permission.
20 *
21 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
22 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
23 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
24 * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
25 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
26 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
27 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
28 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
29 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
30 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
31 * SUCH DAMAGE.
32 */
33
34 /*
35 * modified by Kars de Jong <jongk@cs.utwente.nl>
36 * to use terminfo instead of termcap.
37 * 1999-02-22 Arkadiusz Miśkiewicz <misiek@pld.ORG.PL>
38 * added Native Language Support
39 * 1999-09-19 Bruno Haible <haible@clisp.cons.org>
40 * modified to work correctly in multi-byte locales
41 */
42
43 #include <stdio.h>
44 #include <unistd.h> /* for getopt(), isatty() */
45 #include <string.h> /* for memset(), strcpy() */
46 #include <term.h> /* for setupterm() */
47 #include <stdlib.h> /* for getenv() */
48 #include <limits.h> /* for INT_MAX */
49 #include <signal.h> /* for signal() */
50 #include <errno.h>
51 #include <getopt.h>
52
53 #include "nls.h"
54 #include "xalloc.h"
55 #include "widechar.h"
56 #include "c.h"
57 #include "closestream.h"
58
59 #ifdef HAVE_WIDECHAR
60 /* Output an ASCII character as a wide character */
61 static int put1wc(int c)
62 {
63 if (putwchar(c) == WEOF)
64 return EOF;
65 else
66 return c;
67 }
68 #define putwp(s) tputs(s, STDOUT_FILENO, put1wc)
69 #else
70 #define putwp(s) putp(s)
71 #endif
72
73 static void usage(FILE *out);
74 static int handle_escape(FILE * f);
75 static void filter(FILE *f);
76 static void flushln(void);
77 static void overstrike(void);
78 static void iattr(void);
79 static void initbuf(void);
80 static void fwd(void);
81 static void reverse(void);
82 static void initinfo(void);
83 static void outc(wint_t c, int width);
84 static void setmode(int newmode);
85 static void setcol(int newcol);
86 static void needcol(int col);
87 static void sig_handler(int signo);
88 static void print_out(char *line);
89
90 #define IESC '\033'
91 #define SO '\016'
92 #define SI '\017'
93 #define HFWD '9'
94 #define HREV '8'
95 #define FREV '7'
96
97 #define NORMAL 000
98 #define ALTSET 001 /* Reverse */
99 #define SUPERSC 002 /* Dim */
100 #define SUBSC 004 /* Dim | Ul */
101 #define UNDERL 010 /* Ul */
102 #define BOLD 020 /* Bold */
103
104 int must_use_uc, must_overstrike;
105 char *CURS_UP,
106 *CURS_RIGHT,
107 *CURS_LEFT,
108 *ENTER_STANDOUT,
109 *EXIT_STANDOUT,
110 *ENTER_UNDERLINE,
111 *EXIT_UNDERLINE,
112 *ENTER_DIM,
113 *ENTER_BOLD,
114 *ENTER_REVERSE,
115 *UNDER_CHAR,
116 *EXIT_ATTRIBUTES;
117
118 struct CHAR {
119 char c_mode;
120 wchar_t c_char;
121 int c_width;
122 };
123
124 struct CHAR *obuf;
125 int obuflen;
126 int col, maxcol;
127 int mode;
128 int halfpos;
129 int upln;
130 int iflag;
131
132 static void __attribute__((__noreturn__))
133 usage(FILE *out)
134 {
135 fprintf(out, _(
136 "\nUsage:\n"
137 " %s [options] [file...]\n"), program_invocation_short_name);
138
139 fprintf(out, _(
140 "\nOptions:\n"
141 " -t, --terminal TERMINAL override the TERM environment variable\n"
142 " -i, --indicated underlining is indicated via a separate line\n"
143 " -V, --version output version information and exit\n"
144 " -h, --help display this help and exit\n\n"));
145
146 exit(out == stderr ? EXIT_FAILURE : EXIT_SUCCESS);
147 }
148
149 int main(int argc, char **argv)
150 {
151 int c, ret, tflag = 0;
152 char *termtype;
153 FILE *f;
154
155 static const struct option longopts[] = {
156 { "terminal", required_argument, 0, 't' },
157 { "indicated", no_argument, 0, 'i' },
158 { "version", no_argument, 0, 'V' },
159 { "help", no_argument, 0, 'h' },
160 { NULL, 0, 0, 0 }
161 };
162
163 setlocale(LC_ALL, "");
164 bindtextdomain(PACKAGE, LOCALEDIR);
165 textdomain(PACKAGE);
166 atexit(close_stdout);
167
168 signal(SIGINT, sig_handler);
169 signal(SIGTERM, sig_handler);
170
171 termtype = getenv("TERM");
172
173 /*
174 * FIXME: why terminal type is lpr when command begins with c and has
175 * no terminal? If this behavior can be explained please insert
176 * reference or remove the code. In case this truly is desired command
177 * behavior this should be mentioned in manual page.
178 */
179 if (termtype == NULL || (argv[0][0] == 'c' && !isatty(STDOUT_FILENO)))
180 termtype = "lpr";
181
182 while ((c = getopt_long(argc, argv, "it:T:Vh", longopts, NULL)) != -1)
183 switch (c) {
184
185 case 't':
186 case 'T':
187 /* for nroff compatibility */
188 termtype = optarg;
189 tflag = 1;
190 break;
191 case 'i':
192 iflag = 1;
193 break;
194 case 'V':
195 printf(_("%s from %s\n"), program_invocation_short_name,
196 PACKAGE_STRING);
197 return EXIT_SUCCESS;
198 case 'h':
199 usage(stdout);
200 default:
201 usage(stderr);
202 }
203 setupterm(termtype, STDOUT_FILENO, &ret);
204 switch (ret) {
205
206 case 1:
207 break;
208
209 default:
210 warnx(_("trouble reading terminfo"));
211 /* fall through to ... */
212
213 case 0:
214 if (tflag)
215 warnx(_("terminal `%s' is not known, defaulting to `dumb'"),
216 termtype);
217 setupterm("dumb", STDOUT_FILENO, (int *)0);
218 break;
219 }
220 initinfo();
221 if ((tigetflag("os") && ENTER_BOLD==NULL ) ||
222 (tigetflag("ul") && ENTER_UNDERLINE==NULL && UNDER_CHAR==NULL))
223 must_overstrike = 1;
224 initbuf();
225 if (optind == argc)
226 filter(stdin);
227 else
228 for (; optind < argc; optind++) {
229 f = fopen(argv[optind],"r");
230 if (!f)
231 err(EXIT_FAILURE, _("cannot open %s"),
232 argv[optind]);
233 filter(f);
234 fclose(f);
235 }
236 free(obuf);
237 return EXIT_SUCCESS;
238 }
239
240 static int handle_escape(FILE * f)
241 {
242 wint_t c;
243
244 switch (c = getwc(f)) {
245 case HREV:
246 if (halfpos == 0) {
247 mode |= SUPERSC;
248 halfpos--;
249 } else if (halfpos > 0) {
250 mode &= ~SUBSC;
251 halfpos--;
252 } else {
253 halfpos = 0;
254 reverse();
255 }
256 return 0;
257 case HFWD:
258 if (halfpos == 0) {
259 mode |= SUBSC;
260 halfpos++;
261 } else if (halfpos < 0) {
262 mode &= ~SUPERSC;
263 halfpos++;
264 } else {
265 halfpos = 0;
266 fwd();
267 }
268 return 0;
269 case FREV:
270 reverse();
271 return 0;
272 default:
273 /* unknown escape */
274 ungetwc(c, f);
275 return 1;
276 }
277 }
278
279 static void filter(FILE *f)
280 {
281 wint_t c;
282 int i, w;
283
284 while ((c = getwc(f)) != WEOF)
285 switch (c) {
286
287 case '\b':
288 setcol(col - 1);
289 continue;
290
291 case '\t':
292 setcol((col+8) & ~07);
293 continue;
294
295 case '\r':
296 setcol(0);
297 continue;
298
299 case SO:
300 mode |= ALTSET;
301 continue;
302
303 case SI:
304 mode &= ~ALTSET;
305 continue;
306
307 case IESC:
308 if(handle_escape(f)) {
309 c = getwc(f);
310 errx(EXIT_FAILURE,
311 _("unknown escape sequence in input: %o, %o"),
312 IESC, c);
313 }
314 continue;
315
316 case '_':
317 if (obuf[col].c_char || obuf[col].c_width < 0) {
318 while(col > 0 && obuf[col].c_width < 0)
319 col--;
320 w = obuf[col].c_width;
321 for (i = 0; i < w; i++)
322 obuf[col++].c_mode |= UNDERL | mode;
323 setcol(col);
324 continue;
325 }
326 obuf[col].c_char = '_';
327 obuf[col].c_width = 1;
328 /* fall through */
329 case ' ':
330 setcol(col + 1);
331 continue;
332
333 case '\n':
334 flushln();
335 continue;
336
337 case '\f':
338 flushln();
339 putwchar('\f');
340 continue;
341
342 default:
343 if (!iswprint(c))
344 /* non printable */
345 continue;
346 w = wcwidth(c);
347 needcol(col + w);
348 if (obuf[col].c_char == '\0') {
349 obuf[col].c_char = c;
350 for (i = 0; i < w; i++)
351 obuf[col+i].c_mode = mode;
352 obuf[col].c_width = w;
353 for (i = 1; i < w; i++)
354 obuf[col+i].c_width = -1;
355 } else if (obuf[col].c_char == '_') {
356 obuf[col].c_char = c;
357 for (i = 0; i < w; i++)
358 obuf[col+i].c_mode |= UNDERL|mode;
359 obuf[col].c_width = w;
360 for (i = 1; i < w; i++)
361 obuf[col+i].c_width = -1;
362 } else if ((wint_t) obuf[col].c_char == c) {
363 for (i = 0; i < w; i++)
364 obuf[col+i].c_mode |= BOLD|mode;
365 } else {
366 w = obuf[col].c_width;
367 for (i = 0; i < w; i++)
368 obuf[col+i].c_mode = mode;
369 }
370 setcol(col + w);
371 continue;
372 }
373 if (maxcol)
374 flushln();
375 }
376
377 static void flushln(void)
378 {
379 int lastmode;
380 int i;
381 int hadmodes = 0;
382
383 lastmode = NORMAL;
384 for (i = 0; i < maxcol; i++) {
385 if (obuf[i].c_mode != lastmode) {
386 hadmodes++;
387 setmode(obuf[i].c_mode);
388 lastmode = obuf[i].c_mode;
389 }
390 if (obuf[i].c_char == '\0') {
391 if (upln) {
392 print_out(CURS_RIGHT);
393 } else
394 outc(' ', 1);
395 } else
396 outc(obuf[i].c_char, obuf[i].c_width);
397 if (obuf[i].c_width > 1)
398 i += obuf[i].c_width - 1;
399 }
400 if (lastmode != NORMAL) {
401 setmode(0);
402 }
403 if (must_overstrike && hadmodes)
404 overstrike();
405 putwchar('\n');
406 if (iflag && hadmodes)
407 iattr();
408 fflush(stdout);
409 if (upln)
410 upln--;
411 initbuf();
412 }
413
414 /*
415 * For terminals that can overstrike, overstrike underlines and bolds.
416 * We don't do anything with halfline ups and downs, or Greek.
417 */
418 static void overstrike(void)
419 {
420 register int i;
421 #ifdef __GNUC__
422 register wchar_t *lbuf = __builtin_alloca((maxcol + 1) * sizeof(wchar_t));
423 #else
424 wchar_t lbuf[BUFSIZ];
425 #endif
426 register wchar_t *cp = lbuf;
427 int hadbold=0;
428
429 /* Set up overstrike buffer */
430 for (i = 0; i < maxcol; i++)
431 switch (obuf[i].c_mode) {
432 case NORMAL:
433 default:
434 *cp++ = ' ';
435 break;
436 case UNDERL:
437 *cp++ = '_';
438 break;
439 case BOLD:
440 *cp++ = obuf[i].c_char;
441 if (obuf[i].c_width > 1)
442 i += obuf[i].c_width - 1;
443 hadbold=1;
444 break;
445 }
446 putwchar('\r');
447 for (*cp = ' '; *cp == ' '; cp--)
448 *cp = 0;
449 for (cp = lbuf; *cp; cp++)
450 putwchar(*cp);
451 if (hadbold) {
452 putwchar('\r');
453 for (cp = lbuf; *cp; cp++)
454 putwchar(*cp == '_' ? ' ' : *cp);
455 putwchar('\r');
456 for (cp = lbuf; *cp; cp++)
457 putwchar(*cp == '_' ? ' ' : *cp);
458 }
459 }
460
461 static void iattr(void)
462 {
463 register int i;
464 #ifdef __GNUC__
465 register char *lbuf = __builtin_alloca((maxcol+1)*sizeof(char));
466 #else
467 char lbuf[BUFSIZ];
468 #endif
469 register char *cp = lbuf;
470
471 for (i = 0; i < maxcol; i++)
472 switch (obuf[i].c_mode) {
473 case NORMAL: *cp++ = ' '; break;
474 case ALTSET: *cp++ = 'g'; break;
475 case SUPERSC: *cp++ = '^'; break;
476 case SUBSC: *cp++ = 'v'; break;
477 case UNDERL: *cp++ = '_'; break;
478 case BOLD: *cp++ = '!'; break;
479 default: *cp++ = 'X'; break;
480 }
481 for (*cp = ' '; *cp == ' '; cp--)
482 *cp = 0;
483 for (cp = lbuf; *cp; cp++)
484 putwchar(*cp);
485 putwchar('\n');
486 }
487
488 static void initbuf(void)
489 {
490 if (obuf == NULL) {
491 /* First time. */
492 obuflen = BUFSIZ;
493 obuf = xmalloc(sizeof(struct CHAR) * obuflen);
494 }
495
496 /* assumes NORMAL == 0 */
497 memset(obuf, 0, sizeof(struct CHAR) * obuflen);
498 setcol(0);
499 maxcol = 0;
500 mode &= ALTSET;
501 }
502
503 static void fwd(void)
504 {
505 int oldcol, oldmax;
506
507 oldcol = col;
508 oldmax = maxcol;
509 flushln();
510 setcol(oldcol);
511 maxcol = oldmax;
512 }
513
514 static void reverse(void)
515 {
516 upln++;
517 fwd();
518 print_out(CURS_UP);
519 print_out(CURS_UP);
520 upln++;
521 }
522
523 static void initinfo(void)
524 {
525 CURS_UP = tigetstr("cuu1");
526 CURS_RIGHT = tigetstr("cuf1");
527 CURS_LEFT = tigetstr("cub1");
528 if (CURS_LEFT == NULL)
529 CURS_LEFT = "\b";
530
531 ENTER_STANDOUT = tigetstr("smso");
532 EXIT_STANDOUT = tigetstr("rmso");
533 ENTER_UNDERLINE = tigetstr("smul");
534 EXIT_UNDERLINE = tigetstr("rmul");
535 ENTER_DIM = tigetstr("dim");
536 ENTER_BOLD = tigetstr("bold");
537 ENTER_REVERSE = tigetstr("rev");
538 EXIT_ATTRIBUTES = tigetstr("sgr0");
539
540 if (!ENTER_BOLD && ENTER_REVERSE)
541 ENTER_BOLD = ENTER_REVERSE;
542 if (!ENTER_BOLD && ENTER_STANDOUT)
543 ENTER_BOLD = ENTER_STANDOUT;
544 if (!ENTER_UNDERLINE && ENTER_STANDOUT) {
545 ENTER_UNDERLINE = ENTER_STANDOUT;
546 EXIT_UNDERLINE = EXIT_STANDOUT;
547 }
548 if (!ENTER_DIM && ENTER_STANDOUT)
549 ENTER_DIM = ENTER_STANDOUT;
550 if (!ENTER_REVERSE && ENTER_STANDOUT)
551 ENTER_REVERSE = ENTER_STANDOUT;
552 if (!EXIT_ATTRIBUTES && EXIT_STANDOUT)
553 EXIT_ATTRIBUTES = EXIT_STANDOUT;
554
555 /*
556 * Note that we use REVERSE for the alternate character set,
557 * not the as/ae capabilities. This is because we are modelling
558 * the model 37 teletype (since that's what nroff outputs) and
559 * the typical as/ae is more of a graphics set, not the greek
560 * letters the 37 has.
561 */
562
563 UNDER_CHAR = tigetstr("uc");
564 must_use_uc = (UNDER_CHAR && !ENTER_UNDERLINE);
565 }
566
567 static int curmode = 0;
568
569 static void outc(wint_t c, int width) {
570 int i;
571
572 putwchar(c);
573 if (must_use_uc && (curmode&UNDERL)) {
574 for (i = 0; i < width; i++)
575 print_out(CURS_LEFT);
576 for (i = 0; i < width; i++)
577 print_out(UNDER_CHAR);
578 }
579 }
580
581 static void setmode(int newmode)
582 {
583 if (!iflag) {
584 if (curmode != NORMAL && newmode != NORMAL)
585 setmode(NORMAL);
586 switch (newmode) {
587 case NORMAL:
588 switch (curmode) {
589 case NORMAL:
590 break;
591 case UNDERL:
592 print_out(EXIT_UNDERLINE);
593 break;
594 default:
595 /* This includes standout */
596 print_out(EXIT_ATTRIBUTES);
597 break;
598 }
599 break;
600 case ALTSET:
601 print_out(ENTER_REVERSE);
602 break;
603 case SUPERSC:
604 /*
605 * This only works on a few terminals.
606 * It should be fixed.
607 */
608 print_out(ENTER_UNDERLINE);
609 print_out(ENTER_DIM);
610 break;
611 case SUBSC:
612 print_out(ENTER_DIM);
613 break;
614 case UNDERL:
615 print_out(ENTER_UNDERLINE);
616 break;
617 case BOLD:
618 print_out(ENTER_BOLD);
619 break;
620 default:
621 /*
622 * We should have some provision here for multiple modes
623 * on at once. This will have to come later.
624 */
625 print_out(ENTER_STANDOUT);
626 break;
627 }
628 }
629 curmode = newmode;
630 }
631
632 static void setcol(int newcol) {
633 col = newcol;
634
635 if (col < 0)
636 col = 0;
637 else if (col > maxcol)
638 needcol(col);
639 }
640
641 static void needcol(int acol) {
642 maxcol = acol;
643
644 /* If col >= obuflen, expand obuf until obuflen > col. */
645 while (acol >= obuflen) {
646 /* Paranoid check for obuflen == INT_MAX. */
647 if (obuflen == INT_MAX)
648 errx(EXIT_FAILURE, _("Input line too long."));
649
650 /* Similar paranoia: double only up to INT_MAX. */
651 if (obuflen < (INT_MAX / 2))
652 obuflen *= 2;
653 else
654 obuflen = INT_MAX;
655
656 /* Now we can try to expand obuf. */
657 obuf = xrealloc(obuf, sizeof(struct CHAR) * obuflen);
658 }
659 }
660
661 static void sig_handler(int signo __attribute__ ((__unused__)))
662 {
663 _exit(EXIT_SUCCESS);
664 }
665
666 static void print_out(char *line)
667 {
668 if (line == NULL)
669 return;
670
671 putwp(line);
672 }