nl.c - sbase - suckless unix tools | |
git clone git://git.suckless.org/sbase | |
Log | |
Files | |
Refs | |
README | |
LICENSE | |
--- | |
nl.c (4369B) | |
--- | |
1 /* See LICENSE file for copyright and license details. */ | |
2 #include <limits.h> | |
3 #include <stdint.h> | |
4 #include <stdio.h> | |
5 #include <stdlib.h> | |
6 #include <string.h> | |
7 | |
8 #include "text.h" | |
9 #include "utf.h" | |
10 #include "util.h" | |
11 | |
12 static size_t startnum = 1; | |
13 static size_t incr = 1; | |
14 static size_t blines = 1; | |
15 static size_t delimlen = 2; | |
16 static size_t seplen = 1; | |
17 static int width = 6; | |
18 static int pflag = 0; | |
19 static char type[] = { 'n', 't', 'n' }; /* footer, body, header */ | |
20 static char *delim = "\\:"; | |
21 static char format[6] = "%*ld"; | |
22 static char *sep = "\t"; | |
23 static regex_t preg[3]; | |
24 | |
25 static int | |
26 getsection(struct line *l, int *section) | |
27 { | |
28 size_t i; | |
29 int sectionchanged = 0, newsection = *section; | |
30 | |
31 for (i = 0; (l->len - i) >= delimlen && | |
32 !memcmp(l->data + i, delim, delimlen); i += delimlen) { | |
33 if (!sectionchanged) { | |
34 sectionchanged = 1; | |
35 newsection = 0; | |
36 } else { | |
37 newsection = (newsection + 1) % 3; | |
38 } | |
39 } | |
40 | |
41 if (!(l->len - i) || l->data[i] == '\n') | |
42 *section = newsection; | |
43 else | |
44 sectionchanged = 0; | |
45 | |
46 return sectionchanged; | |
47 } | |
48 | |
49 static void | |
50 nl(const char *fname, FILE *fp) | |
51 { | |
52 static struct line line; | |
53 static size_t size; | |
54 size_t number = startnum, bl = 1; | |
55 ssize_t len; | |
56 int donumber, oldsection, section = 1; | |
57 | |
58 while ((len = getline(&line.data, &size, fp)) > 0) { | |
59 line.len = len; | |
60 donumber = 0; | |
61 oldsection = section; | |
62 | |
63 if (getsection(&line, §ion)) { | |
64 if ((section >= oldsection) && !pflag) | |
65 number = startnum; | |
66 continue; | |
67 } | |
68 | |
69 switch (type[section]) { | |
70 case 't': | |
71 if (line.data[0] != '\n') | |
72 donumber = 1; | |
73 break; | |
74 case 'p': | |
75 if (!regexec(preg + section, line.data, 0, NULL,… | |
76 donumber = 1; | |
77 break; | |
78 case 'a': | |
79 if (line.data[0] == '\n' && bl < blines) { | |
80 ++bl; | |
81 } else { | |
82 donumber = 1; | |
83 bl = 1; | |
84 } | |
85 } | |
86 | |
87 if (donumber) { | |
88 printf(format, width, number); | |
89 fwrite(sep, 1, seplen, stdout); | |
90 number += incr; | |
91 } | |
92 fwrite(line.data, 1, line.len, stdout); | |
93 } | |
94 free(line.data); | |
95 if (ferror(fp)) | |
96 eprintf("getline %s:", fname); | |
97 } | |
98 | |
99 static void | |
100 usage(void) | |
101 { | |
102 eprintf("usage: %s [-p] [-b type] [-d delim] [-f type]\n" | |
103 " [-h type] [-i num] [-l num] [-n format]\n" | |
104 " [-s sep] [-v num] [-w num] [file]\n", argv0); | |
105 } | |
106 | |
107 static char | |
108 getlinetype(char *type, regex_t *preg) | |
109 { | |
110 if (type[0] == 'p') | |
111 eregcomp(preg, type + 1, REG_NOSUB); | |
112 else if (!type[0] || !strchr("ant", type[0])) | |
113 usage(); | |
114 | |
115 return type[0]; | |
116 } | |
117 | |
118 int | |
119 main(int argc, char *argv[]) | |
120 { | |
121 FILE *fp = NULL; | |
122 size_t s; | |
123 int ret = 0; | |
124 char *d, *formattype, *formatblit; | |
125 | |
126 ARGBEGIN { | |
127 case 'd': | |
128 switch (utflen((d = EARGF(usage())))) { | |
129 case 0: | |
130 eprintf("empty logical page delimiter\n"); | |
131 case 1: | |
132 s = strlen(d); | |
133 delim = emalloc(s + 1 + 1); | |
134 estrlcpy(delim, d, s + 1 + 1); | |
135 estrlcat(delim, ":", s + 1 + 1); | |
136 delimlen = s + 1; | |
137 break; | |
138 default: | |
139 delim = d; | |
140 delimlen = strlen(delim); | |
141 break; | |
142 } | |
143 break; | |
144 case 'f': | |
145 type[0] = getlinetype(EARGF(usage()), preg); | |
146 break; | |
147 case 'b': | |
148 type[1] = getlinetype(EARGF(usage()), preg + 1); | |
149 break; | |
150 case 'h': | |
151 type[2] = getlinetype(EARGF(usage()), preg + 2); | |
152 break; | |
153 case 'i': | |
154 incr = estrtonum(EARGF(usage()), 0, MIN(LLONG_MAX, SIZE_… | |
155 break; | |
156 case 'l': | |
157 blines = estrtonum(EARGF(usage()), 0, MIN(LLONG_MAX, SIZ… | |
158 break; | |
159 case 'n': | |
160 formattype = EARGF(usage()); | |
161 estrlcpy(format, "%", sizeof(format)); | |
162 | |
163 if (!strcmp(formattype, "ln")) { | |
164 formatblit = "-"; | |
165 } else if (!strcmp(formattype, "rn")) { | |
166 formatblit = ""; | |
167 } else if (!strcmp(formattype, "rz")) { | |
168 formatblit = "0"; | |
169 } else { | |
170 eprintf("%s: bad format\n", formattype); | |
171 } | |
172 | |
173 estrlcat(format, formatblit, sizeof(format)); | |
174 estrlcat(format, "*ld", sizeof(format)); | |
175 break; | |
176 case 'p': | |
177 pflag = 1; | |
178 break; | |
179 case 's': | |
180 sep = EARGF(usage()); | |
181 seplen = unescape(sep); | |
182 break; | |
183 case 'v': | |
184 startnum = estrtonum(EARGF(usage()), 0, MIN(LLONG_MAX, S… | |
185 break; | |
186 case 'w': | |
187 width = estrtonum(EARGF(usage()), 1, INT_MAX); | |
188 break; | |
189 default: | |
190 usage(); | |
191 } ARGEND | |
192 | |
193 if (argc > 1) | |
194 usage(); | |
195 | |
196 if (!argc) { | |
197 nl("<stdin>", stdin); | |
198 } else { | |
199 if (!strcmp(argv[0], "-")) { | |
200 argv[0] = "<stdin>"; | |
201 fp = stdin; | |
202 } else if (!(fp = fopen(argv[0], "r"))) { | |
203 eprintf("fopen %s:", argv[0]); | |
204 } | |
205 nl(argv[0], fp); | |
206 } | |
207 | |
208 ret |= fp && fp != stdin && fshut(fp, argv[0]); | |
209 ret |= fshut(stdin, "<stdin>") | fshut(stdout, "<stdout>"); | |
210 | |
211 return ret; | |
212 } |