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