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