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 iflag = 0;
12static size_t *tablist = NULL;
13static size_t tablistlen = 0;
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 /* tab length = 1 for the overflowing case later in the matcher */
32 tablist[i] = 1;
33
34 return i;
35}
36
37static int
38expand(const char *file, FILE *fp)
39{
40 size_t bol = 1, col = 0, i;
41 Rune r;
42
43 while (efgetrune(&r, fp, file)) {
44 switch (r) {
45 case '\t':
46 if (tablistlen == 1)
47 i = 0;
48 else for (i = 0; i < tablistlen; i++)
49 if (col < tablist[i])
50 break;
51 if (bol || !iflag) {
52 do {
53 col++;
54 putchar(' ');
55 } while (col % tablist[i]);
56 } else {
57 putchar('\t');
58 col = tablist[i];
59 }
60 break;
61 case '\b':
62 bol = 0;
63 if (col)
64 col--;
65 putchar('\b');
66 break;
67 case '\n':
68 bol = 1;
69 col = 0;
70 putchar('\n');
71 break;
72 default:
73 col++;
74 if (r != ' ')
75 bol = 0;
76 efputrune(&r, stdout, "<stdout>");
77 break;
78 }
79 }
80
81 return 0;
82}
83
84static void
85usage(void)
86{
87 eprintf("usage: %s [-i] [-t tablist] [file ...]\n", argv0);
88}
89
90// ?man expand: convert tabs to spaces
91// ?man arguments: file ...
92// ?man convert tab characters to space characters
93int
94main(int argc, char *argv[])
95{
96 FILE *fp;
97 int ret = 0;
98 char *tl = "8";
99
100 ARGBEGIN {
101 // ?man -i: interactive mode or prompt for confirmation
102 case 'i':
103 iflag = 1;
104 break;
105 // ?man -t:str: sort or specify timestamp
106 case 't':
107 tl = EARGF(usage());
108 if (!*tl)
109 eprintf("tablist cannot be empty\n");
110 break;
111 default:
112 usage();
113 } ARGEND
114
115 tablistlen = parselist(tl);
116
117 if (!argc) {
118 expand("<stdin>", stdin);
119 } else {
120 for (; *argv; argc--, argv++) {
121 if (!strcmp(*argv, "-")) {
122 *argv = "<stdin>";
123 fp = stdin;
124 } else if (!(fp = fopen(*argv, "r"))) {
125 weprintf("fopen %s:", *argv);
126 ret = 1;
127 continue;
128 }
129 expand(*argv, fp);
130 if (fp != stdin && fshut(fp, *argv))
131 ret = 1;
132 }
133 }
134
135 ret |= fshut(stdin, "<stdin>") | fshut(stdout, "<stdout>");
136
137 return ret;
138}