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