(WRITTEN_BY): Rename from AUTHORS.
[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 WRITTEN_BY _("Written by 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             int new_t;
177
178             /* Detect overflow.  */
179             new_t = 10 * tabval + *stops - '0';
180             if (INT_MAX / 10 < tabval || new_t < tabval * 10)
181               {
182                 size_t len = strspn (num_start, "0123456789");
183                 char *bad_num = xstrndup (num_start, len);
184                 error (0, 0, _("tab stop is too large %s"), quote (bad_num));
185                 fail = 1;
186                 stops = num_start + len - 1;
187               }
188             tabval = new_t;
189           }
190         }
191       else
192         {
193           error (0, 0, _("tab size contains invalid character(s): %s"),
194                  quote (stops));
195           fail = 1;
196           break;
197         }
198     }
199
200   if (fail)
201     exit (EXIT_FAILURE);
202
203   add_tabstop (tabval);
204 }
205
206 /* Check that the list of tabstops TABS, with ENTRIES entries,
207    contains only nonzero, ascending values. */
208
209 static void
210 validate_tabstops (int *tabs, int entries)
211 {
212   int prev_tab = 0;
213   int i;
214
215   for (i = 0; i < entries; i++)
216     {
217       if (tabs[i] == 0)
218         error (EXIT_FAILURE, 0, _("tab size cannot be 0"));
219       if (tabs[i] <= prev_tab)
220         error (EXIT_FAILURE, 0, _("tab sizes must be ascending"));
221       prev_tab = tabs[i];
222     }
223 }
224
225 /* Close the old stream pointer FP if it is non-NULL,
226    and return a new one opened to read the next input file.
227    Open a filename of `-' as the standard input.
228    Return NULL if there are no more input files.  */
229
230 static FILE *
231 next_file (FILE *fp)
232 {
233   static char *prev_file;
234   char *file;
235
236   if (fp)
237     {
238       if (ferror (fp))
239         {
240           error (0, errno, "%s", prev_file);
241           exit_status = 1;
242         }
243       if (fp == stdin)
244         clearerr (fp);          /* Also clear EOF. */
245       else if (fclose (fp) == EOF)
246         {
247           error (0, errno, "%s", prev_file);
248           exit_status = 1;
249         }
250     }
251
252   while ((file = *file_list++) != NULL)
253     {
254       if (file[0] == '-' && file[1] == '\0')
255         {
256           have_read_stdin = 1;
257           prev_file = file;
258           return stdin;
259         }
260       fp = fopen (file, "r");
261       if (fp)
262         {
263           prev_file = file;
264           return fp;
265         }
266       error (0, errno, "%s", file);
267       exit_status = 1;
268     }
269   return NULL;
270 }
271
272 /* Change tabs to spaces, writing to stdout.
273    Read each file in `file_list', in order. */
274
275 static void
276 expand (void)
277 {
278   FILE *fp;                     /* Input stream. */
279   int c;                        /* Each input character. */
280   int tab_index = 0;            /* Index in `tab_list' of next tabstop. */
281   int column = 0;               /* Column on screen of the next char. */
282   int next_tab_column;          /* Column the next tab stop is on. */
283   int convert = 1;              /* If nonzero, perform translations. */
284
285   fp = next_file ((FILE *) NULL);
286   if (fp == NULL)
287     return;
288
289   /* Binary I/O will preserve the original EOL style (DOS/Unix) of files.  */
290   SET_BINARY2 (fileno (fp), STDOUT_FILENO);
291
292   for (;;)
293     {
294       c = getc (fp);
295       if (c == EOF)
296         {
297           fp = next_file (fp);
298           if (fp == NULL)
299             break;              /* No more files. */
300           else
301             {
302               SET_BINARY2 (fileno (fp), STDOUT_FILENO);
303               continue;
304             }
305         }
306
307       if (c == '\n')
308         {
309           putchar (c);
310           tab_index = 0;
311           column = 0;
312           convert = 1;
313         }
314       else if (c == '\t' && convert)
315         {
316           if (tab_size == 0)
317             {
318               /* Do not let tab_index == first_free_tab;
319                  stop when it is 1 less. */
320               while (tab_index < first_free_tab - 1
321                      && column >= tab_list[tab_index])
322                 tab_index++;
323               next_tab_column = tab_list[tab_index];
324               if (tab_index < first_free_tab - 1)
325                 tab_index++;
326               if (column >= next_tab_column)
327                 next_tab_column = column + 1; /* Ran out of tab stops. */
328             }
329           else
330             {
331               next_tab_column = column + tab_size - column % tab_size;
332             }
333           while (column < next_tab_column)
334             {
335               putchar (' ');
336               ++column;
337             }
338         }
339       else
340         {
341           if (convert)
342             {
343               if (c == '\b')
344                 {
345                   if (column > 0)
346                     --column;
347                 }
348               else
349                 {
350                   ++column;
351                   if (convert_entire_line == 0)
352                     convert = 0;
353                 }
354             }
355           putchar (c);
356         }
357     }
358 }
359
360 int
361 main (int argc, char **argv)
362 {
363   int tabval = -1;              /* Value of tabstop being read, or -1. */
364   int c;                        /* Option character. */
365
366   bool obsolete_tablist = false;
367
368   have_read_stdin = 0;
369   exit_status = 0;
370   convert_entire_line = 1;
371   tab_list = NULL;
372   first_free_tab = 0;
373   initialize_main (&argc, &argv);
374   program_name = argv[0];
375   setlocale (LC_ALL, "");
376   bindtextdomain (PACKAGE, LOCALEDIR);
377   textdomain (PACKAGE);
378
379   atexit (close_stdout);
380
381   while ((c = getopt_long (argc, argv, "it:,0123456789", longopts, NULL)) != -1)
382     {
383       switch (c)
384         {
385         case 0:
386           break;
387
388         case '?':
389           usage (EXIT_FAILURE);
390         case 'i':
391           convert_entire_line = 0;
392           break;
393         case 't':
394           parse_tabstops (optarg);
395           break;
396         case ',':
397           add_tabstop (tabval);
398           tabval = -1;
399           obsolete_tablist = true;
400           break;
401         case_GETOPT_HELP_CHAR;
402         case_GETOPT_VERSION_CHAR (PROGRAM_NAME, WRITTEN_BY);
403         default:
404           if (tabval == -1)
405             tabval = 0;
406           tabval = tabval * 10 + c - '0';
407           obsolete_tablist = true;
408           break;
409         }
410     }
411
412   if (obsolete_tablist && 200112 <= posix2_version ())
413     {
414       error (0, 0, _("`-LIST' option is obsolete; use `-t LIST'"));
415       usage (EXIT_FAILURE);
416     }
417
418   add_tabstop (tabval);
419
420   validate_tabstops (tab_list, first_free_tab);
421
422   if (first_free_tab == 0)
423     tab_size = 8;
424   else if (first_free_tab == 1)
425     tab_size = tab_list[0];
426   else
427     tab_size = 0;
428
429   file_list = (optind < argc ? &argv[optind] : stdin_argv);
430
431   expand ();
432
433   if (have_read_stdin && fclose (stdin) == EOF)
434     error (EXIT_FAILURE, errno, "-");
435
436   exit (exit_status == 0 ? EXIT_SUCCESS : EXIT_FAILURE);
437 }