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