master xplshn/aruu / cmd / posix / unexpand.c
  1/* See LICENSE file for copyright and license details. */
  2
  3#include <stdint.h>
  4#include <stdlib.h>
  5#include <string.h>
  6
  7#include "utf.h"
  8#include "util.h"
  9
 10static int     aflag      = 0;
 11static size_t *tablist    = NULL;
 12static size_t  tablistlen = 8;
 13
 14static size_t
 15parselist(const char *s)
 16{
 17  size_t i;
 18  char  *p, *tmp;
 19
 20  tmp = estrdup(s);
 21  for (i = 0; (p = strsep(&tmp, " ,")); i++) {
 22    if (*p == '\0')
 23      eprintf("empty field in tablist\n");
 24    tablist    = ereallocarray(tablist, i + 1, sizeof(*tablist));
 25    tablist[i] = estrtonum(p, 1, MIN((unsigned long long)LLONG_MAX, (unsigned long long)SIZE_MAX));
 26    if (i > 0 && tablist[i - 1] >= tablist[i])
 27      eprintf("tablist must be ascending\n");
 28  }
 29  tablist = ereallocarray(tablist, i + 1, sizeof(*tablist));
 30
 31  return i;
 32}
 33
 34static void
 35unexpandspan(size_t last, size_t col)
 36{
 37  size_t off, i, j;
 38  Rune   r;
 39
 40  if (tablistlen == 1) {
 41    i   = 0;
 42    off = last % tablist[i];
 43
 44    if ((col - last) + off >= tablist[i] && last < col)
 45      last -= off;
 46
 47    r = '\t';
 48    for (; last + tablist[i] <= col; last += tablist[i])
 49      efputrune(&r, stdout, "<stdout>");
 50    r = ' ';
 51    for (; last < col; last++)
 52      efputrune(&r, stdout, "<stdout>");
 53  } else {
 54    for (i = 0; i < tablistlen; i++)
 55      if (col < tablist[i])
 56        break;
 57    for (j = 0; j < tablistlen; j++)
 58      if (last < tablist[j])
 59        break;
 60    r = '\t';
 61    for (; j < i; j++) {
 62      efputrune(&r, stdout, "<stdout>");
 63      last = tablist[j];
 64    }
 65    r = ' ';
 66    for (; last < col; last++)
 67      efputrune(&r, stdout, "<stdout>");
 68  }
 69}
 70
 71static void
 72unexpand(const char *file, FILE *fp)
 73{
 74  Rune   r;
 75  size_t last = 0, col = 0, i;
 76  int    bol = 1;
 77
 78  while (efgetrune(&r, fp, file)) {
 79    switch (r) {
 80      case ' ':
 81        if (!bol && !aflag)
 82          last++;
 83        col++;
 84        break;
 85      case '\t':
 86        if (tablistlen == 1) {
 87          if (!bol && !aflag)
 88            last += tablist[0] - col % tablist[0];
 89          col += tablist[0] - col % tablist[0];
 90        } else {
 91          for (i = 0; i < tablistlen; i++)
 92            if (col < tablist[i])
 93              break;
 94          if (!bol && !aflag)
 95            last = tablist[i];
 96          col = tablist[i];
 97        }
 98        break;
 99      case '\b':
100        if (bol || aflag)
101          unexpandspan(last, col);
102        col -= (col > 0);
103        last = col;
104        bol  = 0;
105        break;
106      case '\n':
107        if (bol || aflag)
108          unexpandspan(last, col);
109        last = col = 0;
110        bol        = 1;
111        break;
112      default:
113        if (bol || aflag)
114          unexpandspan(last, col);
115        last = ++col;
116        bol  = 0;
117        break;
118    }
119    if ((r != ' ' && r != '\t') || (!aflag && !bol))
120      efputrune(&r, stdout, "<stdout>");
121  }
122  if (last < col && (bol || aflag))
123    unexpandspan(last, col);
124}
125
126static void
127usage(void)
128{
129  eprintf("usage: %s [-a] [-t tablist] [file ...]\n", argv0);
130}
131
132// ?man unexpand: convert spaces to tabs
133// ?man arguments: file ...
134// ?man convert space characters to tab characters
135int
136main(int argc, char *argv[])
137{
138  FILE *fp;
139  int   ret = 0;
140  char *tl  = "8";
141
142  ARGBEGIN
143  {
144    // ?man -t:str: sort or specify timestamp
145    case 't':
146      tl = EARGF(usage());
147      if (!*tl)
148        eprintf("tablist cannot be empty\n");
149      /* fallthrough */
150    // ?man -a: print or show all entries
151    case 'a':
152      aflag = 1;
153      break;
154    default:
155      usage();
156  }
157  ARGEND
158
159  tablistlen = parselist(tl);
160
161  if (!argc) {
162    unexpand("<stdin>", stdin);
163  } else {
164    for (; *argv; argc--, argv++) {
165      if (!strcmp(*argv, "-")) {
166        *argv = "<stdin>";
167        fp    = stdin;
168      } else if (!(fp = fopen(*argv, "r"))) {
169        weprintf("fopen %s:", *argv);
170        ret = 1;
171        continue;
172      }
173      unexpand(*argv, fp);
174      if (fp != stdin && fshut(fp, *argv))
175        ret = 1;
176    }
177  }
178
179  ret |= fshut(stdin, "<stdin>") | fshut(stdout, "<stdout>");
180
181  return ret;
182}