blob: a7ac8ea84acc58b0e9e74acbc5ebe5fb0b1d12c7 [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 *
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
24#include "libbb.h"
25
26enum {
27 OPT_INITIAL = 1 << 0,
28 OPT_TABS = 1 << 1,
29 OPT_ALL = 1 << 2,
30};
31
32static void xputchar(char c)
33{
34 if (putchar(c) < 0)
35 bb_error_msg_and_die(bb_msg_write_error);
36}
37
38#if ENABLE_EXPAND
39static void expand(FILE *file, unsigned tab_size, unsigned opt)
40{
41 char *line;
42 char *ptr;
43 int convert;
44 int pos;
45
46 /* Increment tab_size by 1 locally.*/
47 tab_size++;
48
49 while ((line = xmalloc_fgets(file)) != NULL) {
50 convert = 1;
51 pos = 0;
52 ptr = line;
53 while (*line) {
54 pos++;
55 if (*line == '\t' && convert) {
56 for (; pos < tab_size; pos++) {
57 xputchar(' ');
58 }
59 } else {
60 if ((opt & OPT_INITIAL) && !isblank(*line)) {
61 convert = 0;
62 }
63 xputchar(*line);
64 }
65 if (pos == tab_size) {
66 pos = 0;
67 }
68 line++;
69 }
70 free(ptr);
71 }
72}
73#endif
74
75#if ENABLE_UNEXPAND
76static void unexpand(FILE *file, unsigned int tab_size, unsigned opt)
77{
78 char *line;
79 char *ptr;
80 int convert;
81 int pos;
82 int i = 0;
83 int column = 0;
84
85 while ((line = xmalloc_fgets(file)) != NULL) {
86 convert = 1;
87 pos = 0;
88 ptr = line;
89 while (*line) {
90 while ((*line == ' ' || *line == '\t') && convert) {
91 pos += (*line == ' ') ? 1 : tab_size;
92 line++;
93 column++;
94 if ((opt & OPT_ALL) && column == tab_size) {
95 column = 0;
96 goto put_tab;
97 }
98 }
99 if (pos) {
100 i = pos / tab_size;
101 if (i) {
102 for (; i > 0; i--) {
103 put_tab:
104 xputchar('\t');
105 }
106 } else {
107 for (i = pos % tab_size; i > 0; i--) {
108 xputchar(' ');
109 }
110 }
111 pos = 0;
112 } else {
113 if (opt & OPT_INITIAL) {
114 convert = 0;
115 }
116 if (opt & OPT_ALL) {
117 column++;
118 }
119 xputchar(*line);
120 line++;
121 }
122 }
123 free(ptr);
124 }
125}
126#endif
127
Denis Vlasenko9b49a5e2007-10-11 10:05:36 +0000128int expand_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
Denis Vlasenko68404f12008-03-17 09:00:54 +0000129int expand_main(int argc ATTRIBUTE_UNUSED, char **argv)
Denis Vlasenko3952f202007-08-13 14:10:24 +0000130{
131 /* Default 8 spaces for 1 tab */
132 const char *opt_t = "8";
133 FILE *file;
134 unsigned tab_size;
135 unsigned opt;
136 int exit_status = EXIT_SUCCESS;
137
138#if ENABLE_FEATURE_EXPAND_LONG_OPTIONS
139 static const char expand_longopts[] ALIGN1 =
140 /* name, has_arg, val */
141 "initial\0" No_argument "i"
142 "tabs\0" Required_argument "t"
143 ;
144#endif
145#if ENABLE_FEATURE_UNEXPAND_LONG_OPTIONS
146 static const char unexpand_longopts[] ALIGN1 =
147 /* name, has_arg, val */
148 "first-only\0" No_argument "i"
149 "tabs\0" Required_argument "t"
150 "all\0" No_argument "a"
151 ;
152#endif
153
154 if (ENABLE_EXPAND && (!ENABLE_UNEXPAND || applet_name[0] == 'e')) {
155 USE_FEATURE_EXPAND_LONG_OPTIONS(applet_long_options = expand_longopts);
Denis Vlasenkofe7cd642007-08-18 15:32:12 +0000156 opt = getopt32(argv, "it:", &opt_t);
Denis Vlasenko62a90cd2008-03-17 09:07:36 +0000157 } else {
Denis Vlasenko3952f202007-08-13 14:10:24 +0000158 USE_FEATURE_UNEXPAND_LONG_OPTIONS(applet_long_options = unexpand_longopts);
159 /* -t NUM sets also -a */
160 opt_complementary = "ta";
Denis Vlasenkofe7cd642007-08-18 15:32:12 +0000161 opt = getopt32(argv, "ft:a", &opt_t);
Denis Vlasenko3952f202007-08-13 14:10:24 +0000162 /* -f --first-only is the default */
163 if (!(opt & OPT_ALL)) opt |= OPT_INITIAL;
164 }
165 tab_size = xatou_range(opt_t, 1, UINT_MAX);
166
167 argv += optind;
168
Denis Vlasenko3952f202007-08-13 14:10:24 +0000169 if (!*argv) {
170 *--argv = (char*)bb_msg_standard_input;
Denis Vlasenko3952f202007-08-13 14:10:24 +0000171 }
Denis Vlasenko3952f202007-08-13 14:10:24 +0000172 do {
Denis Vlasenko62a90cd2008-03-17 09:07:36 +0000173 file = fopen_or_warn_stdin(*argv);
174 if (!file) {
175 exit_status = EXIT_FAILURE;
176 continue;
Denis Vlasenko3952f202007-08-13 14:10:24 +0000177 }
178
179 if (ENABLE_EXPAND && (!ENABLE_UNEXPAND || applet_name[0] == 'e'))
180 USE_EXPAND(expand(file, tab_size, opt));
Denis Vlasenko62a90cd2008-03-17 09:07:36 +0000181 else
Denis Vlasenko3952f202007-08-13 14:10:24 +0000182 USE_UNEXPAND(unexpand(file, tab_size, opt));
183
184 /* Check and close the file */
Denis Vlasenko62a90cd2008-03-17 09:07:36 +0000185 if (fclose_if_not_stdin(file)) {
Denis Vlasenko0c97c9d2007-10-01 11:58:38 +0000186 bb_simple_perror_msg(*argv);
Denis Vlasenko3952f202007-08-13 14:10:24 +0000187 exit_status = EXIT_FAILURE;
188 }
189 /* If stdin also clear EOF */
Denis Vlasenko6a2f7f42007-08-16 10:35:17 +0000190 if (file == stdin)
Denis Vlasenko3952f202007-08-13 14:10:24 +0000191 clearerr(file);
192 } while (*++argv);
193
194 /* Now close stdin also */
195 /* (if we didn't read from it, it's a no-op) */
196 if (fclose(stdin))
197 bb_perror_msg_and_die(bb_msg_standard_input);
198
199 fflush_stdout_and_exit(exit_status);
200}