declare program_name consistently
[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 /* The name this program was run with.  */
55 char const *program_name;
56
57 /* If true, convert blanks even after nonblank characters have been
58    read on the line.  */
59 static bool convert_entire_line;
60
61 /* If nonzero, the size of all tab stops.  If zero, use `tab_list' instead.  */
62 static uintmax_t tab_size;
63
64 /* Array of the explicit column numbers of the tab stops;
65    after `tab_list' is exhausted, each additional tab is replaced
66    by a space.  The first column is column 0.  */
67 static uintmax_t *tab_list;
68
69 /* The number of allocated entries in `tab_list'.  */
70 static size_t n_tabs_allocated;
71
72 /* The index of the first invalid element of `tab_list',
73    where the next element can be added.  */
74 static size_t first_free_tab;
75
76 /* Null-terminated array of input filenames.  */
77 static char **file_list;
78
79 /* Default for `file_list' if no files are given on the command line.  */
80 static char *stdin_argv[] =
81 {
82   "-", NULL
83 };
84
85 /* True if we have ever read standard input.  */
86 static bool have_read_stdin;
87
88 /* The desired exit status.  */
89 static int exit_status;
90
91 static char const shortopts[] = "it:0::1::2::3::4::5::6::7::8::9::";
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       emit_bug_reporting_address ();
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);
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_t))
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       else
186         {
187           error (0, 0, _("tab size contains invalid character(s): %s"),
188                  quote (stops));
189           ok = false;
190           break;
191         }
192     }
193
194   if (!ok)
195     exit (EXIT_FAILURE);
196
197   if (have_tabval)
198     add_tab_stop (tabval);
199 }
200
201 /* Check that the list of tab stops TABS, with ENTRIES entries,
202    contains only nonzero, ascending values.  */
203
204 static void
205 validate_tab_stops (uintmax_t const *tabs, size_t entries)
206 {
207   uintmax_t prev_tab = 0;
208   size_t i;
209
210   for (i = 0; i < entries; i++)
211     {
212       if (tabs[i] == 0)
213         error (EXIT_FAILURE, 0, _("tab size cannot be 0"));
214       if (tabs[i] <= prev_tab)
215         error (EXIT_FAILURE, 0, _("tab sizes must be ascending"));
216       prev_tab = tabs[i];
217     }
218 }
219
220 /* Close the old stream pointer FP if it is non-NULL,
221    and return a new one opened to read the next input file.
222    Open a filename of `-' as the standard input.
223    Return NULL if there are no more input files.  */
224
225 static FILE *
226 next_file (FILE *fp)
227 {
228   static char *prev_file;
229   char *file;
230
231   if (fp)
232     {
233       if (ferror (fp))
234         {
235           error (0, errno, "%s", prev_file);
236           exit_status = EXIT_FAILURE;
237         }
238       if (STREQ (prev_file, "-"))
239         clearerr (fp);          /* Also clear EOF.  */
240       else if (fclose (fp) != 0)
241         {
242           error (0, errno, "%s", prev_file);
243           exit_status = EXIT_FAILURE;
244         }
245     }
246
247   while ((file = *file_list++) != NULL)
248     {
249       if (STREQ (file, "-"))
250         {
251           have_read_stdin = true;
252           prev_file = file;
253           return stdin;
254         }
255       fp = fopen (file, "r");
256       if (fp)
257         {
258           prev_file = file;
259           return fp;
260         }
261       error (0, errno, "%s", file);
262       exit_status = EXIT_FAILURE;
263     }
264   return NULL;
265 }
266
267 /* Change tabs to spaces, writing to stdout.
268    Read each file in `file_list', in order.  */
269
270 static void
271 expand (void)
272 {
273   /* Input stream.  */
274   FILE *fp = next_file (NULL);
275
276   if (!fp)
277     return;
278
279   for (;;)
280     {
281       /* Input character, or EOF.  */
282       int c;
283
284       /* If true, perform translations.  */
285       bool convert = true;
286
287
288       /* The following variables have valid values only when CONVERT
289          is true:  */
290
291       /* Column of next input character.  */
292       uintmax_t column = 0;
293
294       /* Index in TAB_LIST of next tab stop to examine.  */
295       size_t tab_index = 0;
296
297
298       /* Convert a line of text.  */
299
300       do
301         {
302           while ((c = getc (fp)) < 0 && (fp = next_file (fp)))
303             continue;
304
305           if (convert)
306             {
307               if (c == '\t')
308                 {
309                   /* Column the next input tab stop is on.  */
310                   uintmax_t next_tab_column;
311
312                   if (tab_size)
313                     next_tab_column = column + (tab_size - column % tab_size);
314                   else
315                     for (;;)
316                       if (tab_index == first_free_tab)
317                         {
318                           next_tab_column = column + 1;
319                           break;
320                         }
321                       else
322                         {
323                           uintmax_t tab = tab_list[tab_index++];
324                           if (column < tab)
325                             {
326                               next_tab_column = tab;
327                               break;
328                             }
329                         }
330
331                   if (next_tab_column < column)
332                     error (EXIT_FAILURE, 0, _("input line is too long"));
333
334                   while (++column < next_tab_column)
335                     if (putchar (' ') < 0)
336                       error (EXIT_FAILURE, errno, _("write error"));
337
338                   c = ' ';
339                 }
340               else if (c == '\b')
341                 {
342                   /* Go back one column, and force recalculation of the
343                      next tab stop.  */
344                   column -= !!column;
345                   tab_index -= !!tab_index;
346                 }
347               else
348                 {
349                   column++;
350                   if (!column)
351                     error (EXIT_FAILURE, 0, _("input line is too long"));
352                 }
353
354               convert &= convert_entire_line | !! isblank (c);
355             }
356
357           if (c < 0)
358             return;
359
360           if (putchar (c) < 0)
361             error (EXIT_FAILURE, errno, _("write error"));
362         }
363       while (c != '\n');
364     }
365 }
366
367 int
368 main (int argc, char **argv)
369 {
370   int c;
371
372   initialize_main (&argc, &argv);
373   program_name = argv[0];
374   setlocale (LC_ALL, "");
375   bindtextdomain (PACKAGE, LOCALEDIR);
376   textdomain (PACKAGE);
377
378   atexit (close_stdout);
379
380   have_read_stdin = false;
381   exit_status = EXIT_SUCCESS;
382   convert_entire_line = true;
383   tab_list = NULL;
384   first_free_tab = 0;
385
386   while ((c = getopt_long (argc, argv, shortopts, longopts, NULL)) != -1)
387     {
388       switch (c)
389         {
390         case 'i':
391           convert_entire_line = false;
392           break;
393
394         case 't':
395           parse_tab_stops (optarg);
396           break;
397
398         case '0': case '1': case '2': case '3': case '4':
399         case '5': case '6': case '7': case '8': case '9':
400           if (optarg)
401             parse_tab_stops (optarg - 1);
402           else
403             {
404               char tab_stop[2];
405               tab_stop[0] = c;
406               tab_stop[1] = '\0';
407               parse_tab_stops (tab_stop);
408             }
409           break;
410
411         case_GETOPT_HELP_CHAR;
412
413         case_GETOPT_VERSION_CHAR (PROGRAM_NAME, AUTHORS);
414
415         default:
416           usage (EXIT_FAILURE);
417         }
418     }
419
420   validate_tab_stops (tab_list, first_free_tab);
421
422   if (first_free_tab == 0)
423     tab_size = 8;
424   else if (first_free_tab == 1)
425     tab_size = tab_list[0];
426   else
427     tab_size = 0;
428
429   file_list = (optind < argc ? &argv[optind] : stdin_argv);
430
431   expand ();
432
433   if (have_read_stdin && fclose (stdin) != 0)
434     error (EXIT_FAILURE, errno, "-");
435
436   exit (exit_status);
437 }