]> git.ipfire.org Git - thirdparty/util-linux.git/blob - text-utils/ul.c
textutils: introduce and use fgetwc_or_err
[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 <stdlib.h> /* for getenv() */
47 #include <limits.h> /* for INT_MAX */
48 #include <signal.h> /* for signal() */
49 #include <errno.h>
50 #include <getopt.h>
51
52 #if defined(HAVE_NCURSESW_TERM_H)
53 # include <ncursesw/term.h>
54 #elif defined(HAVE_NCURSES_TERM_H)
55 # include <ncurses/term.h>
56 #elif defined(HAVE_TERM_H)
57 # include <term.h>
58 #endif
59
60 #include "nls.h"
61 #include "xalloc.h"
62 #include "widechar.h"
63 #include "c.h"
64 #include "closestream.h"
65 #include "fgetwc_or_err.h"
66
67 #define ESC '\033'
68 #define SO '\016'
69 #define SI '\017'
70 #define HFWD '9'
71 #define HREV '8'
72 #define FREV '7'
73
74 enum {
75 NORMAL_CHARSET = 0, /* Must be zero, see initbuf() */
76 ALTERNATIVE_CHARSET = 1 << 0, /* Reverse */
77 SUPERSCRIPT = 1 << 1, /* Dim */
78 SUBSCRIPT = 1 << 2, /* Dim | Ul */
79 UNDERLINE = 1 << 3, /* Ul */
80 BOLD = 1 << 4, /* Bold */
81 };
82
83 struct term_caps {
84 char *curs_up;
85 char *curs_right;
86 char *curs_left;
87 char *enter_standout;
88 char *exit_standout;
89 char *enter_underline;
90 char *exit_underline;
91 char *enter_dim;
92 char *enter_bold;
93 char *enter_reverse;
94 char *under_char;
95 char *exit_attributes;
96 };
97
98 struct ul_char {
99 wchar_t c_char;
100 int c_width;
101 char c_mode;
102 };
103
104 struct ul_ctl {
105 size_t column;
106 size_t max_column;
107 int half_position;
108 int up_line;
109 int mode;
110 int current_mode;
111 size_t buflen;
112 struct ul_char *buf;
113 unsigned int
114 indicated_opt:1,
115 must_use_uc:1,
116 must_overstrike:1;
117 };
118
119 static void __attribute__((__noreturn__)) usage(void)
120 {
121 FILE *out = stdout;
122
123 fputs(USAGE_HEADER, out);
124 fprintf(out, _(" %s [options] [<file> ...]\n"), program_invocation_short_name);
125
126 fputs(USAGE_SEPARATOR, out);
127 fputs(_("Do underlining.\n"), out);
128
129 fputs(USAGE_OPTIONS, out);
130 fputs(_(" -t, -T, --terminal TERMINAL override the TERM environment variable\n"), out);
131 fputs(_(" -i, --indicated underlining is indicated via a separate line\n"), out);
132 fprintf(out, USAGE_HELP_OPTIONS(30));
133
134 fprintf(out, USAGE_MAN_TAIL("ul(1)"));
135
136 exit(EXIT_SUCCESS);
137 }
138
139 static void need_column(struct ul_ctl *ctl, size_t new_max)
140 {
141 ctl->max_column = new_max;
142
143 while (new_max >= ctl->buflen) {
144 ctl->buflen *= 2;
145 ctl->buf = xreallocarray(ctl->buf, ctl->buflen, sizeof(struct ul_char));
146 }
147 }
148
149 static void set_column(struct ul_ctl *ctl, size_t column)
150 {
151 ctl->column = column;
152
153 if (ctl->max_column < ctl->column)
154 need_column(ctl, ctl->column);
155 }
156
157 static void init_buffer(struct ul_ctl *ctl)
158 {
159 if (ctl->buf == NULL) {
160 /* First time. */
161 ctl->buflen = BUFSIZ;
162 ctl->buf = xcalloc(ctl->buflen, sizeof(struct ul_char));
163 } else
164 /* assumes NORMAL_CHARSET == 0 */
165 memset(ctl->buf, 0, sizeof(struct ul_char) * ctl->max_column);
166
167 set_column(ctl, 0);
168 ctl->max_column = 0;
169 ctl->mode &= ALTERNATIVE_CHARSET;
170 }
171
172 static void init_term_caps(struct ul_ctl *ctl, struct term_caps *const tcs)
173 {
174 tcs->curs_up = tigetstr("cuu1");
175 tcs->curs_right = tigetstr("cuf1");
176 tcs->curs_left = tigetstr("cub1");
177 if (tcs->curs_left == NULL)
178 tcs->curs_left = "\b";
179
180 tcs->enter_standout = tigetstr("smso");
181 tcs->exit_standout = tigetstr("rmso");
182 tcs->enter_underline = tigetstr("smul");
183 tcs->exit_underline = tigetstr("rmul");
184 tcs->enter_dim = tigetstr("dim");
185 tcs->enter_bold = tigetstr("bold");
186 tcs->enter_reverse = tigetstr("rev");
187 tcs->exit_attributes = tigetstr("sgr0");
188
189 if (!tcs->enter_bold && tcs->enter_reverse)
190 tcs->enter_bold = tcs->enter_reverse;
191
192 if (!tcs->enter_bold && tcs->enter_standout)
193 tcs->enter_bold = tcs->enter_standout;
194
195 if (!tcs->enter_underline && tcs->enter_standout) {
196 tcs->enter_underline = tcs->enter_standout;
197 tcs->exit_underline = tcs->exit_standout;
198 }
199
200 if (!tcs->enter_dim && tcs->enter_standout)
201 tcs->enter_dim = tcs->enter_standout;
202
203 if (!tcs->enter_reverse && tcs->enter_standout)
204 tcs->enter_reverse = tcs->enter_standout;
205
206 if (!tcs->exit_attributes && tcs->exit_standout)
207 tcs->exit_attributes = tcs->exit_standout;
208
209 /*
210 * Note that we use REVERSE for the alternate character set,
211 * not the as/ae capabilities. This is because we are modeling
212 * the model 37 teletype (since that's what nroff outputs) and
213 * the typical as/ae is more of a graphics set, not the greek
214 * letters the 37 has.
215 */
216 tcs->under_char = tigetstr("uc");
217 ctl->must_use_uc = (tcs->under_char && !tcs->enter_underline);
218
219 if ((tigetflag("os") && tcs->enter_bold == NULL) ||
220 (tigetflag("ul") && tcs->enter_underline == NULL
221 && tcs->under_char == NULL))
222 ctl->must_overstrike = 1;
223 }
224
225 static void sig_handler(int signo __attribute__((__unused__)))
226 {
227 _exit(EXIT_SUCCESS);
228 }
229
230 static int ul_putwchar(int c)
231 {
232 if (putwchar(c) == WEOF)
233 return EOF;
234 return c;
235 }
236
237 static void print_line(char *line)
238 {
239 if (line == NULL)
240 return;
241 tputs(line, STDOUT_FILENO, ul_putwchar);
242 }
243
244 static void ul_setmode(struct ul_ctl *ctl, struct term_caps const *const tcs,
245 int new_mode)
246 {
247 if (!ctl->indicated_opt) {
248 if (ctl->current_mode != NORMAL_CHARSET && new_mode != NORMAL_CHARSET)
249 ul_setmode(ctl, tcs, NORMAL_CHARSET);
250
251 switch (new_mode) {
252 case NORMAL_CHARSET:
253 switch (ctl->current_mode) {
254 case NORMAL_CHARSET:
255 break;
256 case UNDERLINE:
257 print_line(tcs->exit_underline);
258 break;
259 default:
260 /* This includes standout */
261 print_line(tcs->exit_attributes);
262 break;
263 }
264 break;
265 case ALTERNATIVE_CHARSET:
266 print_line(tcs->enter_reverse);
267 break;
268 case SUPERSCRIPT:
269 /*
270 * This only works on a few terminals.
271 * It should be fixed.
272 */
273 print_line(tcs->enter_underline);
274 print_line(tcs->enter_dim);
275 break;
276 case SUBSCRIPT:
277 print_line(tcs->enter_dim);
278 break;
279 case UNDERLINE:
280 print_line(tcs->enter_underline);
281 break;
282 case BOLD:
283 print_line(tcs->enter_bold);
284 break;
285 default:
286 /*
287 * We should have some provision here for multiple modes
288 * on at once. This will have to come later.
289 */
290 print_line(tcs->enter_standout);
291 break;
292 }
293 }
294 ctl->current_mode = new_mode;
295 }
296
297 static void indicate_attribute(struct ul_ctl *ctl)
298 {
299 size_t i;
300 wchar_t *buf = xcalloc(ctl->max_column + 1, sizeof(wchar_t));
301 wchar_t *p = buf;
302
303 for (i = 0; i < ctl->max_column; i++) {
304 switch (ctl->buf[i].c_mode) {
305 case NORMAL_CHARSET: *p++ = ' '; break;
306 case ALTERNATIVE_CHARSET: *p++ = 'g'; break;
307 case SUPERSCRIPT: *p++ = '^'; break;
308 case SUBSCRIPT: *p++ = 'v'; break;
309 case UNDERLINE: *p++ = '_'; break;
310 case BOLD: *p++ = '!'; break;
311 default: *p++ = 'X'; break;
312 }
313 }
314
315 for (*p = ' '; *p == ' '; p--)
316 *p = 0;
317
318 fputws(buf, stdout);
319 putwchar('\n');
320 free(buf);
321 }
322
323 static void output_char(struct ul_ctl *ctl, struct term_caps const *const tcs,
324 wint_t c, int width)
325 {
326 int i;
327
328 putwchar(c);
329 if (ctl->must_use_uc && (ctl->current_mode & UNDERLINE)) {
330 for (i = 0; i < width; i++)
331 print_line(tcs->curs_left);
332 for (i = 0; i < width; i++)
333 print_line(tcs->under_char);
334 }
335 }
336
337 /*
338 * For terminals that can overstrike, overstrike underlines and bolds.
339 * We don't do anything with halfline ups and downs, or Greek.
340 */
341 static void overstrike(struct ul_ctl *ctl)
342 {
343 size_t i;
344 wchar_t *buf = xcalloc(ctl->max_column + 1, sizeof(wchar_t));
345 wchar_t *p = buf;
346 int had_bold = 0;
347
348 /* Set up overstrike buffer */
349 for (i = 0; i < ctl->max_column; i++) {
350 switch (ctl->buf[i].c_mode) {
351 case NORMAL_CHARSET:
352 default:
353 *p++ = ' ';
354 break;
355 case UNDERLINE:
356 *p++ = '_';
357 break;
358 case BOLD:
359 *p++ = ctl->buf[i].c_char;
360 if (1 < ctl->buf[i].c_width)
361 i += ctl->buf[i].c_width - 1;
362 had_bold = 1;
363 break;
364 }
365 }
366
367 putwchar('\r');
368 for (*p = ' '; *p == ' '; p--)
369 *p = 0;
370 fputws(buf, stdout);
371
372 if (had_bold) {
373 putwchar('\r');
374 for (p = buf; *p; p++)
375 putwchar(*p == '_' ? ' ' : *p);
376 putwchar('\r');
377 for (p = buf; *p; p++)
378 putwchar(*p == '_' ? ' ' : *p);
379 }
380 free(buf);
381 }
382
383 static void flush_line(struct ul_ctl *ctl, struct term_caps const *const tcs)
384 {
385 int last_mode;
386 size_t i;
387 int had_mode = 0;
388
389 last_mode = NORMAL_CHARSET;
390 for (i = 0; i < ctl->max_column; i++) {
391 if (ctl->buf[i].c_mode != last_mode) {
392 had_mode = 1;
393 ul_setmode(ctl, tcs, ctl->buf[i].c_mode);
394 last_mode = ctl->buf[i].c_mode;
395 }
396 if (ctl->buf[i].c_char == '\0') {
397 if (ctl->up_line)
398 print_line(tcs->curs_right);
399 else
400 output_char(ctl, tcs, ' ', 1);
401 } else
402 output_char(ctl, tcs, ctl->buf[i].c_char, ctl->buf[i].c_width);
403 if (1 < ctl->buf[i].c_width)
404 i += ctl->buf[i].c_width - 1;
405 }
406 if (last_mode != NORMAL_CHARSET)
407 ul_setmode(ctl, tcs, NORMAL_CHARSET);
408 if (ctl->must_overstrike && had_mode)
409 overstrike(ctl);
410 putwchar('\n');
411 if (ctl->indicated_opt && had_mode)
412 indicate_attribute(ctl);
413 fflush(stdout);
414 if (ctl->up_line)
415 ctl->up_line--;
416 init_buffer(ctl);
417 }
418
419 static void forward(struct ul_ctl *ctl, struct term_caps const *const tcs)
420 {
421 int old_column, old_maximum;
422
423 old_column = ctl->column;
424 old_maximum = ctl->max_column;
425 flush_line(ctl, tcs);
426 set_column(ctl, old_column);
427 ctl->max_column = old_maximum;
428 }
429
430 static void reverse(struct ul_ctl *ctl, struct term_caps const *const tcs)
431 {
432 ctl->up_line++;
433 forward(ctl, tcs);
434 print_line(tcs->curs_up);
435 print_line(tcs->curs_up);
436 ctl->up_line++;
437 }
438
439 static int handle_escape(struct ul_ctl *ctl, struct term_caps const *const tcs, FILE *f)
440 {
441 wint_t c;
442
443 switch (c = fgetwc_or_err(f)) {
444 case HREV:
445 if (0 < ctl->half_position) {
446 ctl->mode &= ~SUBSCRIPT;
447 ctl->half_position--;
448 } else if (ctl->half_position == 0) {
449 ctl->mode |= SUPERSCRIPT;
450 ctl->half_position--;
451 } else {
452 ctl->half_position = 0;
453 reverse(ctl, tcs);
454 }
455 return 0;
456 case HFWD:
457 if (ctl->half_position < 0) {
458 ctl->mode &= ~SUPERSCRIPT;
459 ctl->half_position++;
460 } else if (ctl->half_position == 0) {
461 ctl->mode |= SUBSCRIPT;
462 ctl->half_position++;
463 } else {
464 ctl->half_position = 0;
465 forward(ctl, tcs);
466 }
467 return 0;
468 case FREV:
469 reverse(ctl, tcs);
470 return 0;
471 default:
472 /* unknown escape */
473 ungetwc(c, f);
474 return 1;
475 }
476 }
477
478 static void filter(struct ul_ctl *ctl, struct term_caps const *const tcs, FILE *f)
479 {
480 wint_t c;
481 int i, width;
482
483 while ((c = fgetwc_or_err(f)) != WEOF) {
484 switch (c) {
485 case '\b':
486 set_column(ctl, ctl->column && 0 < ctl->column ? ctl->column - 1 : 0);
487 continue;
488 case '\t':
489 set_column(ctl, (ctl->column + 8) & ~07);
490 continue;
491 case '\r':
492 set_column(ctl, 0);
493 continue;
494 case SO:
495 ctl->mode |= ALTERNATIVE_CHARSET;
496 continue;
497 case SI:
498 ctl->mode &= ~ALTERNATIVE_CHARSET;
499 continue;
500 case ESC:
501 if (handle_escape(ctl, tcs, f)) {
502 c = fgetwc_or_err(f);
503 errx(EXIT_FAILURE,
504 _("unknown escape sequence in input: %o, %o"), ESC, c);
505 }
506 continue;
507 case '_':
508 if (ctl->buf[ctl->column].c_char || ctl->buf[ctl->column].c_width < 0) {
509 while (ctl->buf[ctl->column].c_width < 0 && 0 < ctl->column)
510 ctl->column--;
511 width = ctl->buf[ctl->column].c_width;
512 for (i = 0; i < width; i++)
513 ctl->buf[ctl->column++].c_mode |= UNDERLINE | ctl->mode;
514 set_column(ctl, 0 < ctl->column ? ctl->column : 0);
515 continue;
516 }
517 ctl->buf[ctl->column].c_char = '_';
518 ctl->buf[ctl->column].c_width = 1;
519 /* fallthrough */
520 case ' ':
521 set_column(ctl, ctl->column + 1);
522 continue;
523 case '\n':
524 flush_line(ctl, tcs);
525 continue;
526 case '\f':
527 flush_line(ctl, tcs);
528 putwchar('\f');
529 continue;
530 default:
531 if (!iswprint(c))
532 /* non printable */
533 continue;
534 width = wcwidth(c);
535 need_column(ctl, ctl->column + width);
536 if (ctl->buf[ctl->column].c_char == '\0') {
537 ctl->buf[ctl->column].c_char = c;
538 for (i = 0; i < width; i++)
539 ctl->buf[ctl->column + i].c_mode = ctl->mode;
540 ctl->buf[ctl->column].c_width = width;
541 for (i = 1; i < width; i++)
542 ctl->buf[ctl->column + i].c_width = -1;
543 } else if (ctl->buf[ctl->column].c_char == '_') {
544 ctl->buf[ctl->column].c_char = c;
545 for (i = 0; i < width; i++)
546 ctl->buf[ctl->column + i].c_mode |= UNDERLINE | ctl->mode;
547 ctl->buf[ctl->column].c_width = width;
548 for (i = 1; i < width; i++)
549 ctl->buf[ctl->column + i].c_width = -1;
550 } else if ((wint_t) ctl->buf[ctl->column].c_char == c) {
551 for (i = 0; i < width; i++)
552 ctl->buf[ctl->column + i].c_mode |= BOLD | ctl->mode;
553 } else {
554 width = ctl->buf[ctl->column].c_width;
555 for (i = 0; i < width; i++)
556 ctl->buf[ctl->column + i].c_mode = ctl->mode;
557 }
558 set_column(ctl, ctl->column + width);
559 continue;
560 }
561 }
562 if (ctl->max_column)
563 flush_line(ctl, tcs);
564 }
565
566 int main(int argc, char **argv)
567 {
568 int c, ret, opt_terminal = 0;
569 char *termtype;
570 struct term_caps tcs = { 0 };
571 struct ul_ctl ctl = { .current_mode = NORMAL_CHARSET };
572 FILE *f;
573
574 static const struct option longopts[] = {
575 { "terminal", required_argument, NULL, 't' },
576 { "indicated", no_argument, NULL, 'i' },
577 { "version", no_argument, NULL, 'V' },
578 { "help", no_argument, NULL, 'h' },
579 { NULL, 0, NULL, 0 }
580 };
581
582 setlocale(LC_ALL, "");
583 bindtextdomain(PACKAGE, LOCALEDIR);
584 textdomain(PACKAGE);
585 close_stdout_atexit();
586
587 signal(SIGINT, sig_handler);
588 signal(SIGTERM, sig_handler);
589
590 termtype = getenv("TERM");
591
592 while ((c = getopt_long(argc, argv, "it:T:Vh", longopts, NULL)) != -1) {
593 switch (c) {
594
595 case 't':
596 case 'T':
597 /* for nroff compatibility */
598 termtype = optarg;
599 opt_terminal = 1;
600 break;
601 case 'i':
602 ctl.indicated_opt = 1;
603 break;
604
605 case 'V':
606 print_version(EXIT_SUCCESS);
607 case 'h':
608 usage();
609 default:
610 errtryhelp(EXIT_FAILURE);
611 }
612 }
613
614 setupterm(termtype, STDOUT_FILENO, &ret);
615 switch (ret) {
616 case 1:
617 break;
618 default:
619 warnx(_("trouble reading terminfo"));
620 /* fallthrough */
621 case 0:
622 if (opt_terminal)
623 warnx(_("terminal `%s' is not known, defaulting to `dumb'"),
624 termtype);
625 setupterm("dumb", STDOUT_FILENO, (int *)0);
626 break;
627 }
628
629 init_term_caps(&ctl, &tcs);
630 init_buffer(&ctl);
631
632 if (optind == argc)
633 filter(&ctl, &tcs, stdin);
634 else {
635 for (; optind < argc; optind++) {
636 f = fopen(argv[optind], "r");
637 if (!f)
638 err(EXIT_FAILURE, _("cannot open %s"), argv[optind]);
639 filter(&ctl, &tcs, f);
640 fclose(f);
641 }
642 }
643
644 free(ctl.buf);
645 del_curterm(cur_term);
646 return EXIT_SUCCESS;
647 }