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