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
11struct Range {
12 size_t min, max;
13 struct Range *next;
14};
15
16static struct 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(struct Range *r)
25{
26 struct 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 struct 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 struct Range *r;
113
114 struct line s;
115 static struct line line;
116 static size_t size;
117 size_t i, n, p;
118 ssize_t len;
119
120 while ((len = getline(&line.data, &size, fp)) > 0) {
121 line.len = len;
122 if (line.data[line.len - 1] == '\n')
123 line.data[--line.len] = '\0';
124 if (mode == 'f' && !memmem(line.data, line.len, delim, delimlen)) {
125 if (!sflag) {
126 fwrite(line.data, 1, line.len, stdout);
127 fputc('\n', stdout);
128 }
129 continue;
130 }
131 for (i = 0, p = 1, s = line, r = list; r; r = r->next) {
132 n = seek(&s, r->min, &p, i);
133 s.data += n;
134 s.len -= n;
135 i += (mode == 'f') ? delimlen : 1;
136 if (!s.len)
137 break;
138 if (!r->max) {
139 fwrite(s.data, 1, s.len, stdout);
140 break;
141 }
142 n = seek(&s, r->max + 1, &p, i);
143 i += (mode == 'f') ? delimlen : 1;
144 if (fwrite(s.data, 1, n, stdout) != n)
145 eprintf("fwrite <stdout>:");
146 s.data += n;
147 s.len -= n;
148 }
149 putchar('\n');
150 }
151 if (ferror(fp))
152 eprintf("getline %s:", fname);
153}
154
155static void
156usage(void)
157{
158 eprintf(
159 "usage: %s -b list [-n] [file ...]\n"
160 " %s -c list [file ...]\n"
161 " %s -f list [-d delim] [-s] [file ...]\n",
162 argv0,
163 argv0,
164 argv0
165 );
166}
167
168// ?man cut: cut out fields from lines
169// ?man arguments: -b list [file ...]
170// ?man print selected parts of lines from files
171int
172main(int argc, char *argv[])
173{
174 FILE *fp;
175 int ret = 0;
176
177 ARGBEGIN
178 {
179 // ?man -b: specify block size or base directory
180 case 'b':
181 // ?man -c: print count or perform stdout action
182 case 'c':
183 // ?man -f:mode: force the operation
184 case 'f':
185 mode = ARGC();
186 parselist(EARGF(usage()));
187 break;
188 // ?man -d:str: specify directory
189 case 'd':
190 delim = EARGF(usage());
191 if (!*delim)
192 eprintf("empty delimiter\n");
193 delimlen = unescape(delim);
194 break;
195 // ?man -n: print line numbers or counts
196 case 'n':
197 nflag = 1;
198 break;
199 // ?man -s: silent mode or print summary
200 case 's':
201 sflag = 1;
202 break;
203 default:
204 usage();
205 }
206 ARGEND
207
208 if (!mode)
209 usage();
210
211 if (!argc)
212 cut(stdin, "<stdin>");
213 else {
214 for (; *argv; argc--, argv++) {
215 if (!strcmp(*argv, "-")) {
216 *argv = "<stdin>";
217 fp = stdin;
218 } else if (!(fp = fopen(*argv, "r"))) {
219 weprintf("fopen %s:", *argv);
220 ret = 1;
221 continue;
222 }
223 cut(fp, *argv);
224 if (fp != stdin && fshut(fp, *argv))
225 ret = 1;
226 }
227 }
228
229 ret |= fshut(stdin, "<stdin>") | fshut(stdout, "<stdout>");
230
231 return ret;
232}