blob: 9ce86ebff891bbd259596b4e36147f2c4cbc341b [file] [log] [blame]
Denis Vlasenko3952f202007-08-13 14:10:24 +00001/* expand - convert tabs to spaces
2 * unexpand - convert spaces to tabs
3 *
4 * Copyright (C) 89, 91, 1995-2006 Free Software Foundation, Inc.
5 *
Denys Vlasenko0ef64bd2010-08-16 20:14:46 +02006 * Licensed under GPLv2 or later, see file LICENSE in this source tree.
Denis Vlasenko3952f202007-08-13 14:10:24 +00007 *
8 * David MacKenzie <djm@gnu.ai.mit.edu>
9 *
10 * Options for expand:
Denys Vlasenkobbc7bee2017-01-21 02:49:58 +010011 * -t num --tabs NUM Convert tabs to num spaces (default 8 spaces).
Denis Vlasenko3952f202007-08-13 14:10:24 +000012 * -i --initial Only convert initial tabs on each line to spaces.
13 *
14 * Options for unexpand:
15 * -a --all Convert all blanks, instead of just initial blanks.
16 * -f --first-only Convert only leading sequences of blanks (default).
Denys Vlasenkobbc7bee2017-01-21 02:49:58 +010017 * -t num --tabs NUM Have tabs num characters apart instead of 8.
Denis Vlasenko3952f202007-08-13 14:10:24 +000018 *
19 * Busybox version (C) 2007 by Tito Ragusa <farmatito@tiscali.it>
20 *
21 * Caveat: this versions of expand and unexpand don't accept tab lists.
22 */
Denys Vlasenkoaf3f4202016-11-23 14:46:56 +010023//config:config EXPAND
24//config: bool "expand"
25//config: default y
26//config: help
27//config: By default, convert all tabs to spaces.
28//config:
29//config:config FEATURE_EXPAND_LONG_OPTIONS
30//config: bool "Enable long options"
31//config: default y
32//config: depends on EXPAND && LONG_OPTS
Denys Vlasenkoaf3f4202016-11-23 14:46:56 +010033//config:
34//config:config UNEXPAND
35//config: bool "unexpand"
36//config: default y
37//config: help
38//config: By default, convert only leading sequences of blanks to tabs.
39//config:
40//config:config FEATURE_UNEXPAND_LONG_OPTIONS
41//config: bool "Enable long options"
42//config: default y
43//config: depends on UNEXPAND && LONG_OPTS
Denys Vlasenkoaf3f4202016-11-23 14:46:56 +010044
45//applet:IF_EXPAND(APPLET(expand, BB_DIR_USR_BIN, BB_SUID_DROP))
Denys Vlasenko205d48e2017-01-29 14:57:33 +010046// APPLET_ODDNAME:name main location suid_type help
Denys Vlasenkoaf3f4202016-11-23 14:46:56 +010047//applet:IF_UNEXPAND(APPLET_ODDNAME(unexpand, expand, BB_DIR_USR_BIN, BB_SUID_DROP, unexpand))
48
49//kbuild:lib-$(CONFIG_EXPAND) += expand.o
50//kbuild:lib-$(CONFIG_UNEXPAND) += expand.o
Pere Orga34425382011-03-31 14:43:25 +020051
52//usage:#define expand_trivial_usage
53//usage: "[-i] [-t N] [FILE]..."
54//usage:#define expand_full_usage "\n\n"
55//usage: "Convert tabs to spaces, writing to stdout\n"
Pere Orga34425382011-03-31 14:43:25 +020056//usage: IF_FEATURE_EXPAND_LONG_OPTIONS(
57//usage: "\n -i,--initial Don't convert tabs after non blanks"
Denys Vlasenkobbc7bee2017-01-21 02:49:58 +010058//usage: "\n -t,--tabs N Tabstops every N chars"
Pere Orga34425382011-03-31 14:43:25 +020059//usage: )
60//usage: IF_NOT_FEATURE_EXPAND_LONG_OPTIONS(
61//usage: "\n -i Don't convert tabs after non blanks"
62//usage: "\n -t Tabstops every N chars"
63//usage: )
64
65//usage:#define unexpand_trivial_usage
66//usage: "[-fa][-t N] [FILE]..."
67//usage:#define unexpand_full_usage "\n\n"
68//usage: "Convert spaces to tabs, writing to stdout\n"
Pere Orga34425382011-03-31 14:43:25 +020069//usage: IF_FEATURE_UNEXPAND_LONG_OPTIONS(
70//usage: "\n -a,--all Convert all blanks"
71//usage: "\n -f,--first-only Convert only leading blanks"
Denys Vlasenkobbc7bee2017-01-21 02:49:58 +010072//usage: "\n -t,--tabs N Tabstops every N chars"
Pere Orga34425382011-03-31 14:43:25 +020073//usage: )
74//usage: IF_NOT_FEATURE_UNEXPAND_LONG_OPTIONS(
75//usage: "\n -a Convert all blanks"
76//usage: "\n -f Convert only leading blanks"
77//usage: "\n -t N Tabstops every N chars"
78//usage: )
79
Denis Vlasenko3952f202007-08-13 14:10:24 +000080#include "libbb.h"
Tomas Heinrichd2b1ba62010-01-04 16:21:31 +010081#include "unicode.h"
Denis Vlasenko3952f202007-08-13 14:10:24 +000082
83enum {
84 OPT_INITIAL = 1 << 0,
85 OPT_TABS = 1 << 1,
86 OPT_ALL = 1 << 2,
87};
88
Denis Vlasenko3952f202007-08-13 14:10:24 +000089#if ENABLE_EXPAND
Tomas Heinrichd2b1ba62010-01-04 16:21:31 +010090static void expand(FILE *file, unsigned tab_size, unsigned opt)
Denis Vlasenko3952f202007-08-13 14:10:24 +000091{
92 char *line;
Denis Vlasenko3952f202007-08-13 14:10:24 +000093
Denis Vlasenko3952f202007-08-13 14:10:24 +000094 while ((line = xmalloc_fgets(file)) != NULL) {
Denis Vlasenko3139ea72008-12-14 15:45:25 +000095 unsigned char c;
Tomas Heinrichd2b1ba62010-01-04 16:21:31 +010096 char *ptr;
97 char *ptr_strbeg;
Denis Vlasenko3139ea72008-12-14 15:45:25 +000098
Tomas Heinrichd2b1ba62010-01-04 16:21:31 +010099 ptr = ptr_strbeg = line;
Denis Vlasenko3139ea72008-12-14 15:45:25 +0000100 while ((c = *ptr) != '\0') {
101 if ((opt & OPT_INITIAL) && !isblank(c)) {
Tomas Heinrichd2b1ba62010-01-04 16:21:31 +0100102 /* not space or tab */
Denis Vlasenko3139ea72008-12-14 15:45:25 +0000103 break;
Denis Vlasenko3952f202007-08-13 14:10:24 +0000104 }
Denis Vlasenko3139ea72008-12-14 15:45:25 +0000105 if (c == '\t') {
Tomas Heinrichd2b1ba62010-01-04 16:21:31 +0100106 unsigned len;
107 *ptr = '\0';
Denys Vlasenko19158a82010-03-26 14:06:56 +0100108# if ENABLE_UNICODE_SUPPORT
Michael Tokarev22bb81f2013-12-09 16:09:35 +0400109 len = unicode_strwidth(ptr_strbeg);
Tomas Heinrichd2b1ba62010-01-04 16:21:31 +0100110# else
111 len = ptr - ptr_strbeg;
112# endif
113 len = tab_size - (len % tab_size);
114 /*while (ptr[1] == '\t') { ptr++; len += tab_size; } - can handle many tabs at once */
115 printf("%s%*s", ptr_strbeg, len, "");
116 ptr_strbeg = ptr + 1;
Denis Vlasenko3952f202007-08-13 14:10:24 +0000117 }
Tomas Heinrichd2b1ba62010-01-04 16:21:31 +0100118 ptr++;
Denis Vlasenko3952f202007-08-13 14:10:24 +0000119 }
Tomas Heinrichd2b1ba62010-01-04 16:21:31 +0100120 fputs(ptr_strbeg, stdout);
Denis Vlasenko3139ea72008-12-14 15:45:25 +0000121 free(line);
Denis Vlasenko3952f202007-08-13 14:10:24 +0000122 }
123}
124#endif
125
126#if ENABLE_UNEXPAND
Denis Vlasenko3139ea72008-12-14 15:45:25 +0000127static void unexpand(FILE *file, unsigned tab_size, unsigned opt)
Denis Vlasenko3952f202007-08-13 14:10:24 +0000128{
129 char *line;
Denis Vlasenko3952f202007-08-13 14:10:24 +0000130
131 while ((line = xmalloc_fgets(file)) != NULL) {
Denis Vlasenkoe40e76f2008-12-26 14:56:03 +0000132 char *ptr = line;
133 unsigned column = 0;
134
135 while (*ptr) {
136 unsigned n;
Tomas Heinrich968951f2010-03-26 09:46:07 +0100137 unsigned len = 0;
Denis Vlasenkoe40e76f2008-12-26 14:56:03 +0000138
139 while (*ptr == ' ') {
Denis Vlasenkoe40e76f2008-12-26 14:56:03 +0000140 ptr++;
Tomas Heinrich968951f2010-03-26 09:46:07 +0100141 len++;
Denis Vlasenko3952f202007-08-13 14:10:24 +0000142 }
Tomas Heinrich968951f2010-03-26 09:46:07 +0100143 column += len;
Denis Vlasenkoe40e76f2008-12-26 14:56:03 +0000144 if (*ptr == '\t') {
145 column += tab_size - (column % tab_size);
146 ptr++;
147 continue;
Denis Vlasenko3952f202007-08-13 14:10:24 +0000148 }
Denis Vlasenkoe40e76f2008-12-26 14:56:03 +0000149
150 n = column / tab_size;
Tomas Heinrich968951f2010-03-26 09:46:07 +0100151 if (n) {
152 len = column = column % tab_size;
153 while (n--)
154 putchar('\t');
155 }
Denis Vlasenkoe40e76f2008-12-26 14:56:03 +0000156
157 if ((opt & OPT_INITIAL) && ptr != line) {
Tomas Heinrich968951f2010-03-26 09:46:07 +0100158 printf("%*s%s", len, "", ptr);
Denis Vlasenkoe40e76f2008-12-26 14:56:03 +0000159 break;
160 }
161 n = strcspn(ptr, "\t ");
Tomas Heinrich968951f2010-03-26 09:46:07 +0100162 printf("%*s%.*s", len, "", n, ptr);
Denys Vlasenko19158a82010-03-26 14:06:56 +0100163# if ENABLE_UNICODE_SUPPORT
Tomas Heinrichd2b1ba62010-01-04 16:21:31 +0100164 {
Michael Tokarev22bb81f2013-12-09 16:09:35 +0400165 char c = ptr[n];
Tomas Heinrichd2b1ba62010-01-04 16:21:31 +0100166 ptr[n] = '\0';
Michael Tokarev22bb81f2013-12-09 16:09:35 +0400167 len = unicode_strwidth(ptr);
Tomas Heinrichd2b1ba62010-01-04 16:21:31 +0100168 ptr[n] = c;
169 }
170# else
171 len = n;
172# endif
Denis Vlasenkoe40e76f2008-12-26 14:56:03 +0000173 ptr += n;
Tomas Heinrichd2b1ba62010-01-04 16:21:31 +0100174 column = (column + len) % tab_size;
Denis Vlasenko3952f202007-08-13 14:10:24 +0000175 }
Denis Vlasenkoe40e76f2008-12-26 14:56:03 +0000176 free(line);
Denis Vlasenko3952f202007-08-13 14:10:24 +0000177 }
178}
179#endif
180
Denis Vlasenko9b49a5e2007-10-11 10:05:36 +0000181int expand_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
Denis Vlasenkoa60f84e2008-07-05 09:18:54 +0000182int expand_main(int argc UNUSED_PARAM, char **argv)
Denis Vlasenko3952f202007-08-13 14:10:24 +0000183{
184 /* Default 8 spaces for 1 tab */
185 const char *opt_t = "8";
186 FILE *file;
187 unsigned tab_size;
188 unsigned opt;
189 int exit_status = EXIT_SUCCESS;
190
191#if ENABLE_FEATURE_EXPAND_LONG_OPTIONS
192 static const char expand_longopts[] ALIGN1 =
193 /* name, has_arg, val */
194 "initial\0" No_argument "i"
195 "tabs\0" Required_argument "t"
196 ;
197#endif
198#if ENABLE_FEATURE_UNEXPAND_LONG_OPTIONS
199 static const char unexpand_longopts[] ALIGN1 =
200 /* name, has_arg, val */
201 "first-only\0" No_argument "i"
202 "tabs\0" Required_argument "t"
203 "all\0" No_argument "a"
204 ;
205#endif
Denys Vlasenko28055022010-01-04 20:49:58 +0100206 init_unicode();
Denis Vlasenko3952f202007-08-13 14:10:24 +0000207
208 if (ENABLE_EXPAND && (!ENABLE_UNEXPAND || applet_name[0] == 'e')) {
Denis Vlasenko5e34ff22009-04-21 11:09:40 +0000209 IF_FEATURE_EXPAND_LONG_OPTIONS(applet_long_options = expand_longopts);
Denis Vlasenkofe7cd642007-08-18 15:32:12 +0000210 opt = getopt32(argv, "it:", &opt_t);
Denis Vlasenko62a90cd2008-03-17 09:07:36 +0000211 } else {
Denis Vlasenko5e34ff22009-04-21 11:09:40 +0000212 IF_FEATURE_UNEXPAND_LONG_OPTIONS(applet_long_options = unexpand_longopts);
Denis Vlasenko3952f202007-08-13 14:10:24 +0000213 /* -t NUM sets also -a */
214 opt_complementary = "ta";
Denis Vlasenkofe7cd642007-08-18 15:32:12 +0000215 opt = getopt32(argv, "ft:a", &opt_t);
Denis Vlasenko3952f202007-08-13 14:10:24 +0000216 /* -f --first-only is the default */
217 if (!(opt & OPT_ALL)) opt |= OPT_INITIAL;
218 }
219 tab_size = xatou_range(opt_t, 1, UINT_MAX);
220
221 argv += optind;
222
Denis Vlasenko3952f202007-08-13 14:10:24 +0000223 if (!*argv) {
224 *--argv = (char*)bb_msg_standard_input;
Denis Vlasenko3952f202007-08-13 14:10:24 +0000225 }
Denis Vlasenko3952f202007-08-13 14:10:24 +0000226 do {
Denis Vlasenko62a90cd2008-03-17 09:07:36 +0000227 file = fopen_or_warn_stdin(*argv);
228 if (!file) {
229 exit_status = EXIT_FAILURE;
230 continue;
Denis Vlasenko3952f202007-08-13 14:10:24 +0000231 }
232
233 if (ENABLE_EXPAND && (!ENABLE_UNEXPAND || applet_name[0] == 'e'))
Denis Vlasenko5e34ff22009-04-21 11:09:40 +0000234 IF_EXPAND(expand(file, tab_size, opt));
Denis Vlasenko62a90cd2008-03-17 09:07:36 +0000235 else
Denis Vlasenko5e34ff22009-04-21 11:09:40 +0000236 IF_UNEXPAND(unexpand(file, tab_size, opt));
Denis Vlasenko3952f202007-08-13 14:10:24 +0000237
238 /* Check and close the file */
Denis Vlasenko62a90cd2008-03-17 09:07:36 +0000239 if (fclose_if_not_stdin(file)) {
Denis Vlasenko0c97c9d2007-10-01 11:58:38 +0000240 bb_simple_perror_msg(*argv);
Denis Vlasenko3952f202007-08-13 14:10:24 +0000241 exit_status = EXIT_FAILURE;
242 }
243 /* If stdin also clear EOF */
Denis Vlasenko6a2f7f42007-08-16 10:35:17 +0000244 if (file == stdin)
Denis Vlasenko3952f202007-08-13 14:10:24 +0000245 clearerr(file);
246 } while (*++argv);
247
248 /* Now close stdin also */
249 /* (if we didn't read from it, it's a no-op) */
250 if (fclose(stdin))
251 bb_perror_msg_and_die(bb_msg_standard_input);
252
253 fflush_stdout_and_exit(exit_status);
254}