1 /* expand - convert tabs to spaces
2    Copyright (C) 1989-2023 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 3 of the License, or
7    (at your option) 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, see <https://www.gnu.org/licenses/>.  */
16 
17 /* By default, convert all tabs to spaces.
18    Preserves backspace characters in the output; they decrement the
19    column count for tab calculations.
20    The default action is equivalent to -8.
21 
22    Options:
23    --tabs=tab1[,tab2[,...]]
24    -t tab1[,tab2[,...]]
25    -tab1[,tab2[,...]]	If only one tab stop is given, set the tabs tab1
26                         columns apart instead of the default 8.  Otherwise,
27                         set the tabs at columns tab1, tab2, etc. (numbered from
28                         0); replace any tabs beyond the tab stops given with
29                         single spaces.
30    --initial
31    -i			Only convert initial tabs on each line to spaces.
32 
33    David MacKenzie <djm@gnu.ai.mit.edu> */
34 
35 #include <config.h>
36 
37 #include <stdio.h>
38 #include <getopt.h>
39 #include <sys/types.h>
40 #include "system.h"
41 #include "expand-common.h"
42 
43 /* The official name of this program (e.g., no 'g' prefix).  */
44 #define PROGRAM_NAME "expand"
45 
46 #define AUTHORS proper_name ("David MacKenzie")
47 
48 static char const shortopts[] = "it:0::1::2::3::4::5::6::7::8::9::";
49 
50 static struct option const longopts[] =
51 {
52   {"tabs", required_argument, nullptr, 't'},
53   {"initial", no_argument, nullptr, 'i'},
54   {GETOPT_HELP_OPTION_DECL},
55   {GETOPT_VERSION_OPTION_DECL},
56   {nullptr, 0, nullptr, 0}
57 };
58 
59 void
usage(int status)60 usage (int status)
61 {
62   if (status != EXIT_SUCCESS)
63     emit_try_help ();
64   else
65     {
66       printf (_("\
67 Usage: %s [OPTION]... [FILE]...\n\
68 "),
69               program_name);
70       fputs (_("\
71 Convert tabs in each FILE to spaces, writing to standard output.\n\
72 "), stdout);
73 
74       emit_stdin_note ();
75       emit_mandatory_arg_note ();
76 
77       fputs (_("\
78   -i, --initial    do not convert tabs after non blanks\n\
79   -t, --tabs=N     have tabs N characters apart, not 8\n\
80 "), stdout);
81       emit_tab_list_info ();
82       fputs (HELP_OPTION_DESCRIPTION, stdout);
83       fputs (VERSION_OPTION_DESCRIPTION, stdout);
84       emit_ancillary_info (PROGRAM_NAME);
85     }
86   exit (status);
87 }
88 
89 
90 /* Change tabs to spaces, writing to stdout.
91    Read each file in 'file_list', in order.  */
92 
93 static void
expand(void)94 expand (void)
95 {
96   /* Input stream.  */
97   FILE *fp = next_file (nullptr);
98 
99   if (!fp)
100     return;
101 
102   while (true)
103     {
104       /* Input character, or EOF.  */
105       int c;
106 
107       /* If true, perform translations.  */
108       bool convert = true;
109 
110 
111       /* The following variables have valid values only when CONVERT
112          is true:  */
113 
114       /* Column of next input character.  */
115       uintmax_t column = 0;
116 
117       /* Index in TAB_LIST of next tab stop to examine.  */
118       size_t tab_index = 0;
119 
120 
121       /* Convert a line of text.  */
122 
123       do
124         {
125           while ((c = getc (fp)) < 0 && (fp = next_file (fp)))
126             continue;
127 
128           if (convert)
129             {
130               if (c == '\t')
131                 {
132                   /* Column the next input tab stop is on.  */
133                   uintmax_t next_tab_column;
134                   bool last_tab;
135 
136                   next_tab_column = get_next_tab_column (column, &tab_index,
137                                                          &last_tab);
138 
139                   if (last_tab)
140                     next_tab_column = column + 1;
141 
142                   if (next_tab_column < column)
143                     error (EXIT_FAILURE, 0, _("input line is too long"));
144 
145                   while (++column < next_tab_column)
146                     if (putchar (' ') < 0)
147                       write_error ();
148 
149                   c = ' ';
150                 }
151               else if (c == '\b')
152                 {
153                   /* Go back one column, and force recalculation of the
154                      next tab stop.  */
155                   column -= !!column;
156                   tab_index -= !!tab_index;
157                 }
158               else
159                 {
160                   column++;
161                   if (!column)
162                     error (EXIT_FAILURE, 0, _("input line is too long"));
163                 }
164 
165               convert &= convert_entire_line || !! isblank (c);
166             }
167 
168           if (c < 0)
169             return;
170 
171           if (putchar (c) < 0)
172             write_error ();
173         }
174       while (c != '\n');
175     }
176 }
177 
178 int
main(int argc,char ** argv)179 main (int argc, char **argv)
180 {
181   int c;
182 
183   initialize_main (&argc, &argv);
184   set_program_name (argv[0]);
185   setlocale (LC_ALL, "");
186   bindtextdomain (PACKAGE, LOCALEDIR);
187   textdomain (PACKAGE);
188 
189   atexit (close_stdout);
190   convert_entire_line = true;
191 
192   while ((c = getopt_long (argc, argv, shortopts, longopts, nullptr)) != -1)
193     {
194       switch (c)
195         {
196         case 'i':
197           convert_entire_line = false;
198           break;
199 
200         case 't':
201           parse_tab_stops (optarg);
202           break;
203 
204         case '0': case '1': case '2': case '3': case '4':
205         case '5': case '6': case '7': case '8': case '9':
206           if (optarg)
207             parse_tab_stops (optarg - 1);
208           else
209             {
210               char tab_stop[2];
211               tab_stop[0] = c;
212               tab_stop[1] = '\0';
213               parse_tab_stops (tab_stop);
214             }
215           break;
216 
217         case_GETOPT_HELP_CHAR;
218 
219         case_GETOPT_VERSION_CHAR (PROGRAM_NAME, AUTHORS);
220 
221         default:
222           usage (EXIT_FAILURE);
223         }
224     }
225 
226   finalize_tab_stops ();
227 
228   set_file_list (optind < argc ? &argv[optind] : nullptr);
229 
230   expand ();
231 
232   cleanup_file_list_stdin ();
233 
234   return exit_status;
235 }
236