1 /* expand - convert tabs to spaces
2 Copyright (C) 89, 91, 1995-2004 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 spaces 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 tabstops 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 whitespace\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_tabstop (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 tabstops STOPS
147 to the list of tabstops. */
150 parse_tabstops (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_tabstop (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_tabstop (tabval);
204 /* Check that the list of tabstops TABS, with ENTRIES entries,
205 contains only nonzero, ascending values. */
208 validate_tabstops (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) == EOF)
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. */
276 FILE *fp; /* Input stream. */
277 size_t tab_index = 0; /* Index in `tab_list' of next tabstop. */
278 uintmax_t column = 0; /* Column of next char. */
279 uintmax_t next_tab_column; /* Column the next tab stop is on. */
280 bool convert = true; /* If true, perform translations. */
282 fp = next_file ((FILE *) NULL);
286 /* Binary I/O will preserve the original EOL style (DOS/Unix) of files. */
287 SET_BINARY2 (fileno (fp), STDOUT_FILENO);
297 SET_BINARY2 (fileno (fp), STDOUT_FILENO);
310 else if (c == '\t' && convert)
314 /* Do not let tab_index == first_free_tab;
315 stop when it is 1 less. */
316 while (tab_index < first_free_tab - 1
317 && column >= tab_list[tab_index])
319 next_tab_column = tab_list[tab_index];
320 if (tab_index < first_free_tab - 1)
322 if (column >= next_tab_column)
323 next_tab_column = column + 1; /* Ran out of tab stops. */
327 next_tab_column = column + tab_size - column % tab_size;
329 if (next_tab_column < column)
330 error (EXIT_FAILURE, 0, _("input line is too long"));
331 while (column < next_tab_column)
346 tab_index -= (tab_index != 0);
353 error (EXIT_FAILURE, 0, _("input line is too long"));
354 convert &= convert_entire_line;
363 main (int argc, char **argv)
365 bool have_tabval = false;
366 uintmax_t tabval IF_LINT (= 0);
369 bool obsolete_tablist = false;
371 initialize_main (&argc, &argv);
372 program_name = argv[0];
373 setlocale (LC_ALL, "");
374 bindtextdomain (PACKAGE, LOCALEDIR);
375 textdomain (PACKAGE);
377 atexit (close_stdout);
379 have_read_stdin = false;
380 exit_status = EXIT_SUCCESS;
381 convert_entire_line = true;
385 while ((c = getopt_long (argc, argv, "it:,0123456789", longopts, NULL))
394 usage (EXIT_FAILURE);
396 convert_entire_line = false;
399 parse_tabstops (optarg);
403 add_tabstop (tabval);
405 obsolete_tablist = true;
407 case_GETOPT_HELP_CHAR;
408 case_GETOPT_VERSION_CHAR (PROGRAM_NAME, AUTHORS);
415 tabval = tabval * 10 + c - '0';
416 obsolete_tablist = true;
421 if (obsolete_tablist && 200112 <= posix2_version ())
423 error (0, 0, _("`-LIST' option is obsolete; use `-t LIST'"));
424 usage (EXIT_FAILURE);
428 add_tabstop (tabval);
430 validate_tabstops (tab_list, first_free_tab);
432 if (first_free_tab == 0)
434 else if (first_free_tab == 1)
435 tab_size = tab_list[0];
439 file_list = (optind < argc ? &argv[optind] : stdin_argv);
443 if (have_read_stdin && fclose (stdin) == EOF)
444 error (EXIT_FAILURE, errno, "-");