aboutsummaryrefslogtreecommitdiff
path: root/toys/posix/expand.c
blob: bbe6fdf1252726ef4c72e9f2666e60c5bd2acda5 (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
129
130
131
132
133
134
135
136
137
138
139
140
141
142
/* expand.c - expands tabs to space
 *
 * Copyright 2012 Jonathan Clairembault <jonathan at clairembault dot fr>
 *
 * See http://http://pubs.opengroup.org/onlinepubs/9699919799/nframe.html

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

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

    Command expand. Expands tabs to space according to tabstops.

    -t  tablist
    Specify the tab stops.  The argument tablist consists of either a single 
    strictly positive decimal integer or a list of tabstops. If a single number 
    is given, tabs are set that number of column positions apart instead of the 
    default 8.

    If a list of tabstops is given, the list is made of two or more strictly 
    positive decimal integers, separated by <blank> or <comma> characters, in 
    strictly ascending order. The <tab> characters are set at those specific 
    column positions.

    In the event of expand having to process a <tab> at a position beyond the 
    last of those specified in a multiple tab-stop list, the <tab> is replaced 
    by a single <space> in the output.

    Any <backspace> characters shall be copied to the output and cause the 
    column position count for tab stop calculations to be decremented; the 
    column position count shall not be decremented below zero.
*/

#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");
  /* expand every file */
  loopfiles(toys.optargs, expand_file);
  /* free tablist */
  llist_traverse(TT.tablist.next, free);
}