merge with 1.5.2
[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     {
158       fprintf (stderr, "%s\n", version_string);
159       exit (0);
160     }
161
162   if (flag_help)
163     usage ();
164
165   add_tabstop (tabval);
166
167   validate_tabstops (tab_list, first_free_tab);
168
169   if (first_free_tab == 0)
170     tab_size = 8;
171   else if (first_free_tab == 1)
172     tab_size = tab_list[0];
173   else
174     tab_size = 0;
175
176   if (optind == argc)
177     file_list = stdin_argv;
178   else
179     file_list = &argv[optind];
180
181   expand ();
182
183   if (have_read_stdin && fclose (stdin) == EOF)
184     error (1, errno, "-");
185   if (ferror (stdout) || fclose (stdout) == EOF)
186     error (1, errno, "write error");
187
188   exit (exit_status);
189 }
190
191 /* Add the comma or blank separated list of tabstops STOPS
192    to the list of tabstops. */
193
194 static void
195 parse_tabstops (stops)
196      char *stops;
197 {
198   int tabval = -1;
199
200   for (; *stops; stops++)
201     {
202       if (*stops == ',' || ISBLANK (*stops))
203         {
204           add_tabstop (tabval);
205           tabval = -1;
206         }
207       else if (ISDIGIT (*stops))
208         {
209           if (tabval == -1)
210             tabval = 0;
211           tabval = tabval * 10 + *stops - '0';
212         }
213       else
214         error (1, 0, "tab size contains an invalid character");
215     }
216
217   add_tabstop (tabval);
218 }
219
220 /* Add tab stop TABVAL to the end of `tab_list', except
221    if TABVAL is -1, do nothing. */
222
223 static void
224 add_tabstop (tabval)
225      int tabval;
226 {
227   if (tabval == -1)
228     return;
229   if (first_free_tab % TABLIST_BLOCK == 0)
230     tab_list = (int *) xrealloc (tab_list, first_free_tab + TABLIST_BLOCK);
231   tab_list[first_free_tab++] = tabval;
232 }
233
234 /* Check that the list of tabstops TABS, with ENTRIES entries,
235    contains only nonzero, ascending values. */
236
237 static void
238 validate_tabstops (tabs, entries)
239      int *tabs;
240      int entries;
241 {
242   int prev_tab = 0;
243   int i;
244   
245   for (i = 0; i < entries; i++)
246     {
247       if (tabs[i] == 0)
248         error (1, 0, "tab size cannot be 0");
249       if (tabs[i] <= prev_tab)
250         error (1, 0, "tab sizes must be ascending");
251       prev_tab = tabs[i];
252     }
253 }
254
255 /* Change tabs to spaces, writing to stdout.
256    Read each file in `file_list', in order. */
257
258 static void
259 expand ()
260 {
261   FILE *fp;                     /* Input stream. */
262   int c;                        /* Each input character. */
263   int tab_index = 0;            /* Index in `tab_list' of next tabstop. */
264   int column = 0;               /* Column on screen of the next char. */
265   int next_tab_column;          /* Column the next tab stop is on. */
266   int convert = 1;              /* If nonzero, perform translations. */
267
268   fp = next_file ((FILE *) NULL);
269   for (;;)
270     {
271       c = getc (fp);
272       if (c == EOF)
273         {
274           fp = next_file (fp);
275           if (fp == NULL)
276             break;              /* No more files. */
277           else
278             continue;
279         }
280
281       if (c == '\n')
282         {
283           putchar (c);
284           tab_index = 0;
285           column = 0;
286           convert = 1;
287         }
288       else if (c == '\t' && convert)
289         {
290           if (tab_size == 0)
291             {
292               /* Do not let tab_index == first_free_tab;
293                  stop when it is 1 less. */
294               while (tab_index < first_free_tab - 1
295                      && column >= tab_list[tab_index])
296                 tab_index++;
297               next_tab_column = tab_list[tab_index];
298               if (tab_index < first_free_tab - 1)
299                 tab_index++;
300               if (column >= next_tab_column)
301                 next_tab_column = column + 1; /* Ran out of tab stops. */
302             }
303           else
304             {
305               next_tab_column = column + tab_size - column % tab_size;
306             }
307           while (column < next_tab_column)
308             {
309               putchar (' ');
310               ++column;
311             }
312         }
313       else
314         {
315           if (convert)
316             {
317               if (c == '\b')
318                 {
319                   if (column > 0)
320                     --column;
321                 }
322               else
323                 {
324                   ++column;
325                   if (convert_entire_line == 0)
326                     convert = 0;
327                 }
328             }
329           putchar (c);
330         }
331     }
332 }
333
334 /* Close the old stream pointer FP if it is non-NULL,
335    and return a new one opened to read the next input file.
336    Open a filename of `-' as the standard input.
337    Return NULL if there are no more input files.  */
338
339 static FILE *
340 next_file (fp)
341      FILE *fp;
342 {
343   static char *prev_file;
344   char *file;
345
346   if (fp)
347     {
348       if (ferror (fp))
349         {
350           error (0, errno, "%s", prev_file);
351           exit_status = 1;
352         }
353       if (fp == stdin)
354         clearerr (fp);          /* Also clear EOF. */
355       else if (fclose (fp) == EOF)
356         {
357           error (0, errno, "%s", prev_file);
358           exit_status = 1;
359         }
360     }
361
362   while ((file = *file_list++) != NULL)
363     {
364       if (file[0] == '-' && file[1] == '\0')
365         {
366           have_read_stdin = 1;
367           prev_file = file;
368           return stdin;
369         }
370       fp = fopen (file, "r");
371       if (fp)
372         {
373           prev_file = file;
374           return fp;
375         }
376       error (0, errno, "%s", file);
377       exit_status = 1;
378     }
379   return NULL;
380 }
381
382 static void
383 usage ()
384 {
385   fprintf (stderr, "\
386 Usage: %s [-tab1[,tab2[,...]]] [-t tab1[,tab2[,...]]] [-i]\n\
387        [--tabs=tab1[,tab2[,...]]] [--initial] [--help] [--version] [file...]\n",
388            program_name);
389   exit (1);
390 }