176273Sbrian/*- 276273Sbrian * Copyright (c) 1999 The NetBSD Foundation, Inc. 376273Sbrian * All rights reserved. 476273Sbrian * 576273Sbrian * This code is derived from software contributed to The NetBSD Foundation 676273Sbrian * by Klaus Klein. 776273Sbrian * 876273Sbrian * Redistribution and use in source and binary forms, with or without 976273Sbrian * modification, are permitted provided that the following conditions 1076273Sbrian * are met: 1176273Sbrian * 1. Redistributions of source code must retain the above copyright 1276273Sbrian * notice, this list of conditions and the following disclaimer. 1376273Sbrian * 2. Redistributions in binary form must reproduce the above copyright 1476273Sbrian * notice, this list of conditions and the following disclaimer in the 1576273Sbrian * documentation and/or other materials provided with the distribution. 1676273Sbrian * 1776273Sbrian * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS 1876273Sbrian * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED 1976273Sbrian * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 2076273Sbrian * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS 2176273Sbrian * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 2276273Sbrian * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 2376273Sbrian * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 2476273Sbrian * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 2576273Sbrian * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 2676273Sbrian * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 2776273Sbrian * POSSIBILITY OF SUCH DAMAGE. 2876273Sbrian */ 2976273Sbrian 3076273Sbrian#include <sys/cdefs.h> 3176273Sbrian#ifndef lint 3276273Sbrian__COPYRIGHT( 3376273Sbrian"@(#) Copyright (c) 1999\ 3476273Sbrian The NetBSD Foundation, Inc. All rights reserved."); 3576273Sbrian__RCSID("$FreeBSD$"); 3676273Sbrian#endif 3776273Sbrian 38189168Sdas#define _WITH_GETLINE 3976273Sbrian#include <sys/types.h> 4076273Sbrian 4197337Stjr#include <err.h> 4276273Sbrian#include <errno.h> 4376273Sbrian#include <limits.h> 4476273Sbrian#include <locale.h> 4576273Sbrian#include <regex.h> 4676273Sbrian#include <stdio.h> 4776273Sbrian#include <stdlib.h> 4876273Sbrian#include <string.h> 4976273Sbrian#include <unistd.h> 50132078Stjr#include <wchar.h> 5176273Sbrian 5276273Sbriantypedef enum { 5376273Sbrian number_all, /* number all lines */ 5476273Sbrian number_nonempty, /* number non-empty lines */ 5576273Sbrian number_none, /* no line numbering */ 5676273Sbrian number_regex /* number lines matching regular expression */ 5776273Sbrian} numbering_type; 5876273Sbrian 5976273Sbrianstruct numbering_property { 6076273Sbrian const char * const name; /* for diagnostics */ 6176273Sbrian numbering_type type; /* numbering type */ 6276273Sbrian regex_t expr; /* for type == number_regex */ 6376273Sbrian}; 6476273Sbrian 6576273Sbrian/* line numbering formats */ 6676273Sbrian#define FORMAT_LN "%-*d" /* left justified, leading zeros suppressed */ 6776273Sbrian#define FORMAT_RN "%*d" /* right justified, leading zeros suppressed */ 6876273Sbrian#define FORMAT_RZ "%0*d" /* right justified, leading zeros kept */ 6976273Sbrian 7076273Sbrian#define FOOTER 0 7176273Sbrian#define BODY 1 7276273Sbrian#define HEADER 2 7376273Sbrian#define NP_LAST HEADER 7476273Sbrian 7576273Sbrianstatic struct numbering_property numbering_properties[NP_LAST + 1] = { 76226362Sed { .name = "footer", .type = number_none }, 77226362Sed { .name = "body", .type = number_nonempty }, 78226362Sed { .name = "header", .type = number_none } 7976273Sbrian}; 8076273Sbrian 8176273Sbrian#define max(a, b) ((a) > (b) ? (a) : (b)) 8276273Sbrian 8376273Sbrian/* 8476273Sbrian * Maximum number of characters required for a decimal representation of a 8576273Sbrian * (signed) int; courtesy of tzcode. 8676273Sbrian */ 8776273Sbrian#define INT_STRLEN_MAXIMUM \ 8876273Sbrian ((sizeof (int) * CHAR_BIT - 1) * 302 / 1000 + 2) 8976273Sbrian 9092921Simpstatic void filter(void); 9192921Simpstatic void parse_numbering(const char *, int); 9292921Simpstatic void usage(void); 9376273Sbrian 9476273Sbrian/* 9576273Sbrian * Dynamically allocated buffer suitable for string representation of ints. 9676273Sbrian */ 9776273Sbrianstatic char *intbuffer; 9876273Sbrian 99132078Stjr/* delimiter characters that indicate the start of a logical page section */ 100132078Stjrstatic char delim[2 * MB_LEN_MAX]; 101132078Stjrstatic int delimlen; 102132078Stjr 10376273Sbrian/* 10476273Sbrian * Configurable parameters. 10576273Sbrian */ 10676273Sbrian 10776273Sbrian/* line numbering format */ 10876273Sbrianstatic const char *format = FORMAT_RN; 10976273Sbrian 11076273Sbrian/* increment value used to number logical page lines */ 11176273Sbrianstatic int incr = 1; 11276273Sbrian 11376273Sbrian/* number of adjacent blank lines to be considered (and numbered) as one */ 11476273Sbrianstatic unsigned int nblank = 1; 11576273Sbrian 11676273Sbrian/* whether to restart numbering at logical page delimiters */ 11776273Sbrianstatic int restart = 1; 11876273Sbrian 11976273Sbrian/* characters used in separating the line number and the corrsp. text line */ 12076273Sbrianstatic const char *sep = "\t"; 12176273Sbrian 12276273Sbrian/* initial value used to number logical page lines */ 12376273Sbrianstatic int startnum = 1; 12476273Sbrian 12576273Sbrian/* number of characters to be used for the line number */ 12676273Sbrian/* should be unsigned but required signed by `*' precision conversion */ 12776273Sbrianstatic int width = 6; 12876273Sbrian 12976273Sbrian 13076273Sbrianint 131226362Sedmain(int argc, char *argv[]) 13276273Sbrian{ 133144840Sstefanf int c; 13476273Sbrian long val; 13576273Sbrian unsigned long uval; 13676273Sbrian char *ep; 137132078Stjr size_t intbuffersize, clen; 138132078Stjr char delim1[MB_LEN_MAX] = { '\\' }, delim2[MB_LEN_MAX] = { ':' }; 139132078Stjr size_t delim1len = 1, delim2len = 1; 14076273Sbrian 14176273Sbrian (void)setlocale(LC_ALL, ""); 14276273Sbrian 14376273Sbrian while ((c = getopt(argc, argv, "pb:d:f:h:i:l:n:s:v:w:")) != -1) { 14476273Sbrian switch (c) { 14576273Sbrian case 'p': 14676273Sbrian restart = 0; 14776273Sbrian break; 14876273Sbrian case 'b': 14976273Sbrian parse_numbering(optarg, BODY); 15076273Sbrian break; 15176273Sbrian case 'd': 152132078Stjr clen = mbrlen(optarg, MB_CUR_MAX, NULL); 153132078Stjr if (clen == (size_t)-1 || clen == (size_t)-2) 154132078Stjr errc(EXIT_FAILURE, EILSEQ, NULL); 155132078Stjr if (clen != 0) { 156132078Stjr memcpy(delim1, optarg, delim1len = clen); 157132078Stjr clen = mbrlen(optarg + delim1len, 158132078Stjr MB_CUR_MAX, NULL); 159132078Stjr if (clen == (size_t)-1 || 160132078Stjr clen == (size_t)-2) 161132078Stjr errc(EXIT_FAILURE, EILSEQ, NULL); 162132078Stjr if (clen != 0) { 163132078Stjr memcpy(delim2, optarg + delim1len, 164132078Stjr delim2len = clen); 165132078Stjr if (optarg[delim1len + clen] != '\0') 166132078Stjr errx(EXIT_FAILURE, 167132078Stjr "invalid delim argument -- %s", 168132078Stjr optarg); 169132078Stjr } 17076273Sbrian } 17176273Sbrian break; 17276273Sbrian case 'f': 17376273Sbrian parse_numbering(optarg, FOOTER); 17476273Sbrian break; 17576273Sbrian case 'h': 17676273Sbrian parse_numbering(optarg, HEADER); 17776273Sbrian break; 17876273Sbrian case 'i': 17976273Sbrian errno = 0; 18076273Sbrian val = strtol(optarg, &ep, 10); 18176273Sbrian if ((ep != NULL && *ep != '\0') || 18297338Stjr ((val == LONG_MIN || val == LONG_MAX) && errno != 0)) 18397338Stjr errx(EXIT_FAILURE, 18497338Stjr "invalid incr argument -- %s", optarg); 18576273Sbrian incr = (int)val; 18676273Sbrian break; 18776273Sbrian case 'l': 18876273Sbrian errno = 0; 18976273Sbrian uval = strtoul(optarg, &ep, 10); 19076273Sbrian if ((ep != NULL && *ep != '\0') || 19197338Stjr (uval == ULONG_MAX && errno != 0)) 19297338Stjr errx(EXIT_FAILURE, 19397338Stjr "invalid num argument -- %s", optarg); 19476273Sbrian nblank = (unsigned int)uval; 19576273Sbrian break; 19676273Sbrian case 'n': 19776273Sbrian if (strcmp(optarg, "ln") == 0) { 19876273Sbrian format = FORMAT_LN; 19976273Sbrian } else if (strcmp(optarg, "rn") == 0) { 20076273Sbrian format = FORMAT_RN; 20176273Sbrian } else if (strcmp(optarg, "rz") == 0) { 20276273Sbrian format = FORMAT_RZ; 20397338Stjr } else 20497338Stjr errx(EXIT_FAILURE, 20597338Stjr "illegal format -- %s", optarg); 20676273Sbrian break; 20776273Sbrian case 's': 20876273Sbrian sep = optarg; 20976273Sbrian break; 21076273Sbrian case 'v': 21176273Sbrian errno = 0; 21276273Sbrian val = strtol(optarg, &ep, 10); 21376273Sbrian if ((ep != NULL && *ep != '\0') || 21497338Stjr ((val == LONG_MIN || val == LONG_MAX) && errno != 0)) 21597338Stjr errx(EXIT_FAILURE, 21697338Stjr "invalid startnum value -- %s", optarg); 21776273Sbrian startnum = (int)val; 21876273Sbrian break; 21976273Sbrian case 'w': 22076273Sbrian errno = 0; 22176273Sbrian val = strtol(optarg, &ep, 10); 22276273Sbrian if ((ep != NULL && *ep != '\0') || 22397338Stjr ((val == LONG_MIN || val == LONG_MAX) && errno != 0)) 22497338Stjr errx(EXIT_FAILURE, 22597338Stjr "invalid width value -- %s", optarg); 22676273Sbrian width = (int)val; 22797338Stjr if (!(width > 0)) 22897338Stjr errx(EXIT_FAILURE, 22997338Stjr "width argument must be > 0 -- %d", 23076273Sbrian width); 23176273Sbrian break; 23276273Sbrian case '?': 23376273Sbrian default: 23476273Sbrian usage(); 23576273Sbrian /* NOTREACHED */ 23676273Sbrian } 23776273Sbrian } 23876273Sbrian argc -= optind; 23976273Sbrian argv += optind; 24076273Sbrian 24176273Sbrian switch (argc) { 24276273Sbrian case 0: 24376273Sbrian break; 24476273Sbrian case 1: 24597337Stjr if (freopen(argv[0], "r", stdin) == NULL) 24697337Stjr err(EXIT_FAILURE, "%s", argv[0]); 24776273Sbrian break; 24876273Sbrian default: 24976273Sbrian usage(); 25076273Sbrian /* NOTREACHED */ 25176273Sbrian } 25276273Sbrian 253132078Stjr /* Generate the delimiter sequence */ 254132078Stjr memcpy(delim, delim1, delim1len); 255132078Stjr memcpy(delim + delim1len, delim2, delim2len); 256132078Stjr delimlen = delim1len + delim2len; 257132078Stjr 25876273Sbrian /* Allocate a buffer suitable for preformatting line number. */ 259226362Sed intbuffersize = max((int)INT_STRLEN_MAXIMUM, width) + 1; /* NUL */ 26097337Stjr if ((intbuffer = malloc(intbuffersize)) == NULL) 26197337Stjr err(EXIT_FAILURE, "cannot allocate preformatting buffer"); 26276273Sbrian 26376273Sbrian /* Do the work. */ 26476273Sbrian filter(); 26576273Sbrian 26676273Sbrian exit(EXIT_SUCCESS); 26776273Sbrian /* NOTREACHED */ 26876273Sbrian} 26976273Sbrian 27076273Sbrianstatic void 271226362Sedfilter(void) 27276273Sbrian{ 273189168Sdas char *buffer; 274189168Sdas size_t buffersize; 275189168Sdas ssize_t linelen; 27676273Sbrian int line; /* logical line number */ 27776273Sbrian int section; /* logical page section */ 27876273Sbrian unsigned int adjblank; /* adjacent blank lines */ 27976273Sbrian int consumed; /* intbuffer measurement */ 280165462Simp int donumber = 0, idx; 28176273Sbrian 28276273Sbrian adjblank = 0; 28376273Sbrian line = startnum; 28476273Sbrian section = BODY; 28576273Sbrian 286189168Sdas buffer = NULL; 287189168Sdas buffersize = 0; 288189168Sdas while ((linelen = getline(&buffer, &buffersize, stdin)) > 0) { 28976273Sbrian for (idx = FOOTER; idx <= NP_LAST; idx++) { 29076273Sbrian /* Does it look like a delimiter? */ 291189168Sdas if (delimlen * (idx + 1) > linelen) 292189168Sdas break; 293132078Stjr if (memcmp(buffer + delimlen * idx, delim, 294189168Sdas delimlen) != 0) 29576273Sbrian break; 296189168Sdas /* Was this the whole line? */ 297189168Sdas if (buffer[delimlen * (idx + 1)] == '\n') { 298189168Sdas section = idx; 299189168Sdas adjblank = 0; 300189168Sdas if (restart) 301189168Sdas line = startnum; 302189168Sdas goto nextline; 30376273Sbrian } 30476273Sbrian } 30576273Sbrian 30676273Sbrian switch (numbering_properties[section].type) { 30776273Sbrian case number_all: 30876273Sbrian /* 30976273Sbrian * Doing this for number_all only is disputable, but 31076273Sbrian * the standard expresses an explicit dependency on 31176273Sbrian * `-b a' etc. 31276273Sbrian */ 31376273Sbrian if (buffer[0] == '\n' && ++adjblank < nblank) 31476273Sbrian donumber = 0; 31576273Sbrian else 31676273Sbrian donumber = 1, adjblank = 0; 31776273Sbrian break; 31876273Sbrian case number_nonempty: 31976273Sbrian donumber = (buffer[0] != '\n'); 32076273Sbrian break; 32176273Sbrian case number_none: 32276273Sbrian donumber = 0; 32376273Sbrian break; 32476273Sbrian case number_regex: 32576273Sbrian donumber = 32676273Sbrian (regexec(&numbering_properties[section].expr, 32776273Sbrian buffer, 0, NULL, 0) == 0); 32876273Sbrian break; 32976273Sbrian } 33076273Sbrian 33176273Sbrian if (donumber) { 33276273Sbrian /* Note: sprintf() is safe here. */ 33376273Sbrian consumed = sprintf(intbuffer, format, width, line); 33476273Sbrian (void)printf("%s", 33576273Sbrian intbuffer + max(0, consumed - width)); 33676273Sbrian line += incr; 33776273Sbrian } else { 33876273Sbrian (void)printf("%*s", width, ""); 33976273Sbrian } 340189168Sdas (void)fputs(sep, stdout); 341189168Sdas (void)fwrite(buffer, linelen, 1, stdout); 34276273Sbrian 34397337Stjr if (ferror(stdout)) 34497337Stjr err(EXIT_FAILURE, "output error"); 34576273Sbriannextline: 34676273Sbrian ; 34776273Sbrian } 34876273Sbrian 34997337Stjr if (ferror(stdin)) 35097337Stjr err(EXIT_FAILURE, "input error"); 351189168Sdas 352189168Sdas free(buffer); 35376273Sbrian} 35476273Sbrian 35576273Sbrian/* 35676273Sbrian * Various support functions. 35776273Sbrian */ 35876273Sbrian 35976273Sbrianstatic void 360226362Sedparse_numbering(const char *argstr, int section) 36176273Sbrian{ 36276273Sbrian int error; 36376273Sbrian char errorbuf[NL_TEXTMAX]; 36476273Sbrian 36576273Sbrian switch (argstr[0]) { 36676273Sbrian case 'a': 36776273Sbrian numbering_properties[section].type = number_all; 36876273Sbrian break; 36976273Sbrian case 'n': 37076273Sbrian numbering_properties[section].type = number_none; 37176273Sbrian break; 37276273Sbrian case 't': 37376273Sbrian numbering_properties[section].type = number_nonempty; 37476273Sbrian break; 37576273Sbrian case 'p': 37676273Sbrian /* If there was a previous expression, throw it away. */ 37776273Sbrian if (numbering_properties[section].type == number_regex) 37876273Sbrian regfree(&numbering_properties[section].expr); 37976273Sbrian else 38076273Sbrian numbering_properties[section].type = number_regex; 38176273Sbrian 38276273Sbrian /* Compile/validate the supplied regular expression. */ 38376273Sbrian if ((error = regcomp(&numbering_properties[section].expr, 38476273Sbrian &argstr[1], REG_NEWLINE|REG_NOSUB)) != 0) { 38576273Sbrian (void)regerror(error, 38676273Sbrian &numbering_properties[section].expr, 38776273Sbrian errorbuf, sizeof (errorbuf)); 38897338Stjr errx(EXIT_FAILURE, 38997338Stjr "%s expr: %s -- %s", 39076273Sbrian numbering_properties[section].name, errorbuf, 39176273Sbrian &argstr[1]); 39276273Sbrian } 39376273Sbrian break; 39476273Sbrian default: 39597338Stjr errx(EXIT_FAILURE, 39697338Stjr "illegal %s line numbering type -- %s", 39776273Sbrian numbering_properties[section].name, argstr); 39876273Sbrian } 39976273Sbrian} 40076273Sbrian 40176273Sbrianstatic void 402226362Sedusage(void) 40376273Sbrian{ 40476273Sbrian 405119025Stjr (void)fprintf(stderr, 406119025Stjr"usage: nl [-p] [-b type] [-d delim] [-f type] [-h type] [-i incr] [-l num]\n" 407119025Stjr" [-n format] [-s sep] [-v startnum] [-w width] [file]\n"); 40876273Sbrian exit(EXIT_FAILURE); 40976273Sbrian} 410