Don't include headers already included by system.h:
[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 number of bytes added at a time to the amount of memory
57    allocated for the list of tabstops. */
58 #define TABLIST_BLOCK 256
59
60 /* The name this program was run with. */
61 char *program_name;
62
63 /* If nonzero, convert blanks even after nonblank characters have been
64    read on the line. */
65 static int convert_entire_line;
66
67 /* If nonzero, the size of all tab stops.  If zero, use `tab_list' instead. */
68 static int tab_size;
69
70 /* Array of the explicit column numbers of the tab stops;
71    after `tab_list' is exhausted, each additional tab is replaced
72    by a space.  The first column is column 0. */
73 static int *tab_list;
74
75 /* The index of the first invalid element of `tab_list',
76    where the next element can be added. */
77 static int first_free_tab;
78
79 /* Null-terminated array of input filenames. */
80 static char **file_list;
81
82 /* Default for `file_list' if no files are given on the command line. */
83 static char *stdin_argv[] =
84 {
85   "-", NULL
86 };
87
88 /* Nonzero if we have ever read standard input. */
89 static int have_read_stdin;
90
91 /* Status to return to the system. */
92 static int exit_status;
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 != 0)
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 whitespace\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 == 0 ? EXIT_SUCCESS : EXIT_FAILURE);
135 }
136
137 /* Add tab stop TABVAL to the end of `tab_list', except
138    if TABVAL is -1, do nothing. */
139
140 static void
141 add_tabstop (int tabval)
142 {
143   if (tabval == -1)
144     return;
145   if (first_free_tab % TABLIST_BLOCK == 0)
146     tab_list = xrealloc (tab_list, (first_free_tab
147                                     + TABLIST_BLOCK * sizeof (tab_list[0])));
148   tab_list[first_free_tab++] = tabval;
149 }
150
151 /* Add the comma or blank separated list of tabstops STOPS
152    to the list of tabstops. */
153
154 static void
155 parse_tabstops (char const *stops)
156 {
157   int tabval = -1;
158   char const *num_start IF_LINT (= NULL);
159   int fail = 0;
160
161   for (; *stops; stops++)
162     {
163       if (*stops == ',' || ISBLANK (*stops))
164         {
165           add_tabstop (tabval);
166           tabval = -1;
167         }
168       else if (ISDIGIT (*stops))
169         {
170           if (tabval == -1)
171             {
172               tabval = 0;
173               num_start = stops;
174             }
175           {
176             /* Detect overflow.  */
177             int prev = tabval;
178             tabval = tabval * 10 + *stops - '0';
179             if (tabval < prev)
180               {
181                 size_t len = strspn (num_start, "0123456789");
182                 char *bad_num = xstrndup (num_start, len);
183                 error (0, 0, _("tab stop is too large %s"), quote (bad_num));
184                 fail = 1;
185                 stops = num_start + len - 1;
186               }
187           }
188         }
189       else
190         {
191           error (0, 0, _("tab size contains invalid character(s): %s"),
192                  quote (stops));
193           fail = 1;
194           break;
195         }
196     }
197
198   if (fail)
199     exit (EXIT_FAILURE);
200
201   add_tabstop (tabval);
202 }
203
204 /* Check that the list of tabstops TABS, with ENTRIES entries,
205    contains only nonzero, ascending values. */
206
207 static void
208 validate_tabstops (int *tabs, int entries)
209 {
210   int prev_tab = 0;
211   int 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 = 1;
240         }
241       if (fp == stdin)
242         clearerr (fp);          /* Also clear EOF. */
243       else if (fclose (fp) == EOF)
244         {
245           error (0, errno, "%s", prev_file);
246           exit_status = 1;
247         }
248     }
249
250   while ((file = *file_list++) != NULL)
251     {
252       if (file[0] == '-' && file[1] == '\0')
253         {
254           have_read_stdin = 1;
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 = 1;
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   FILE *fp;                     /* Input stream. */
277   int c;                        /* Each input character. */
278   int tab_index = 0;            /* Index in `tab_list' of next tabstop. */
279   int column = 0;               /* Column on screen of the next char. */
280   int next_tab_column;          /* Column the next tab stop is on. */
281   int convert = 1;              /* If nonzero, perform translations. */
282
283   fp = next_file ((FILE *) NULL);
284   if (fp == NULL)
285     return;
286
287   /* Binary I/O will preserve the original EOL style (DOS/Unix) of files.  */
288   SET_BINARY2 (fileno (fp), STDOUT_FILENO);
289
290   for (;;)
291     {
292       c = getc (fp);
293       if (c == EOF)
294         {
295           fp = next_file (fp);
296           if (fp == NULL)
297             break;              /* No more files. */
298           else
299             {
300               SET_BINARY2 (fileno (fp), STDOUT_FILENO);
301               continue;
302             }
303         }
304
305       if (c == '\n')
306         {
307           putchar (c);
308           tab_index = 0;
309           column = 0;
310           convert = 1;
311         }
312       else if (c == '\t' && convert)
313         {
314           if (tab_size == 0)
315             {
316               /* Do not let tab_index == first_free_tab;
317                  stop when it is 1 less. */
318               while (tab_index < first_free_tab - 1
319                      && column >= tab_list[tab_index])
320                 tab_index++;
321               next_tab_column = tab_list[tab_index];
322               if (tab_index < first_free_tab - 1)
323                 tab_index++;
324               if (column >= next_tab_column)
325                 next_tab_column = column + 1; /* Ran out of tab stops. */
326             }
327           else
328             {
329               next_tab_column = column + tab_size - column % tab_size;
330             }
331           while (column < next_tab_column)
332             {
333               putchar (' ');
334               ++column;
335             }
336         }
337       else
338         {
339           if (convert)
340             {
341               if (c == '\b')
342                 {
343                   if (column > 0)
344                     --column;
345                 }
346               else
347                 {
348                   ++column;
349                   if (convert_entire_line == 0)
350                     convert = 0;
351                 }
352             }
353           putchar (c);
354         }
355     }
356 }
357
358 int
359 main (int argc, char **argv)
360 {
361   int tabval = -1;              /* Value of tabstop being read, or -1. */
362   int c;                        /* Option character. */
363
364   bool obsolete_tablist = false;
365
366   have_read_stdin = 0;
367   exit_status = 0;
368   convert_entire_line = 1;
369   tab_list = NULL;
370   first_free_tab = 0;
371   initialize_main (&argc, &argv);
372   program_name = argv[0];
373   setlocale (LC_ALL, "");
374   bindtextdomain (PACKAGE, LOCALEDIR);
375   textdomain (PACKAGE);
376
377   atexit (close_stdout);
378
379   while ((c = getopt_long (argc, argv, "it:,0123456789", longopts, NULL)) != -1)
380     {
381       switch (c)
382         {
383         case 0:
384           break;
385
386         case '?':
387           usage (EXIT_FAILURE);
388         case 'i':
389           convert_entire_line = 0;
390           break;
391         case 't':
392           parse_tabstops (optarg);
393           break;
394         case ',':
395           add_tabstop (tabval);
396           tabval = -1;
397           obsolete_tablist = true;
398           break;
399         case_GETOPT_HELP_CHAR;
400         case_GETOPT_VERSION_CHAR (PROGRAM_NAME, AUTHORS);
401         default:
402           if (tabval == -1)
403             tabval = 0;
404           tabval = tabval * 10 + c - '0';
405           obsolete_tablist = true;
406           break;
407         }
408     }
409
410   if (obsolete_tablist && 200112 <= posix2_version ())
411     {
412       error (0, 0, _("`-LIST' option is obsolete; use `-t LIST'"));
413       usage (EXIT_FAILURE);
414     }
415
416   add_tabstop (tabval);
417
418   validate_tabstops (tab_list, first_free_tab);
419
420   if (first_free_tab == 0)
421     tab_size = 8;
422   else if (first_free_tab == 1)
423     tab_size = tab_list[0];
424   else
425     tab_size = 0;
426
427   file_list = (optind < argc ? &argv[optind] : stdin_argv);
428
429   expand ();
430
431   if (have_read_stdin && fclose (stdin) == EOF)
432     error (EXIT_FAILURE, errno, "-");
433
434   exit (exit_status == 0 ? EXIT_SUCCESS : EXIT_FAILURE);
435 }