busybox/coreutils/expand.c
<<
>>
Prefs
   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.1 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 (5.3 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
  60enum {
  61        OPT_INITIAL     = 1 << 0,
  62        OPT_TABS        = 1 << 1,
  63        OPT_ALL         = 1 << 2,
  64};
  65
  66//FIXME: does not work properly with input containing NULs
  67//coreutils 8.30 preserves NULs but treats them as chars of width zero:
  68//AB<nul><tab>C will expand <tab> to 6 spaces, not 5.
  69
  70#if ENABLE_EXPAND
  71static void expand(FILE *file, unsigned tab_size, unsigned opt)
  72{
  73
  74        for (;;) {
  75                char *line;
  76                char *ptr;
  77                char *ptr_strbeg;
  78//commented-out code handles NULs, +90 bytes of code, not tested much
  79//              size_t linelen;
  80//              unsigned len = 0;
  81
  82//              linelen = 1024 * 1024;
  83//              line = xmalloc_fgets_str_len(file, "\n", &linelen);
  84                line = xmalloc_fgets(file); //
  85                if (!line)
  86                        break;
  87                ptr = ptr_strbeg = line;
  88                for (;;) {
  89                        unsigned char c = *ptr;
  90                        if (c == '\0') {
  91//                              size_t rem = line + linelen - ptr;
  92//                              if (rem > 0) {
  93//# if ENABLE_UNICODE_SUPPORT
  94//                                      len += unicode_strwidth(ptr_strbeg);
  95//# else
  96//                                      len += ptr - ptr_strbeg;
  97//# endif
  98//                                      printf("%s%c", ptr_strbeg, '\0');
  99//                                      memmove(ptr, ptr + 1, rem + 1);
 100//                                      ptr_strbeg = ptr;
 101//                                      linelen--;
 102//                                      continue;
 103//                              }
 104                                break;
 105                        }
 106                        if ((opt & OPT_INITIAL) && !isblank(c)) {
 107                                /* not space or tab */
 108                                break;
 109                        }
 110                        if (c == '\t') {
 111                                unsigned len = 0; //
 112                                *ptr = '\0';
 113# if ENABLE_UNICODE_SUPPORT
 114                                len += unicode_strwidth(ptr_strbeg);
 115# else
 116                                len += ptr - ptr_strbeg;
 117# endif
 118                                len = tab_size - (len % tab_size);
 119                                /*while (ptr[1] == '\t') { ptr++; len += tab_size; } - can handle many tabs at once */
 120                                printf("%s%*s", ptr_strbeg, len, "");
 121//                              len = 0;
 122                                ptr_strbeg = ptr + 1;
 123                        }
 124                        ptr++;
 125                }
 126                fputs_stdout(ptr_strbeg);
 127                free(line);
 128        }
 129}
 130#endif
 131
 132#if ENABLE_UNEXPAND
 133static void unexpand(FILE *file, unsigned tab_size, unsigned opt)
 134{
 135        char *line;
 136
 137        while ((line = xmalloc_fgets(file)) != NULL) {
 138                char *ptr = line;
 139                unsigned column = 0;
 140
 141                while (*ptr) {
 142                        unsigned n;
 143                        unsigned len = 0;
 144
 145                        while (*ptr == ' ') {
 146                                ptr++;
 147                                len++;
 148                        }
 149                        column += len;
 150                        if (*ptr == '\t') {
 151                                column += tab_size - (column % tab_size);
 152                                ptr++;
 153                                continue;
 154                        }
 155
 156                        n = column / tab_size;
 157                        if (n) {
 158                                len = column = column % tab_size;
 159                                while (n--)
 160                                        putchar('\t');
 161                        }
 162
 163                        if (!(opt & OPT_ALL) && ptr != line) {
 164                                printf("%*s%s", len, "", ptr);
 165                                break;
 166                        }
 167                        n = strcspn(ptr, "\t ");
 168                        printf("%*s%.*s", len, "", n, ptr);
 169# if ENABLE_UNICODE_SUPPORT
 170                        {
 171                                char c = ptr[n];
 172                                ptr[n] = '\0';
 173                                len = unicode_strwidth(ptr);
 174                                ptr[n] = c;
 175                        }
 176# else
 177                        len = n;
 178# endif
 179                        ptr += n;
 180                        column = (column + len) % tab_size;
 181                }
 182                free(line);
 183        }
 184}
 185#endif
 186
 187int expand_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
 188int expand_main(int argc UNUSED_PARAM, char **argv)
 189{
 190        /* Default 8 spaces for 1 tab */
 191        const char *opt_t = "8";
 192        FILE *file;
 193        unsigned tab_size;
 194        unsigned opt;
 195        int exit_status = EXIT_SUCCESS;
 196
 197        init_unicode();
 198
 199        if (ENABLE_EXPAND && (!ENABLE_UNEXPAND || applet_name[0] == 'e')) {
 200                opt = getopt32long(argv, "it:",
 201                                "initial\0"          No_argument       "i"
 202                                "tabs\0"             Required_argument "t"
 203                                , &opt_t
 204                );
 205        } else {
 206                opt = getopt32long(argv, "^"
 207                                "ft:a"
 208                                "\0"
 209                                "ta" /* -t NUM sets -a */,
 210                                "first-only\0"       No_argument       "f"
 211                                "tabs\0"             Required_argument "t"
 212                                "all\0"              No_argument       "a"
 213                                , &opt_t
 214                );
 215                /* -t implies -a, but an explicit -f overrides */
 216                if (opt & OPT_INITIAL) opt &= ~OPT_ALL;
 217        }
 218        tab_size = xatou_range(opt_t, 1, UINT_MAX);
 219
 220        argv += optind;
 221
 222        if (!*argv) {
 223                *--argv = (char*)bb_msg_standard_input;
 224        }
 225        do {
 226                file = fopen_or_warn_stdin(*argv);
 227                if (!file) {
 228                        exit_status = EXIT_FAILURE;
 229                        continue;
 230                }
 231
 232                if (ENABLE_EXPAND && (!ENABLE_UNEXPAND || applet_name[0] == 'e'))
 233                        IF_EXPAND(expand(file, tab_size, opt));
 234                else
 235                        IF_UNEXPAND(unexpand(file, tab_size, opt));
 236
 237                /* Check and close the file */
 238                if (fclose_if_not_stdin(file)) {
 239                        bb_simple_perror_msg(*argv);
 240                        exit_status = EXIT_FAILURE;
 241                }
 242                /* If stdin also clear EOF */
 243                if (file == stdin)
 244                        clearerr(file);
 245        } while (*++argv);
 246
 247        /* Now close stdin also */
 248        /* (if we didn't read from it, it's a no-op) */
 249        if (fclose(stdin))
 250                bb_simple_perror_msg_and_die(bb_msg_standard_input);
 251
 252        fflush_stdout_and_exit(exit_status);
 253}
 254