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