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