(usage): Say that
[platform/upstream/coreutils.git] / src / unexpand.c
1 /* unexpand - convert spaces to tabs
2    Copyright (C) 89, 91, 1995-2001 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, 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 #include <stdio.h>
41 #include <getopt.h>
42 #include <sys/types.h>
43 #include "system.h"
44 #include "closeout.h"
45
46 #include "error.h"
47
48 /* The official name of this program (e.g., no `g' prefix).  */
49 #define PROGRAM_NAME "unexpand"
50
51 #define AUTHORS "David MacKenzie"
52
53 /* The number of bytes added at a time to the amount of memory
54    allocated for the output line. */
55 #define OUTPUT_BLOCK 256
56
57 /* The number of bytes added at a time to the amount of memory
58    allocated for the list of tabstops. */
59 #define TABLIST_BLOCK 256
60
61 /* A sentinel value that's placed at the end of the list of tab stops.
62    This value must be a large number, but not so large that adding the
63    length of a line to it would cause the column variable to overflow.  */
64 #define TAB_STOP_SENTINEL INT_MAX
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 /* For long options that have no equivalent short option, use a
101    non-character as a pseudo short option, starting with CHAR_MAX + 1.  */
102 enum
103 {
104   CONVERT_FIRST_ONLY_OPTION = CHAR_MAX + 1
105 };
106
107 static struct option const longopts[] =
108 {
109   {"tabs", required_argument, NULL, 't'},
110   {"all", no_argument, NULL, 'a'},
111   {"first-only", no_argument, NULL, CONVERT_FIRST_ONLY_OPTION},
112   {GETOPT_HELP_OPTION_DECL},
113   {GETOPT_VERSION_OPTION_DECL},
114   {NULL, 0, NULL, 0}
115 };
116
117 /* Add tab stop TABVAL to the end of `tab_list', except
118    if TABVAL is -1, do nothing. */
119
120 static void
121 add_tabstop (int tabval)
122 {
123   if (tabval == -1)
124     return;
125   if (first_free_tab % TABLIST_BLOCK == 0)
126     tab_list = (int *) xrealloc ((char *) tab_list,
127                                  first_free_tab + TABLIST_BLOCK);
128   tab_list[first_free_tab++] = tabval;
129 }
130
131 /* Add the comma or blank separated list of tabstops STOPS
132    to the list of tabstops. */
133
134 static void
135 parse_tabstops (const char *stops)
136 {
137   int tabval = -1;
138
139   for (; *stops; stops++)
140     {
141       if (*stops == ',' || ISBLANK (*stops))
142         {
143           add_tabstop (tabval);
144           tabval = -1;
145         }
146       else if (ISDIGIT (*stops))
147         {
148           if (tabval == -1)
149             tabval = 0;
150           tabval = tabval * 10 + *stops - '0';
151         }
152       else
153         error (EXIT_FAILURE, 0, _("tab size contains an invalid character"));
154     }
155
156   add_tabstop (tabval);
157 }
158
159 /* Check that the list of tabstops TABS, with ENTRIES entries,
160    contains only nonzero, ascending values. */
161
162 static void
163 validate_tabstops (const int *tabs, int entries)
164 {
165   int prev_tab = 0;
166   int i;
167
168   for (i = 0; i < entries; i++)
169     {
170       if (tabs[i] == 0)
171         error (EXIT_FAILURE, 0, _("tab size cannot be 0"));
172       if (tabs[i] <= prev_tab)
173         error (EXIT_FAILURE, 0, _("tab sizes must be ascending"));
174       prev_tab = tabs[i];
175     }
176 }
177
178 /* Close the old stream pointer FP if it is non-NULL,
179    and return a new one opened to read the next input file.
180    Open a filename of `-' as the standard input.
181    Return NULL if there are no more input files.  */
182
183 static FILE *
184 next_file (FILE *fp)
185 {
186   static char *prev_file;
187   char *file;
188
189   if (fp)
190     {
191       if (ferror (fp))
192         {
193           error (0, errno, "%s", prev_file);
194           exit_status = 1;
195         }
196       if (fp == stdin)
197         clearerr (fp);          /* Also clear EOF. */
198       else if (fclose (fp) == EOF)
199         {
200           error (0, errno, "%s", prev_file);
201           exit_status = 1;
202         }
203     }
204
205   while ((file = *file_list++) != NULL)
206     {
207       if (file[0] == '-' && file[1] == '\0')
208         {
209           have_read_stdin = 1;
210           prev_file = file;
211           return stdin;
212         }
213       fp = fopen (file, "r");
214       if (fp)
215         {
216           prev_file = file;
217           return fp;
218         }
219       error (0, errno, "%s", file);
220       exit_status = 1;
221     }
222   return NULL;
223 }
224
225 /* Change spaces to tabs, writing to stdout.
226    Read each file in `file_list', in order. */
227
228 static void
229 unexpand (void)
230 {
231   FILE *fp;                     /* Input stream. */
232   int c;                        /* Each input character. */
233   /* Index in `tab_list' of next tabstop: */
234   int tab_index = 0;            /* For calculating width of pending tabs. */
235   int print_tab_index = 0;      /* For printing as many tabs as possible. */
236   unsigned int column = 0;      /* Column on screen of next char. */
237   int next_tab_column;          /* Column the next tab stop is on. */
238   int convert = 1;              /* If nonzero, perform translations. */
239   unsigned int pending = 0;     /* Pending columns of blanks. */
240
241   fp = next_file ((FILE *) NULL);
242   if (fp == NULL)
243     return;
244
245   /* Binary I/O will preserve the original EOL style (DOS/Unix) of files.  */
246   SET_BINARY2 (fileno (fp), STDOUT_FILENO);
247
248   for (;;)
249     {
250       c = getc (fp);
251
252       if (c == ' ' && convert && column < TAB_STOP_SENTINEL)
253         {
254           ++pending;
255           ++column;
256         }
257       else if (c == '\t' && convert)
258         {
259           if (tab_size == 0)
260             {
261               /* Do not let tab_index == first_free_tab;
262                  stop when it is 1 less. */
263               while (tab_index < first_free_tab - 1
264                      && column >= tab_list[tab_index])
265                 tab_index++;
266               next_tab_column = tab_list[tab_index];
267               if (tab_index < first_free_tab - 1)
268                 tab_index++;
269               if (column >= next_tab_column)
270                 {
271                   convert = 0;  /* Ran out of tab stops. */
272                   goto flush_pend;
273                 }
274             }
275           else
276             {
277               next_tab_column = column + tab_size - column % tab_size;
278             }
279           pending += next_tab_column - column;
280           column = next_tab_column;
281         }
282       else
283         {
284         flush_pend:
285           /* Flush pending spaces.  Print as many tabs as possible,
286              then print the rest as spaces. */
287           if (pending == 1)
288             {
289               putchar (' ');
290               pending = 0;
291             }
292           column -= pending;
293           while (pending > 0)
294             {
295               if (tab_size == 0)
296                 {
297                   /* Do not let print_tab_index == first_free_tab;
298                      stop when it is 1 less. */
299                   while (print_tab_index < first_free_tab - 1
300                          && column >= tab_list[print_tab_index])
301                     print_tab_index++;
302                   next_tab_column = tab_list[print_tab_index];
303                   if (print_tab_index < first_free_tab - 1)
304                     print_tab_index++;
305                 }
306               else
307                 {
308                   next_tab_column = column + tab_size - column % tab_size;
309                 }
310               if (next_tab_column - column <= pending)
311                 {
312                   putchar ('\t');
313                   pending -= next_tab_column - column;
314                   column = next_tab_column;
315                 }
316               else
317                 {
318                   --print_tab_index;
319                   column += pending;
320                   while (pending != 0)
321                     {
322                       putchar (' ');
323                       pending--;
324                     }
325                 }
326             }
327
328           if (c == EOF)
329             {
330               fp = next_file (fp);
331               if (fp == NULL)
332                 break;          /* No more files. */
333               else
334                 {
335                   SET_BINARY2 (fileno (fp), STDOUT_FILENO);
336                   continue;
337                 }
338             }
339
340           if (convert)
341             {
342               if (c == '\b')
343                 {
344                   if (column > 0)
345                     --column;
346                 }
347               else
348                 {
349                   ++column;
350                   if (convert_entire_line == 0)
351                     convert = 0;
352                 }
353             }
354
355           putchar (c);
356
357           if (c == '\n')
358             {
359               tab_index = print_tab_index = 0;
360               column = pending = 0;
361               convert = 1;
362             }
363         }
364     }
365 }
366
367 void
368 usage (int status)
369 {
370   if (status != 0)
371     fprintf (stderr, _("Try `%s --help' for more information.\n"),
372              program_name);
373   else
374     {
375       printf (_("\
376 Usage: %s [OPTION]... [FILE]...\n\
377 "),
378               program_name);
379       printf (_("\
380 Convert spaces in each FILE to tabs, writing to standard output.\n\
381 With no FILE, or when FILE is -, read standard input.\n\
382 \n\
383 Mandatory arguments to long options are mandatory for short options too.\n\
384   -a, --all           convert all whitespace, instead of initial whitespace\n\
385   -t, --tabs=NUMBER   have tabs NUMBER characters apart instead of 8\n\
386   -t, --tabs=LIST     use comma separated list of explicit tab positions\n\
387       --help          display this help and exit\n\
388       --version       output version information and exit\n\
389 \n\
390 Instead of -t NUMBER or -t LIST, -NUMBER or -LIST may be used.\n\
391 "));
392       puts (_("\nReport bugs to <bug-textutils@gnu.org>."));
393     }
394   exit (status == 0 ? EXIT_SUCCESS : EXIT_FAILURE);
395 }
396
397 int
398 main (int argc, char **argv)
399 {
400   int tabval = -1;              /* Value of tabstop being read, or -1. */
401   int c;                        /* Option character. */
402
403   /* If nonzero, cancel the effect of any -a (explicit or implicit in -t),
404      so that only leading white space will be considered.  */
405   int convert_first_only = 0;
406
407   program_name = argv[0];
408   setlocale (LC_ALL, "");
409   bindtextdomain (PACKAGE, LOCALEDIR);
410   textdomain (PACKAGE);
411
412   atexit (close_stdout);
413
414   have_read_stdin = 0;
415   exit_status = 0;
416   convert_entire_line = 0;
417   tab_list = NULL;
418   first_free_tab = 0;
419
420   while ((c = getopt_long (argc, argv, "at:,0123456789", longopts, NULL)) != -1)
421     {
422       switch (c)
423         {
424         case 0:
425           break;
426
427         case '?':
428           usage (1);
429         case 'a':
430           convert_entire_line = 1;
431           break;
432         case 't':
433           convert_entire_line = 1;
434           parse_tabstops (optarg);
435           break;
436         case CONVERT_FIRST_ONLY_OPTION:
437           convert_first_only = 1;
438           break;
439         case ',':
440           add_tabstop (tabval);
441           tabval = -1;
442           break;
443         case_GETOPT_HELP_CHAR;
444         case_GETOPT_VERSION_CHAR (PROGRAM_NAME, AUTHORS);
445         default:
446           if (tabval == -1)
447             tabval = 0;
448           tabval = tabval * 10 + c - '0';
449           break;
450         }
451     }
452
453   if (convert_first_only)
454     convert_entire_line = 0;
455
456   add_tabstop (tabval);
457
458   validate_tabstops (tab_list, first_free_tab);
459
460   if (first_free_tab == 0)
461     tab_size = 8;
462   else if (first_free_tab == 1)
463     tab_size = tab_list[0];
464   else
465     {
466       /* Append a sentinel to the list of tab stop indices.  */
467       add_tabstop (TAB_STOP_SENTINEL);
468       tab_size = 0;
469     }
470
471   if (optind == argc)
472     file_list = stdin_argv;
473   else
474     file_list = &argv[optind];
475
476   unexpand ();
477
478   if (have_read_stdin && fclose (stdin) == EOF)
479     error (EXIT_FAILURE, errno, "-");
480   exit (exit_status == 0 ? EXIT_SUCCESS : EXIT_FAILURE);
481 }