timeout: add the --kill-after option
[coreutils/ericb.git] / src / expand.c
blobfa1fdfeb786a1024723fabdc85101653ac4e9ff6
1 /* expand - convert tabs to spaces
2 Copyright (C) 1989, 1991, 1995-2006, 2008-2010 Free Software Foundation,
3 Inc.
5 This program is free software: you can redistribute it and/or modify
6 it under the terms of the GNU General Public License as published by
7 the Free Software Foundation, either version 3 of the License, or
8 (at your option) any later version.
10 This program is distributed in the hope that it will be useful,
11 but WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 GNU General Public License for more details.
15 You should have received a copy of the GNU General Public License
16 along with this program. If not, see <http://www.gnu.org/licenses/>. */
18 /* By default, convert all tabs to spaces.
19 Preserves backspace characters in the output; they decrement the
20 column count for tab calculations.
21 The default action is equivalent to -8.
23 Options:
24 --tabs=tab1[,tab2[,...]]
25 -t tab1[,tab2[,...]]
26 -tab1[,tab2[,...]] If only one tab stop is given, set the tabs tab1
27 columns apart instead of the default 8. Otherwise,
28 set the tabs at columns tab1, tab2, etc. (numbered from
29 0); replace any tabs beyond the tab stops given with
30 single spaces.
31 --initial
32 -i Only convert initial tabs on each line to spaces.
34 David MacKenzie <djm@gnu.ai.mit.edu> */
36 #include <config.h>
38 #include <stdio.h>
39 #include <getopt.h>
40 #include <sys/types.h>
41 #include "system.h"
42 #include "error.h"
43 #include "quote.h"
44 #include "xstrndup.h"
46 /* The official name of this program (e.g., no `g' prefix). */
47 #define PROGRAM_NAME "expand"
49 #define AUTHORS proper_name ("David MacKenzie")
51 /* If true, convert blanks even after nonblank characters have been
52 read on the line. */
53 static bool convert_entire_line;
55 /* If nonzero, the size of all tab stops. If zero, use `tab_list' instead. */
56 static uintmax_t tab_size;
58 /* Array of the explicit column numbers of the tab stops;
59 after `tab_list' is exhausted, each additional tab is replaced
60 by a space. The first column is column 0. */
61 static uintmax_t *tab_list;
63 /* The number of allocated entries in `tab_list'. */
64 static size_t n_tabs_allocated;
66 /* The index of the first invalid element of `tab_list',
67 where the next element can be added. */
68 static size_t first_free_tab;
70 /* Null-terminated array of input filenames. */
71 static char **file_list;
73 /* Default for `file_list' if no files are given on the command line. */
74 static char *stdin_argv[] =
76 (char *) "-", NULL
79 /* True if we have ever read standard input. */
80 static bool have_read_stdin;
82 /* The desired exit status. */
83 static int exit_status;
85 static char const shortopts[] = "it:0::1::2::3::4::5::6::7::8::9::";
87 static struct option const longopts[] =
89 {"tabs", required_argument, NULL, 't'},
90 {"initial", no_argument, NULL, 'i'},
91 {GETOPT_HELP_OPTION_DECL},
92 {GETOPT_VERSION_OPTION_DECL},
93 {NULL, 0, NULL, 0}
96 void
97 usage (int status)
99 if (status != EXIT_SUCCESS)
100 fprintf (stderr, _("Try `%s --help' for more information.\n"),
101 program_name);
102 else
104 printf (_("\
105 Usage: %s [OPTION]... [FILE]...\n\
107 program_name);
108 fputs (_("\
109 Convert tabs in each FILE to spaces, writing to standard output.\n\
110 With no FILE, or when FILE is -, read standard input.\n\
112 "), stdout);
113 fputs (_("\
114 Mandatory arguments to long options are mandatory for short options too.\n\
115 "), stdout);
116 fputs (_("\
117 -i, --initial do not convert tabs after non blanks\n\
118 -t, --tabs=NUMBER have tabs NUMBER characters apart, not 8\n\
119 "), stdout);
120 fputs (_("\
121 -t, --tabs=LIST use comma separated list of explicit tab positions\n\
122 "), stdout);
123 fputs (HELP_OPTION_DESCRIPTION, stdout);
124 fputs (VERSION_OPTION_DESCRIPTION, stdout);
125 emit_ancillary_info ();
127 exit (status);
130 /* Add tab stop TABVAL to the end of `tab_list'. */
132 static void
133 add_tab_stop (uintmax_t tabval)
135 if (first_free_tab == n_tabs_allocated)
136 tab_list = X2NREALLOC (tab_list, &n_tabs_allocated);
137 tab_list[first_free_tab++] = tabval;
140 /* Add the comma or blank separated list of tab stops STOPS
141 to the list of tab stops. */
143 static void
144 parse_tab_stops (char const *stops)
146 bool have_tabval = false;
147 uintmax_t tabval IF_LINT (= 0);
148 char const *num_start IF_LINT (= NULL);
149 bool ok = true;
151 for (; *stops; stops++)
153 if (*stops == ',' || isblank (to_uchar (*stops)))
155 if (have_tabval)
156 add_tab_stop (tabval);
157 have_tabval = false;
159 else if (ISDIGIT (*stops))
161 if (!have_tabval)
163 tabval = 0;
164 have_tabval = true;
165 num_start = stops;
168 /* Detect overflow. */
169 if (!DECIMAL_DIGIT_ACCUMULATE (tabval, *stops - '0', uintmax_t))
171 size_t len = strspn (num_start, "0123456789");
172 char *bad_num = xstrndup (num_start, len);
173 error (0, 0, _("tab stop is too large %s"), quote (bad_num));
174 free (bad_num);
175 ok = false;
176 stops = num_start + len - 1;
179 else
181 error (0, 0, _("tab size contains invalid character(s): %s"),
182 quote (stops));
183 ok = false;
184 break;
188 if (!ok)
189 exit (EXIT_FAILURE);
191 if (have_tabval)
192 add_tab_stop (tabval);
195 /* Check that the list of tab stops TABS, with ENTRIES entries,
196 contains only nonzero, ascending values. */
198 static void
199 validate_tab_stops (uintmax_t const *tabs, size_t entries)
201 uintmax_t prev_tab = 0;
202 size_t i;
204 for (i = 0; i < entries; i++)
206 if (tabs[i] == 0)
207 error (EXIT_FAILURE, 0, _("tab size cannot be 0"));
208 if (tabs[i] <= prev_tab)
209 error (EXIT_FAILURE, 0, _("tab sizes must be ascending"));
210 prev_tab = tabs[i];
214 /* Close the old stream pointer FP if it is non-NULL,
215 and return a new one opened to read the next input file.
216 Open a filename of `-' as the standard input.
217 Return NULL if there are no more input files. */
219 static FILE *
220 next_file (FILE *fp)
222 static char *prev_file;
223 char *file;
225 if (fp)
227 if (ferror (fp))
229 error (0, errno, "%s", prev_file);
230 exit_status = EXIT_FAILURE;
232 if (STREQ (prev_file, "-"))
233 clearerr (fp); /* Also clear EOF. */
234 else if (fclose (fp) != 0)
236 error (0, errno, "%s", prev_file);
237 exit_status = EXIT_FAILURE;
241 while ((file = *file_list++) != NULL)
243 if (STREQ (file, "-"))
245 have_read_stdin = true;
246 prev_file = file;
247 return stdin;
249 fp = fopen (file, "r");
250 if (fp)
252 prev_file = file;
253 return fp;
255 error (0, errno, "%s", file);
256 exit_status = EXIT_FAILURE;
258 return NULL;
261 /* Change tabs to spaces, writing to stdout.
262 Read each file in `file_list', in order. */
264 static void
265 expand (void)
267 /* Input stream. */
268 FILE *fp = next_file (NULL);
270 if (!fp)
271 return;
273 for (;;)
275 /* Input character, or EOF. */
276 int c;
278 /* If true, perform translations. */
279 bool convert = true;
282 /* The following variables have valid values only when CONVERT
283 is true: */
285 /* Column of next input character. */
286 uintmax_t column = 0;
288 /* Index in TAB_LIST of next tab stop to examine. */
289 size_t tab_index = 0;
292 /* Convert a line of text. */
296 while ((c = getc (fp)) < 0 && (fp = next_file (fp)))
297 continue;
299 if (convert)
301 if (c == '\t')
303 /* Column the next input tab stop is on. */
304 uintmax_t next_tab_column;
306 if (tab_size)
307 next_tab_column = column + (tab_size - column % tab_size);
308 else
309 for (;;)
310 if (tab_index == first_free_tab)
312 next_tab_column = column + 1;
313 break;
315 else
317 uintmax_t tab = tab_list[tab_index++];
318 if (column < tab)
320 next_tab_column = tab;
321 break;
325 if (next_tab_column < column)
326 error (EXIT_FAILURE, 0, _("input line is too long"));
328 while (++column < next_tab_column)
329 if (putchar (' ') < 0)
330 error (EXIT_FAILURE, errno, _("write error"));
332 c = ' ';
334 else if (c == '\b')
336 /* Go back one column, and force recalculation of the
337 next tab stop. */
338 column -= !!column;
339 tab_index -= !!tab_index;
341 else
343 column++;
344 if (!column)
345 error (EXIT_FAILURE, 0, _("input line is too long"));
348 convert &= convert_entire_line || !! isblank (c);
351 if (c < 0)
352 return;
354 if (putchar (c) < 0)
355 error (EXIT_FAILURE, errno, _("write error"));
357 while (c != '\n');
362 main (int argc, char **argv)
364 int c;
366 initialize_main (&argc, &argv);
367 set_program_name (argv[0]);
368 setlocale (LC_ALL, "");
369 bindtextdomain (PACKAGE, LOCALEDIR);
370 textdomain (PACKAGE);
372 atexit (close_stdout);
374 have_read_stdin = false;
375 exit_status = EXIT_SUCCESS;
376 convert_entire_line = true;
377 tab_list = NULL;
378 first_free_tab = 0;
380 while ((c = getopt_long (argc, argv, shortopts, longopts, NULL)) != -1)
382 switch (c)
384 case 'i':
385 convert_entire_line = false;
386 break;
388 case 't':
389 parse_tab_stops (optarg);
390 break;
392 case '0': case '1': case '2': case '3': case '4':
393 case '5': case '6': case '7': case '8': case '9':
394 if (optarg)
395 parse_tab_stops (optarg - 1);
396 else
398 char tab_stop[2];
399 tab_stop[0] = c;
400 tab_stop[1] = '\0';
401 parse_tab_stops (tab_stop);
403 break;
405 case_GETOPT_HELP_CHAR;
407 case_GETOPT_VERSION_CHAR (PROGRAM_NAME, AUTHORS);
409 default:
410 usage (EXIT_FAILURE);
414 validate_tab_stops (tab_list, first_free_tab);
416 if (first_free_tab == 0)
417 tab_size = 8;
418 else if (first_free_tab == 1)
419 tab_size = tab_list[0];
420 else
421 tab_size = 0;
423 file_list = (optind < argc ? &argv[optind] : stdin_argv);
425 expand ();
427 if (have_read_stdin && fclose (stdin) != 0)
428 error (EXIT_FAILURE, errno, "-");
430 exit (exit_status);