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 iflag = 0;
11static size_t *tablist = NULL;
12static size_t tablistlen = 0;
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 /* tab length = 1 for the overflowing case later in the matcher */
31 tablist[i] = 1;
32
33 return i;
34}
35
36static int
37expand(const char *file, FILE *fp)
38{
39 size_t bol = 1, col = 0, i;
40 Rune r;
41
42 while (efgetrune(&r, fp, file)) {
43 switch (r) {
44 case '\t':
45 if (tablistlen == 1)
46 i = 0;
47 else
48 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 {
102 // ?man -i: interactive mode or prompt for confirmation
103 case 'i':
104 iflag = 1;
105 break;
106 // ?man -t:str: sort or specify timestamp
107 case 't':
108 tl = EARGF(usage());
109 if (!*tl)
110 eprintf("tablist cannot be empty\n");
111 break;
112 default:
113 usage();
114 }
115 ARGEND
116
117 tablistlen = parselist(tl);
118
119 if (!argc) {
120 expand("<stdin>", stdin);
121 } else {
122 for (; *argv; argc--, argv++) {
123 if (!strcmp(*argv, "-")) {
124 *argv = "<stdin>";
125 fp = stdin;
126 } else if (!(fp = fopen(*argv, "r"))) {
127 weprintf("fopen %s:", *argv);
128 ret = 1;
129 continue;
130 }
131 expand(*argv, fp);
132 if (fp != stdin && fshut(fp, *argv))
133 ret = 1;
134 }
135 }
136
137 ret |= fshut(stdin, "<stdin>") | fshut(stdout, "<stdout>");
138
139 return ret;
140}