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 source tree.
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 //config:config EXPAND
24 //config:	bool "expand (5.1 kb)"
25 //config:	default y
26 //config:	help
27 //config:	By default, convert all tabs to spaces.
28 //config:
29 //config:config UNEXPAND
30 //config:	bool "unexpand (5.3 kb)"
31 //config:	default y
32 //config:	help
33 //config:	By default, convert only leading sequences of blanks to tabs.
34 
35 //applet:IF_EXPAND(APPLET(expand, BB_DIR_USR_BIN, BB_SUID_DROP))
36 //                   APPLET_ODDNAME:name      main    location        suid_type     help
37 //applet:IF_UNEXPAND(APPLET_ODDNAME(unexpand, expand, BB_DIR_USR_BIN, BB_SUID_DROP, unexpand))
38 
39 //kbuild:lib-$(CONFIG_EXPAND) += expand.o
40 //kbuild:lib-$(CONFIG_UNEXPAND) += expand.o
41 
42 //usage:#define expand_trivial_usage
43 //usage:       "[-i] [-t N] [FILE]..."
44 //usage:#define expand_full_usage "\n\n"
45 //usage:       "Convert tabs to spaces, writing to stdout\n"
46 //usage:     "\n	-i	Don't convert tabs after non blanks"
47 //usage:     "\n	-t	Tabstops every N chars"
48 
49 //usage:#define unexpand_trivial_usage
50 //usage:       "[-fa][-t N] [FILE]..."
51 //usage:#define unexpand_full_usage "\n\n"
52 //usage:       "Convert spaces to tabs, writing to stdout\n"
53 //usage:     "\n	-a	Convert all blanks"
54 //usage:     "\n	-f	Convert only leading blanks"
55 //usage:     "\n	-t N	Tabstops every N chars"
56 
57 #include "libbb.h"
58 #include "unicode.h"
59 
60 enum {
61 	OPT_INITIAL     = 1 << 0,
62 	OPT_TABS        = 1 << 1,
63 	OPT_ALL         = 1 << 2,
64 };
65 
66 //FIXME: does not work properly with input containing NULs
67 //coreutils 8.30 preserves NULs but treats them as chars of width zero:
68 //AB<nul><tab>C will expand <tab> to 6 spaces, not 5.
69 
70 #if ENABLE_EXPAND
expand(FILE * file,unsigned tab_size,unsigned opt)71 static void expand(FILE *file, unsigned tab_size, unsigned opt)
72 {
73 
74 	for (;;) {
75 		char *line;
76 		char *ptr;
77 		char *ptr_strbeg;
78 //commented-out code handles NULs, +90 bytes of code, not tested much
79 //		size_t linelen;
80 //		unsigned len = 0;
81 
82 //		linelen = 1024 * 1024;
83 //		line = xmalloc_fgets_str_len(file, "\n", &linelen);
84 		line = xmalloc_fgets(file); //
85 		if (!line)
86 			break;
87 		ptr = ptr_strbeg = line;
88 		for (;;) {
89 			unsigned char c = *ptr;
90 			if (c == '\0') {
91 //				size_t rem = line + linelen - ptr;
92 //				if (rem > 0) {
93 //# if ENABLE_UNICODE_SUPPORT
94 //					len += unicode_strwidth(ptr_strbeg);
95 //# else
96 //					len += ptr - ptr_strbeg;
97 //# endif
98 //					printf("%s%c", ptr_strbeg, '\0');
99 //					memmove(ptr, ptr + 1, rem + 1);
100 //					ptr_strbeg = ptr;
101 //					linelen--;
102 //					continue;
103 //				}
104 				break;
105 			}
106 			if ((opt & OPT_INITIAL) && !isblank(c)) {
107 				/* not space or tab */
108 				break;
109 			}
110 			if (c == '\t') {
111 				unsigned len = 0; //
112 				*ptr = '\0';
113 # if ENABLE_UNICODE_SUPPORT
114 				len += unicode_strwidth(ptr_strbeg);
115 # else
116 				len += ptr - ptr_strbeg;
117 # endif
118 				len = tab_size - (len % tab_size);
119 				/*while (ptr[1] == '\t') { ptr++; len += tab_size; } - can handle many tabs at once */
120 				printf("%s%*s", ptr_strbeg, len, "");
121 //				len = 0;
122 				ptr_strbeg = ptr + 1;
123 			}
124 			ptr++;
125 		}
126 		fputs_stdout(ptr_strbeg);
127 		free(line);
128 	}
129 }
130 #endif
131 
132 #if ENABLE_UNEXPAND
unexpand(FILE * file,unsigned tab_size,unsigned opt)133 static void unexpand(FILE *file, unsigned tab_size, unsigned opt)
134 {
135 	char *line;
136 
137 	while ((line = xmalloc_fgets(file)) != NULL) {
138 		char *ptr = line;
139 		unsigned column = 0;
140 
141 		while (*ptr) {
142 			unsigned n;
143 			unsigned len = 0;
144 
145 			while (*ptr == ' ') {
146 				ptr++;
147 				len++;
148 			}
149 			column += len;
150 			if (*ptr == '\t') {
151 				column += tab_size - (column % tab_size);
152 				ptr++;
153 				continue;
154 			}
155 
156 			n = column / tab_size;
157 			if (n) {
158 				len = column = column % tab_size;
159 				while (n--)
160 					putchar('\t');
161 			}
162 
163 			if (!(opt & OPT_ALL) && ptr != line) {
164 				printf("%*s%s", len, "", ptr);
165 				break;
166 			}
167 			n = strcspn(ptr, "\t ");
168 			printf("%*s%.*s", len, "", n, ptr);
169 # if ENABLE_UNICODE_SUPPORT
170 			{
171 				char c = ptr[n];
172 				ptr[n] = '\0';
173 				len = unicode_strwidth(ptr);
174 				ptr[n] = c;
175 			}
176 # else
177 			len = n;
178 # endif
179 			ptr += n;
180 			column = (column + len) % tab_size;
181 		}
182 		free(line);
183 	}
184 }
185 #endif
186 
187 int expand_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
expand_main(int argc UNUSED_PARAM,char ** argv)188 int expand_main(int argc UNUSED_PARAM, char **argv)
189 {
190 	/* Default 8 spaces for 1 tab */
191 	const char *opt_t = "8";
192 	FILE *file;
193 	unsigned tab_size;
194 	unsigned opt;
195 	int exit_status = EXIT_SUCCESS;
196 
197 	init_unicode();
198 
199 	if (ENABLE_EXPAND && (!ENABLE_UNEXPAND || applet_name[0] == 'e')) {
200 		opt = getopt32long(argv, "it:",
201 				"initial\0"          No_argument       "i"
202 				"tabs\0"             Required_argument "t"
203 				, &opt_t
204 		);
205 	} else {
206 		opt = getopt32long(argv, "^"
207 				"ft:a"
208 				"\0"
209 				"ta" /* -t NUM sets -a */,
210 				"first-only\0"       No_argument       "f"
211 				"tabs\0"             Required_argument "t"
212 				"all\0"              No_argument       "a"
213 				, &opt_t
214 		);
215 		/* -t implies -a, but an explicit -f overrides */
216 		if (opt & OPT_INITIAL) opt &= ~OPT_ALL;
217 	}
218 	tab_size = xatou_range(opt_t, 1, UINT_MAX);
219 
220 	argv += optind;
221 
222 	if (!*argv) {
223 		*--argv = (char*)bb_msg_standard_input;
224 	}
225 	do {
226 		file = fopen_or_warn_stdin(*argv);
227 		if (!file) {
228 			exit_status = EXIT_FAILURE;
229 			continue;
230 		}
231 
232 		if (ENABLE_EXPAND && (!ENABLE_UNEXPAND || applet_name[0] == 'e'))
233 			IF_EXPAND(expand(file, tab_size, opt));
234 		else
235 			IF_UNEXPAND(unexpand(file, tab_size, opt));
236 
237 		/* Check and close the file */
238 		if (fclose_if_not_stdin(file)) {
239 			bb_simple_perror_msg(*argv);
240 			exit_status = EXIT_FAILURE;
241 		}
242 		/* If stdin also clear EOF */
243 		if (file == stdin)
244 			clearerr(file);
245 	} while (*++argv);
246 
247 	/* Now close stdin also */
248 	/* (if we didn't read from it, it's a no-op) */
249 	if (fclose(stdin))
250 		bb_simple_perror_msg_and_die(bb_msg_standard_input);
251 
252 	fflush_stdout_and_exit(exit_status);
253 }
254