* src/expand.c (parse_tab_stops): Add comment to make this function
[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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, 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 "quote.h"
44 #include "xstrndup.h"
45
46 /* The official name of this program (e.g., no `g' prefix).  */
47 #define PROGRAM_NAME "expand"
48
49 #define AUTHORS "David MacKenzie"
50
51 /* The number of bytes added at a time to the amount of memory
52    allocated for the output line.  */
53 #define OUTPUT_BLOCK 256
54
55 /* The name this program was run with.  */
56 char *program_name;
57
58 /* If true, convert blanks even after nonblank characters have been
59    read on the line.  */
60 static bool convert_entire_line;
61
62 /* If nonzero, the size of all tab stops.  If zero, use `tab_list' instead.  */
63 static uintmax_t tab_size;
64
65 /* Array of the explicit column numbers of the tab stops;
66    after `tab_list' is exhausted, each additional tab is replaced
67    by a space.  The first column is column 0.  */
68 static uintmax_t *tab_list;
69
70 /* The number of allocated entries in `tab_list'.  */
71 static size_t n_tabs_allocated;
72
73 /* The index of the first invalid element of `tab_list',
74    where the next element can be added.  */
75 static size_t first_free_tab;
76
77 /* Null-terminated array of input filenames.  */
78 static char **file_list;
79
80 /* Default for `file_list' if no files are given on the command line.  */
81 static char *stdin_argv[] =
82 {
83   "-", NULL
84 };
85
86 /* True if we have ever read standard input.  */
87 static bool have_read_stdin;
88
89 /* The desired exit status.  */
90 static int exit_status;
91
92 static char const shortopts[] = "it:0::1::2::3::4::5::6::7::8::9::";
93
94 static struct option const longopts[] =
95 {
96   {"tabs", required_argument, NULL, 't'},
97   {"initial", no_argument, NULL, 'i'},
98   {GETOPT_HELP_OPTION_DECL},
99   {GETOPT_VERSION_OPTION_DECL},
100   {NULL, 0, NULL, 0}
101 };
102
103 void
104 usage (int status)
105 {
106   if (status != EXIT_SUCCESS)
107     fprintf (stderr, _("Try `%s --help' for more information.\n"),
108              program_name);
109   else
110     {
111       printf (_("\
112 Usage: %s [OPTION]... [FILE]...\n\
113 "),
114               program_name);
115       fputs (_("\
116 Convert tabs in each FILE to spaces, writing to standard output.\n\
117 With no FILE, or when FILE is -, read standard input.\n\
118 \n\
119 "), stdout);
120       fputs (_("\
121 Mandatory arguments to long options are mandatory for short options too.\n\
122 "), stdout);
123       fputs (_("\
124   -i, --initial       do not convert tabs after non blanks\n\
125   -t, --tabs=NUMBER   have tabs NUMBER characters apart, not 8\n\
126 "), stdout);
127       fputs (_("\
128   -t, --tabs=LIST     use comma separated list of explicit tab positions\n\
129 "), stdout);
130       fputs (HELP_OPTION_DESCRIPTION, stdout);
131       fputs (VERSION_OPTION_DESCRIPTION, stdout);
132       printf (_("\nReport bugs to <%s>.\n"), PACKAGE_BUGREPORT);
133     }
134   exit (status);
135 }
136
137 /* Add tab stop TABVAL to the end of `tab_list'.  */
138
139 static void
140 add_tab_stop (uintmax_t tabval)
141 {
142   if (first_free_tab == n_tabs_allocated)
143     tab_list = X2NREALLOC (tab_list, &n_tabs_allocated);
144   tab_list[first_free_tab++] = tabval;
145 }
146
147 /* Add the comma or blank separated list of tab stops STOPS
148    to the list of tab stops.  */
149
150 static void
151 parse_tab_stops (char const *stops)
152 {
153   bool have_tabval = false;
154   uintmax_t tabval IF_LINT (= 0);
155   char const *num_start IF_LINT (= NULL);
156   bool ok = true;
157
158   for (; *stops; stops++)
159     {
160       if (*stops == ',' || ISBLANK (to_uchar (*stops)))
161         {
162           if (have_tabval)
163             add_tab_stop (tabval);
164           have_tabval = false;
165         }
166       else if (ISDIGIT (*stops))
167         {
168           if (!have_tabval)
169             {
170               tabval = 0;
171               have_tabval = true;
172               num_start = stops;
173             }
174
175           /* Detect overflow.  */
176           if (!DECIMAL_DIGIT_ACCUMULATE (tabval, *stops - '0', uintmax_t))
177             {
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));
181               free (bad_num);
182               ok = false;
183               stops = num_start + len - 1;
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 (STREQ (prev_file, "-"))
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 (STREQ (file, "-"))
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   for (;;)
281     {
282       /* Input character, or EOF.  */
283       int c;
284
285       /* If true, perform translations.  */
286       bool convert = true;
287
288
289       /* The following variables have valid values only when CONVERT
290          is true:  */
291
292       /* Column of next input character.  */
293       uintmax_t column = 0;
294
295       /* Index in TAB_LIST of next tab stop to examine.  */
296       size_t tab_index = 0;
297
298
299       /* Convert a line of text.  */
300
301       do
302         {
303           while ((c = getc (fp)) < 0 && (fp = next_file (fp)))
304             continue;
305
306           if (convert)
307             {
308               if (c == '\t')
309                 {
310                   /* Column the next input tab stop is on.  */
311                   uintmax_t next_tab_column;
312
313                   if (tab_size)
314                     next_tab_column = column + (tab_size - column % tab_size);
315                   else
316                     for (;;)
317                       if (tab_index == first_free_tab)
318                         {
319                           next_tab_column = column + 1;
320                           break;
321                         }
322                       else
323                         {
324                           uintmax_t tab = tab_list[tab_index++];
325                           if (column < tab)
326                             {
327                               next_tab_column = tab;
328                               break;
329                             }
330                         }
331
332                   if (next_tab_column < column)
333                     error (EXIT_FAILURE, 0, _("input line is too long"));
334
335                   while (++column < next_tab_column)
336                     if (putchar (' ') < 0)
337                       error (EXIT_FAILURE, errno, _("write error"));
338
339                   c = ' ';
340                 }
341               else if (c == '\b')
342                 {
343                   /* Go back one column, and force recalculation of the
344                      next tab stop.  */
345                   column -= !!column;
346                   tab_index -= !!tab_index;
347                 }
348               else
349                 {
350                   column++;
351                   if (!column)
352                     error (EXIT_FAILURE, 0, _("input line is too long"));
353                 }
354
355               convert &= convert_entire_line | ISBLANK (c);
356             }
357
358           if (c < 0)
359             return;
360
361           if (putchar (c) < 0)
362             error (EXIT_FAILURE, errno, _("write error"));
363         }
364       while (c != '\n');
365     }
366 }
367
368 int
369 main (int argc, char **argv)
370 {
371   int c;
372
373   initialize_main (&argc, &argv);
374   program_name = argv[0];
375   setlocale (LC_ALL, "");
376   bindtextdomain (PACKAGE, LOCALEDIR);
377   textdomain (PACKAGE);
378
379   atexit (close_stdout);
380
381   have_read_stdin = false;
382   exit_status = EXIT_SUCCESS;
383   convert_entire_line = true;
384   tab_list = NULL;
385   first_free_tab = 0;
386
387   while ((c = getopt_long (argc, argv, shortopts, longopts, NULL)) != -1)
388     {
389       switch (c)
390         {
391         case 'i':
392           convert_entire_line = false;
393           break;
394
395         case 't':
396           parse_tab_stops (optarg);
397           break;
398
399         case '0': case '1': case '2': case '3': case '4':
400         case '5': case '6': case '7': case '8': case '9':
401           if (optarg)
402             parse_tab_stops (optarg - 1);
403           else
404             {
405               char tab_stop[2];
406               tab_stop[0] = c;
407               tab_stop[1] = '\0';
408               parse_tab_stops (tab_stop);
409             }
410           break;
411
412         case_GETOPT_HELP_CHAR;
413
414         case_GETOPT_VERSION_CHAR (PROGRAM_NAME, AUTHORS);
415
416         default:
417           usage (EXIT_FAILURE);
418         }
419     }
420
421   validate_tab_stops (tab_list, first_free_tab);
422
423   if (first_free_tab == 0)
424     tab_size = 8;
425   else if (first_free_tab == 1)
426     tab_size = tab_list[0];
427   else
428     tab_size = 0;
429
430   file_list = (optind < argc ? &argv[optind] : stdin_argv);
431
432   expand ();
433
434   if (have_read_stdin && fclose (stdin) != 0)
435     error (EXIT_FAILURE, errno, "-");
436
437   exit (exit_status);
438 }