]>
Commit | Line | Data |
---|---|---|
6dbe3af9 KZ |
1 | /*- |
2 | * Copyright (c) 1990 The Regents of the University of California. | |
3 | * All rights reserved. | |
4 | * | |
5 | * This code is derived from software contributed to Berkeley by | |
6 | * Michael Rendell of the Memorial University of Newfoundland. | |
7 | * | |
8 | * Redistribution and use in source and binary forms, with or without | |
9 | * modification, are permitted provided that the following conditions | |
10 | * are met: | |
11 | * 1. Redistributions of source code must retain the above copyright | |
12 | * notice, this list of conditions and the following disclaimer. | |
13 | * 2. Redistributions in binary form must reproduce the above copyright | |
14 | * notice, this list of conditions and the following disclaimer in the | |
15 | * documentation and/or other materials provided with the distribution. | |
16 | * 3. All advertising materials mentioning features or use of this software | |
17 | * must display the following acknowledgement: | |
18 | * This product includes software developed by the University of | |
19 | * California, Berkeley and its contributors. | |
20 | * 4. Neither the name of the University nor the names of its contributors | |
21 | * may be used to endorse or promote products derived from this software | |
22 | * without specific prior written permission. | |
23 | * | |
24 | * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND | |
25 | * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE | |
26 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE | |
27 | * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE | |
28 | * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL | |
29 | * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS | |
30 | * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) | |
31 | * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT | |
32 | * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY | |
33 | * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF | |
34 | * SUCH DAMAGE. | |
35 | * | |
36 | * Wed Jun 22 22:15:41 1994, faith@cs.unc.edu: Added internationalization | |
37 | * patches from Andries.Brouwer@cwi.nl | |
38 | * Wed Sep 14 22:31:17 1994: patches from Carl Christofferson | |
39 | * (cchris@connected.com) | |
b50945d4 | 40 | * 1999-02-22 Arkadiusz MiĆkiewicz <misiek@pld.ORG.PL> |
eb63b9b8 KZ |
41 | * added Native Language Support |
42 | * 1999-09-19 Bruno Haible <haible@clisp.cons.org> | |
43 | * modified to work correctly in multi-byte locales | |
7eda085c | 44 | * |
6dbe3af9 KZ |
45 | */ |
46 | ||
7d2600e2 SK |
47 | /* |
48 | * This command is deprecated. The utility is in maintenance mode, | |
49 | * meaning we keep them in source tree for backward compatibility | |
50 | * only. Do not waste time making this command better, unless the | |
51 | * fix is about security or other very critical issue. | |
52 | * | |
53 | * See Documentation/deprecated.txt for more information. | |
54 | */ | |
55 | ||
6dbe3af9 | 56 | #include <ctype.h> |
3375fd9c SK |
57 | #include <errno.h> |
58 | #include <getopt.h> | |
6dbe3af9 | 59 | #include <stdio.h> |
3375fd9c SK |
60 | #include <stdlib.h> |
61 | #include <string.h> | |
fd6b7a7f | 62 | #include <unistd.h> |
eb63b9b8 | 63 | |
c40b3cd0 | 64 | #include "c.h" |
b87cbe84 | 65 | #include "closestream.h" |
3375fd9c | 66 | #include "nls.h" |
9f60a692 | 67 | #include "optutils.h" |
3375fd9c SK |
68 | #include "strutils.h" |
69 | #include "widechar.h" | |
70 | #include "xalloc.h" | |
6dbe3af9 | 71 | |
6dbe3af9 | 72 | #define SPACE ' ' /* space */ |
3375fd9c | 73 | #define BS '\b' /* backspace */ |
6dbe3af9 KZ |
74 | #define NL '\n' /* newline */ |
75 | #define CR '\r' /* carriage return */ | |
3375fd9c SK |
76 | #define TAB '\t' /* tab */ |
77 | #define VT '\v' /* vertical tab (aka reverse line feed) */ | |
78 | ||
6dbe3af9 | 79 | #define ESC '\033' /* escape */ |
3375fd9c SK |
80 | #define RLF '\a' /* ESC-007 reverse line feed */ |
81 | #define RHLF BS /* ESC-010 reverse half-line feed */ | |
82 | #define FHLF TAB /* ESC-011 forward half-line feed */ | |
83 | ||
84 | #define SO '\016' /* activate the G1 character set */ | |
85 | #define SI '\017' /* activate the G0 character set */ | |
6dbe3af9 KZ |
86 | |
87 | /* build up at least this many lines before flushing them out */ | |
88 | #define BUFFER_MARGIN 32 | |
89 | ||
f5ab4ee3 SK |
90 | /* number of lines to allocate */ |
91 | #define NALLOC 64 | |
92 | ||
c40b3cd0 | 93 | #if HAS_FEATURE_ADDRESS_SANITIZER || defined(FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION) |
598ea3fd KZ |
94 | # define COL_DEALLOCATE_ON_EXIT |
95 | #endif | |
96 | ||
3375fd9c | 97 | /* SI & SO charset mode */ |
81c9867d | 98 | enum { |
6c5a421e SK |
99 | CS_NORMAL, |
100 | CS_ALTERNATE | |
81c9867d | 101 | }; |
6dbe3af9 | 102 | |
81c9867d | 103 | struct col_char { |
8f36d392 | 104 | size_t c_column; /* column character is in */ |
eb63b9b8 | 105 | wchar_t c_char; /* character in question */ |
66ee8158 | 106 | int c_width; /* character width */ |
62dee017 KZ |
107 | |
108 | uint8_t c_set:1; /* character set (currently only 2) */ | |
81c9867d SK |
109 | }; |
110 | ||
111 | struct col_line { | |
112 | struct col_char *l_line; /* characters on the line */ | |
113 | struct col_line *l_prev; /* previous line */ | |
114 | struct col_line *l_next; /* next line */ | |
115 | size_t l_lsize; /* allocated sizeof l_line */ | |
116 | size_t l_line_len; /* strlen(l_line) */ | |
117 | size_t l_max_col; /* max column in the line */ | |
62dee017 KZ |
118 | |
119 | uint8_t l_needs_sort:1; /* set if chars went in out of order */ | |
6dbe3af9 KZ |
120 | }; |
121 | ||
598ea3fd | 122 | #ifdef COL_DEALLOCATE_ON_EXIT |
86c6d3ff SK |
123 | /* |
124 | * Free memory before exit when compiling LeakSanitizer. | |
125 | */ | |
126 | struct col_alloc { | |
81c9867d | 127 | struct col_line *l; |
86c6d3ff SK |
128 | struct col_alloc *next; |
129 | }; | |
130 | #endif | |
131 | ||
31a61cbe | 132 | struct col_ctl { |
81c9867d SK |
133 | struct col_line *lines; |
134 | struct col_line *l; /* current line */ | |
8f36d392 | 135 | size_t max_bufd_lines; /* max # lines to keep in memory */ |
81c9867d | 136 | struct col_line *line_freelist; |
8f36d392 | 137 | size_t nblank_lines; /* # blanks after last flushed line */ |
598ea3fd | 138 | #ifdef COL_DEALLOCATE_ON_EXIT |
86c6d3ff SK |
139 | struct col_alloc *alloc_root; /* first of line allocations */ |
140 | struct col_alloc *alloc_head; /* latest line allocation */ | |
141 | #endif | |
62dee017 | 142 | unsigned int |
81c9867d | 143 | last_set:1, /* char_set of last char printed */ |
31a61cbe SK |
144 | compress_spaces:1, /* if doing space -> tab conversion */ |
145 | fine:1, /* if `fine' resolution (half lines) */ | |
146 | no_backspaces:1, /* if not to output any backspaces */ | |
147 | pass_unknown_seqs:1; /* whether to pass unknown control sequences */ | |
148 | }; | |
6dbe3af9 | 149 | |
0148d75b | 150 | struct col_lines { |
81c9867d | 151 | struct col_char *c; |
0148d75b | 152 | wint_t ch; |
8f36d392 SK |
153 | size_t adjust; |
154 | size_t cur_col; | |
155 | ssize_t cur_line; | |
156 | size_t extra_lines; | |
157 | size_t max_line; | |
158 | size_t nflushd_lines; | |
159 | size_t this_line; | |
62dee017 | 160 | |
81c9867d SK |
161 | unsigned int |
162 | cur_set:1, | |
163 | warned:1; | |
0148d75b SK |
164 | }; |
165 | ||
6e1eda6f | 166 | static void __attribute__((__noreturn__)) usage(void) |
2ebef870 | 167 | { |
6e1eda6f | 168 | FILE *out = stdout; |
f688c298 SK |
169 | fprintf(out, _( |
170 | "\nUsage:\n" | |
171 | " %s [options]\n"), program_invocation_short_name); | |
172 | ||
451dbcfa | 173 | fputs(USAGE_SEPARATOR, out); |
62dee017 | 174 | fputs(_("Filter out reverse line feeds from standard input.\n"), out); |
451dbcfa | 175 | |
f688c298 SK |
176 | fprintf(out, _( |
177 | "\nOptions:\n" | |
178 | " -b, --no-backspaces do not output backspaces\n" | |
179 | " -f, --fine permit forward half line feeds\n" | |
180 | " -p, --pass pass unknown control sequences\n" | |
181 | " -h, --tabs convert spaces to tabs\n" | |
182 | " -x, --spaces convert tabs to spaces\n" | |
183 | " -l, --lines NUM buffer at least NUM lines\n" | |
b3054454 | 184 | )); |
bad4c729 MY |
185 | fprintf(out, " -H, --help %s\n", USAGE_OPTSTR_HELP); |
186 | fprintf(out, " -V, --version %s\n", USAGE_OPTSTR_VERSION); | |
f688c298 | 187 | |
bad4c729 | 188 | fprintf(out, USAGE_MAN_TAIL("col(1)")); |
6e1eda6f | 189 | exit(EXIT_SUCCESS); |
2ebef870 KZ |
190 | } |
191 | ||
fd8270b4 | 192 | static inline void col_putchar(wchar_t ch) |
2ebef870 | 193 | { |
fd8270b4 | 194 | if (putwchar(ch) == WEOF) |
3375fd9c | 195 | err(EXIT_FAILURE, _("write failed")); |
2ebef870 KZ |
196 | } |
197 | ||
f5ab4ee3 SK |
198 | /* |
199 | * Print a number of newline/half newlines. If fine flag is set, nblank_lines | |
200 | * is the number of half line feeds, otherwise it is the number of whole line | |
201 | * feeds. | |
202 | */ | |
31a61cbe | 203 | static void flush_blanks(struct col_ctl *ctl) |
f5ab4ee3 | 204 | { |
e15ed087 SK |
205 | int half = 0; |
206 | ssize_t i, nb = ctl->nblank_lines; | |
f5ab4ee3 | 207 | |
f5ab4ee3 | 208 | if (nb & 1) { |
31a61cbe | 209 | if (ctl->fine) |
f5ab4ee3 SK |
210 | half = 1; |
211 | else | |
212 | nb++; | |
213 | } | |
214 | nb /= 2; | |
215 | for (i = nb; --i >= 0;) | |
fd8270b4 | 216 | col_putchar(NL); |
e15ed087 | 217 | |
f5ab4ee3 | 218 | if (half) { |
fd8270b4 SK |
219 | col_putchar(ESC); |
220 | col_putchar('9'); | |
f5ab4ee3 | 221 | if (!nb) |
fd8270b4 | 222 | col_putchar(CR); |
f5ab4ee3 | 223 | } |
31a61cbe | 224 | ctl->nblank_lines = 0; |
f5ab4ee3 SK |
225 | } |
226 | ||
227 | /* | |
228 | * Write a line to stdout taking care of space to tab conversion (-h flag) | |
229 | * and character set shifts. | |
230 | */ | |
81c9867d | 231 | static void flush_line(struct col_ctl *ctl, struct col_line *l) |
f5ab4ee3 | 232 | { |
81c9867d | 233 | struct col_char *c, *endc; |
8f36d392 | 234 | size_t nchars = l->l_line_len, last_col = 0, this_col; |
f5ab4ee3 SK |
235 | |
236 | if (l->l_needs_sort) { | |
81c9867d | 237 | static struct col_char *sorted = NULL; |
8f36d392 SK |
238 | static size_t count_size = 0, *count = NULL, sorted_size = 0; |
239 | size_t i, tot; | |
f5ab4ee3 SK |
240 | |
241 | /* | |
242 | * Do an O(n) sort on l->l_line by column being careful to | |
243 | * preserve the order of characters in the same column. | |
244 | */ | |
e15ed087 | 245 | if (sorted_size < l->l_lsize) { |
f5ab4ee3 | 246 | sorted_size = l->l_lsize; |
64d6d400 | 247 | sorted = xreallocarray(sorted, sorted_size, sizeof(struct col_char)); |
f5ab4ee3 | 248 | } |
e15ed087 | 249 | if (count_size <= l->l_max_col) { |
f5ab4ee3 | 250 | count_size = l->l_max_col + 1; |
64d6d400 | 251 | count = xreallocarray(count, count_size, sizeof(size_t)); |
f5ab4ee3 | 252 | } |
8f36d392 SK |
253 | memset(count, 0, sizeof(size_t) * l->l_max_col + 1); |
254 | for (i = nchars, c = l->l_line; c && 0 < i; i--, c++) | |
f5ab4ee3 SK |
255 | count[c->c_column]++; |
256 | ||
257 | /* | |
258 | * calculate running total (shifted down by 1) to use as | |
259 | * indices into new line. | |
260 | */ | |
261 | for (tot = 0, i = 0; i <= l->l_max_col; i++) { | |
8f36d392 | 262 | size_t save = count[i]; |
f5ab4ee3 SK |
263 | count[i] = tot; |
264 | tot += save; | |
265 | } | |
266 | ||
8f36d392 | 267 | for (i = nchars, c = l->l_line; 0 < i; i--, c++) |
f5ab4ee3 SK |
268 | sorted[count[c->c_column]++] = *c; |
269 | c = sorted; | |
270 | } else | |
271 | c = l->l_line; | |
e15ed087 SK |
272 | |
273 | while (0 < nchars) { | |
f5ab4ee3 SK |
274 | this_col = c->c_column; |
275 | endc = c; | |
3375fd9c SK |
276 | |
277 | /* find last character */ | |
f5ab4ee3 SK |
278 | do { |
279 | ++endc; | |
e15ed087 | 280 | } while (0 < --nchars && this_col == endc->c_column); |
f5ab4ee3 | 281 | |
31a61cbe | 282 | if (ctl->no_backspaces) { |
3375fd9c | 283 | /* print only the last character */ |
f5ab4ee3 | 284 | c = endc - 1; |
e15ed087 | 285 | if (0 < nchars && endc->c_column < this_col + c->c_width) |
f5ab4ee3 SK |
286 | continue; |
287 | } | |
288 | ||
e15ed087 | 289 | if (last_col < this_col) { |
3375fd9c | 290 | /* tabs and spaces handling */ |
8f36d392 | 291 | ssize_t nspace = this_col - last_col; |
f5ab4ee3 | 292 | |
e15ed087 | 293 | if (ctl->compress_spaces && 1 < nspace) { |
8f36d392 | 294 | ssize_t ntabs; |
f5ab4ee3 SK |
295 | |
296 | ntabs = this_col / 8 - last_col / 8; | |
e15ed087 | 297 | if (0 < ntabs) { |
f5ab4ee3 | 298 | nspace = this_col & 7; |
e15ed087 | 299 | while (0 <= --ntabs) |
fd8270b4 | 300 | col_putchar(TAB); |
f5ab4ee3 SK |
301 | } |
302 | } | |
e15ed087 | 303 | while (0 <= --nspace) |
fd8270b4 | 304 | col_putchar(SPACE); |
f5ab4ee3 SK |
305 | last_col = this_col; |
306 | } | |
307 | ||
308 | for (;;) { | |
3375fd9c | 309 | /* SO / SI character set changing */ |
31a61cbe | 310 | if (c->c_set != ctl->last_set) { |
f5ab4ee3 SK |
311 | switch (c->c_set) { |
312 | case CS_NORMAL: | |
fd8270b4 | 313 | col_putchar(SI); |
f5ab4ee3 SK |
314 | break; |
315 | case CS_ALTERNATE: | |
fd8270b4 | 316 | col_putchar(SO); |
12234f46 SK |
317 | break; |
318 | default: | |
319 | abort(); | |
f5ab4ee3 | 320 | } |
31a61cbe | 321 | ctl->last_set = c->c_set; |
f5ab4ee3 | 322 | } |
3375fd9c SK |
323 | |
324 | /* output a character */ | |
fd8270b4 | 325 | col_putchar(c->c_char); |
3375fd9c SK |
326 | |
327 | /* rubout control chars from output */ | |
e15ed087 | 328 | if (c + 1 < endc) { |
f5ab4ee3 | 329 | int i; |
e15ed087 SK |
330 | |
331 | for (i = 0; i < c->c_width; i++) | |
fd8270b4 | 332 | col_putchar(BS); |
f5ab4ee3 | 333 | } |
e15ed087 SK |
334 | |
335 | if (endc <= ++c) | |
f5ab4ee3 SK |
336 | break; |
337 | } | |
338 | last_col += (c - 1)->c_width; | |
339 | } | |
340 | } | |
341 | ||
81c9867d | 342 | static struct col_line *alloc_line(struct col_ctl *ctl) |
f5ab4ee3 | 343 | { |
81c9867d | 344 | struct col_line *l; |
8f36d392 | 345 | size_t i; |
f5ab4ee3 | 346 | |
31a61cbe | 347 | if (!ctl->line_freelist) { |
81c9867d | 348 | l = xmalloc(sizeof(struct col_line) * NALLOC); |
598ea3fd | 349 | #ifdef COL_DEALLOCATE_ON_EXIT |
86c6d3ff SK |
350 | if (ctl->alloc_root == NULL) { |
351 | ctl->alloc_root = xcalloc(1, sizeof(struct col_alloc)); | |
352 | ctl->alloc_root->l = l; | |
353 | ctl->alloc_head = ctl->alloc_root; | |
354 | } else { | |
355 | ctl->alloc_head->next = xcalloc(1, sizeof(struct col_alloc)); | |
356 | ctl->alloc_head = ctl->alloc_head->next; | |
357 | ctl->alloc_head->l = l; | |
358 | } | |
359 | #endif | |
31a61cbe | 360 | ctl->line_freelist = l; |
f5ab4ee3 SK |
361 | for (i = 1; i < NALLOC; i++, l++) |
362 | l->l_next = l + 1; | |
363 | l->l_next = NULL; | |
364 | } | |
31a61cbe SK |
365 | l = ctl->line_freelist; |
366 | ctl->line_freelist = l->l_next; | |
f5ab4ee3 | 367 | |
81c9867d | 368 | memset(l, 0, sizeof(struct col_line)); |
f5ab4ee3 SK |
369 | return l; |
370 | } | |
371 | ||
81c9867d | 372 | static void free_line(struct col_ctl *ctl, struct col_line *l) |
f5ab4ee3 | 373 | { |
31a61cbe SK |
374 | l->l_next = ctl->line_freelist; |
375 | ctl->line_freelist = l; | |
f5ab4ee3 SK |
376 | } |
377 | ||
8f36d392 | 378 | static void flush_lines(struct col_ctl *ctl, ssize_t nflush) |
f5ab4ee3 | 379 | { |
81c9867d | 380 | struct col_line *l; |
f5ab4ee3 | 381 | |
e15ed087 | 382 | while (0 <= --nflush) { |
31a61cbe SK |
383 | l = ctl->lines; |
384 | ctl->lines = l->l_next; | |
f5ab4ee3 | 385 | if (l->l_line) { |
31a61cbe SK |
386 | flush_blanks(ctl); |
387 | flush_line(ctl, l); | |
f5ab4ee3 | 388 | } |
31a61cbe | 389 | ctl->nblank_lines++; |
3375fd9c | 390 | free(l->l_line); |
31a61cbe | 391 | free_line(ctl, l); |
f5ab4ee3 | 392 | } |
31a61cbe SK |
393 | if (ctl->lines) |
394 | ctl->lines->l_prev = NULL; | |
f5ab4ee3 SK |
395 | } |
396 | ||
0148d75b | 397 | static int handle_not_graphic(struct col_ctl *ctl, struct col_lines *lns) |
6591d3bd | 398 | { |
0148d75b | 399 | switch (lns->ch) { |
3375fd9c | 400 | case BS: |
0148d75b | 401 | if (lns->cur_col == 0) |
3375fd9c | 402 | return 1; /* can't go back further */ |
0148d75b SK |
403 | if (lns->c) |
404 | lns->cur_col -= lns->c->c_width; | |
6591d3bd | 405 | else |
0148d75b | 406 | lns->cur_col -= 1; |
6591d3bd SK |
407 | return 1; |
408 | case CR: | |
0148d75b | 409 | lns->cur_col = 0; |
6591d3bd | 410 | return 1; |
3375fd9c SK |
411 | case ESC: |
412 | switch (getwchar()) { /* just ignore EOF */ | |
6591d3bd | 413 | case RLF: |
0148d75b | 414 | lns->cur_line -= 2; |
6591d3bd SK |
415 | break; |
416 | case RHLF: | |
0148d75b | 417 | lns->cur_line -= 1; |
6591d3bd SK |
418 | break; |
419 | case FHLF: | |
0148d75b | 420 | lns->cur_line += 1; |
8f36d392 | 421 | if (0 < lns->cur_line && lns->max_line < (size_t)lns->cur_line) |
0148d75b | 422 | lns->max_line = lns->cur_line; |
12234f46 SK |
423 | break; |
424 | default: | |
425 | break; | |
6591d3bd SK |
426 | } |
427 | return 1; | |
428 | case NL: | |
0148d75b | 429 | lns->cur_line += 2; |
8f36d392 | 430 | if (0 < lns->cur_line && lns->max_line < (size_t)lns->cur_line) |
0148d75b SK |
431 | lns->max_line = lns->cur_line; |
432 | lns->cur_col = 0; | |
6591d3bd SK |
433 | return 1; |
434 | case SPACE: | |
0148d75b | 435 | lns->cur_col += 1; |
6591d3bd SK |
436 | return 1; |
437 | case SI: | |
0148d75b | 438 | lns->cur_set = CS_NORMAL; |
6591d3bd SK |
439 | return 1; |
440 | case SO: | |
0148d75b | 441 | lns->cur_set = CS_ALTERNATE; |
6591d3bd SK |
442 | return 1; |
443 | case TAB: /* adjust column */ | |
0148d75b SK |
444 | lns->cur_col |= 7; |
445 | lns->cur_col += 1; | |
6591d3bd SK |
446 | return 1; |
447 | case VT: | |
0148d75b | 448 | lns->cur_line -= 2; |
6591d3bd | 449 | return 1; |
12234f46 SK |
450 | default: |
451 | break; | |
6591d3bd | 452 | } |
0148d75b | 453 | if (iswspace(lns->ch)) { |
e15ed087 | 454 | if (0 < wcwidth(lns->ch)) |
0148d75b | 455 | lns->cur_col += wcwidth(lns->ch); |
6591d3bd SK |
456 | return 1; |
457 | } | |
458 | ||
459 | if (!ctl->pass_unknown_seqs) | |
460 | return 1; | |
461 | return 0; | |
462 | } | |
463 | ||
0148d75b | 464 | static void update_cur_line(struct col_ctl *ctl, struct col_lines *lns) |
812e849e | 465 | { |
8f36d392 | 466 | ssize_t nmove; |
812e849e | 467 | |
0148d75b SK |
468 | lns->adjust = 0; |
469 | nmove = lns->cur_line - lns->this_line; | |
812e849e SK |
470 | if (!ctl->fine) { |
471 | /* round up to next line */ | |
0148d75b SK |
472 | if (lns->cur_line & 1) { |
473 | lns->adjust = 1; | |
812e849e SK |
474 | nmove++; |
475 | } | |
476 | } | |
477 | if (nmove < 0) { | |
478 | for (; nmove < 0 && ctl->l->l_prev; nmove++) | |
479 | ctl->l = ctl->l->l_prev; | |
e15ed087 | 480 | |
812e849e | 481 | if (nmove) { |
0148d75b | 482 | if (lns->nflushd_lines == 0) { |
812e849e SK |
483 | /* |
484 | * Allow backup past first line if nothing | |
485 | * has been flushed yet. | |
486 | */ | |
487 | for (; nmove < 0; nmove++) { | |
81c9867d | 488 | struct col_line *lnew = alloc_line(ctl); |
812e849e SK |
489 | ctl->l->l_prev = lnew; |
490 | lnew->l_next = ctl->l; | |
491 | ctl->l = ctl->lines = lnew; | |
0148d75b | 492 | lns->extra_lines += 1; |
812e849e SK |
493 | } |
494 | } else { | |
8f36d392 | 495 | if (!lns->warned) { |
812e849e | 496 | warnx(_("warning: can't back up %s."), |
0148d75b | 497 | lns->cur_line < 0 ? |
812e849e SK |
498 | _("past first line") : |
499 | _("-- line already flushed")); | |
8f36d392 SK |
500 | lns->warned = 1; |
501 | } | |
0148d75b | 502 | lns->cur_line -= nmove; |
812e849e SK |
503 | } |
504 | } | |
505 | } else { | |
506 | /* may need to allocate here */ | |
e15ed087 | 507 | for (; 0 < nmove && ctl->l->l_next; nmove--) |
812e849e | 508 | ctl->l = ctl->l->l_next; |
e15ed087 SK |
509 | |
510 | for (; 0 < nmove; nmove--) { | |
81c9867d | 511 | struct col_line *lnew = alloc_line(ctl); |
812e849e SK |
512 | lnew->l_prev = ctl->l; |
513 | ctl->l->l_next = lnew; | |
514 | ctl->l = lnew; | |
515 | } | |
516 | } | |
e15ed087 | 517 | |
0148d75b SK |
518 | lns->this_line = lns->cur_line + lns->adjust; |
519 | nmove = lns->this_line - lns->nflushd_lines; | |
3375fd9c | 520 | |
e15ed087 | 521 | if (0 < nmove && ctl->max_bufd_lines + BUFFER_MARGIN <= (size_t)nmove) { |
0148d75b | 522 | lns->nflushd_lines += nmove - ctl->max_bufd_lines; |
812e849e SK |
523 | flush_lines(ctl, nmove - ctl->max_bufd_lines); |
524 | } | |
525 | } | |
526 | ||
9f60a692 | 527 | static void parse_options(struct col_ctl *ctl, int argc, char **argv) |
6dbe3af9 | 528 | { |
f688c298 | 529 | static const struct option longopts[] = { |
87918040 SK |
530 | { "no-backspaces", no_argument, NULL, 'b' }, |
531 | { "fine", no_argument, NULL, 'f' }, | |
532 | { "pass", no_argument, NULL, 'p' }, | |
533 | { "tabs", no_argument, NULL, 'h' }, | |
534 | { "spaces", no_argument, NULL, 'x' }, | |
535 | { "lines", required_argument, NULL, 'l' }, | |
536 | { "version", no_argument, NULL, 'V' }, | |
537 | { "help", no_argument, NULL, 'H' }, | |
538 | { NULL, 0, NULL, 0 } | |
f688c298 | 539 | }; |
9f60a692 SK |
540 | static const ul_excl_t excl[] = { |
541 | { 'h', 'x' }, | |
542 | { 0 } | |
543 | }; | |
544 | int excl_st[ARRAY_SIZE(excl)] = UL_EXCL_STATUS_INIT; | |
545 | int opt; | |
f688c298 | 546 | |
9f60a692 SK |
547 | while ((opt = getopt_long(argc, argv, "bfhl:pxVH", longopts, NULL)) != -1) { |
548 | err_exclusive_options(opt, longopts, excl, excl_st); | |
2ebef870 | 549 | |
6dbe3af9 KZ |
550 | switch (opt) { |
551 | case 'b': /* do not output backspaces */ | |
9f60a692 | 552 | ctl->no_backspaces = 1; |
6dbe3af9 KZ |
553 | break; |
554 | case 'f': /* allow half forward line feeds */ | |
9f60a692 | 555 | ctl->fine = 1; |
6dbe3af9 KZ |
556 | break; |
557 | case 'h': /* compress spaces into tabs */ | |
9f60a692 | 558 | ctl->compress_spaces = 1; |
6dbe3af9 | 559 | break; |
b07e72d9 SK |
560 | case 'l': |
561 | /* | |
562 | * Buffered line count, which is a value in half | |
563 | * lines e.g. twice the amount specified. | |
564 | */ | |
9f60a692 | 565 | ctl->max_bufd_lines = strtou32_or_err(optarg, _("bad -l argument")) * 2; |
6dbe3af9 | 566 | break; |
22853e4a | 567 | case 'p': |
9f60a692 | 568 | ctl->pass_unknown_seqs = 1; |
22853e4a | 569 | break; |
6dbe3af9 | 570 | case 'x': /* do not compress spaces into tabs */ |
9f60a692 | 571 | ctl->compress_spaces = 0; |
6dbe3af9 | 572 | break; |
2c308875 | 573 | |
f688c298 | 574 | case 'V': |
2c308875 | 575 | print_version(EXIT_SUCCESS); |
f688c298 | 576 | case 'H': |
6e1eda6f | 577 | usage(); |
6dbe3af9 | 578 | default: |
677ec86c | 579 | errtryhelp(EXIT_FAILURE); |
6dbe3af9 | 580 | } |
9f60a692 | 581 | } |
6dbe3af9 | 582 | |
6e1eda6f RM |
583 | if (optind != argc) { |
584 | warnx(_("bad usage")); | |
585 | errtryhelp(EXIT_FAILURE); | |
586 | } | |
9f60a692 SK |
587 | } |
588 | ||
598ea3fd | 589 | #ifdef COL_DEALLOCATE_ON_EXIT |
86c6d3ff SK |
590 | static void free_line_allocations(struct col_alloc *root) |
591 | { | |
592 | struct col_alloc *next; | |
593 | ||
594 | while (root) { | |
595 | next = root->next; | |
596 | free(root->l); | |
597 | free(root); | |
598 | root = next; | |
599 | } | |
600 | } | |
601 | #endif | |
602 | ||
14a36f96 SK |
603 | static void process_char(struct col_ctl *ctl, struct col_lines *lns) |
604 | { | |
605 | /* Deal printable characters */ | |
606 | if (!iswgraph(lns->ch) && handle_not_graphic(ctl, lns)) | |
607 | return; | |
608 | ||
609 | /* Must stuff ch in a line - are we at the right one? */ | |
610 | if ((size_t)lns->cur_line != lns->this_line - lns->adjust) | |
611 | update_cur_line(ctl, lns); | |
612 | ||
613 | /* Does line buffer need to grow? */ | |
614 | if (ctl->l->l_lsize <= ctl->l->l_line_len + 1) { | |
615 | size_t need; | |
616 | ||
617 | need = ctl->l->l_lsize ? ctl->l->l_lsize * 2 : NALLOC; | |
618 | ctl->l->l_line = xrealloc(ctl->l->l_line, need * sizeof(struct col_char)); | |
619 | ctl->l->l_lsize = need; | |
620 | } | |
621 | ||
622 | /* Store character */ | |
623 | lns->c = &ctl->l->l_line[ctl->l->l_line_len++]; | |
624 | lns->c->c_char = lns->ch; | |
625 | lns->c->c_set = lns->cur_set; | |
626 | ||
627 | if (0 < lns->cur_col) | |
628 | lns->c->c_column = lns->cur_col; | |
629 | else | |
630 | lns->c->c_column = 0; | |
631 | lns->c->c_width = wcwidth(lns->ch); | |
632 | ||
633 | /* | |
634 | * If things are put in out of order, they will need sorting | |
635 | * when it is flushed. | |
636 | */ | |
637 | if (lns->cur_col < ctl->l->l_max_col) | |
638 | ctl->l->l_needs_sort = 1; | |
639 | else | |
640 | ctl->l->l_max_col = lns->cur_col; | |
641 | if (0 < lns->c->c_width) | |
642 | lns->cur_col += lns->c->c_width; | |
643 | ||
644 | } | |
645 | ||
9f60a692 SK |
646 | int main(int argc, char **argv) |
647 | { | |
648 | struct col_ctl ctl = { | |
649 | .compress_spaces = 1, | |
650 | .last_set = CS_NORMAL, | |
8f36d392 | 651 | .max_bufd_lines = BUFFER_MARGIN * 2, |
9f60a692 | 652 | }; |
0148d75b SK |
653 | struct col_lines lns = { |
654 | .cur_set = CS_NORMAL, | |
655 | }; | |
9f60a692 SK |
656 | int ret = EXIT_SUCCESS; |
657 | ||
658 | setlocale(LC_ALL, ""); | |
659 | bindtextdomain(PACKAGE, LOCALEDIR); | |
660 | textdomain(PACKAGE); | |
661 | close_stdout_atexit(); | |
6dbe3af9 | 662 | |
812e849e | 663 | ctl.lines = ctl.l = alloc_line(&ctl); |
2ebef870 | 664 | |
9f60a692 SK |
665 | parse_options(&ctl, argc, argv); |
666 | ||
8e0ec3b4 | 667 | while (feof(stdin) == 0) { |
e9ce5ccc | 668 | errno = 0; |
3375fd9c | 669 | /* Get character */ |
14a36f96 | 670 | lns.ch = getwchar(); |
6dbe3af9 | 671 | |
14a36f96 SK |
672 | if (lns.ch == WEOF) { |
673 | if (errno == EILSEQ) { | |
674 | /* Illegal multibyte sequence */ | |
675 | int c; | |
676 | char buf[5]; | |
677 | size_t len, i; | |
3375fd9c | 678 | |
14a36f96 SK |
679 | c = getchar(); |
680 | if (c == EOF) | |
681 | break; | |
682 | sprintf(buf, "\\x%02x", (unsigned char) c); | |
683 | len = strlen(buf); | |
684 | for (i = 0; i < len; i++) { | |
685 | lns.ch = buf[i]; | |
686 | process_char(&ctl, &lns); | |
687 | } | |
688 | } else | |
689 | /* end of file */ | |
690 | break; | |
691 | } else | |
692 | /* the common case */ | |
693 | process_char(&ctl, &lns); | |
6dbe3af9 | 694 | } |
3375fd9c | 695 | |
6dbe3af9 | 696 | /* goto the last line that had a character on it */ |
812e849e | 697 | for (; ctl.l->l_next; ctl.l = ctl.l->l_next) |
0148d75b | 698 | lns.this_line++; |
86c6d3ff | 699 | if (lns.max_line == 0 && lns.cur_col == 0) { |
598ea3fd | 700 | #ifdef COL_DEALLOCATE_ON_EXIT |
86c6d3ff SK |
701 | free_line_allocations(ctl.alloc_root); |
702 | #endif | |
b6b5272b | 703 | return EXIT_SUCCESS; /* no lines, so just exit */ |
86c6d3ff | 704 | } |
0148d75b | 705 | flush_lines(&ctl, lns.this_line - lns.nflushd_lines + lns.extra_lines + 1); |
6dbe3af9 KZ |
706 | |
707 | /* make sure we leave things in a sane state */ | |
31a61cbe | 708 | if (ctl.last_set != CS_NORMAL) |
fd8270b4 | 709 | col_putchar(SI); |
6dbe3af9 KZ |
710 | |
711 | /* flush out the last few blank lines */ | |
0148d75b SK |
712 | ctl.nblank_lines = lns.max_line - lns.this_line; |
713 | if (lns.max_line & 1) | |
31a61cbe SK |
714 | ctl.nblank_lines++; |
715 | else if (!ctl.nblank_lines) | |
6dbe3af9 | 716 | /* missing a \n on the last line? */ |
31a61cbe SK |
717 | ctl.nblank_lines = 2; |
718 | flush_blanks(&ctl); | |
598ea3fd | 719 | #ifdef COL_DEALLOCATE_ON_EXIT |
86c6d3ff SK |
720 | free_line_allocations(ctl.alloc_root); |
721 | #endif | |
e9ce5ccc | 722 | return ret; |
6dbe3af9 | 723 | } |