busybox/coreutils/fold.c
<<
>>
Prefs
   1/* vi: set sw=4 ts=4: */
   2/*
   3 * fold -- wrap each input line to fit in specified width.
   4 *
   5 * Written by David MacKenzie, djm@gnu.ai.mit.edu.
   6 * Copyright (C) 91, 1995-2002 Free Software Foundation, Inc.
   7 *
   8 * Modified for busybox based on coreutils v 5.0
   9 * Copyright (C) 2003 Glenn McGrath
  10 *
  11 * Licensed under GPLv2 or later, see file LICENSE in this source tree.
  12 */
  13//config:config FOLD
  14//config:       bool "fold (4.6 kb)"
  15//config:       default y
  16//config:       help
  17//config:       Wrap text to fit a specific width.
  18
  19//applet:IF_FOLD(APPLET_NOEXEC(fold, fold, BB_DIR_USR_BIN, BB_SUID_DROP, fold))
  20
  21//kbuild:lib-$(CONFIG_FOLD) += fold.o
  22
  23//usage:#define fold_trivial_usage
  24//usage:       "[-bs] [-w WIDTH] [FILE]..."
  25//usage:#define fold_full_usage "\n\n"
  26//usage:       "Wrap input lines in each FILE (or stdin), writing to stdout\n"
  27//usage:     "\n        -b      Count bytes rather than columns"
  28//usage:     "\n        -s      Break at spaces"
  29//usage:     "\n        -w      Use WIDTH columns instead of 80"
  30
  31#include "libbb.h"
  32#include "unicode.h"
  33
  34/* This is a NOEXEC applet. Be very careful! */
  35
  36/* Must match getopt32 call */
  37#define FLAG_COUNT_BYTES        1
  38#define FLAG_BREAK_SPACES       2
  39#define FLAG_WIDTH              4
  40
  41/* Assuming the current column is COLUMN, return the column that
  42   printing C will move the cursor to.
  43   The first column is 0. */
  44static int adjust_column(unsigned column, char c)
  45{
  46        if (option_mask32 & FLAG_COUNT_BYTES)
  47                return ++column;
  48
  49        if (c == '\t')
  50                return column + 8 - column % 8;
  51
  52        if (c == '\b') {
  53                if ((int)--column < 0)
  54                        column = 0;
  55        }
  56        else if (c == '\r')
  57                column = 0;
  58        else { /* just a printable char */
  59                if (unicode_status != UNICODE_ON /* every byte is a new char */
  60                 || (c & 0xc0) != 0x80 /* it isn't a 2nd+ byte of a Unicode char */
  61                ) {
  62                        column++;
  63                }
  64        }
  65        return column;
  66}
  67
  68/* Note that this function can write NULs, unlike fputs etc. */
  69static void write2stdout(const void *buf, unsigned size)
  70{
  71        fwrite(buf, 1, size, stdout);
  72}
  73
  74int fold_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
  75int fold_main(int argc UNUSED_PARAM, char **argv)
  76{
  77        char *line_out = NULL;
  78        const char *w_opt = "80";
  79        unsigned width;
  80        smallint exitcode = EXIT_SUCCESS;
  81
  82        init_unicode();
  83
  84        if (ENABLE_INCLUDE_SUSv2) {
  85                /* Turn any numeric options into -w options.  */
  86                int i;
  87                for (i = 1; argv[i]; i++) {
  88                        const char *a = argv[i];
  89                        if (*a == '-') {
  90                                a++;
  91                                if (*a == '-' && !a[1]) /* "--" */
  92                                        break;
  93                                if (isdigit(*a))
  94                                        argv[i] = xasprintf("-w%s", a);
  95                        }
  96                }
  97        }
  98
  99        getopt32(argv, "bsw:", &w_opt);
 100        width = xatou_range(w_opt, 1, 10000);
 101
 102        argv += optind;
 103        if (!*argv)
 104                *--argv = (char*)"-";
 105
 106        do {
 107                FILE *istream = fopen_or_warn_stdin(*argv);
 108                int c;
 109                unsigned column = 0;     /* Screen column where next char will go */
 110                unsigned offset_out = 0; /* Index in 'line_out' for next char */
 111
 112                if (istream == NULL) {
 113                        exitcode = EXIT_FAILURE;
 114                        continue;
 115                }
 116
 117                while ((c = getc(istream)) != EOF) {
 118                        /* We grow line_out in chunks of 0x1000 bytes */
 119                        if ((offset_out & 0xfff) == 0) {
 120                                line_out = xrealloc(line_out, offset_out + 0x1000);
 121                        }
 122 rescan:
 123                        line_out[offset_out] = c;
 124                        if (c == '\n') {
 125                                write2stdout(line_out, offset_out + 1);
 126                                column = offset_out = 0;
 127                                continue;
 128                        }
 129                        column = adjust_column(column, c);
 130                        if (column <= width || offset_out == 0) {
 131                                /* offset_out == 0 case happens
 132                                 * with small width (say, 1) and tabs.
 133                                 * The very first tab already goes to column 8,
 134                                 * but we must not wrap it */
 135                                offset_out++;
 136                                continue;
 137                        }
 138
 139                        /* This character would make the line too long.
 140                         * Print the line plus a newline, and make this character
 141                         * start the next line */
 142                        if (option_mask32 & FLAG_BREAK_SPACES) {
 143                                unsigned i;
 144                                unsigned logical_end;
 145
 146                                /* Look for the last blank. */
 147                                for (logical_end = offset_out - 1; (int)logical_end >= 0; logical_end--) {
 148                                        if (!isblank(line_out[logical_end]))
 149                                                continue;
 150
 151                                        /* Found a space or tab.
 152                                         * Output up to and including it, and start a new line */
 153                                        logical_end++;
 154                                        /*line_out[logical_end] = '\n'; - NO! this nukes one buffered character */
 155                                        write2stdout(line_out, logical_end);
 156                                        putchar('\n');
 157                                        /* Move the remainder to the beginning of the next line.
 158                                         * The areas being copied here might overlap. */
 159                                        memmove(line_out, line_out + logical_end, offset_out - logical_end);
 160                                        offset_out -= logical_end;
 161                                        for (column = i = 0; i < offset_out; i++) {
 162                                                column = adjust_column(column, line_out[i]);
 163                                        }
 164                                        goto rescan;
 165                                }
 166                                /* No blank found, wrap will split the overlong word */
 167                        }
 168                        /* Output what we accumulated up to now, and start a new line */
 169                        line_out[offset_out] = '\n';
 170                        write2stdout(line_out, offset_out + 1);
 171                        column = offset_out = 0;
 172                        goto rescan;
 173                } /* while (not EOF) */
 174
 175                if (offset_out) {
 176                        write2stdout(line_out, offset_out);
 177                }
 178
 179                if (fclose_if_not_stdin(istream)) {
 180                        bb_simple_perror_msg(*argv);
 181                        exitcode = EXIT_FAILURE;
 182                }
 183        } while (*++argv);
 184
 185        fflush_stdout_and_exit(exitcode);
 186}
 187