0ccb908e744a141f0fe49bafb4671ef1ac1f1d76
[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, sizeof *tab_list);
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           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       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 (fp == stdin)
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 (file[0] == '-' && file[1] == '\0')
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   /* Binary I/O will preserve the original EOL style (DOS/Unix) of files.  */
280   SET_BINARY2 (fileno (fp), STDOUT_FILENO);
281
282   for (;;)
283     {
284       /* Input character, or EOF.  */
285       int c;
286
287       /* If true, perform translations.  */
288       bool convert = true;
289
290
291       /* The following variables have valid values only when CONVERT
292          is true:  */
293
294       /* Column of next input character.  */
295       uintmax_t column = 0;
296
297       /* Index in TAB_LIST of next tab stop to examine.  */
298       size_t tab_index = 0;
299
300
301       /* Convert a line of text.  */
302
303       do
304         {
305           while ((c = getc (fp)) < 0 && (fp = next_file (fp)))
306             SET_BINARY2 (fileno (fp), STDOUT_FILENO);
307
308           if (convert)
309             {
310               if (c == '\t')
311                 {
312                   /* Column the next input tab stop is on.  */
313                   uintmax_t next_tab_column;
314
315                   if (tab_size)
316                     next_tab_column = column + (tab_size - column % tab_size);
317                   else
318                     for (;;)
319                       if (tab_index == first_free_tab)
320                         {
321                           next_tab_column = column + 1;
322                           break;
323                         }
324                       else
325                         {
326                           uintmax_t tab = tab_list[tab_index++];
327                           if (column < tab)
328                             {
329                               next_tab_column = tab;
330                               break;
331                             }
332                         }
333
334                   if (next_tab_column < column)
335                     error (EXIT_FAILURE, 0, _("input line is too long"));
336
337                   while (++column < next_tab_column)
338                     if (putchar (' ') < 0)
339                       error (EXIT_FAILURE, errno, _("write error"));
340
341                   c = ' ';
342                 }
343               else if (c == '\b')
344                 {
345                   /* Go back one column, and force recalculation of the
346                      next tab stop.  */
347                   column -= !!column;
348                   tab_index -= !!tab_index;
349                 }
350               else
351                 {
352                   column++;
353                   if (!column)
354                     error (EXIT_FAILURE, 0, _("input line is too long"));
355                 }
356
357               convert &= convert_entire_line | ISBLANK (c);
358             }
359
360           if (c < 0)
361             return;
362
363           if (putchar (c) < 0)
364             error (EXIT_FAILURE, errno, _("write error"));
365         }
366       while (c != '\n');
367     }
368 }
369
370 int
371 main (int argc, char **argv)
372 {
373   int c;
374
375   initialize_main (&argc, &argv);
376   program_name = argv[0];
377   setlocale (LC_ALL, "");
378   bindtextdomain (PACKAGE, LOCALEDIR);
379   textdomain (PACKAGE);
380
381   atexit (close_stdout);
382
383   have_read_stdin = false;
384   exit_status = EXIT_SUCCESS;
385   convert_entire_line = true;
386   tab_list = NULL;
387   first_free_tab = 0;
388
389   while ((c = getopt_long (argc, argv, shortopts, longopts, NULL)) != -1)
390     {
391       switch (c)
392         {
393         case 'i':
394           convert_entire_line = false;
395           break;
396
397         case 't':
398           parse_tab_stops (optarg);
399           break;
400
401         case '0': case '1': case '2': case '3': case '4':
402         case '5': case '6': case '7': case '8': case '9':
403           if (optarg)
404             parse_tab_stops (optarg - 1);
405           else
406             {
407               char tab_stop[2];
408               tab_stop[0] = c;
409               tab_stop[1] = '\0';
410               parse_tab_stops (tab_stop);
411             }
412           break;
413
414         case_GETOPT_HELP_CHAR;
415
416         case_GETOPT_VERSION_CHAR (PROGRAM_NAME, AUTHORS);
417
418         default:
419           usage (EXIT_FAILURE);
420         }
421     }
422
423   validate_tab_stops (tab_list, first_free_tab);
424
425   if (first_free_tab == 0)
426     tab_size = 8;
427   else if (first_free_tab == 1)
428     tab_size = tab_list[0];
429   else
430     tab_size = 0;
431
432   file_list = (optind < argc ? &argv[optind] : stdin_argv);
433
434   expand ();
435
436   if (have_read_stdin && fclose (stdin) != 0)
437     error (EXIT_FAILURE, errno, "-");
438
439   exit (exit_status);
440 }