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