*** empty log message ***
[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       fputs (_("\
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 "), stdout);
384      fputs (_("\
385 Mandatory arguments to long options are mandatory for short options too.\n\
386 "), stdout);
387      fputs (_("\
388   -a, --all           convert all whitespace, instead of initial whitespace\n\
389   -t, --tabs=NUMBER   have tabs NUMBER characters apart instead of 8\n\
390   -t, --tabs=LIST     use comma separated list of explicit tab positions\n\
391 "), stdout);
392      fputs (HELP_OPTION_DESCRIPTION, stdout);
393      fputs (VERSION_OPTION_DESCRIPTION, stdout);
394      fputs (_("\
395 \n\
396 Instead of -t NUMBER or -t LIST, -NUMBER or -LIST may be used.\n\
397 "), stdout);
398       puts (_("\nReport bugs to <bug-textutils@gnu.org>."));
399     }
400   exit (status == 0 ? EXIT_SUCCESS : EXIT_FAILURE);
401 }
402
403 int
404 main (int argc, char **argv)
405 {
406   int tabval = -1;              /* Value of tabstop being read, or -1. */
407   int c;                        /* Option character. */
408
409   /* If nonzero, cancel the effect of any -a (explicit or implicit in -t),
410      so that only leading white space will be considered.  */
411   int convert_first_only = 0;
412
413   program_name = argv[0];
414   setlocale (LC_ALL, "");
415   bindtextdomain (PACKAGE, LOCALEDIR);
416   textdomain (PACKAGE);
417
418   atexit (close_stdout);
419
420   have_read_stdin = 0;
421   exit_status = 0;
422   convert_entire_line = 0;
423   tab_list = NULL;
424   first_free_tab = 0;
425
426   while ((c = getopt_long (argc, argv, "at:,0123456789", longopts, NULL)) != -1)
427     {
428       switch (c)
429         {
430         case 0:
431           break;
432
433         case '?':
434           usage (1);
435         case 'a':
436           convert_entire_line = 1;
437           break;
438         case 't':
439           convert_entire_line = 1;
440           parse_tabstops (optarg);
441           break;
442         case CONVERT_FIRST_ONLY_OPTION:
443           convert_first_only = 1;
444           break;
445         case ',':
446           add_tabstop (tabval);
447           tabval = -1;
448           break;
449         case_GETOPT_HELP_CHAR;
450         case_GETOPT_VERSION_CHAR (PROGRAM_NAME, AUTHORS);
451         default:
452           if (tabval == -1)
453             tabval = 0;
454           tabval = tabval * 10 + c - '0';
455           break;
456         }
457     }
458
459   if (convert_first_only)
460     convert_entire_line = 0;
461
462   add_tabstop (tabval);
463
464   validate_tabstops (tab_list, first_free_tab);
465
466   if (first_free_tab == 0)
467     tab_size = 8;
468   else if (first_free_tab == 1)
469     tab_size = tab_list[0];
470   else
471     {
472       /* Append a sentinel to the list of tab stop indices.  */
473       add_tabstop (TAB_STOP_SENTINEL);
474       tab_size = 0;
475     }
476
477   if (optind == argc)
478     file_list = stdin_argv;
479   else
480     file_list = &argv[optind];
481
482   unexpand ();
483
484   if (have_read_stdin && fclose (stdin) == EOF)
485     error (EXIT_FAILURE, errno, "-");
486   exit (exit_status == 0 ? EXIT_SUCCESS : EXIT_FAILURE);
487 }