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