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