(add_tabstop): Give correct size when reallocating tab_list buffer.
[platform/upstream/coreutils.git] / src / unexpand.c
1 /* unexpand - convert spaces to tabs
2    Copyright (C) 1989, 1991, 1995 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 only maximal strings of initial blanks and tabs
19    into tabs.
20    Preserves backspace characters in the output; they decrement the
21    column count for tab calculations.
22    The default action is equivalent to -8.
23
24    Options:
25    --tabs=tab1[,tab2[,...]]
26    -t tab1[,tab2[,...]]
27    -tab1[,tab2[,...]]   If only one tab stop is given, set the tabs tab1
28                         spaces apart instead of the default 8.  Otherwise,
29                         set the tabs at columns tab1, tab2, etc. (numbered from
30                         0); replace any tabs beyond the tabstops given with
31                         single spaces.
32    --all
33    -a                   Use tabs wherever they would replace 2 or more spaces,
34                         not just at the beginnings of lines.
35
36    David MacKenzie <djm@gnu.ai.mit.edu> */
37
38 #include <config.h>
39
40 /* Get isblank from GNU libc.  */
41 #define _GNU_SOURCE
42
43 #include <stdio.h>
44 #include <getopt.h>
45 #include <sys/types.h>
46 #include "system.h"
47 #include "version.h"
48 #include "error.h"
49
50 /* The number of bytes added at a time to the amount of memory
51    allocated for the output line. */
52 #define OUTPUT_BLOCK 256
53
54 /* The number of bytes added at a time to the amount of memory
55    allocated for the list of tabstops. */
56 #define TABLIST_BLOCK 256
57
58 char *xmalloc ();
59 char *xrealloc ();
60
61 static FILE *next_file ();
62 static void add_tabstop ();
63 static void parse_tabstops ();
64 static void unexpand ();
65 static void usage ();
66 static void validate_tabstops ();
67
68 /* The name this program was run with. */
69 char *program_name;
70
71 /* If nonzero, convert blanks even after nonblank characters have been
72    read on the line. */
73 static int convert_entire_line;
74
75 /* If nonzero, the size of all tab stops.  If zero, use `tab_list' instead. */
76 static int tab_size;
77
78 /* Array of the explicit column numbers of the tab stops;
79    after `tab_list' is exhausted, the rest of the line is printed
80    unchanged.  The first column is column 0. */
81 static int *tab_list;
82
83 /* The index of the first invalid element of `tab_list',
84    where the next element can be added. */
85 static int first_free_tab;
86
87 /* Null-terminated array of input filenames. */
88 static char **file_list;
89
90 /* Default for `file_list' if no files are given on the command line. */
91 static char *stdin_argv[] =
92 {
93   "-", NULL
94 };
95
96 /* Nonzero if we have ever read standard input. */
97 static int have_read_stdin;
98
99 /* Status to return to the system. */
100 static int exit_status;
101
102 /* If non-zero, display usage information and exit.  */
103 static int show_help;
104
105 /* If non-zero, print the version on standard output then exit.  */
106 static int show_version;
107
108 static struct option const longopts[] =
109 {
110   {"tabs", required_argument, NULL, 't'},
111   {"all", no_argument, NULL, 'a'},
112   {"help", no_argument, &show_help, 1},
113   {"version", no_argument, &show_version, 1},
114   {NULL, 0, NULL, 0}
115 };
116
117 void
118 main (argc, argv)
119      int argc;
120      char **argv;
121 {
122   int tabval = -1;              /* Value of tabstop being read, or -1. */
123   int c;                        /* Option character. */
124
125   have_read_stdin = 0;
126   exit_status = 0;
127   convert_entire_line = 0;
128   tab_list = NULL;
129   first_free_tab = 0;
130   program_name = argv[0];
131
132   while ((c = getopt_long (argc, argv, "at:,0123456789", longopts, (int *) 0))
133          != EOF)
134     {
135       switch (c)
136         {
137         case 0:
138           break;
139
140         case '?':
141           usage (1);
142         case 'a':
143           convert_entire_line = 1;
144           break;
145         case 't':
146           convert_entire_line = 1;
147           parse_tabstops (optarg);
148           break;
149         case ',':
150           add_tabstop (tabval);
151           tabval = -1;
152           break;
153         default:
154           if (tabval == -1)
155             tabval = 0;
156           tabval = tabval * 10 + c - '0';
157           break;
158         }
159     }
160
161   if (show_version)
162     {
163       printf ("unexpand - %s\n", version_string);
164       exit (0);
165     }
166
167   if (show_help)
168     usage (0);
169
170   add_tabstop (tabval);
171
172   validate_tabstops (tab_list, first_free_tab);
173
174   if (first_free_tab == 0)
175     tab_size = 8;
176   else if (first_free_tab == 1)
177     tab_size = tab_list[0];
178   else
179     tab_size = 0;
180
181   if (optind == argc)
182     file_list = stdin_argv;
183   else
184     file_list = &argv[optind];
185
186   unexpand ();
187
188   if (have_read_stdin && fclose (stdin) == EOF)
189     error (1, errno, "-");
190   if (fclose (stdout) == EOF)
191     error (1, errno, "write error");
192   exit (exit_status);
193 }
194
195 /* Add the comma or blank separated list of tabstops STOPS
196    to the list of tabstops. */
197
198 static void
199 parse_tabstops (stops)
200      char *stops;
201 {
202   int tabval = -1;
203
204   for (; *stops; stops++)
205     {
206       if (*stops == ',' || ISBLANK (*stops))
207         {
208           add_tabstop (tabval);
209           tabval = -1;
210         }
211       else if (ISDIGIT (*stops))
212         {
213           if (tabval == -1)
214             tabval = 0;
215           tabval = tabval * 10 + *stops - '0';
216         }
217       else
218         error (1, 0, "tab size contains an invalid character");
219     }
220
221   add_tabstop (tabval);
222 }
223
224 /* Add tab stop TABVAL to the end of `tab_list', except
225    if TABVAL is -1, do nothing. */
226
227 static void
228 add_tabstop (tabval)
229      int tabval;
230 {
231   if (tabval == -1)
232     return;
233   if (first_free_tab % TABLIST_BLOCK == 0)
234     tab_list = (int *) xrealloc (tab_list, first_free_tab + TABLIST_BLOCK);
235   tab_list[first_free_tab++] = tabval;
236 }
237
238 /* Check that the list of tabstops TABS, with ENTRIES entries,
239    contains only nonzero, ascending values. */
240
241 static void
242 validate_tabstops (tabs, entries)
243      int *tabs;
244      int entries;
245 {
246   int prev_tab = 0;
247   int i;
248
249   for (i = 0; i < entries; i++)
250     {
251       if (tabs[i] == 0)
252         error (1, 0, "tab size cannot be 0");
253       if (tabs[i] <= prev_tab)
254         error (1, 0, "tab sizes must be ascending");
255       prev_tab = tabs[i];
256     }
257 }
258
259 /* Change spaces to tabs, writing to stdout.
260    Read each file in `file_list', in order. */
261
262 static void
263 unexpand ()
264 {
265   FILE *fp;                     /* Input stream. */
266   int c;                        /* Each input character. */
267   /* Index in `tab_list' of next tabstop: */
268   int tab_index = 0;            /* For calculating width of pending tabs. */
269   int print_tab_index = 0;      /* For printing as many tabs as possible. */
270   int column = 0;               /* Column on screen of next char. */
271   int next_tab_column;          /* Column the next tab stop is on. */
272   int convert = 1;              /* If nonzero, perform translations. */
273   int pending = 0;              /* Pending columns of blanks. */
274
275   fp = next_file ((FILE *) NULL);
276   if (fp == NULL)
277     return;
278
279   for (;;)
280     {
281       c = getc (fp);
282       if (c == EOF)
283         {
284           fp = next_file (fp);
285           if (fp == NULL)
286             break;              /* No more files. */
287           else
288             continue;
289         }
290
291       if (c == ' ' && convert)
292         {
293           ++pending;
294           ++column;
295         }
296       else if (c == '\t' && convert)
297         {
298           if (tab_size == 0)
299             {
300               /* Do not let tab_index == first_free_tab;
301                  stop when it is 1 less. */
302               while (tab_index < first_free_tab - 1
303                      && column >= tab_list[tab_index])
304                 tab_index++;
305               next_tab_column = tab_list[tab_index];
306               if (tab_index < first_free_tab - 1)
307                 tab_index++;
308               if (column >= next_tab_column)
309                 {
310                   convert = 0;  /* Ran out of tab stops. */
311                   goto flush_pend;
312                 }
313             }
314           else
315             {
316               next_tab_column = column + tab_size - column % tab_size;
317             }
318           pending += next_tab_column - column;
319           column = next_tab_column;
320         }
321       else
322         {
323         flush_pend:
324           /* Flush pending spaces.  Print as many tabs as possible,
325              then print the rest as spaces. */
326           if (pending == 1)
327             {
328               putchar (' ');
329               pending = 0;
330             }
331           column -= pending;
332           while (pending != 0)
333             {
334               if (tab_size == 0)
335                 {
336                   /* Do not let tab_index == first_free_tab;
337                      stop when it is 1 less. */
338                   while (tab_index < first_free_tab - 1
339                          && column >= tab_list[tab_index])
340                     print_tab_index++;
341                   next_tab_column = tab_list[print_tab_index];
342                   if (print_tab_index < first_free_tab - 1)
343                     print_tab_index++;
344                 }
345               else
346                 {
347                   next_tab_column = column + tab_size - column % tab_size;
348                 }
349               if (next_tab_column - column <= pending)
350                 {
351                   putchar ('\t');
352                   pending -= next_tab_column - column;
353                   column = next_tab_column;
354                 }
355               else
356                 {
357                   --print_tab_index;
358                   column += pending;
359                   while (pending != 0)
360                     {
361                       putchar (' ');
362                       pending--;
363                     }
364                 }
365             }
366
367           if (convert)
368             {
369               if (c == '\b')
370                 {
371                   if (column > 0)
372                     --column;
373                 }
374               else
375                 {
376                   ++column;
377                   if (convert_entire_line == 0)
378                     convert = 0;
379                 }
380             }
381
382           putchar (c);
383
384           if (c == '\n')
385             {
386               tab_index = print_tab_index = 0;
387               column = pending = 0;
388               convert = 1;
389             }
390         }
391     }
392 }
393
394 /* Close the old stream pointer FP if it is non-NULL,
395    and return a new one opened to read the next input file.
396    Open a filename of `-' as the standard input.
397    Return NULL if there are no more input files.  */
398
399 static FILE *
400 next_file (fp)
401      FILE *fp;
402 {
403   static char *prev_file;
404   char *file;
405
406   if (fp)
407     {
408       if (ferror (fp))
409         {
410           error (0, errno, "%s", prev_file);
411           exit_status = 1;
412         }
413       if (fp == stdin)
414         clearerr (fp);          /* Also clear EOF. */
415       else if (fclose (fp) == EOF)
416         {
417           error (0, errno, "%s", prev_file);
418           exit_status = 1;
419         }
420     }
421
422   while ((file = *file_list++) != NULL)
423     {
424       if (file[0] == '-' && file[1] == '\0')
425         {
426           have_read_stdin = 1;
427           prev_file = file;
428           return stdin;
429         }
430       fp = fopen (file, "r");
431       if (fp)
432         {
433           prev_file = file;
434           return fp;
435         }
436       error (0, errno, "%s", file);
437       exit_status = 1;
438     }
439   return NULL;
440 }
441
442 static void
443 usage (status)
444      int status;
445 {
446   if (status != 0)
447     fprintf (stderr, "Try `%s --help' for more information.\n",
448              program_name);
449   else
450     {
451       printf ("\
452 Usage: %s [OPTION]... [FILE]...\n\
453 ",
454               program_name);
455       printf ("\
456 \n\
457   -a, --all           convert all whitespace, instead of initial whitespace\n\
458   -t, --tabs=NUMBER   have tabs NUMBER characters apart instead of 8\n\
459   -t, --tabs=LIST     use comma separated list of explicit tab positions\n\
460       --help          display this help and exit\n\
461       --version       output version information and exit\n\
462 \n\
463 Instead of -t NUMBER or -t LIST, -NUMBER or -LIST may be used.  With\n\
464 no FILE, or when FILE is -, read standard input.\n\
465 ");
466     }
467   exit (status);
468 }