649b4c175a0a5ec28f68a92dd06c2c910bf63ded
[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 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
26 enum {
27         OPT_INITIAL     = 1 << 0,
28         OPT_TABS        = 1 << 1,
29         OPT_ALL         = 1 << 2,
30 };
31
32 #if ENABLE_EXPAND
33 static 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_FEATURE_ASSUME_UNICODE
52                                 len = bb_mbstrlen(ptr_strbeg);
53 # else
54                                 len = ptr - ptr_strbeg;
55 # endif
56                                 len = tab_size - (len % tab_size);
57                                 /*while (ptr[1] == '\t') { ptr++; len += tab_size; } - can handle many tabs at once */
58                                 printf("%s%*s", ptr_strbeg, len, "");
59                                 ptr_strbeg = ptr + 1;
60                         }
61                         ptr++;
62                 }
63                 fputs(ptr_strbeg, stdout);
64                 free(line);
65         }
66 }
67 #endif
68
69 #if ENABLE_UNEXPAND
70 static void unexpand(FILE *file, unsigned tab_size, unsigned opt)
71 {
72         char *line;
73
74         while ((line = xmalloc_fgets(file)) != NULL) {
75                 char *ptr = line;
76                 unsigned column = 0;
77
78                 while (*ptr) {
79                         unsigned n;
80                         unsigned len;
81
82                         while (*ptr == ' ') {
83                                 column++;
84                                 ptr++;
85                         }
86                         if (*ptr == '\t') {
87                                 column += tab_size - (column % tab_size);
88                                 ptr++;
89                                 continue;
90                         }
91
92                         n = column / tab_size;
93                         column = column % tab_size;
94                         while (n--)
95                                 putchar('\t');
96
97                         if ((opt & OPT_INITIAL) && ptr != line) {
98                                 printf("%*s%s", column, "", ptr);
99                                 break;
100                         }
101                         n = strcspn(ptr, "\t ");
102                         printf("%*s%.*s", column, "", n, ptr);
103 # if ENABLE_FEATURE_ASSUME_UNICODE
104                         {
105                                 char c;
106                                 c = ptr[n];
107                                 ptr[n] = '\0';
108                                 len = bb_mbstrlen(ptr);
109                                 ptr[n] = c;
110                         }
111 # else
112                         len = n;
113 # endif
114                         ptr += n;
115                         column = (column + len) % tab_size;
116                 }
117                 free(line);
118         }
119 }
120 #endif
121
122 int expand_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
123 int expand_main(int argc UNUSED_PARAM, char **argv)
124 {
125         /* Default 8 spaces for 1 tab */
126         const char *opt_t = "8";
127         FILE *file;
128         unsigned tab_size;
129         unsigned opt;
130         int exit_status = EXIT_SUCCESS;
131
132 #if ENABLE_FEATURE_EXPAND_LONG_OPTIONS
133         static const char expand_longopts[] ALIGN1 =
134                 /* name, has_arg, val */
135                 "initial\0"          No_argument       "i"
136                 "tabs\0"             Required_argument "t"
137         ;
138 #endif
139 #if ENABLE_FEATURE_UNEXPAND_LONG_OPTIONS
140         static const char unexpand_longopts[] ALIGN1 =
141                 /* name, has_arg, val */
142                 "first-only\0"       No_argument       "i"
143                 "tabs\0"             Required_argument "t"
144                 "all\0"              No_argument       "a"
145         ;
146 #endif
147         init_unicode();
148
149         if (ENABLE_EXPAND && (!ENABLE_UNEXPAND || applet_name[0] == 'e')) {
150                 IF_FEATURE_EXPAND_LONG_OPTIONS(applet_long_options = expand_longopts);
151                 opt = getopt32(argv, "it:", &opt_t);
152         } else {
153                 IF_FEATURE_UNEXPAND_LONG_OPTIONS(applet_long_options = unexpand_longopts);
154                 /* -t NUM sets also -a */
155                 opt_complementary = "ta";
156                 opt = getopt32(argv, "ft:a", &opt_t);
157                 /* -f --first-only is the default */
158                 if (!(opt & OPT_ALL)) opt |= OPT_INITIAL;
159         }
160         tab_size = xatou_range(opt_t, 1, UINT_MAX);
161
162         argv += optind;
163
164         if (!*argv) {
165                 *--argv = (char*)bb_msg_standard_input;
166         }
167         do {
168                 file = fopen_or_warn_stdin(*argv);
169                 if (!file) {
170                         exit_status = EXIT_FAILURE;
171                         continue;
172                 }
173
174                 if (ENABLE_EXPAND && (!ENABLE_UNEXPAND || applet_name[0] == 'e'))
175                         IF_EXPAND(expand(file, tab_size, opt));
176                 else
177                         IF_UNEXPAND(unexpand(file, tab_size, opt));
178
179                 /* Check and close the file */
180                 if (fclose_if_not_stdin(file)) {
181                         bb_simple_perror_msg(*argv);
182                         exit_status = EXIT_FAILURE;
183                 }
184                 /* If stdin also clear EOF */
185                 if (file == stdin)
186                         clearerr(file);
187         } while (*++argv);
188
189         /* Now close stdin also */
190         /* (if we didn't read from it, it's a no-op) */
191         if (fclose(stdin))
192                 bb_perror_msg_and_die(bb_msg_standard_input);
193
194         fflush_stdout_and_exit(exit_status);
195 }