cksum: code shrink
[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 (5.8 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 (6 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 #if ENABLE_EXPAND
67 static void expand(FILE *file, unsigned tab_size, unsigned opt)
68 {
69         char *line;
70
71         while ((line = xmalloc_fgets(file)) != NULL) {
72                 unsigned char c;
73                 char *ptr;
74                 char *ptr_strbeg;
75
76                 ptr = ptr_strbeg = line;
77                 while ((c = *ptr) != '\0') {
78                         if ((opt & OPT_INITIAL) && !isblank(c)) {
79                                 /* not space or tab */
80                                 break;
81                         }
82                         if (c == '\t') {
83                                 unsigned len;
84                                 *ptr = '\0';
85 # if ENABLE_UNICODE_SUPPORT
86                                 len = unicode_strwidth(ptr_strbeg);
87 # else
88                                 len = ptr - ptr_strbeg;
89 # endif
90                                 len = tab_size - (len % tab_size);
91                                 /*while (ptr[1] == '\t') { ptr++; len += tab_size; } - can handle many tabs at once */
92                                 printf("%s%*s", ptr_strbeg, len, "");
93                                 ptr_strbeg = ptr + 1;
94                         }
95                         ptr++;
96                 }
97                 fputs(ptr_strbeg, stdout);
98                 free(line);
99         }
100 }
101 #endif
102
103 #if ENABLE_UNEXPAND
104 static void unexpand(FILE *file, unsigned tab_size, unsigned opt)
105 {
106         char *line;
107
108         while ((line = xmalloc_fgets(file)) != NULL) {
109                 char *ptr = line;
110                 unsigned column = 0;
111
112                 while (*ptr) {
113                         unsigned n;
114                         unsigned len = 0;
115
116                         while (*ptr == ' ') {
117                                 ptr++;
118                                 len++;
119                         }
120                         column += len;
121                         if (*ptr == '\t') {
122                                 column += tab_size - (column % tab_size);
123                                 ptr++;
124                                 continue;
125                         }
126
127                         n = column / tab_size;
128                         if (n) {
129                                 len = column = column % tab_size;
130                                 while (n--)
131                                         putchar('\t');
132                         }
133
134                         if ((opt & OPT_INITIAL) && ptr != line) {
135                                 printf("%*s%s", len, "", ptr);
136                                 break;
137                         }
138                         n = strcspn(ptr, "\t ");
139                         printf("%*s%.*s", len, "", n, ptr);
140 # if ENABLE_UNICODE_SUPPORT
141                         {
142                                 char c = ptr[n];
143                                 ptr[n] = '\0';
144                                 len = unicode_strwidth(ptr);
145                                 ptr[n] = c;
146                         }
147 # else
148                         len = n;
149 # endif
150                         ptr += n;
151                         column = (column + len) % tab_size;
152                 }
153                 free(line);
154         }
155 }
156 #endif
157
158 int expand_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
159 int expand_main(int argc UNUSED_PARAM, char **argv)
160 {
161         /* Default 8 spaces for 1 tab */
162         const char *opt_t = "8";
163         FILE *file;
164         unsigned tab_size;
165         unsigned opt;
166         int exit_status = EXIT_SUCCESS;
167
168         init_unicode();
169
170         if (ENABLE_EXPAND && (!ENABLE_UNEXPAND || applet_name[0] == 'e')) {
171                 opt = getopt32long(argv, "it:",
172                                 "initial\0"          No_argument       "i"
173                                 "tabs\0"             Required_argument "t"
174                                 , &opt_t
175                 );
176         } else {
177                 opt = getopt32long(argv, "^"
178                                 "ft:a"
179                                 "\0"
180                                 "ta" /* -t NUM sets -a */,
181                                 "first-only\0"       No_argument       "i"
182                                 "tabs\0"             Required_argument "t"
183                                 "all\0"              No_argument       "a"
184                                 , &opt_t
185                 );
186                 /* -f --first-only is the default */
187                 if (!(opt & OPT_ALL)) opt |= OPT_INITIAL;
188         }
189         tab_size = xatou_range(opt_t, 1, UINT_MAX);
190
191         argv += optind;
192
193         if (!*argv) {
194                 *--argv = (char*)bb_msg_standard_input;
195         }
196         do {
197                 file = fopen_or_warn_stdin(*argv);
198                 if (!file) {
199                         exit_status = EXIT_FAILURE;
200                         continue;
201                 }
202
203                 if (ENABLE_EXPAND && (!ENABLE_UNEXPAND || applet_name[0] == 'e'))
204                         IF_EXPAND(expand(file, tab_size, opt));
205                 else
206                         IF_UNEXPAND(unexpand(file, tab_size, opt));
207
208                 /* Check and close the file */
209                 if (fclose_if_not_stdin(file)) {
210                         bb_simple_perror_msg(*argv);
211                         exit_status = EXIT_FAILURE;
212                 }
213                 /* If stdin also clear EOF */
214                 if (file == stdin)
215                         clearerr(file);
216         } while (*++argv);
217
218         /* Now close stdin also */
219         /* (if we didn't read from it, it's a no-op) */
220         if (fclose(stdin))
221                 bb_perror_msg_and_die(bb_msg_standard_input);
222
223         fflush_stdout_and_exit(exit_status);
224 }