1 /* expand - convert tabs to spaces
2 Copyright (C) 89, 91, 1995-2005 Free Software Foundation, Inc.
4 This program is free software; you can redistribute it and/or modify
5 it under the terms of the GNU General Public License as published by
6 the Free Software Foundation; either version 2, or (at your option)
9 This program is distributed in the hope that it will be useful,
10 but WITHOUT ANY WARRANTY; without even the implied warranty of
11 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 GNU General Public License for more details.
14 You should have received a copy of the GNU General Public License
15 along with this program; if not, write to the Free Software Foundation,
16 Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */
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.
24 --tabs=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
32 -i Only convert initial tabs on each line to spaces.
34 David MacKenzie <djm@gnu.ai.mit.edu> */
40 #include <sys/types.h>
47 /* The official name of this program (e.g., no `g' prefix). */
48 #define PROGRAM_NAME "expand"
50 #define AUTHORS "David MacKenzie"
52 /* The number of bytes added at a time to the amount of memory
53 allocated for the output line. */
54 #define OUTPUT_BLOCK 256
56 /* The name this program was run with. */
59 /* If true, convert blanks even after nonblank characters have been
61 static bool convert_entire_line;
63 /* If nonzero, the size of all tab stops. If zero, use `tab_list' instead. */
64 static uintmax_t tab_size;
66 /* Array of the explicit column numbers of the tab stops;
67 after `tab_list' is exhausted, each additional tab is replaced
68 by a space. The first column is column 0. */
69 static uintmax_t *tab_list;
71 /* The number of allocated entries in `tab_list'. */
72 static size_t n_tabs_allocated;
74 /* The index of the first invalid element of `tab_list',
75 where the next element can be added. */
76 static size_t first_free_tab;
78 /* Null-terminated array of input filenames. */
79 static char **file_list;
81 /* Default for `file_list' if no files are given on the command line. */
82 static char *stdin_argv[] =
87 /* True if we have ever read standard input. */
88 static bool have_read_stdin;
90 /* The desired exit status. */
91 static int exit_status;
93 static struct option const longopts[] =
95 {"tabs", required_argument, NULL, 't'},
96 {"initial", no_argument, NULL, 'i'},
97 {GETOPT_HELP_OPTION_DECL},
98 {GETOPT_VERSION_OPTION_DECL},
105 if (status != EXIT_SUCCESS)
106 fprintf (stderr, _("Try `%s --help' for more information.\n"),
111 Usage: %s [OPTION]... [FILE]...\n\
115 Convert tabs in each FILE to spaces, writing to standard output.\n\
116 With no FILE, or when FILE is -, read standard input.\n\
120 Mandatory arguments to long options are mandatory for short options too.\n\
123 -i, --initial do not convert tabs after non blanks\n\
124 -t, --tabs=NUMBER have tabs NUMBER characters apart, not 8\n\
127 -t, --tabs=LIST use comma separated list of explicit tab positions\n\
129 fputs (HELP_OPTION_DESCRIPTION, stdout);
130 fputs (VERSION_OPTION_DESCRIPTION, stdout);
131 printf (_("\nReport bugs to <%s>.\n"), PACKAGE_BUGREPORT);
136 /* Add tab stop TABVAL to the end of `tab_list'. */
139 add_tab_stop (uintmax_t tabval)
141 if (first_free_tab == n_tabs_allocated)
142 tab_list = x2nrealloc (tab_list, &n_tabs_allocated, sizeof *tab_list);
143 tab_list[first_free_tab++] = tabval;
146 /* Add the comma or blank separated list of tab stops STOPS
147 to the list of tab stops. */
150 parse_tab_stops (char const *stops)
152 bool have_tabval = false;
153 uintmax_t tabval IF_LINT (= 0);
154 char const *num_start IF_LINT (= NULL);
157 for (; *stops; stops++)
159 if (*stops == ',' || ISBLANK (to_uchar (*stops)))
162 add_tab_stop (tabval);
165 else if (ISDIGIT (*stops))
174 /* Detect overflow. */
175 uintmax_t new_t = 10 * tabval + *stops - '0';
176 if (UINTMAX_MAX / 10 < tabval || new_t < tabval * 10)
178 size_t len = strspn (num_start, "0123456789");
179 char *bad_num = xstrndup (num_start, len);
180 error (0, 0, _("tab stop is too large %s"), quote (bad_num));
183 stops = num_start + len - 1;
190 error (0, 0, _("tab size contains invalid character(s): %s"),
201 add_tab_stop (tabval);
204 /* Check that the list of tab stops TABS, with ENTRIES entries,
205 contains only nonzero, ascending values. */
208 validate_tab_stops (uintmax_t const *tabs, size_t entries)
210 uintmax_t prev_tab = 0;
213 for (i = 0; i < entries; i++)
216 error (EXIT_FAILURE, 0, _("tab size cannot be 0"));
217 if (tabs[i] <= prev_tab)
218 error (EXIT_FAILURE, 0, _("tab sizes must be ascending"));
223 /* Close the old stream pointer FP if it is non-NULL,
224 and return a new one opened to read the next input file.
225 Open a filename of `-' as the standard input.
226 Return NULL if there are no more input files. */
231 static char *prev_file;
238 error (0, errno, "%s", prev_file);
239 exit_status = EXIT_FAILURE;
242 clearerr (fp); /* Also clear EOF. */
243 else if (fclose (fp) != 0)
245 error (0, errno, "%s", prev_file);
246 exit_status = EXIT_FAILURE;
250 while ((file = *file_list++) != NULL)
252 if (file[0] == '-' && file[1] == '\0')
254 have_read_stdin = true;
258 fp = fopen (file, "r");
264 error (0, errno, "%s", file);
265 exit_status = EXIT_FAILURE;
270 /* Change tabs to spaces, writing to stdout.
271 Read each file in `file_list', in order. */
277 FILE *fp = next_file (NULL);
282 /* Binary I/O will preserve the original EOL style (DOS/Unix) of files. */
283 SET_BINARY2 (fileno (fp), STDOUT_FILENO);
287 /* Input character, or EOF. */
290 /* If true, perform translations. */
294 /* The following variables have valid values only when CONVERT
297 /* Column of next input character. */
298 uintmax_t column = 0;
300 /* Index in TAB_LIST of next tab stop to examine. */
301 size_t tab_index = 0;
304 /* Convert a line of text. */
308 while ((c = getc (fp)) < 0 && (fp = next_file (fp)))
309 SET_BINARY2 (fileno (fp), STDOUT_FILENO);
315 /* Column the next input tab stop is on. */
316 uintmax_t next_tab_column;
319 next_tab_column = column + (tab_size - column % tab_size);
322 if (tab_index == first_free_tab)
324 next_tab_column = column + 1;
329 uintmax_t tab = tab_list[tab_index++];
332 next_tab_column = tab;
337 if (next_tab_column < column)
338 error (EXIT_FAILURE, 0, _("input line is too long"));
340 while (++column < next_tab_column)
341 if (putchar (' ') < 0)
342 error (EXIT_FAILURE, errno, _("write error"));
348 /* Go back one column, and force recalculation of the
351 tab_index -= !!tab_index;
357 error (EXIT_FAILURE, 0, _("input line is too long"));
360 convert &= convert_entire_line | ISBLANK (c);
367 error (EXIT_FAILURE, errno, _("write error"));
374 main (int argc, char **argv)
376 bool have_tabval = false;
377 uintmax_t tabval IF_LINT (= 0);
380 bool obsolete_tablist = false;
382 initialize_main (&argc, &argv);
383 program_name = argv[0];
384 setlocale (LC_ALL, "");
385 bindtextdomain (PACKAGE, LOCALEDIR);
386 textdomain (PACKAGE);
388 atexit (close_stdout);
390 have_read_stdin = false;
391 exit_status = EXIT_SUCCESS;
392 convert_entire_line = true;
396 while ((c = getopt_long (argc, argv, "it:,0123456789", longopts, NULL))
402 usage (EXIT_FAILURE);
404 convert_entire_line = false;
407 parse_tab_stops (optarg);
411 add_tab_stop (tabval);
413 obsolete_tablist = true;
415 case_GETOPT_HELP_CHAR;
416 case_GETOPT_VERSION_CHAR (PROGRAM_NAME, AUTHORS);
424 uintmax_t new_t = tabval * 10 + c - '0';
425 if (UINTMAX_MAX / 10 < tabval || new_t < tabval * 10)
426 error (EXIT_FAILURE, 0, _("tab stop value is too large"));
429 obsolete_tablist = true;
434 if (obsolete_tablist && 200112 <= posix2_version ())
436 error (0, 0, _("`-LIST' option is obsolete; use `-t LIST'"));
437 usage (EXIT_FAILURE);
441 add_tab_stop (tabval);
443 validate_tab_stops (tab_list, first_free_tab);
445 if (first_free_tab == 0)
447 else if (first_free_tab == 1)
448 tab_size = tab_list[0];
452 file_list = (optind < argc ? &argv[optind] : stdin_argv);
456 if (have_read_stdin && fclose (stdin) != 0)
457 error (EXIT_FAILURE, errno, "-");