master xplshn/aruu / cmd / posix / nl.c
  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, &section)) {
 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}