(usage): Say that
[platform/upstream/coreutils.git] / src / expand.c
1 /* expand - convert tabs to spaces
2    Copyright (C) 89, 91, 1995-2001 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 "closeout.h"
43 #include "error.h"
44
45 /* The official name of this program (e.g., no `g' prefix).  */
46 #define PROGRAM_NAME "expand"
47
48 #define AUTHORS "David MacKenzie"
49
50 /* The number of bytes added at a time to the amount of memory
51    allocated for the output line. */
52 #define OUTPUT_BLOCK 256
53
54 /* The number of bytes added at a time to the amount of memory
55    allocated for the list of tabstops. */
56 #define TABLIST_BLOCK 256
57
58 /* The name this program was run with. */
59 char *program_name;
60
61 /* If nonzero, convert blanks even after nonblank characters have been
62    read on the line. */
63 static int convert_entire_line;
64
65 /* If nonzero, the size of all tab stops.  If zero, use `tab_list' instead. */
66 static int tab_size;
67
68 /* Array of the explicit column numbers of the tab stops;
69    after `tab_list' is exhausted, each additional tab is replaced
70    by a space.  The first column is column 0. */
71 static int *tab_list;
72
73 /* The index of the first invalid element of `tab_list',
74    where the next element can be added. */
75 static int first_free_tab;
76
77 /* Null-terminated array of input filenames. */
78 static char **file_list;
79
80 /* Default for `file_list' if no files are given on the command line. */
81 static char *stdin_argv[] =
82 {
83   "-", NULL
84 };
85
86 /* Nonzero if we have ever read standard input. */
87 static int have_read_stdin;
88
89 /* Status to return to the system. */
90 static int exit_status;
91
92 static struct option const longopts[] =
93 {
94   {"tabs", required_argument, NULL, 't'},
95   {"initial", no_argument, NULL, 'i'},
96   {GETOPT_HELP_OPTION_DECL},
97   {GETOPT_VERSION_OPTION_DECL},
98   {NULL, 0, NULL, 0}
99 };
100
101 void
102 usage (int status)
103 {
104   if (status != 0)
105     fprintf (stderr, _("Try `%s --help' for more information.\n"),
106              program_name);
107   else
108     {
109       printf (_("\
110 Usage: %s [OPTION]... [FILE]...\n\
111 "),
112               program_name);
113       printf (_("\
114 Convert tabs in each FILE to spaces, writing to standard output.\n\
115 With no FILE, or when FILE is -, read standard input.\n\
116 \n\
117 Mandatory arguments to long options are mandatory for short options too.\n\
118   -i, --initial       do not convert TABs after non whitespace\n\
119   -t, --tabs=NUMBER   have tabs NUMBER characters apart, not 8\n\
120   -t, --tabs=LIST     use comma separated list of explicit tab positions\n\
121       --help          display this help and exit\n\
122       --version       output version information and exit\n\
123 \n\
124 Instead of -t NUMBER or -t LIST, -NUMBER or -LIST may be used.\n\
125 "));
126       puts (_("\nReport bugs to <bug-textutils@gnu.org>."));
127     }
128   exit (status == 0 ? EXIT_SUCCESS : EXIT_FAILURE);
129 }
130
131 /* Add tab stop TABVAL to the end of `tab_list', except
132    if TABVAL is -1, do nothing. */
133
134 static void
135 add_tabstop (int tabval)
136 {
137   if (tabval == -1)
138     return;
139   if (first_free_tab % TABLIST_BLOCK == 0)
140     tab_list = (int *) xrealloc ((char *) tab_list,
141                                  (first_free_tab
142                                   + TABLIST_BLOCK * sizeof (tab_list[0])));
143   tab_list[first_free_tab++] = tabval;
144 }
145
146 /* Add the comma or blank separated list of tabstops STOPS
147    to the list of tabstops. */
148
149 static void
150 parse_tabstops (char *stops)
151 {
152   int tabval = -1;
153
154   for (; *stops; stops++)
155     {
156       if (*stops == ',' || ISBLANK (*stops))
157         {
158           add_tabstop (tabval);
159           tabval = -1;
160         }
161       else if (ISDIGIT (*stops))
162         {
163           if (tabval == -1)
164             tabval = 0;
165           tabval = tabval * 10 + *stops - '0';
166         }
167       else
168         error (EXIT_FAILURE, 0, _("tab size contains an invalid character"));
169     }
170
171   add_tabstop (tabval);
172 }
173
174 /* Check that the list of tabstops TABS, with ENTRIES entries,
175    contains only nonzero, ascending values. */
176
177 static void
178 validate_tabstops (int *tabs, int entries)
179 {
180   int prev_tab = 0;
181   int i;
182
183   for (i = 0; i < entries; i++)
184     {
185       if (tabs[i] == 0)
186         error (EXIT_FAILURE, 0, _("tab size cannot be 0"));
187       if (tabs[i] <= prev_tab)
188         error (EXIT_FAILURE, 0, _("tab sizes must be ascending"));
189       prev_tab = tabs[i];
190     }
191 }
192
193 /* Close the old stream pointer FP if it is non-NULL,
194    and return a new one opened to read the next input file.
195    Open a filename of `-' as the standard input.
196    Return NULL if there are no more input files.  */
197
198 static FILE *
199 next_file (FILE *fp)
200 {
201   static char *prev_file;
202   char *file;
203
204   if (fp)
205     {
206       if (ferror (fp))
207         {
208           error (0, errno, "%s", prev_file);
209           exit_status = 1;
210         }
211       if (fp == stdin)
212         clearerr (fp);          /* Also clear EOF. */
213       else if (fclose (fp) == EOF)
214         {
215           error (0, errno, "%s", prev_file);
216           exit_status = 1;
217         }
218     }
219
220   while ((file = *file_list++) != NULL)
221     {
222       if (file[0] == '-' && file[1] == '\0')
223         {
224           have_read_stdin = 1;
225           prev_file = file;
226           return stdin;
227         }
228       fp = fopen (file, "r");
229       if (fp)
230         {
231           prev_file = file;
232           return fp;
233         }
234       error (0, errno, "%s", file);
235       exit_status = 1;
236     }
237   return NULL;
238 }
239
240 /* Change tabs to spaces, writing to stdout.
241    Read each file in `file_list', in order. */
242
243 static void
244 expand (void)
245 {
246   FILE *fp;                     /* Input stream. */
247   int c;                        /* Each input character. */
248   int tab_index = 0;            /* Index in `tab_list' of next tabstop. */
249   int column = 0;               /* Column on screen of the next char. */
250   int next_tab_column;          /* Column the next tab stop is on. */
251   int convert = 1;              /* If nonzero, perform translations. */
252
253   fp = next_file ((FILE *) NULL);
254   if (fp == NULL)
255     return;
256
257   /* Binary I/O will preserve the original EOL style (DOS/Unix) of files.  */
258   SET_BINARY2 (fileno (fp), STDOUT_FILENO);
259
260   for (;;)
261     {
262       c = getc (fp);
263       if (c == EOF)
264         {
265           fp = next_file (fp);
266           if (fp == NULL)
267             break;              /* No more files. */
268           else
269             {
270               SET_BINARY2 (fileno (fp), STDOUT_FILENO);
271               continue;
272             }
273         }
274
275       if (c == '\n')
276         {
277           putchar (c);
278           tab_index = 0;
279           column = 0;
280           convert = 1;
281         }
282       else if (c == '\t' && convert)
283         {
284           if (tab_size == 0)
285             {
286               /* Do not let tab_index == first_free_tab;
287                  stop when it is 1 less. */
288               while (tab_index < first_free_tab - 1
289                      && column >= tab_list[tab_index])
290                 tab_index++;
291               next_tab_column = tab_list[tab_index];
292               if (tab_index < first_free_tab - 1)
293                 tab_index++;
294               if (column >= next_tab_column)
295                 next_tab_column = column + 1; /* Ran out of tab stops. */
296             }
297           else
298             {
299               next_tab_column = column + tab_size - column % tab_size;
300             }
301           while (column < next_tab_column)
302             {
303               putchar (' ');
304               ++column;
305             }
306         }
307       else
308         {
309           if (convert)
310             {
311               if (c == '\b')
312                 {
313                   if (column > 0)
314                     --column;
315                 }
316               else
317                 {
318                   ++column;
319                   if (convert_entire_line == 0)
320                     convert = 0;
321                 }
322             }
323           putchar (c);
324         }
325     }
326 }
327
328 int
329 main (int argc, char **argv)
330 {
331   int tabval = -1;              /* Value of tabstop being read, or -1. */
332   int c;                        /* Option character. */
333
334   have_read_stdin = 0;
335   exit_status = 0;
336   convert_entire_line = 1;
337   tab_list = NULL;
338   first_free_tab = 0;
339   program_name = argv[0];
340   setlocale (LC_ALL, "");
341   bindtextdomain (PACKAGE, LOCALEDIR);
342   textdomain (PACKAGE);
343
344   atexit (close_stdout);
345
346   while ((c = getopt_long (argc, argv, "it:,0123456789", longopts, NULL)) != -1)
347     {
348       switch (c)
349         {
350         case 0:
351           break;
352
353         case '?':
354           usage (1);
355         case 'i':
356           convert_entire_line = 0;
357           break;
358         case 't':
359           parse_tabstops (optarg);
360           break;
361         case ',':
362           add_tabstop (tabval);
363           tabval = -1;
364           break;
365         case_GETOPT_HELP_CHAR;
366         case_GETOPT_VERSION_CHAR (PROGRAM_NAME, AUTHORS);
367         default:
368           if (tabval == -1)
369             tabval = 0;
370           tabval = tabval * 10 + c - '0';
371           break;
372         }
373     }
374
375   add_tabstop (tabval);
376
377   validate_tabstops (tab_list, first_free_tab);
378
379   if (first_free_tab == 0)
380     tab_size = 8;
381   else if (first_free_tab == 1)
382     tab_size = tab_list[0];
383   else
384     tab_size = 0;
385
386   if (optind == argc)
387     file_list = stdin_argv;
388   else
389     file_list = &argv[optind];
390
391   expand ();
392
393   if (have_read_stdin && fclose (stdin) == EOF)
394     error (EXIT_FAILURE, errno, "-");
395
396   exit (exit_status == 0 ? EXIT_SUCCESS : EXIT_FAILURE);
397 }