(parse_tab_stops, main): Update use of DECIMAL_DIGIT_ACCUMULATE.
[platform/upstream/coreutils.git] / src / expand.c
1 /* expand - convert tabs to spaces
2    Copyright (C) 89, 91, 1995-2005 Free Software Foundation, Inc.
3
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)
7    any later version.
8
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.
13
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.  */
17
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.
22
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.
33
34    David MacKenzie <djm@gnu.ai.mit.edu> */
35
36 #include <config.h>
37
38 #include <stdio.h>
39 #include <getopt.h>
40 #include <sys/types.h>
41 #include "system.h"
42 #include "error.h"
43 #include "posixver.h"
44 #include "quote.h"
45 #include "xstrndup.h"
46
47 /* The official name of this program (e.g., no `g' prefix).  */
48 #define PROGRAM_NAME "expand"
49
50 #define AUTHORS "David MacKenzie"
51
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
55
56 /* The name this program was run with.  */
57 char *program_name;
58
59 /* If true, convert blanks even after nonblank characters have been
60    read on the line.  */
61 static bool convert_entire_line;
62
63 /* If nonzero, the size of all tab stops.  If zero, use `tab_list' instead.  */
64 static uintmax_t tab_size;
65
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;
70
71 /* The number of allocated entries in `tab_list'.  */
72 static size_t n_tabs_allocated;
73
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;
77
78 /* Null-terminated array of input filenames.  */
79 static char **file_list;
80
81 /* Default for `file_list' if no files are given on the command line.  */
82 static char *stdin_argv[] =
83 {
84   "-", NULL
85 };
86
87 /* True if we have ever read standard input.  */
88 static bool have_read_stdin;
89
90 /* The desired exit status.  */
91 static int exit_status;
92
93 static struct option const longopts[] =
94 {
95   {"tabs", required_argument, NULL, 't'},
96   {"initial", no_argument, NULL, 'i'},
97   {GETOPT_HELP_OPTION_DECL},
98   {GETOPT_VERSION_OPTION_DECL},
99   {NULL, 0, NULL, 0}
100 };
101
102 void
103 usage (int status)
104 {
105   if (status != EXIT_SUCCESS)
106     fprintf (stderr, _("Try `%s --help' for more information.\n"),
107              program_name);
108   else
109     {
110       printf (_("\
111 Usage: %s [OPTION]... [FILE]...\n\
112 "),
113               program_name);
114       fputs (_("\
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\
117 \n\
118 "), stdout);
119       fputs (_("\
120 Mandatory arguments to long options are mandatory for short options too.\n\
121 "), stdout);
122       fputs (_("\
123   -i, --initial       do not convert tabs after non blanks\n\
124   -t, --tabs=NUMBER   have tabs NUMBER characters apart, not 8\n\
125 "), stdout);
126       fputs (_("\
127   -t, --tabs=LIST     use comma separated list of explicit tab positions\n\
128 "), stdout);
129       fputs (HELP_OPTION_DESCRIPTION, stdout);
130       fputs (VERSION_OPTION_DESCRIPTION, stdout);
131       printf (_("\nReport bugs to <%s>.\n"), PACKAGE_BUGREPORT);
132     }
133   exit (status);
134 }
135
136 /* Add tab stop TABVAL to the end of `tab_list'.  */
137
138 static void
139 add_tab_stop (uintmax_t tabval)
140 {
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;
144 }
145
146 /* Add the comma or blank separated list of tab stops STOPS
147    to the list of tab stops.  */
148
149 static void
150 parse_tab_stops (char const *stops)
151 {
152   bool have_tabval = false;
153   uintmax_t tabval IF_LINT (= 0);
154   char const *num_start IF_LINT (= NULL);
155   bool ok = true;
156
157   for (; *stops; stops++)
158     {
159       if (*stops == ',' || ISBLANK (to_uchar (*stops)))
160         {
161           if (have_tabval)
162             add_tab_stop (tabval);
163           have_tabval = false;
164         }
165       else if (ISDIGIT (*stops))
166         {
167           if (!have_tabval)
168             {
169               tabval = 0;
170               have_tabval = true;
171               num_start = stops;
172             }
173           {
174             /* Detect overflow.  */
175             if (!DECIMAL_DIGIT_ACCUMULATE (tabval, *stops - '0', UINTMAX_MAX))
176               {
177                 size_t len = strspn (num_start, "0123456789");
178                 char *bad_num = xstrndup (num_start, len);
179                 error (0, 0, _("tab stop is too large %s"), quote (bad_num));
180                 free (bad_num);
181                 ok = false;
182                 stops = num_start + len - 1;
183               }
184           }
185         }
186       else
187         {
188           error (0, 0, _("tab size contains invalid character(s): %s"),
189                  quote (stops));
190           ok = false;
191           break;
192         }
193     }
194
195   if (!ok)
196     exit (EXIT_FAILURE);
197
198   if (have_tabval)
199     add_tab_stop (tabval);
200 }
201
202 /* Check that the list of tab stops TABS, with ENTRIES entries,
203    contains only nonzero, ascending values.  */
204
205 static void
206 validate_tab_stops (uintmax_t const *tabs, size_t entries)
207 {
208   uintmax_t prev_tab = 0;
209   size_t i;
210
211   for (i = 0; i < entries; i++)
212     {
213       if (tabs[i] == 0)
214         error (EXIT_FAILURE, 0, _("tab size cannot be 0"));
215       if (tabs[i] <= prev_tab)
216         error (EXIT_FAILURE, 0, _("tab sizes must be ascending"));
217       prev_tab = tabs[i];
218     }
219 }
220
221 /* Close the old stream pointer FP if it is non-NULL,
222    and return a new one opened to read the next input file.
223    Open a filename of `-' as the standard input.
224    Return NULL if there are no more input files.  */
225
226 static FILE *
227 next_file (FILE *fp)
228 {
229   static char *prev_file;
230   char *file;
231
232   if (fp)
233     {
234       if (ferror (fp))
235         {
236           error (0, errno, "%s", prev_file);
237           exit_status = EXIT_FAILURE;
238         }
239       if (fp == stdin)
240         clearerr (fp);          /* Also clear EOF.  */
241       else if (fclose (fp) != 0)
242         {
243           error (0, errno, "%s", prev_file);
244           exit_status = EXIT_FAILURE;
245         }
246     }
247
248   while ((file = *file_list++) != NULL)
249     {
250       if (file[0] == '-' && file[1] == '\0')
251         {
252           have_read_stdin = true;
253           prev_file = file;
254           return stdin;
255         }
256       fp = fopen (file, "r");
257       if (fp)
258         {
259           prev_file = file;
260           return fp;
261         }
262       error (0, errno, "%s", file);
263       exit_status = EXIT_FAILURE;
264     }
265   return NULL;
266 }
267
268 /* Change tabs to spaces, writing to stdout.
269    Read each file in `file_list', in order.  */
270
271 static void
272 expand (void)
273 {
274   /* Input stream.  */
275   FILE *fp = next_file (NULL);
276
277   if (!fp)
278     return;
279
280   /* Binary I/O will preserve the original EOL style (DOS/Unix) of files.  */
281   SET_BINARY2 (fileno (fp), STDOUT_FILENO);
282
283   for (;;)
284     {
285       /* Input character, or EOF.  */
286       int c;
287
288       /* If true, perform translations.  */
289       bool convert = true;
290
291
292       /* The following variables have valid values only when CONVERT
293          is true:  */
294
295       /* Column of next input character.  */
296       uintmax_t column = 0;
297
298       /* Index in TAB_LIST of next tab stop to examine.  */
299       size_t tab_index = 0;
300
301
302       /* Convert a line of text.  */
303
304       do
305         {
306           while ((c = getc (fp)) < 0 && (fp = next_file (fp)))
307             SET_BINARY2 (fileno (fp), STDOUT_FILENO);
308
309           if (convert)
310             {
311               if (c == '\t')
312                 {
313                   /* Column the next input tab stop is on.  */
314                   uintmax_t next_tab_column;
315
316                   if (tab_size)
317                     next_tab_column = column + (tab_size - column % tab_size);
318                   else
319                     for (;;)
320                       if (tab_index == first_free_tab)
321                         {
322                           next_tab_column = column + 1;
323                           break;
324                         }
325                       else
326                         {
327                           uintmax_t tab = tab_list[tab_index++];
328                           if (column < tab)
329                             {
330                               next_tab_column = tab;
331                               break;
332                             }
333                         }
334
335                   if (next_tab_column < column)
336                     error (EXIT_FAILURE, 0, _("input line is too long"));
337
338                   while (++column < next_tab_column)
339                     if (putchar (' ') < 0)
340                       error (EXIT_FAILURE, errno, _("write error"));
341
342                   c = ' ';
343                 }
344               else if (c == '\b')
345                 {
346                   /* Go back one column, and force recalculation of the
347                      next tab stop.  */
348                   column -= !!column;
349                   tab_index -= !!tab_index;
350                 }
351               else
352                 {
353                   column++;
354                   if (!column)
355                     error (EXIT_FAILURE, 0, _("input line is too long"));
356                 }
357
358               convert &= convert_entire_line | ISBLANK (c);
359             }
360
361           if (c < 0)
362             return;
363
364           if (putchar (c) < 0)
365             error (EXIT_FAILURE, errno, _("write error"));
366         }
367       while (c != '\n');
368     }
369 }
370
371 int
372 main (int argc, char **argv)
373 {
374   bool have_tabval = false;
375   uintmax_t tabval IF_LINT (= 0);
376   int c;
377
378   bool obsolete_tablist = false;
379
380   initialize_main (&argc, &argv);
381   program_name = argv[0];
382   setlocale (LC_ALL, "");
383   bindtextdomain (PACKAGE, LOCALEDIR);
384   textdomain (PACKAGE);
385
386   atexit (close_stdout);
387
388   have_read_stdin = false;
389   exit_status = EXIT_SUCCESS;
390   convert_entire_line = true;
391   tab_list = NULL;
392   first_free_tab = 0;
393
394   while ((c = getopt_long (argc, argv, "it:,0123456789", longopts, NULL))
395          != -1)
396     {
397       switch (c)
398         {
399         case '?':
400           usage (EXIT_FAILURE);
401         case 'i':
402           convert_entire_line = false;
403           break;
404         case 't':
405           parse_tab_stops (optarg);
406           break;
407         case ',':
408           if (have_tabval)
409             add_tab_stop (tabval);
410           have_tabval = false;
411           obsolete_tablist = true;
412           break;
413         case_GETOPT_HELP_CHAR;
414         case_GETOPT_VERSION_CHAR (PROGRAM_NAME, AUTHORS);
415         default:
416           if (!have_tabval)
417             {
418               tabval = 0;
419               have_tabval = true;
420             }
421           {
422             if (!DECIMAL_DIGIT_ACCUMULATE (tabval, c - '0', UINTMAX_MAX))
423               error (EXIT_FAILURE, 0, _("tab stop value is too large"));
424           }
425           obsolete_tablist = true;
426           break;
427         }
428     }
429
430   if (obsolete_tablist && 200112 <= posix2_version ())
431     {
432       error (0, 0, _("`-LIST' option is obsolete; use `-t LIST'"));
433       usage (EXIT_FAILURE);
434     }
435
436   if (have_tabval)
437     add_tab_stop (tabval);
438
439   validate_tab_stops (tab_list, first_free_tab);
440
441   if (first_free_tab == 0)
442     tab_size = 8;
443   else if (first_free_tab == 1)
444     tab_size = tab_list[0];
445   else
446     tab_size = 0;
447
448   file_list = (optind < argc ? &argv[optind] : stdin_argv);
449
450   expand ();
451
452   if (have_read_stdin && fclose (stdin) != 0)
453     error (EXIT_FAILURE, errno, "-");
454
455   exit (exit_status);
456 }