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