maint: remove unnecessary inttostr usage in printf
[coreutils.git] / src / unexpand.c
blobaca67dd7389f3b3d28439455afdea28fc101bb40
1 /* unexpand - convert blanks to tabs
2 Copyright (C) 1989-2024 Free Software Foundation, Inc.
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.
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.
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/>. */
17 /* By default, convert only maximal strings of initial blanks and tabs
18 into tabs.
19 Preserves backspace characters in the output; they decrement the
20 column count for tab calculations.
21 The default action is equivalent to -8.
23 Options:
24 --tabs=tab1[,tab2[,...]]
25 -t tab1[,tab2[,...]]
26 -tab1[,tab2[,...]] If only one tab stop is given, set the tabs tab1
27 columns apart instead of the default 8. Otherwise,
28 set the tabs at columns tab1, tab2, etc. (numbered from
29 0); preserve any blanks beyond the tab stops given.
30 --all
31 -a Use tabs wherever they would replace 2 or more blanks,
32 not just at the beginnings of lines.
34 David MacKenzie <djm@gnu.ai.mit.edu> */
36 #include <config.h>
38 #include <ctype.h>
39 #include <stdio.h>
40 #include <getopt.h>
41 #include <sys/types.h>
42 #include "system.h"
43 #include "expand-common.h"
45 /* The official name of this program (e.g., no 'g' prefix). */
46 #define PROGRAM_NAME "unexpand"
48 #define AUTHORS proper_name ("David MacKenzie")
52 /* For long options that have no equivalent short option, use a
53 non-character as a pseudo short option, starting with CHAR_MAX + 1. */
54 enum
56 CONVERT_FIRST_ONLY_OPTION = CHAR_MAX + 1
59 static struct option const longopts[] =
61 {"tabs", required_argument, nullptr, 't'},
62 {"all", no_argument, nullptr, 'a'},
63 {"first-only", no_argument, nullptr, CONVERT_FIRST_ONLY_OPTION},
64 {GETOPT_HELP_OPTION_DECL},
65 {GETOPT_VERSION_OPTION_DECL},
66 {nullptr, 0, nullptr, 0}
69 void
70 usage (int status)
72 if (status != EXIT_SUCCESS)
73 emit_try_help ();
74 else
76 printf (_("\
77 Usage: %s [OPTION]... [FILE]...\n\
78 "),
79 program_name);
80 fputs (_("\
81 Convert blanks in each FILE to tabs, writing to standard output.\n\
82 "), stdout);
84 emit_stdin_note ();
85 emit_mandatory_arg_note ();
87 fputs (_("\
88 -a, --all convert all blanks, instead of just initial blanks\n\
89 --first-only convert only leading sequences of blanks (overrides -a)\n\
90 -t, --tabs=N have tabs N characters apart instead of 8 (enables -a)\n\
91 "), stdout);
92 emit_tab_list_info ();
93 fputs (HELP_OPTION_DESCRIPTION, stdout);
94 fputs (VERSION_OPTION_DESCRIPTION, stdout);
95 emit_ancillary_info (PROGRAM_NAME);
97 exit (status);
100 /* Change blanks to tabs, writing to stdout.
101 Read each file in 'file_list', in order. */
103 static void
104 unexpand (void)
106 /* Input stream. */
107 FILE *fp = next_file (nullptr);
109 /* The array of pending blanks. In non-POSIX locales, blanks can
110 include characters other than spaces, so the blanks must be
111 stored, not merely counted. */
112 char *pending_blank;
114 if (!fp)
115 return;
117 /* The worst case is a non-blank character, then one blank, then a
118 tab stop, then MAX_COLUMN_WIDTH - 1 blanks, then a non-blank; so
119 allocate MAX_COLUMN_WIDTH bytes to store the blanks. */
120 pending_blank = xmalloc (max_column_width);
122 while (true)
124 /* Input character, or EOF. */
125 int c;
127 /* If true, perform translations. */
128 bool convert = true;
131 /* The following variables have valid values only when CONVERT
132 is true: */
134 /* Column of next input character. */
135 uintmax_t column = 0;
137 /* Column the next input tab stop is on. */
138 uintmax_t next_tab_column = 0;
140 /* Index in TAB_LIST of next tab stop to examine. */
141 size_t tab_index = 0;
143 /* If true, the first pending blank came just before a tab stop. */
144 bool one_blank_before_tab_stop = false;
146 /* If true, the previous input character was a blank. This is
147 initially true, since initial strings of blanks are treated
148 as if the line was preceded by a blank. */
149 bool prev_blank = true;
151 /* Number of pending columns of blanks. */
152 size_t pending = 0;
155 /* Convert a line of text. */
159 while ((c = getc (fp)) < 0 && (fp = next_file (fp)))
160 continue;
162 if (convert)
164 bool blank = !! isblank (c);
166 if (blank)
168 bool last_tab;
170 next_tab_column = get_next_tab_column (column, &tab_index,
171 &last_tab);
173 if (last_tab)
174 convert = false;
176 if (convert)
178 if (next_tab_column < column)
179 error (EXIT_FAILURE, 0, _("input line is too long"));
181 if (c == '\t')
183 column = next_tab_column;
185 if (pending)
186 pending_blank[0] = '\t';
188 else
190 column++;
192 if (! (prev_blank && column == next_tab_column))
194 /* It is not yet known whether the pending blanks
195 will be replaced by tabs. */
196 if (column == next_tab_column)
197 one_blank_before_tab_stop = true;
198 pending_blank[pending++] = c;
199 prev_blank = true;
200 continue;
203 /* Replace the pending blanks by a tab or two. */
204 pending_blank[0] = c = '\t';
207 /* Discard pending blanks, unless it was a single
208 blank just before the previous tab stop. */
209 pending = one_blank_before_tab_stop;
212 else if (c == '\b')
214 /* Go back one column, and force recalculation of the
215 next tab stop. */
216 column -= !!column;
217 next_tab_column = column;
218 tab_index -= !!tab_index;
220 else
222 column++;
223 if (!column)
224 error (EXIT_FAILURE, 0, _("input line is too long"));
227 if (pending)
229 if (pending > 1 && one_blank_before_tab_stop)
230 pending_blank[0] = '\t';
231 if (fwrite (pending_blank, 1, pending, stdout) != pending)
232 write_error ();
233 pending = 0;
234 one_blank_before_tab_stop = false;
237 prev_blank = blank;
238 convert &= convert_entire_line || blank;
241 if (c < 0)
243 free (pending_blank);
244 return;
247 if (putchar (c) < 0)
248 write_error ();
250 while (c != '\n');
255 main (int argc, char **argv)
257 bool have_tabval = false;
258 uintmax_t tabval IF_LINT ( = 0);
259 int c;
261 /* If true, cancel the effect of any -a (explicit or implicit in -t),
262 so that only leading blanks will be considered. */
263 bool convert_first_only = false;
265 initialize_main (&argc, &argv);
266 set_program_name (argv[0]);
267 setlocale (LC_ALL, "");
268 bindtextdomain (PACKAGE, LOCALEDIR);
269 textdomain (PACKAGE);
271 atexit (close_stdout);
273 while ((c = getopt_long (argc, argv, ",0123456789at:", longopts, nullptr))
274 != -1)
276 switch (c)
278 case '?':
279 usage (EXIT_FAILURE);
280 case 'a':
281 convert_entire_line = true;
282 break;
283 case 't':
284 convert_entire_line = true;
285 parse_tab_stops (optarg);
286 break;
287 case CONVERT_FIRST_ONLY_OPTION:
288 convert_first_only = true;
289 break;
290 case ',':
291 if (have_tabval)
292 add_tab_stop (tabval);
293 have_tabval = false;
294 break;
295 case_GETOPT_HELP_CHAR;
296 case_GETOPT_VERSION_CHAR (PROGRAM_NAME, AUTHORS);
297 default:
298 if (!have_tabval)
300 tabval = 0;
301 have_tabval = true;
303 if (!DECIMAL_DIGIT_ACCUMULATE (tabval, c - '0'))
304 error (EXIT_FAILURE, 0, _("tab stop value is too large"));
305 break;
309 if (convert_first_only)
310 convert_entire_line = false;
312 if (have_tabval)
313 add_tab_stop (tabval);
315 finalize_tab_stops ();
317 set_file_list ((optind < argc) ? &argv[optind] : nullptr);
319 unexpand ();
321 cleanup_file_list_stdin ();
323 return exit_status;