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