Annotation of src/usr.bin/nl/nl.c, Revision 1.1
1.1 ! jca 1: /* $OpenBSD$ */
! 2: /* $NetBSD: nl.c,v 1.11 2011/08/16 12:00:46 christos Exp $ */
! 3:
! 4: /*-
! 5: * Copyright (c) 1999 The NetBSD Foundation, Inc.
! 6: * All rights reserved.
! 7: *
! 8: * This code is derived from software contributed to The NetBSD Foundation
! 9: * by Klaus Klein.
! 10: *
! 11: * Redistribution and use in source and binary forms, with or without
! 12: * modification, are permitted provided that the following conditions
! 13: * are met:
! 14: * 1. Redistributions of source code must retain the above copyright
! 15: * notice, this list of conditions and the following disclaimer.
! 16: * 2. Redistributions in binary form must reproduce the above copyright
! 17: * notice, this list of conditions and the following disclaimer in the
! 18: * documentation and/or other materials provided with the distribution.
! 19: *
! 20: * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
! 21: * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
! 22: * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
! 23: * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
! 24: * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
! 25: * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
! 26: * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
! 27: * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
! 28: * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
! 29: * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
! 30: * POSSIBILITY OF SUCH DAMAGE.
! 31: */
! 32:
! 33: #include <sys/cdefs.h>
! 34:
! 35: #include <err.h>
! 36: #include <errno.h>
! 37: #include <limits.h>
! 38: #include <locale.h>
! 39: #include <regex.h>
! 40: #include <stdio.h>
! 41: #include <stdlib.h>
! 42: #include <string.h>
! 43: #include <wchar.h>
! 44:
! 45: typedef enum {
! 46: number_all, /* number all lines */
! 47: number_nonempty, /* number non-empty lines */
! 48: number_none, /* no line numbering */
! 49: number_regex /* number lines matching regular expression */
! 50: } numbering_type;
! 51:
! 52: struct numbering_property {
! 53: const char * const name; /* for diagnostics */
! 54: numbering_type type; /* numbering type */
! 55: regex_t expr; /* for type == number_regex */
! 56: };
! 57:
! 58: /* line numbering formats */
! 59: #define FORMAT_LN "%-*d" /* left justified, leading zeros suppressed */
! 60: #define FORMAT_RN "%*d" /* right justified, leading zeros suppressed */
! 61: #define FORMAT_RZ "%0*d" /* right justified, leading zeros kept */
! 62:
! 63: #define FOOTER 0
! 64: #define BODY 1
! 65: #define HEADER 2
! 66: #define NP_LAST HEADER
! 67:
! 68: static struct numbering_property numbering_properties[NP_LAST + 1] = {
! 69: { "footer", number_none, { 0, 0, 0, 0 } },
! 70: { "body", number_nonempty, { 0, 0, 0, 0 } },
! 71: { "header", number_none, { 0, 0, 0, 0 } },
! 72: };
! 73:
! 74: void filter(void);
! 75: void parse_numbering(const char *, int);
! 76: __dead void usage(void);
! 77:
! 78: /*
! 79: * Delimiter characters that indicate the start of a logical page section.
! 80: */
! 81: static char delim[2 * MB_LEN_MAX];
! 82: static int delimlen;
! 83:
! 84: /*
! 85: * Configurable parameters.
! 86: */
! 87:
! 88: /* line numbering format */
! 89: static const char *format = FORMAT_RN;
! 90:
! 91: /* increment value used to number logical page lines */
! 92: static int incr = 1;
! 93:
! 94: /* number of adjacent blank lines to be considered (and numbered) as one */
! 95: static unsigned int nblank = 1;
! 96:
! 97: /* whether to restart numbering at logical page delimiters */
! 98: static int restart = 1;
! 99:
! 100: /* characters used in separating the line number and the corrsp. text line */
! 101: static const char *sep = "\t";
! 102:
! 103: /* initial value used to number logical page lines */
! 104: static int startnum = 1;
! 105:
! 106: /* number of characters to be used for the line number */
! 107: /* should be unsigned but required signed by `*' precision conversion */
! 108: static int width = 6;
! 109:
! 110:
! 111: int
! 112: main(int argc, char *argv[])
! 113: {
! 114: int c;
! 115: size_t clen;
! 116: char delim1[MB_LEN_MAX] = { '\\' }, delim2[MB_LEN_MAX] = { ':' };
! 117: size_t delim1len = 1, delim2len = 1;
! 118: const char *errstr;
! 119:
! 120: (void)setlocale(LC_ALL, "");
! 121:
! 122: while ((c = getopt(argc, argv, "pb:d:f:h:i:l:n:s:v:w:")) != -1) {
! 123: switch (c) {
! 124: case 'p':
! 125: restart = 0;
! 126: break;
! 127: case 'b':
! 128: parse_numbering(optarg, BODY);
! 129: break;
! 130: case 'd':
! 131: clen = mbrlen(optarg, MB_CUR_MAX, NULL);
! 132: if (clen == (size_t)-1 || clen == (size_t)-2) {
! 133: errno = EILSEQ;
! 134: err(EXIT_FAILURE, NULL);
! 135: }
! 136: if (clen != 0) {
! 137: memcpy(delim1, optarg, delim1len = clen);
! 138: clen = mbrlen(optarg + delim1len,
! 139: MB_CUR_MAX, NULL);
! 140: if (clen == (size_t)-1 || clen == (size_t)-2) {
! 141: errno = EILSEQ;
! 142: err(EXIT_FAILURE, NULL);
! 143: }
! 144: if (clen != 0) {
! 145: memcpy(delim2, optarg + delim1len,
! 146: delim2len = clen);
! 147: if (optarg[delim1len + clen] != '\0') {
! 148: errx(EXIT_FAILURE,
! 149: "invalid delimiter: %s",
! 150: optarg);
! 151: }
! 152: }
! 153: }
! 154: break;
! 155: case 'f':
! 156: parse_numbering(optarg, FOOTER);
! 157: break;
! 158: case 'h':
! 159: parse_numbering(optarg, HEADER);
! 160: break;
! 161: case 'i':
! 162: incr = strtonum(optarg, INT_MIN, INT_MAX, &errstr);
! 163: if (errstr)
! 164: errx(EXIT_FAILURE, "increment value is %s: %s",
! 165: errstr, optarg);
! 166: break;
! 167: case 'l':
! 168: nblank = strtonum(optarg, 0, UINT_MAX, &errstr);
! 169: if (errstr)
! 170: errx(EXIT_FAILURE,
! 171: "blank line value is %s: %s",
! 172: errstr, optarg);
! 173: break;
! 174: case 'n':
! 175: if (strcmp(optarg, "ln") == 0) {
! 176: format = FORMAT_LN;
! 177: } else if (strcmp(optarg, "rn") == 0) {
! 178: format = FORMAT_RN;
! 179: } else if (strcmp(optarg, "rz") == 0) {
! 180: format = FORMAT_RZ;
! 181: } else
! 182: errx(EXIT_FAILURE,
! 183: "illegal format -- %s", optarg);
! 184: break;
! 185: case 's':
! 186: sep = optarg;
! 187: break;
! 188: case 'v':
! 189: startnum = strtonum(optarg, INT_MIN, INT_MAX, &errstr);
! 190: if (errstr)
! 191: errx(EXIT_FAILURE,
! 192: "initial logical page value is %s: %s",
! 193: errstr, optarg);
! 194: break;
! 195: case 'w':
! 196: width = strtonum(optarg, 1, INT_MAX, &errstr);
! 197: if (errstr)
! 198: errx(EXIT_FAILURE, "width is %s: %s", errstr,
! 199: optarg);
! 200: break;
! 201: case '?':
! 202: default:
! 203: usage();
! 204: /* NOTREACHED */
! 205: }
! 206: }
! 207: argc -= optind;
! 208: argv += optind;
! 209:
! 210: switch (argc) {
! 211: case 0:
! 212: break;
! 213: case 1:
! 214: if (strcmp(argv[0], "-") != 0 &&
! 215: freopen(argv[0], "r", stdin) == NULL)
! 216: err(EXIT_FAILURE, "%s", argv[0]);
! 217: break;
! 218: default:
! 219: usage();
! 220: /* NOTREACHED */
! 221: }
! 222:
! 223: /* Generate the delimiter sequence */
! 224: memcpy(delim, delim1, delim1len);
! 225: memcpy(delim + delim1len, delim2, delim2len);
! 226: delimlen = delim1len + delim2len;
! 227:
! 228: /* Do the work. */
! 229: filter();
! 230:
! 231: exit(EXIT_SUCCESS);
! 232: }
! 233:
! 234: void
! 235: filter(void)
! 236: {
! 237: char *buffer;
! 238: size_t buffersize;
! 239: ssize_t linelen;
! 240: int line; /* logical line number */
! 241: int section; /* logical page section */
! 242: unsigned int adjblank; /* adjacent blank lines */
! 243: int donumber = 0, idx;
! 244:
! 245: adjblank = 0;
! 246: line = startnum;
! 247: section = BODY;
! 248:
! 249: buffer = NULL;
! 250: buffersize = 0;
! 251: while ((linelen = getline(&buffer, &buffersize, stdin)) > 0) {
! 252: for (idx = FOOTER; idx <= NP_LAST; idx++) {
! 253: /* Does it look like a delimiter? */
! 254: if (delimlen * (idx + 1) > linelen)
! 255: break;
! 256: if (memcmp(buffer + delimlen * idx, delim,
! 257: delimlen) != 0)
! 258: break;
! 259: /* Was this the whole line? */
! 260: if (buffer[delimlen * (idx + 1)] == '\n') {
! 261: section = idx;
! 262: adjblank = 0;
! 263: if (restart)
! 264: line = startnum;
! 265: goto nextline;
! 266: }
! 267: }
! 268:
! 269: switch (numbering_properties[section].type) {
! 270: case number_all:
! 271: /*
! 272: * Doing this for number_all only is disputable, but
! 273: * the standard expresses an explicit dependency on
! 274: * `-b a' etc.
! 275: */
! 276: if (buffer[0] == '\n' && ++adjblank < nblank)
! 277: donumber = 0;
! 278: else
! 279: donumber = 1, adjblank = 0;
! 280: break;
! 281: case number_nonempty:
! 282: donumber = (buffer[0] != '\n');
! 283: break;
! 284: case number_none:
! 285: donumber = 0;
! 286: break;
! 287: case number_regex:
! 288: donumber =
! 289: (regexec(&numbering_properties[section].expr,
! 290: buffer, 0, NULL, 0) == 0);
! 291: break;
! 292: }
! 293:
! 294: if (donumber) {
! 295: (void)printf(format, width, line);
! 296: line += incr;
! 297: (void)fputs(sep, stdout);
! 298: } else {
! 299: (void)printf("%*s", width, "");
! 300: }
! 301: (void)fwrite(buffer, linelen, 1, stdout);
! 302:
! 303: if (ferror(stdout))
! 304: err(EXIT_FAILURE, "output error");
! 305: nextline:
! 306: ;
! 307: }
! 308:
! 309: if (ferror(stdin))
! 310: err(EXIT_FAILURE, "input error");
! 311:
! 312: free(buffer);
! 313: }
! 314:
! 315: /*
! 316: * Various support functions.
! 317: */
! 318:
! 319: void
! 320: parse_numbering(const char *argstr, int section)
! 321: {
! 322: int error;
! 323: char errorbuf[NL_TEXTMAX];
! 324:
! 325: switch (argstr[0]) {
! 326: case 'a':
! 327: numbering_properties[section].type = number_all;
! 328: break;
! 329: case 'n':
! 330: numbering_properties[section].type = number_none;
! 331: break;
! 332: case 't':
! 333: numbering_properties[section].type = number_nonempty;
! 334: break;
! 335: case 'p':
! 336: /* If there was a previous expression, throw it away. */
! 337: if (numbering_properties[section].type == number_regex)
! 338: regfree(&numbering_properties[section].expr);
! 339: else
! 340: numbering_properties[section].type = number_regex;
! 341:
! 342: /* Compile/validate the supplied regular expression. */
! 343: if ((error = regcomp(&numbering_properties[section].expr,
! 344: &argstr[1], REG_NEWLINE|REG_NOSUB)) != 0) {
! 345: (void)regerror(error,
! 346: &numbering_properties[section].expr,
! 347: errorbuf, sizeof(errorbuf));
! 348: errx(EXIT_FAILURE,
! 349: "%s expr: %s -- %s",
! 350: numbering_properties[section].name, errorbuf,
! 351: &argstr[1]);
! 352: }
! 353: break;
! 354: default:
! 355: errx(EXIT_FAILURE,
! 356: "illegal %s line numbering type -- %s",
! 357: numbering_properties[section].name, argstr);
! 358: }
! 359: }
! 360:
! 361: __dead void
! 362: usage(void)
! 363: {
! 364: (void)fprintf(stderr, "usage: %s [-p] [-b type] [-d delim] [-f type] "
! 365: "[-h type] [-i incr] [-l num]\n\t[-n format] [-s sep] "
! 366: "[-v startnum] [-w width] [file]\n", getprogname());
! 367: exit(EXIT_FAILURE);
! 368: }