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