Always call error with errno (not zero) after failed fclose or non-zero ferror.
[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
44 /* The number of bytes added at a time to the amount of memory
45    allocated for the output line. */
46 #define OUTPUT_BLOCK 256
47
48 /* The number of bytes added at a time to the amount of memory
49    allocated for the list of tabstops. */
50 #define TABLIST_BLOCK 256
51
52 char *xmalloc ();
53 char *xrealloc ();
54 void error ();
55
56 static FILE *next_file ();
57 static void add_tabstop ();
58 static void expand ();
59 static void parse_tabstops ();
60 static void usage ();
61 static void validate_tabstops ();
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 /* The name this program was run with. */
95 char *program_name;
96
97 static struct option const longopts[] =
98 {
99   {"tabs", required_argument, NULL, 't'},
100   {"initial", no_argument, NULL, 'i'},
101   {NULL, 0, NULL, 0}
102 };
103
104 void
105 main (argc, argv)
106      int argc;
107      char **argv;
108 {
109   int tabval = -1;              /* Value of tabstop being read, or -1. */
110   int c;                        /* Option character. */
111
112   have_read_stdin = 0;
113   exit_status = 0;
114   convert_entire_line = 1;
115   tab_list = NULL;
116   first_free_tab = 0;
117   program_name = argv[0];
118
119   while ((c = getopt_long (argc, argv, "it:,0123456789", longopts, (int *) 0))
120          != EOF)
121     {
122       switch (c)
123         {
124         case '?':
125           usage ();
126         case 'i':
127           convert_entire_line = 0;
128           break;
129         case 't':
130           parse_tabstops (optarg);
131           break;
132         case ',':
133           add_tabstop (tabval);
134           tabval = -1;
135           break;
136         default:
137           if (tabval == -1)
138             tabval = 0;
139           tabval = tabval * 10 + c - '0';
140           break;
141         }
142     }
143
144   add_tabstop (tabval);
145
146   validate_tabstops (tab_list, first_free_tab);
147
148   if (first_free_tab == 0)
149     tab_size = 8;
150   else if (first_free_tab == 1)
151     tab_size = tab_list[0];
152   else
153     tab_size = 0;
154
155   if (optind == argc)
156     file_list = stdin_argv;
157   else
158     file_list = &argv[optind];
159
160   expand ();
161
162   if (have_read_stdin && fclose (stdin) == EOF)
163     error (1, errno, "-");
164   if (ferror (stdout) || fclose (stdout) == EOF)
165     error (1, errno, "write error");
166
167   exit (exit_status);
168 }
169
170 /* Add the comma or blank separated list of tabstops STOPS
171    to the list of tabstops. */
172
173 static void
174 parse_tabstops (stops)
175      char *stops;
176 {
177   int tabval = -1;
178
179   for (; *stops; stops++)
180     {
181       if (*stops == ',' || ISBLANK (*stops))
182         {
183           add_tabstop (tabval);
184           tabval = -1;
185         }
186       else if (ISDIGIT (*stops))
187         {
188           if (tabval == -1)
189             tabval = 0;
190           tabval = tabval * 10 + *stops - '0';
191         }
192       else
193         error (1, 0, "tab size contains an invalid character");
194     }
195
196   add_tabstop (tabval);
197 }
198
199 /* Add tab stop TABVAL to the end of `tab_list', except
200    if TABVAL is -1, do nothing. */
201
202 static void
203 add_tabstop (tabval)
204      int tabval;
205 {
206   if (tabval == -1)
207     return;
208   if (first_free_tab % TABLIST_BLOCK == 0)
209     tab_list = (int *) xrealloc (tab_list, first_free_tab + TABLIST_BLOCK);
210   tab_list[first_free_tab++] = tabval;
211 }
212
213 /* Check that the list of tabstops TABS, with ENTRIES entries,
214    contains only nonzero, ascending values. */
215
216 static void
217 validate_tabstops (tabs, entries)
218      int *tabs;
219      int entries;
220 {
221   int prev_tab = 0;
222   int i;
223   
224   for (i = 0; i < entries; i++)
225     {
226       if (tabs[i] == 0)
227         error (1, 0, "tab size cannot be 0");
228       if (tabs[i] <= prev_tab)
229         error (1, 0, "tab sizes must be ascending");
230       prev_tab = tabs[i];
231     }
232 }
233
234 /* Change tabs to spaces, writing to stdout.
235    Read each file in `file_list', in order. */
236
237 static void
238 expand ()
239 {
240   FILE *fp;                     /* Input stream. */
241   int c;                        /* Each input character. */
242   int tab_index = 0;            /* Index in `tab_list' of next tabstop. */
243   int column = 0;               /* Column on screen of the next char. */
244   int next_tab_column;          /* Column the next tab stop is on. */
245   int convert = 1;              /* If nonzero, perform translations. */
246
247   fp = next_file ((FILE *) NULL);
248   for (;;)
249     {
250       c = getc (fp);
251       if (c == EOF)
252         {
253           fp = next_file (fp);
254           if (fp == NULL)
255             break;              /* No more files. */
256           else
257             continue;
258         }
259
260       if (c == '\n')
261         {
262           putchar (c);
263           tab_index = 0;
264           column = 0;
265           convert = 1;
266         }
267       else if (c == '\t' && convert)
268         {
269           if (tab_size == 0)
270             {
271               /* Do not let tab_index == first_free_tab;
272                  stop when it is 1 less. */
273               while (tab_index < first_free_tab - 1
274                      && column >= tab_list[tab_index])
275                 tab_index++;
276               next_tab_column = tab_list[tab_index];
277               if (tab_index < first_free_tab - 1)
278                 tab_index++;
279               if (column >= next_tab_column)
280                 next_tab_column = column + 1; /* Ran out of tab stops. */
281             }
282           else
283             {
284               next_tab_column = column + tab_size - column % tab_size;
285             }
286           while (column < next_tab_column)
287             {
288               putchar (' ');
289               ++column;
290             }
291         }
292       else
293         {
294           if (convert)
295             {
296               if (c == '\b')
297                 {
298                   if (column > 0)
299                     --column;
300                 }
301               else
302                 {
303                   ++column;
304                   if (convert_entire_line == 0)
305                     convert = 0;
306                 }
307             }
308           putchar (c);
309         }
310     }
311 }
312
313 /* Close the old stream pointer FP if it is non-NULL,
314    and return a new one opened to read the next input file.
315    Open a filename of `-' as the standard input.
316    Return NULL if there are no more input files.  */
317
318 static FILE *
319 next_file (fp)
320      FILE *fp;
321 {
322   static char *prev_file;
323   char *file;
324
325   if (fp)
326     {
327       if (ferror (fp))
328         {
329           error (0, errno, "%s", prev_file);
330           exit_status = 1;
331         }
332       if (fp == stdin)
333         clearerr (fp);          /* Also clear EOF. */
334       else if (fclose (fp) == EOF)
335         {
336           error (0, errno, "%s", prev_file);
337           exit_status = 1;
338         }
339     }
340
341   while ((file = *file_list++) != NULL)
342     {
343       if (file[0] == '-' && file[1] == '\0')
344         {
345           have_read_stdin = 1;
346           prev_file = file;
347           return stdin;
348         }
349       fp = fopen (file, "r");
350       if (fp)
351         {
352           prev_file = file;
353           return fp;
354         }
355       error (0, errno, "%s", file);
356       exit_status = 1;
357     }
358   return NULL;
359 }
360
361 static void
362 usage ()
363 {
364   fprintf (stderr, "\
365 Usage: %s [-tab1[,tab2[,...]]] [-t tab1[,tab2[,...]]] [-i]\n\
366        [--tabs=tab1[,tab2[,...]]] [--initial] [file...]\n",
367            program_name);
368   exit (1);
369 }