1/* See LICENSE file for copyright and license details. */
2
3#include <stdio.h>
4#include <stdlib.h>
5#include <string.h>
6
7#include "text.h"
8#include "utf.h"
9#include "util.h"
10
11typedef struct Range {
12 size_t min, max;
13 struct Range *next;
14} Range;
15
16static Range *list = NULL;
17static char mode = 0;
18static char *delim = "\t";
19static size_t delimlen = 1;
20static int nflag = 0;
21static int sflag = 0;
22
23static void
24insert(Range *r)
25{
26 Range *l, *p, *t;
27
28 for (p = NULL, l = list; l; p = l, l = l->next) {
29 if (r->max && r->max + 1 < l->min) {
30 r->next = l;
31 break;
32 } else if (!l->max || r->min < l->max + 2) {
33 l->min = MIN(r->min, l->min);
34 for (p = l, t = l->next; t; p = t, t = t->next)
35 if (r->max && r->max + 1 < t->min)
36 break;
37 l->max = (p->max && r->max) ? MAX(p->max, r->max) : 0;
38 l->next = t;
39 return;
40 }
41 }
42 if (p)
43 p->next = r;
44 else
45 list = r;
46}
47
48static void
49parselist(char *str)
50{
51 char *s;
52 size_t n = 1;
53 Range *r;
54
55 if (!*str)
56 eprintf("empty list\n");
57 for (s = str; *s; s++) {
58 if (*s == ' ')
59 *s = ',';
60 if (*s == ',')
61 n++;
62 }
63 r = ereallocarray(NULL, n, sizeof(*r));
64 for (s = str; n; n--, s++) {
65 r->min = (*s == '-') ? 1 : strtoul(s, &s, 10);
66 r->max = (*s == '-') ? strtoul(s + 1, &s, 10) : r->min;
67 r->next = NULL;
68 if (!r->min || (r->max && r->max < r->min) || (*s && *s != ','))
69 eprintf("bad list value\n");
70 insert(r++);
71 }
72}
73
74static size_t
75seek(struct line *s, size_t pos, size_t *prev, size_t count)
76{
77 size_t n = pos - *prev, i, j;
78
79 if (mode == 'b') {
80 if (n >= s->len)
81 return s->len;
82 if (nflag)
83 while (n && !UTF8_POINT(s->data[n]))
84 n--;
85 *prev += n;
86 return n;
87 } else if (mode == 'c') {
88 for (n++, i = 0; i < s->len; i++)
89 if (UTF8_POINT(s->data[i]) && !--n)
90 break;
91 } else {
92 for (i = (count < delimlen + 1) ? 0 : delimlen; n && i < s->len;) {
93 if ((s->len - i) >= delimlen && !memcmp(s->data + i, delim, delimlen)) {
94 if (!--n && count)
95 break;
96 i += delimlen;
97 continue;
98 }
99 for (j = 1; j + i <= s->len && !fullrune(s->data + i, j); j++)
100 ;
101 i += j;
102 }
103 }
104 *prev = pos;
105
106 return i;
107}
108
109static void
110cut(FILE *fp, const char *fname)
111{
112 Range *r;
113 struct line s;
114 static struct line line;
115 static size_t size;
116 size_t i, n, p;
117 ssize_t len;
118
119 while ((len = getline(&line.data, &size, fp)) > 0) {
120 line.len = len;
121 if (line.data[line.len - 1] == '\n')
122 line.data[--line.len] = '\0';
123 if (mode == 'f' && !memmem(line.data, line.len, delim, delimlen)) {
124 if (!sflag) {
125 fwrite(line.data, 1, line.len, stdout);
126 fputc('\n', stdout);
127 }
128 continue;
129 }
130 for (i = 0, p = 1, s = line, r = list; r; r = r->next) {
131 n = seek(&s, r->min, &p, i);
132 s.data += n;
133 s.len -= n;
134 i += (mode == 'f') ? delimlen : 1;
135 if (!s.len)
136 break;
137 if (!r->max) {
138 fwrite(s.data, 1, s.len, stdout);
139 break;
140 }
141 n = seek(&s, r->max + 1, &p, i);
142 i += (mode == 'f') ? delimlen : 1;
143 if (fwrite(s.data, 1, n, stdout) != n)
144 eprintf("fwrite <stdout>:");
145 s.data += n;
146 s.len -= n;
147 }
148 putchar('\n');
149 }
150 if (ferror(fp))
151 eprintf("getline %s:", fname);
152}
153
154static void
155usage(void)
156{
157 eprintf(
158 "usage: %s -b list [-n] [file ...]\n"
159 " %s -c list [file ...]\n"
160 " %s -f list [-d delim] [-s] [file ...]\n",
161 argv0,
162 argv0,
163 argv0
164 );
165}
166
167// ?man cut: cut out fields from lines
168// ?man arguments: -b list [file ...]
169// ?man print selected parts of lines from files
170int
171main(int argc, char *argv[])
172{
173 FILE *fp;
174 int ret = 0;
175
176 ARGBEGIN
177 {
178 // ?man -b: specify block size or base directory
179 case 'b':
180 // ?man -c: print count or perform stdout action
181 case 'c':
182 // ?man -f:mode: force the operation
183 case 'f':
184 mode = ARGC();
185 parselist(EARGF(usage()));
186 break;
187 // ?man -d:str: specify directory
188 case 'd':
189 delim = EARGF(usage());
190 if (!*delim)
191 eprintf("empty delimiter\n");
192 delimlen = unescape(delim);
193 break;
194 // ?man -n: print line numbers or counts
195 case 'n':
196 nflag = 1;
197 break;
198 // ?man -s: silent mode or print summary
199 case 's':
200 sflag = 1;
201 break;
202 default:
203 usage();
204 }
205 ARGEND
206
207 if (!mode)
208 usage();
209
210 if (!argc)
211 cut(stdin, "<stdin>");
212 else {
213 for (; *argv; argc--, argv++) {
214 if (!strcmp(*argv, "-")) {
215 *argv = "<stdin>";
216 fp = stdin;
217 } else if (!(fp = fopen(*argv, "r"))) {
218 weprintf("fopen %s:", *argv);
219 ret = 1;
220 continue;
221 }
222 cut(fp, *argv);
223 if (fp != stdin && fshut(fp, *argv))
224 ret = 1;
225 }
226 }
227
228 ret |= fshut(stdin, "<stdin>") | fshut(stdout, "<stdout>");
229
230 return ret;
231}