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, &section)) {
     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, 0))
     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_MAX));
    155 		break;
    156 	case 'l':
    157 		blines = estrtonum(EARGF(usage()), 0, MIN(LLONG_MAX, SIZE_MAX));
    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, SIZE_MAX));
    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 }