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 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 128int expand_main(int argc, char **argv); 129int expand_main(int argc, char **argv) 130{ 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); 156 opt = getopt32(argv, "it:", &opt_t); 157 } else if (ENABLE_UNEXPAND) { 158 USE_FEATURE_UNEXPAND_LONG_OPTIONS(applet_long_options = unexpand_longopts); 159 /* -t NUM sets also -a */ 160 opt_complementary = "ta"; 161 opt = getopt32(argv, "ft:a", &opt_t); 162 /* -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 169 /* If no args are given, read from stdin */ 170 if (!*argv) { 171 *--argv = (char*)bb_msg_standard_input; 172 goto use_stdin; 173 } 174 175 do { 176 if (NOT_LONE_CHAR(*argv, '-')) { 177 file = fopen_or_warn(*argv, "r"); 178 if (!file) { 179 exit_status = EXIT_FAILURE; 180 continue; 181 } 182 } else { 183 use_stdin: 184 file = stdin; 185 } 186 187 if (ENABLE_EXPAND && (!ENABLE_UNEXPAND || applet_name[0] == 'e')) 188 USE_EXPAND(expand(file, tab_size, opt)); 189 else if (ENABLE_UNEXPAND) 190 USE_UNEXPAND(unexpand(file, tab_size, opt)); 191 192 /* Check and close the file */ 193 /* We do want all of them to execute, thus | instead of || */ 194 if (ferror(file) | fclose_if_not_stdin(file)) { 195 bb_perror_msg("%s", *argv); 196 exit_status = EXIT_FAILURE; 197 } 198 /* If stdin also clear EOF */ 199 if (file == stdin) 200 clearerr(file); 201 } while (*++argv); 202 203 /* Now close stdin also */ 204 /* (if we didn't read from it, it's a no-op) */ 205 if (fclose(stdin)) 206 bb_perror_msg_and_die(bb_msg_standard_input); 207 208 fflush_stdout_and_exit(exit_status); 209} 210