1 1.15 ginsbach /* $NetBSD: nl.c,v 1.15 2020/12/31 04:07:37 ginsbach Exp $ */ 2 1.1 kleink 3 1.1 kleink /*- 4 1.1 kleink * Copyright (c) 1999 The NetBSD Foundation, Inc. 5 1.1 kleink * All rights reserved. 6 1.1 kleink * 7 1.1 kleink * This code is derived from software contributed to The NetBSD Foundation 8 1.1 kleink * by Klaus Klein. 9 1.1 kleink * 10 1.1 kleink * Redistribution and use in source and binary forms, with or without 11 1.1 kleink * modification, are permitted provided that the following conditions 12 1.1 kleink * are met: 13 1.1 kleink * 1. Redistributions of source code must retain the above copyright 14 1.1 kleink * notice, this list of conditions and the following disclaimer. 15 1.1 kleink * 2. Redistributions in binary form must reproduce the above copyright 16 1.1 kleink * notice, this list of conditions and the following disclaimer in the 17 1.1 kleink * documentation and/or other materials provided with the distribution. 18 1.1 kleink * 19 1.1 kleink * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS 20 1.1 kleink * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED 21 1.1 kleink * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 22 1.1 kleink * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS 23 1.1 kleink * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 24 1.1 kleink * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 25 1.1 kleink * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 26 1.1 kleink * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 27 1.1 kleink * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 28 1.1 kleink * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 29 1.1 kleink * POSSIBILITY OF SUCH DAMAGE. 30 1.1 kleink */ 31 1.1 kleink 32 1.1 kleink #include <sys/cdefs.h> 33 1.1 kleink #ifndef lint 34 1.9 lukem __COPYRIGHT("@(#) Copyright (c) 1999\ 35 1.1 kleink The NetBSD Foundation, Inc. All rights reserved."); 36 1.15 ginsbach __RCSID("$NetBSD: nl.c,v 1.15 2020/12/31 04:07:37 ginsbach Exp $"); 37 1.1 kleink #endif 38 1.1 kleink 39 1.1 kleink #include <errno.h> 40 1.1 kleink #include <limits.h> 41 1.1 kleink #include <locale.h> 42 1.1 kleink #include <regex.h> 43 1.1 kleink #include <stdio.h> 44 1.1 kleink #include <stdlib.h> 45 1.6 matt #include <string.h> 46 1.1 kleink #include <unistd.h> 47 1.11 christos #include <err.h> 48 1.1 kleink 49 1.1 kleink typedef enum { 50 1.1 kleink number_all, /* number all lines */ 51 1.1 kleink number_nonempty, /* number non-empty lines */ 52 1.1 kleink number_none, /* no line numbering */ 53 1.1 kleink number_regex /* number lines matching regular expression */ 54 1.1 kleink } numbering_type; 55 1.1 kleink 56 1.1 kleink struct numbering_property { 57 1.1 kleink const char * const name; /* for diagnostics */ 58 1.1 kleink numbering_type type; /* numbering type */ 59 1.1 kleink regex_t expr; /* for type == number_regex */ 60 1.1 kleink }; 61 1.1 kleink 62 1.1 kleink /* line numbering formats */ 63 1.1 kleink #define FORMAT_LN "%-*d" /* left justified, leading zeros suppressed */ 64 1.1 kleink #define FORMAT_RN "%*d" /* right justified, leading zeros suppressed */ 65 1.1 kleink #define FORMAT_RZ "%0*d" /* right justified, leading zeros kept */ 66 1.1 kleink 67 1.1 kleink #define FOOTER 0 68 1.1 kleink #define BODY 1 69 1.1 kleink #define HEADER 2 70 1.1 kleink #define NP_LAST HEADER 71 1.1 kleink 72 1.1 kleink static struct numbering_property numbering_properties[NP_LAST + 1] = { 73 1.10 lukem { "footer", number_none, { 0, 0, 0, 0 } }, 74 1.10 lukem { "body", number_nonempty, { 0, 0, 0, 0 } }, 75 1.10 lukem { "header", number_none, { 0, 0, 0, 0 } }, 76 1.1 kleink }; 77 1.1 kleink 78 1.1 kleink #define max(a, b) ((a) > (b) ? (a) : (b)) 79 1.1 kleink 80 1.1 kleink /* 81 1.1 kleink * Maximum number of characters required for a decimal representation of a 82 1.1 kleink * (signed) int; courtesy of tzcode. 83 1.1 kleink */ 84 1.1 kleink #define INT_STRLEN_MAXIMUM \ 85 1.1 kleink ((sizeof (int) * CHAR_BIT - 1) * 302 / 1000 + 2) 86 1.1 kleink 87 1.11 christos static void filter(void); 88 1.11 christos static void parse_numbering(const char *, int); 89 1.11 christos static void usage(void) __attribute__((__noreturn__)); 90 1.1 kleink 91 1.1 kleink /* 92 1.1 kleink * Pointer to dynamically allocated input line buffer, and its size. 93 1.1 kleink */ 94 1.1 kleink static char *buffer; 95 1.1 kleink static size_t buffersize; 96 1.1 kleink 97 1.1 kleink /* 98 1.1 kleink * Dynamically allocated buffer suitable for string representation of ints. 99 1.1 kleink */ 100 1.1 kleink static char *intbuffer; 101 1.11 christos static size_t intbuffersize; 102 1.1 kleink 103 1.1 kleink /* 104 1.1 kleink * Configurable parameters. 105 1.1 kleink */ 106 1.1 kleink /* delimiter characters that indicate the start of a logical page section */ 107 1.1 kleink static char delim[2] = { '\\', ':' }; 108 1.1 kleink 109 1.1 kleink /* line numbering format */ 110 1.1 kleink static const char *format = FORMAT_RN; 111 1.1 kleink 112 1.1 kleink /* increment value used to number logical page lines */ 113 1.1 kleink static int incr = 1; 114 1.1 kleink 115 1.1 kleink /* number of adjacent blank lines to be considered (and numbered) as one */ 116 1.1 kleink static unsigned int nblank = 1; 117 1.1 kleink 118 1.1 kleink /* whether to restart numbering at logical page delimiters */ 119 1.1 kleink static int restart = 1; 120 1.1 kleink 121 1.1 kleink /* characters used in separating the line number and the corrsp. text line */ 122 1.1 kleink static const char *sep = "\t"; 123 1.1 kleink 124 1.1 kleink /* initial value used to number logical page lines */ 125 1.1 kleink static int startnum = 1; 126 1.1 kleink 127 1.1 kleink /* number of characters to be used for the line number */ 128 1.1 kleink /* should be unsigned but required signed by `*' precision conversion */ 129 1.1 kleink static int width = 6; 130 1.1 kleink 131 1.1 kleink 132 1.1 kleink int 133 1.11 christos main(int argc, char *argv[]) 134 1.1 kleink { 135 1.1 kleink int c; 136 1.3 kleink long val; 137 1.3 kleink unsigned long uval; 138 1.1 kleink char *ep; 139 1.1 kleink 140 1.1 kleink (void)setlocale(LC_ALL, ""); 141 1.1 kleink 142 1.1 kleink /* 143 1.1 kleink * Note: this implementation strictly conforms to the XBD Utility 144 1.1 kleink * Syntax Guidelines and does not permit the optional `file' operand 145 1.1 kleink * to be intermingled with the options, which is defined in the 146 1.1 kleink * XCU specification (Issue 5) but declared an obsolescent feature that 147 1.1 kleink * will be removed from a future issue. It shouldn't matter, though. 148 1.1 kleink */ 149 1.1 kleink while ((c = getopt(argc, argv, "pb:d:f:h:i:l:n:s:v:w:")) != -1) { 150 1.1 kleink switch (c) { 151 1.1 kleink case 'p': 152 1.1 kleink restart = 0; 153 1.1 kleink break; 154 1.1 kleink case 'b': 155 1.1 kleink parse_numbering(optarg, BODY); 156 1.1 kleink break; 157 1.1 kleink case 'd': 158 1.1 kleink if (optarg[0] != '\0') 159 1.1 kleink delim[0] = optarg[0]; 160 1.14 ginsbach if (optarg[1] != '\0') { 161 1.1 kleink delim[1] = optarg[1]; 162 1.14 ginsbach /* at most two delimiter characters */ 163 1.14 ginsbach if (optarg[2] != '\0') { 164 1.14 ginsbach errx(EXIT_FAILURE, 165 1.14 ginsbach "invalid delim argument -- %s", 166 1.14 ginsbach optarg); 167 1.14 ginsbach /* NOTREACHED */ 168 1.14 ginsbach } 169 1.1 kleink } 170 1.1 kleink break; 171 1.1 kleink case 'f': 172 1.1 kleink parse_numbering(optarg, FOOTER); 173 1.1 kleink break; 174 1.1 kleink case 'h': 175 1.1 kleink parse_numbering(optarg, HEADER); 176 1.1 kleink break; 177 1.1 kleink case 'i': 178 1.1 kleink errno = 0; 179 1.1 kleink val = strtol(optarg, &ep, 10); 180 1.1 kleink if ((ep != NULL && *ep != '\0') || 181 1.11 christos ((val == LONG_MIN || val == LONG_MAX) && errno != 0)) 182 1.11 christos errx(EXIT_FAILURE, 183 1.11 christos "invalid incr argument -- %s", optarg); 184 1.1 kleink incr = (int)val; 185 1.1 kleink break; 186 1.1 kleink case 'l': 187 1.1 kleink errno = 0; 188 1.1 kleink uval = strtoul(optarg, &ep, 10); 189 1.1 kleink if ((ep != NULL && *ep != '\0') || 190 1.11 christos (uval == ULONG_MAX && errno != 0)) 191 1.11 christos errx(EXIT_FAILURE, 192 1.11 christos "invalid num argument -- %s", optarg); 193 1.1 kleink nblank = (unsigned int)uval; 194 1.1 kleink break; 195 1.1 kleink case 'n': 196 1.1 kleink if (strcmp(optarg, "ln") == 0) { 197 1.1 kleink format = FORMAT_LN; 198 1.1 kleink } else if (strcmp(optarg, "rn") == 0) { 199 1.1 kleink format = FORMAT_RN; 200 1.1 kleink } else if (strcmp(optarg, "rz") == 0) { 201 1.1 kleink format = FORMAT_RZ; 202 1.11 christos } else 203 1.11 christos errx(EXIT_FAILURE, 204 1.11 christos "illegal format -- %s", optarg); 205 1.1 kleink break; 206 1.1 kleink case 's': 207 1.1 kleink sep = optarg; 208 1.1 kleink break; 209 1.1 kleink case 'v': 210 1.1 kleink errno = 0; 211 1.1 kleink val = strtol(optarg, &ep, 10); 212 1.1 kleink if ((ep != NULL && *ep != '\0') || 213 1.11 christos ((val == LONG_MIN || val == LONG_MAX) && errno != 0)) 214 1.11 christos errx(EXIT_FAILURE, 215 1.11 christos "invalid startnum value -- %s", optarg); 216 1.1 kleink startnum = (int)val; 217 1.1 kleink break; 218 1.1 kleink case 'w': 219 1.1 kleink errno = 0; 220 1.1 kleink val = strtol(optarg, &ep, 10); 221 1.1 kleink if ((ep != NULL && *ep != '\0') || 222 1.11 christos ((val == LONG_MIN || val == LONG_MAX) && errno != 0)) 223 1.11 christos errx(EXIT_FAILURE, 224 1.11 christos "invalid width value -- %s", optarg); 225 1.1 kleink width = (int)val; 226 1.11 christos if (!(width > 0)) 227 1.11 christos errx(EXIT_FAILURE, 228 1.11 christos "width argument must be > 0 -- %d", 229 1.1 kleink width); 230 1.1 kleink break; 231 1.1 kleink case '?': 232 1.1 kleink default: 233 1.1 kleink usage(); 234 1.1 kleink /* NOTREACHED */ 235 1.1 kleink } 236 1.1 kleink } 237 1.1 kleink argc -= optind; 238 1.1 kleink argv += optind; 239 1.1 kleink 240 1.1 kleink switch (argc) { 241 1.1 kleink case 0: 242 1.1 kleink break; 243 1.1 kleink case 1: 244 1.12 wiz if (strcmp(argv[0], "-") != 0 && 245 1.12 wiz freopen(argv[0], "r", stdin) == NULL) 246 1.11 christos err(EXIT_FAILURE, "Cannot open `%s'", argv[0]); 247 1.1 kleink break; 248 1.1 kleink default: 249 1.1 kleink usage(); 250 1.1 kleink /* NOTREACHED */ 251 1.1 kleink } 252 1.1 kleink 253 1.1 kleink /* Determine the maximum input line length to operate on. */ 254 1.1 kleink if ((val = sysconf(_SC_LINE_MAX)) == -1) /* ignore errno */ 255 1.1 kleink val = LINE_MAX; 256 1.1 kleink /* Allocate sufficient buffer space (including the terminating NUL). */ 257 1.1 kleink buffersize = (size_t)val + 1; 258 1.11 christos if ((buffer = malloc(buffersize)) == NULL) 259 1.11 christos err(EXIT_FAILURE, "Cannot allocate input line buffer"); 260 1.1 kleink 261 1.1 kleink /* Allocate a buffer suitable for preformatting line number. */ 262 1.10 lukem intbuffersize = max((int)INT_STRLEN_MAXIMUM, width) + 1; /* NUL */ 263 1.11 christos if ((intbuffer = malloc(intbuffersize)) == NULL) 264 1.11 christos err(EXIT_FAILURE, "cannot allocate preformatting buffer"); 265 1.1 kleink 266 1.1 kleink /* Do the work. */ 267 1.1 kleink filter(); 268 1.1 kleink 269 1.11 christos return EXIT_SUCCESS; 270 1.1 kleink /* NOTREACHED */ 271 1.1 kleink } 272 1.1 kleink 273 1.1 kleink static void 274 1.11 christos filter(void) 275 1.1 kleink { 276 1.1 kleink int line; /* logical line number */ 277 1.1 kleink int section; /* logical page section */ 278 1.1 kleink unsigned int adjblank; /* adjacent blank lines */ 279 1.1 kleink int consumed; /* intbuffer measurement */ 280 1.1 kleink int donumber, idx; 281 1.1 kleink 282 1.1 kleink adjblank = 0; 283 1.1 kleink line = startnum; 284 1.1 kleink section = BODY; 285 1.2 kleink #ifdef __GNUC__ 286 1.7 mrg donumber = 0; /* avoid bogus `uninitialized' warning */ 287 1.2 kleink #endif 288 1.1 kleink 289 1.1 kleink while (fgets(buffer, (int)buffersize, stdin) != NULL) { 290 1.1 kleink for (idx = FOOTER; idx <= NP_LAST; idx++) { 291 1.1 kleink /* Does it look like a delimiter? */ 292 1.1 kleink if (buffer[2 * idx + 0] == delim[0] && 293 1.1 kleink buffer[2 * idx + 1] == delim[1]) { 294 1.1 kleink /* Was this the whole line? */ 295 1.1 kleink if (buffer[2 * idx + 2] == '\n') { 296 1.1 kleink section = idx; 297 1.1 kleink adjblank = 0; 298 1.1 kleink if (restart) 299 1.1 kleink line = startnum; 300 1.1 kleink goto nextline; 301 1.1 kleink } 302 1.1 kleink } else { 303 1.1 kleink break; 304 1.1 kleink } 305 1.1 kleink } 306 1.1 kleink 307 1.1 kleink switch (numbering_properties[section].type) { 308 1.1 kleink case number_all: 309 1.1 kleink /* 310 1.1 kleink * Doing this for number_all only is disputable, but 311 1.1 kleink * the standard expresses an explicit dependency on 312 1.1 kleink * `-b a' etc. 313 1.1 kleink */ 314 1.1 kleink if (buffer[0] == '\n' && ++adjblank < nblank) 315 1.1 kleink donumber = 0; 316 1.1 kleink else 317 1.1 kleink donumber = 1, adjblank = 0; 318 1.1 kleink break; 319 1.1 kleink case number_nonempty: 320 1.1 kleink donumber = (buffer[0] != '\n'); 321 1.1 kleink break; 322 1.1 kleink case number_none: 323 1.1 kleink donumber = 0; 324 1.1 kleink break; 325 1.1 kleink case number_regex: 326 1.1 kleink donumber = 327 1.1 kleink (regexec(&numbering_properties[section].expr, 328 1.1 kleink buffer, 0, NULL, 0) == 0); 329 1.1 kleink break; 330 1.1 kleink } 331 1.1 kleink 332 1.1 kleink if (donumber) { 333 1.11 christos consumed = snprintf(intbuffer, intbuffersize, format, 334 1.11 christos width, line); 335 1.15 ginsbach (void)printf("%s%s", 336 1.15 ginsbach intbuffer + max(0, consumed - width), sep); 337 1.1 kleink line += incr; 338 1.1 kleink } else { 339 1.15 ginsbach (void)printf("%*s%*s", width, "", (int)strlen(sep), ""); 340 1.1 kleink } 341 1.15 ginsbach (void)printf("%s", buffer); 342 1.1 kleink 343 1.11 christos if (ferror(stdout)) 344 1.11 christos err(EXIT_FAILURE, "output error"); 345 1.1 kleink nextline: 346 1.1 kleink ; 347 1.1 kleink } 348 1.1 kleink 349 1.11 christos if (ferror(stdin)) 350 1.11 christos err(EXIT_FAILURE, "input error"); 351 1.1 kleink } 352 1.1 kleink 353 1.1 kleink /* 354 1.1 kleink * Various support functions. 355 1.1 kleink */ 356 1.1 kleink 357 1.1 kleink static void 358 1.11 christos parse_numbering(const char *argstr, int section) 359 1.1 kleink { 360 1.1 kleink int error; 361 1.5 kleink char errorbuf[NL_TEXTMAX]; 362 1.1 kleink 363 1.1 kleink switch (argstr[0]) { 364 1.1 kleink case 'a': 365 1.1 kleink numbering_properties[section].type = number_all; 366 1.1 kleink break; 367 1.1 kleink case 'n': 368 1.1 kleink numbering_properties[section].type = number_none; 369 1.1 kleink break; 370 1.1 kleink case 't': 371 1.1 kleink numbering_properties[section].type = number_nonempty; 372 1.1 kleink break; 373 1.1 kleink case 'p': 374 1.1 kleink /* If there was a previous expression, throw it away. */ 375 1.1 kleink if (numbering_properties[section].type == number_regex) 376 1.1 kleink regfree(&numbering_properties[section].expr); 377 1.1 kleink else 378 1.1 kleink numbering_properties[section].type = number_regex; 379 1.1 kleink 380 1.1 kleink /* Compile/validate the supplied regular expression. */ 381 1.1 kleink if ((error = regcomp(&numbering_properties[section].expr, 382 1.1 kleink &argstr[1], REG_NEWLINE|REG_NOSUB)) != 0) { 383 1.1 kleink (void)regerror(error, 384 1.1 kleink &numbering_properties[section].expr, 385 1.1 kleink errorbuf, sizeof (errorbuf)); 386 1.11 christos errx(EXIT_FAILURE, 387 1.11 christos "%s expr: %s -- %s", 388 1.1 kleink numbering_properties[section].name, errorbuf, 389 1.1 kleink &argstr[1]); 390 1.1 kleink } 391 1.1 kleink break; 392 1.1 kleink default: 393 1.11 christos errx(EXIT_FAILURE, 394 1.11 christos "illegal %s line numbering type -- %s", 395 1.1 kleink numbering_properties[section].name, argstr); 396 1.1 kleink } 397 1.1 kleink } 398 1.1 kleink 399 1.1 kleink static void 400 1.11 christos usage(void) 401 1.1 kleink { 402 1.11 christos (void)fprintf(stderr, "Usage: %s [-p] [-b type] [-d delim] [-f type] " 403 1.11 christos "[-h type] [-i incr] [-l num]\n\t[-n format] [-s sep] " 404 1.11 christos "[-v startnum] [-w width] [file]\n", getprogname()); 405 1.1 kleink exit(EXIT_FAILURE); 406 1.1 kleink } 407