ab0b8fb1565527e884a315057e342f58d76cc5bc
[platform/upstream/coreutils.git] / src / unexpand.c
1 /* unexpand - convert spaces to tabs
2    Copyright (C) 89, 91, 1995-2003 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 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 "error.h"
45 #include "posixver.h"
46
47 /* The official name of this program (e.g., no `g' prefix).  */
48 #define PROGRAM_NAME "unexpand"
49
50 #define WRITTEN_BY _("Written by David MacKenzie.")
51
52 /* The number of bytes added at a time to the amount of memory
53    allocated for the output line. */
54 #define OUTPUT_BLOCK 256
55
56 /* The number of bytes added at a time to the amount of memory
57    allocated for the list of tabstops. */
58 #define TABLIST_BLOCK 256
59
60 /* A sentinel value that's placed at the end of the list of tab stops.
61    This value must be a large number, but not so large that adding the
62    length of a line to it would cause the column variable to overflow.  */
63 #define TAB_STOP_SENTINEL INT_MAX
64
65 /* The name this program was run with. */
66 char *program_name;
67
68 /* If nonzero, convert blanks even after nonblank characters have been
69    read on the line. */
70 static int convert_entire_line;
71
72 /* If nonzero, the size of all tab stops.  If zero, use `tab_list' instead. */
73 static int tab_size;
74
75 /* Array of the explicit column numbers of the tab stops;
76    after `tab_list' is exhausted, the rest of the line is printed
77    unchanged.  The first column is column 0. */
78 static int *tab_list;
79
80 /* The index of the first invalid element of `tab_list',
81    where the next element can be added. */
82 static int first_free_tab;
83
84 /* Null-terminated array of input filenames. */
85 static char **file_list;
86
87 /* Default for `file_list' if no files are given on the command line. */
88 static char *stdin_argv[] =
89 {
90   "-", NULL
91 };
92
93 /* Nonzero if we have ever read standard input. */
94 static int have_read_stdin;
95
96 /* Status to return to the system. */
97 static int exit_status;
98
99 /* For long options that have no equivalent short option, use a
100    non-character as a pseudo short option, starting with CHAR_MAX + 1.  */
101 enum
102 {
103   CONVERT_FIRST_ONLY_OPTION = CHAR_MAX + 1
104 };
105
106 static struct option const longopts[] =
107 {
108   {"tabs", required_argument, NULL, 't'},
109   {"all", no_argument, NULL, 'a'},
110   {"first-only", no_argument, NULL, CONVERT_FIRST_ONLY_OPTION},
111   {GETOPT_HELP_OPTION_DECL},
112   {GETOPT_VERSION_OPTION_DECL},
113   {NULL, 0, NULL, 0}
114 };
115
116 /* Add tab stop TABVAL to the end of `tab_list', except
117    if TABVAL is -1, do nothing. */
118
119 static void
120 add_tabstop (int tabval)
121 {
122   if (tabval == -1)
123     return;
124   if (first_free_tab % TABLIST_BLOCK == 0)
125     tab_list = xrealloc (tab_list, first_free_tab + TABLIST_BLOCK);
126   tab_list[first_free_tab++] = tabval;
127 }
128
129 /* Add the comma or blank separated list of tabstops STOPS
130    to the list of tabstops. */
131
132 static void
133 parse_tabstops (const char *stops)
134 {
135   int tabval = -1;
136
137   for (; *stops; stops++)
138     {
139       if (*stops == ',' || ISBLANK (*stops))
140         {
141           add_tabstop (tabval);
142           tabval = -1;
143         }
144       else if (ISDIGIT (*stops))
145         {
146           if (tabval == -1)
147             tabval = 0;
148           tabval = tabval * 10 + *stops - '0';
149         }
150       else
151         error (EXIT_FAILURE, 0, _("tab size contains an invalid character"));
152     }
153
154   add_tabstop (tabval);
155 }
156
157 /* Check that the list of tabstops TABS, with ENTRIES entries,
158    contains only nonzero, ascending values. */
159
160 static void
161 validate_tabstops (const int *tabs, int entries)
162 {
163   int prev_tab = 0;
164   int i;
165
166   for (i = 0; i < entries; i++)
167     {
168       if (tabs[i] == 0)
169         error (EXIT_FAILURE, 0, _("tab size cannot be 0"));
170       if (tabs[i] <= prev_tab)
171         error (EXIT_FAILURE, 0, _("tab sizes must be ascending"));
172       prev_tab = tabs[i];
173     }
174 }
175
176 /* Close the old stream pointer FP if it is non-NULL,
177    and return a new one opened to read the next input file.
178    Open a filename of `-' as the standard input.
179    Return NULL if there are no more input files.  */
180
181 static FILE *
182 next_file (FILE *fp)
183 {
184   static char *prev_file;
185   char *file;
186
187   if (fp)
188     {
189       if (ferror (fp))
190         {
191           error (0, errno, "%s", prev_file);
192           exit_status = 1;
193         }
194       if (fp == stdin)
195         clearerr (fp);          /* Also clear EOF. */
196       else if (fclose (fp) == EOF)
197         {
198           error (0, errno, "%s", prev_file);
199           exit_status = 1;
200         }
201     }
202
203   while ((file = *file_list++) != NULL)
204     {
205       if (file[0] == '-' && file[1] == '\0')
206         {
207           have_read_stdin = 1;
208           prev_file = file;
209           return stdin;
210         }
211       fp = fopen (file, "r");
212       if (fp)
213         {
214           prev_file = file;
215           return fp;
216         }
217       error (0, errno, "%s", file);
218       exit_status = 1;
219     }
220   return NULL;
221 }
222
223 /* Change spaces to tabs, writing to stdout.
224    Read each file in `file_list', in order. */
225
226 static void
227 unexpand (void)
228 {
229   FILE *fp;                     /* Input stream. */
230   int c;                        /* Each input character. */
231   /* Index in `tab_list' of next tabstop: */
232   int tab_index = 0;            /* For calculating width of pending tabs. */
233   int print_tab_index = 0;      /* For printing as many tabs as possible. */
234   unsigned int column = 0;      /* Column on screen of next char. */
235   int next_tab_column;          /* Column the next tab stop is on. */
236   int convert = 1;              /* If nonzero, perform translations. */
237   unsigned int pending = 0;     /* Pending columns of blanks. */
238   int saved_errno;
239
240   fp = next_file ((FILE *) NULL);
241   if (fp == NULL)
242     return;
243
244   /* Binary I/O will preserve the original EOL style (DOS/Unix) of files.  */
245   SET_BINARY2 (fileno (fp), STDOUT_FILENO);
246
247   for (;;)
248     {
249       c = getc (fp);
250       saved_errno = errno;
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               errno = saved_errno;
331               fp = next_file (fp);
332               if (fp == NULL)
333                 break;          /* No more files. */
334               else
335                 {
336                   SET_BINARY2 (fileno (fp), STDOUT_FILENO);
337                   continue;
338                 }
339             }
340
341           if (convert)
342             {
343               if (c == '\b')
344                 {
345                   if (column > 0)
346                     --column;
347                 }
348               else
349                 {
350                   ++column;
351                   if (convert_entire_line == 0)
352                     convert = 0;
353                 }
354             }
355
356           putchar (c);
357
358           if (c == '\n')
359             {
360               tab_index = print_tab_index = 0;
361               column = pending = 0;
362               convert = 1;
363             }
364         }
365     }
366 }
367
368 void
369 usage (int status)
370 {
371   if (status != 0)
372     fprintf (stderr, _("Try `%s --help' for more information.\n"),
373              program_name);
374   else
375     {
376       printf (_("\
377 Usage: %s [OPTION]... [FILE]...\n\
378 "),
379               program_name);
380       fputs (_("\
381 Convert spaces in each FILE to tabs, writing to standard output.\n\
382 With no FILE, or when FILE is -, read standard input.\n\
383 \n\
384 "), stdout);
385       fputs (_("\
386 Mandatory arguments to long options are mandatory for short options too.\n\
387 "), stdout);
388       fputs (_("\
389   -a, --all        convert all whitespace, instead of just initial whitespace\n\
390       --first-only convert only leading sequences of whitespace (overrides -a)\n\
391   -t, --tabs=N     have tabs N characters apart instead of 8 (enables -a)\n\
392   -t, --tabs=LIST  use comma separated LIST of tab positions (enables -a)\n\
393 "), stdout);
394       fputs (HELP_OPTION_DESCRIPTION, stdout);
395       fputs (VERSION_OPTION_DESCRIPTION, stdout);
396       printf (_("\nReport bugs to <%s>.\n"), PACKAGE_BUGREPORT);
397     }
398   exit (status == 0 ? EXIT_SUCCESS : EXIT_FAILURE);
399 }
400
401 int
402 main (int argc, char **argv)
403 {
404   int tabval = -1;              /* Value of tabstop being read, or -1. */
405   int c;                        /* Option character. */
406
407   /* If nonzero, cancel the effect of any -a (explicit or implicit in -t),
408      so that only leading white space will be considered.  */
409   int convert_first_only = 0;
410
411   bool obsolete_tablist = false;
412
413   initialize_main (&argc, &argv);
414   program_name = argv[0];
415   setlocale (LC_ALL, "");
416   bindtextdomain (PACKAGE, LOCALEDIR);
417   textdomain (PACKAGE);
418
419   atexit (close_stdout);
420
421   have_read_stdin = 0;
422   exit_status = 0;
423   convert_entire_line = 0;
424   tab_list = NULL;
425   first_free_tab = 0;
426
427   while ((c = getopt_long (argc, argv, ",0123456789at:", longopts, NULL))
428          != -1)
429     {
430       switch (c)
431         {
432         case 0:
433           break;
434
435         case '?':
436           usage (EXIT_FAILURE);
437         case 'a':
438           convert_entire_line = 1;
439           break;
440         case 't':
441           convert_entire_line = 1;
442           parse_tabstops (optarg);
443           break;
444         case CONVERT_FIRST_ONLY_OPTION:
445           convert_first_only = 1;
446           break;
447         case ',':
448           add_tabstop (tabval);
449           tabval = -1;
450           obsolete_tablist = true;
451           break;
452         case_GETOPT_HELP_CHAR;
453         case_GETOPT_VERSION_CHAR (PROGRAM_NAME, WRITTEN_BY);
454         default:
455           if (tabval == -1)
456             tabval = 0;
457           tabval = tabval * 10 + c - '0';
458           obsolete_tablist = true;
459           break;
460         }
461     }
462
463   if (obsolete_tablist && 200112 <= posix2_version ())
464     {
465       error (0, 0,
466              _("`-LIST' option is obsolete; use `--first-only -t LIST'"));
467       usage (EXIT_FAILURE);
468     }
469
470   if (convert_first_only)
471     convert_entire_line = 0;
472
473   add_tabstop (tabval);
474
475   validate_tabstops (tab_list, first_free_tab);
476
477   if (first_free_tab == 0)
478     tab_size = 8;
479   else if (first_free_tab == 1)
480     tab_size = tab_list[0];
481   else
482     {
483       /* Append a sentinel to the list of tab stop indices.  */
484       add_tabstop (TAB_STOP_SENTINEL);
485       tab_size = 0;
486     }
487
488   file_list = (optind < argc ? &argv[optind] : stdin_argv);
489
490   unexpand ();
491
492   if (have_read_stdin && fclose (stdin) == EOF)
493     error (EXIT_FAILURE, errno, "-");
494   exit (exit_status == 0 ? EXIT_SUCCESS : EXIT_FAILURE);
495 }