1/* expand - convert tabs to spaces
2 * unexpand - convert spaces to tabs
3 *
4 * Copyright (C) 89, 91, 1995-2006 Free Software Foundation, Inc.
5 *
6 * Licensed under GPLv2 or later, see file LICENSE in this tarball for details.
7 *
8 * David MacKenzie <djm@gnu.ai.mit.edu>
9 *
10 * Options for expand:
11 * -t num  --tabs=NUM      Convert tabs to num spaces (default 8 spaces).
12 * -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).
17 * -t num  --tabs=NUM      Have tabs num characters apart instead of 8.
18 *
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 */
23#include "libbb.h"
24#include "unicode.h"
25
26enum {
27	OPT_INITIAL     = 1 << 0,
28	OPT_TABS        = 1 << 1,
29	OPT_ALL         = 1 << 2,
30};
31
32#if ENABLE_EXPAND
33static void expand(FILE *file, unsigned tab_size, unsigned opt)
34{
35	char *line;
36
37	while ((line = xmalloc_fgets(file)) != NULL) {
38		unsigned char c;
39		char *ptr;
40		char *ptr_strbeg;
41
42		ptr = ptr_strbeg = line;
43		while ((c = *ptr) != '\0') {
44			if ((opt & OPT_INITIAL) && !isblank(c)) {
45				/* not space or tab */
46				break;
47			}
48			if (c == '\t') {
49				unsigned len;
50				*ptr = '\0';
51# if ENABLE_UNICODE_SUPPORT
52				{
53					uni_stat_t uni_stat;
54					printable_string(&uni_stat, ptr_strbeg);
55					len = uni_stat.unicode_width;
56				}
57# else
58				len = ptr - ptr_strbeg;
59# endif
60				len = tab_size - (len % tab_size);
61				/*while (ptr[1] == '\t') { ptr++; len += tab_size; } - can handle many tabs at once */
62				printf("%s%*s", ptr_strbeg, len, "");
63				ptr_strbeg = ptr + 1;
64			}
65			ptr++;
66		}
67		fputs(ptr_strbeg, stdout);
68		free(line);
69	}
70}
71#endif
72
73#if ENABLE_UNEXPAND
74static void unexpand(FILE *file, unsigned tab_size, unsigned opt)
75{
76	char *line;
77
78	while ((line = xmalloc_fgets(file)) != NULL) {
79		char *ptr = line;
80		unsigned column = 0;
81
82		while (*ptr) {
83			unsigned n;
84			unsigned len = 0;
85
86			while (*ptr == ' ') {
87				ptr++;
88				len++;
89			}
90			column += len;
91			if (*ptr == '\t') {
92				column += tab_size - (column % tab_size);
93				ptr++;
94				continue;
95			}
96
97			n = column / tab_size;
98			if (n) {
99				len = column = column % tab_size;
100				while (n--)
101					putchar('\t');
102			}
103
104			if ((opt & OPT_INITIAL) && ptr != line) {
105				printf("%*s%s", len, "", ptr);
106				break;
107			}
108			n = strcspn(ptr, "\t ");
109			printf("%*s%.*s", len, "", n, ptr);
110# if ENABLE_UNICODE_SUPPORT
111			{
112				char c;
113				uni_stat_t uni_stat;
114				c = ptr[n];
115				ptr[n] = '\0';
116				printable_string(&uni_stat, ptr);
117				len = uni_stat.unicode_width;
118				ptr[n] = c;
119			}
120# else
121			len = n;
122# endif
123			ptr += n;
124			column = (column + len) % tab_size;
125		}
126		free(line);
127	}
128}
129#endif
130
131int expand_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
132int expand_main(int argc UNUSED_PARAM, char **argv)
133{
134	/* Default 8 spaces for 1 tab */
135	const char *opt_t = "8";
136	FILE *file;
137	unsigned tab_size;
138	unsigned opt;
139	int exit_status = EXIT_SUCCESS;
140
141#if ENABLE_FEATURE_EXPAND_LONG_OPTIONS
142	static const char expand_longopts[] ALIGN1 =
143		/* name, has_arg, val */
144		"initial\0"          No_argument       "i"
145		"tabs\0"             Required_argument "t"
146	;
147#endif
148#if ENABLE_FEATURE_UNEXPAND_LONG_OPTIONS
149	static const char unexpand_longopts[] ALIGN1 =
150		/* name, has_arg, val */
151		"first-only\0"       No_argument       "i"
152		"tabs\0"             Required_argument "t"
153		"all\0"              No_argument       "a"
154	;
155#endif
156	init_unicode();
157
158	if (ENABLE_EXPAND && (!ENABLE_UNEXPAND || applet_name[0] == 'e')) {
159		IF_FEATURE_EXPAND_LONG_OPTIONS(applet_long_options = expand_longopts);
160		opt = getopt32(argv, "it:", &opt_t);
161	} else {
162		IF_FEATURE_UNEXPAND_LONG_OPTIONS(applet_long_options = unexpand_longopts);
163		/* -t NUM sets also -a */
164		opt_complementary = "ta";
165		opt = getopt32(argv, "ft:a", &opt_t);
166		/* -f --first-only is the default */
167		if (!(opt & OPT_ALL)) opt |= OPT_INITIAL;
168	}
169	tab_size = xatou_range(opt_t, 1, UINT_MAX);
170
171	argv += optind;
172
173	if (!*argv) {
174		*--argv = (char*)bb_msg_standard_input;
175	}
176	do {
177		file = fopen_or_warn_stdin(*argv);
178		if (!file) {
179			exit_status = EXIT_FAILURE;
180			continue;
181		}
182
183		if (ENABLE_EXPAND && (!ENABLE_UNEXPAND || applet_name[0] == 'e'))
184			IF_EXPAND(expand(file, tab_size, opt));
185		else
186			IF_UNEXPAND(unexpand(file, tab_size, opt));
187
188		/* Check and close the file */
189		if (fclose_if_not_stdin(file)) {
190			bb_simple_perror_msg(*argv);
191			exit_status = EXIT_FAILURE;
192		}
193		/* If stdin also clear EOF */
194		if (file == stdin)
195			clearerr(file);
196	} while (*++argv);
197
198	/* Now close stdin also */
199	/* (if we didn't read from it, it's a no-op) */
200	if (fclose(stdin))
201		bb_perror_msg_and_die(bb_msg_standard_input);
202
203	fflush_stdout_and_exit(exit_status);
204}
205