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