expand: fix incorrect expansion exactly on tab boundary; shrink the code
[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
24 #include "libbb.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, int tab_size, unsigned opt)
34 {
35         char *line;
36
37         tab_size = -tab_size;
38
39         while ((line = xmalloc_fgets(file)) != NULL) {
40                 int pos;
41                 unsigned char c;
42                 char *ptr = line;
43
44                 goto start;
45                 while ((c = *ptr) != '\0') {
46                         if ((opt & OPT_INITIAL) && !isblank(c)) {
47                                 fputs(ptr, stdout);
48                                 break;
49                         }
50                         ptr++;
51                         if (c == '\t') {
52                                 c = ' ';
53                                 while (++pos < 0)
54                                         bb_putchar(c);
55                         }
56                         bb_putchar(c);
57                         if (++pos >= 0) {
58  start:
59                                 pos = tab_size;
60                         }
61                 }
62                 free(line);
63         }
64 }
65 #endif
66
67 #if ENABLE_UNEXPAND
68 static void unexpand(FILE *file, unsigned tab_size, unsigned opt)
69 {
70         char *line;
71         char *ptr;
72         int convert;
73         int pos;
74         int i = 0;
75         unsigned column = 0;
76
77         while ((line = xmalloc_fgets(file)) != NULL) {
78                 convert = 1;
79                 pos = 0;
80                 ptr = line;
81                 while (*line) {
82                         while ((*line == ' ' || *line == '\t') && convert) {
83                                 pos += (*line == ' ') ? 1 : tab_size;
84                                 line++;
85                                 column++;
86                                 if ((opt & OPT_ALL) && column == tab_size) {
87                                         column = 0;
88                                         goto put_tab;
89                                 }
90                         }
91                         if (pos) {
92                                 i = pos / tab_size;
93                                 if (i) {
94                                         for (; i > 0; i--) {
95  put_tab:
96                                                 bb_putchar('\t');
97                                         }
98                                 } else {
99                                         for (i = pos % tab_size; i > 0; i--) {
100                                                 bb_putchar(' ');
101                                         }
102                                 }
103                                 pos = 0;
104                         } else {
105                                 if (opt & OPT_INITIAL) {
106                                         convert = 0;
107                                 }
108                                 if (opt & OPT_ALL) {
109                                         column++;
110                                 }
111                                 bb_putchar(*line);
112                                 line++;
113                         }
114                 }
115                 free(ptr);
116         }
117 }
118 #endif
119
120 int expand_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
121 int expand_main(int argc UNUSED_PARAM, char **argv)
122 {
123         /* Default 8 spaces for 1 tab */
124         const char *opt_t = "8";
125         FILE *file;
126         unsigned tab_size;
127         unsigned opt;
128         int exit_status = EXIT_SUCCESS;
129
130 #if ENABLE_FEATURE_EXPAND_LONG_OPTIONS
131         static const char expand_longopts[] ALIGN1 =
132                 /* name, has_arg, val */
133                 "initial\0"          No_argument       "i"
134                 "tabs\0"             Required_argument "t"
135         ;
136 #endif
137 #if ENABLE_FEATURE_UNEXPAND_LONG_OPTIONS
138         static const char unexpand_longopts[] ALIGN1 =
139                 /* name, has_arg, val */
140                 "first-only\0"       No_argument       "i"
141                 "tabs\0"             Required_argument "t"
142                 "all\0"              No_argument       "a"
143         ;
144 #endif
145
146         if (ENABLE_EXPAND && (!ENABLE_UNEXPAND || applet_name[0] == 'e')) {
147                 USE_FEATURE_EXPAND_LONG_OPTIONS(applet_long_options = expand_longopts);
148                 opt = getopt32(argv, "it:", &opt_t);
149         } else {
150                 USE_FEATURE_UNEXPAND_LONG_OPTIONS(applet_long_options = unexpand_longopts);
151                 /* -t NUM sets also -a */
152                 opt_complementary = "ta";
153                 opt = getopt32(argv, "ft:a", &opt_t);
154                 /* -f --first-only is the default */
155                 if (!(opt & OPT_ALL)) opt |= OPT_INITIAL;
156         }
157         tab_size = xatou_range(opt_t, 1, UINT_MAX);
158
159         argv += optind;
160
161         if (!*argv) {
162                 *--argv = (char*)bb_msg_standard_input;
163         }
164         do {
165                 file = fopen_or_warn_stdin(*argv);
166                 if (!file) {
167                         exit_status = EXIT_FAILURE;
168                         continue;
169                 }
170
171                 if (ENABLE_EXPAND && (!ENABLE_UNEXPAND || applet_name[0] == 'e'))
172                         USE_EXPAND(expand(file, tab_size, opt));
173                 else
174                         USE_UNEXPAND(unexpand(file, tab_size, opt));
175
176                 /* Check and close the file */
177                 if (fclose_if_not_stdin(file)) {
178                         bb_simple_perror_msg(*argv);
179                         exit_status = EXIT_FAILURE;
180                 }
181                 /* If stdin also clear EOF */
182                 if (file == stdin)
183                         clearerr(file);
184         } while (*++argv);
185
186         /* Now close stdin also */
187         /* (if we didn't read from it, it's a no-op) */
188         if (fclose(stdin))
189                 bb_perror_msg_and_die(bb_msg_standard_input);
190
191         fflush_stdout_and_exit(exit_status);
192 }