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