aboutsummaryrefslogtreecommitdiff
path: root/toys/posix/expand.c
blob: a322926a1cd268001392be980d9403ec09946541 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
/* expand.c - expands tabs to space
 *
 * Copyright 2012 Jonathan Clairembault <jonathan at clairembault dot fr>
 *
 * See http://pubs.opengroup.org/onlinepubs/9699919799/utilities/expand.html

USE_EXPAND(NEWTOY(expand, "t:", TOYFLAG_USR|TOYFLAG_BIN))

config EXPAND
  bool "expand"
  default y
  help
    usage: expand [-t tablist] [file...]

    Expand tabs to spaces according to tabstops.

    -t	tablist

      Specify tab stops, either a single number instead of the default 8,
      or a list of increasing numbers (comma or space separated, after which
      each additional tab becomes one space).
*/

#define FOR_expand
#include "toys.h"

GLOBALS(
  char *t_flags;
  struct offset_list tablist;
)

static void build_tablist(char *tabstops)
{
  char *ctx;
  struct offset_list *tablist = &TT.tablist;
  char *s, *ref;
  off_t stop, last_stop;

  /* for every tabstop decode and add to list */
  for (stop = last_stop = 0, s = ref = xstrdup(tabstops); ;
       last_stop = stop, s = NULL) {
    char *tabstop = strtok_r(s, " ,", &ctx);

    if (!tabstop) return;

    stop = xstrtoul(tabstop, NULL, 0);
    if (stop <= last_stop) {
      free(ref);
      toys.exithelp = 1;
      error_exit("tablist ascending order");
    }
    tablist->next = xzalloc(sizeof(*tablist));
    tablist->next->off = stop;
    tablist = tablist->next;
  }

  free(ref);
}

static void expand_file(int fd, char *name)
{
  ssize_t rdn;
  char *rdbuf, *wrbuf;
  size_t wrbuflen, rdbuflen;
  ssize_t rdbufi = 0, wrbufi = 0;
  ssize_t wrlinei;
  int hastablist = !!TT.tablist.next->next;
  struct offset_list *tablist = TT.tablist.next;
  ssize_t stop = tablist->off;

  wrbuflen = rdbuflen = ARRAY_LEN(toybuf)/2;
  rdbuf = toybuf;
  wrbuf = toybuf + rdbuflen;
  do {
    rdn = readall(fd, rdbuf, rdbuflen);
    if (rdn < 0) perror_exit("%s", name);
    for (rdbufi=0, wrbufi=0; rdbufi<rdn; rdbufi++) {
      if (wrbufi == wrbuflen) { /* flush expand buffer when full */
        writeall(STDOUT_FILENO, wrbuf, wrbuflen);
        wrbufi = 0;
      }
      if (rdbuf[rdbufi] == '\t') { /* expand tab */
        size_t count;
        size_t tabsize;

        /* search next tab stop */
        while(tablist && (stop <= wrlinei)) {
          stop = hastablist ? tablist->off : stop + tablist->off;
          tablist = hastablist ? tablist->next : tablist;
        }
        tabsize = ((stop - wrlinei < 2)) ? 1 : stop - wrlinei;
        while (tabsize) { /* long expand */
          count = min(tabsize, wrbuflen - wrbufi);
          memset(wrbuf + wrbufi, ' ', count);
          tabsize -= count;
          if (tabsize) { /* flush expand buffer when full */
            writeall(STDOUT_FILENO, wrbuf, wrbuflen);
            wrbufi = 0;
          } else wrbufi += count;
        }
        wrlinei += count;
      } else { /* copy input to output */
        wrbuf[wrbufi++] = rdbuf[rdbufi];
        if (rdbuf[rdbufi] == '\b') /* go back one column on backspace */
          wrlinei -= !!wrlinei; /* do not go below zero */
        else
          wrlinei += 1;
        /* flush expand buffer and reset tablist at newline */
        if (rdbuf[rdbufi] == '\n') {
          writeall(STDOUT_FILENO, wrbuf, wrbufi);
          tablist = TT.tablist.next;
          stop = tablist->off;
          wrbufi = wrlinei = 0;
        }
      }
    }
  } while (rdn == rdbuflen);
  /* flush last expand buffer */
  writeall(STDOUT_FILENO, wrbuf, wrbufi);
}

void expand_main(void)
{
  build_tablist((toys.optflags & FLAG_t) ? TT.t_flags : "8");

  loopfiles(toys.optargs, expand_file);
  if (CFG_TOYBOX_FREE) llist_traverse(TT.tablist.next, free);
}