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