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}