1 /***********************************************************************
2 *                                                                      *
3 *               This software is part of the ast package               *
4 *          Copyright (c) 1992-2012 AT&T Intellectual Property          *
5 *                      and is licensed under the                       *
6 *                 Eclipse Public License, Version 1.0                  *
7 *                    by AT&T Intellectual Property                     *
8 *                                                                      *
9 *                A copy of the License is available at                 *
10 *          http://www.eclipse.org/org/documents/epl-v10.html           *
11 *         (with md5 checksum b35adb5213ca9657e911e9befb180842)         *
12 *                                                                      *
13 *              Information and Software Systems Research               *
14 *                            AT&T Research                             *
15 *                           Florham Park NJ                            *
16 *                                                                      *
17 *                 Glenn Fowler <gsf@research.att.com>                  *
18 *                  David Korn <dgk@research.att.com>                   *
19 *                                                                      *
20 ***********************************************************************/
21 #pragma prototyped
22 /*
23  * nl.c
24  * Written by David Korn
25  * AT&T Labs
26  * Mon Mar 24 10:10:10 EST 2003
27  */
28 
29 
30 static const char usage[] =
31 "[-?\n@(#)$Id: nl (AT&T Research) 2003-03-24 $\n]"
32 USAGE_LICENSE
33 "[+NAME? nl - line numbering filter ]"
34 "[+DESCRIPTION?\bnl\b reads lines from the file \afile\a or from standard "
35 	"input if \afile\a is omitted or is \b-\b, and writes the lines to "
36 	"standard output with possible line numbering preceding each line.]"
37 "[+?The \bnl\b utility treats its input in terms of logical pages and resets "
38 	"numbering on each logical page.  A logical page consists of a header, "
39 	"a body, and a footer any of which can be empty, and each can use "
40 	"their own line numbering options.]"
41 "[+?The start of logical page sections consist of input lines containing only "
42 	"the two delimiter characters whose defaults are \b\\\b and \b:\b as "
43 		"follows:]{"
44 		"[d1d2d1d2d1d2?Header.]"
45 		"[d1d2d1d2?Body.]"
46 		"[d1d2?Footer.]"
47 	"}"
48 "[+?\bnl\b assumes that the first section is a logical body until it encounters "
49 	"one of the section delimiters.]"
50 ""
51 "[b:body-numbering]:[type:=t?\atype\a can be one of the following:]{"
52 		"[+a?Number all lines.]"
53 		"[+t?Number only non-empty lines.]"
54 		"[+n?No line numbering.]"
55 		"[+p\astring\a?Number only lines that contain the basic "
56 			"regular expression \astring\a.]"
57 	"}"
58 "[d:section-delimiter]:[delim:=\\::?\adelim\a specifies the two delimiter "
59 	"characters that indicate start of a section.  If only one character "
60 	"is specified, the second character remains unchanged.]"
61 "[f:footer-numbering]:[type:=n?\atype\a is the same as for the \bb\b option.]"
62 "[h:header-numbering]:[type:=n?\atype\a is the same as for the \bb\b option.]"
63 "[i:page-increment]#[incr:=1?\aincr\a is the increment used to number logical "
64 	"page lines.]"
65 "[l:join-blank-lines]#[num:=1?\anum\a is the number of blank lines to be "
66 	"treated as one]"
67 "[n:number-format]:[format?\aformat\a specifies the line numbering format.  "
68 	"It must be one of the following:]{"
69 		"[101:ln?left justified, leading zeros supressed.]"
70 		"[102:rn?right justified, leading zeros supressed.]"
71 		"[103:rz?right justified, leading zeros kept.]"
72 	"}"
73 "[p:no-renumber?Start renumbering at logical page delimiters.]"
74 "[s:number-separator]:[sep:=\\t?\asep\a is the string that is used to separate the line number "
75 	"and the corresponding text line.]"
76 "[v:starting-line-number]#[startnum:=1?\astartnum\a is the initial value to number "
77 	"logical pages.]"
78 "[w:number-width]#[width:=6?\awidth\a is the number of characters to be used "
79 	"for line numbering.]"
80 
81 "\n"
82 "\n[file]\n"
83 "\n"
84 "[+EXIT STATUS?]{"
85         "[+0?Success.]"
86         "[+>0?An error occurred.]"
87 "}"
88 "[+SEE ALSO?\bpr\b(1), \bregex\b(3)]"
89 ;
90 
91 #include	<cmd.h>
92 #include	<regex.h>
93 
94 typedef struct _nl_
95 {
96 	void	*section[3];
97 	char	*sep;
98 	int	delim1;
99 	int	delim2;
100 	int	format;
101 	int	startnum;
102 	int	incr;
103 	int	width;
104 	int	blines;
105 	int	pflag;
106 } Nl_t;
107 
108 static const char letter_a, letter_t, letter_n;
109 #define TYPE_ALL	((void*)(&letter_a))
110 #define TYPE_NONE	((void*)(&letter_n))
111 #define TYPE_TEXT	((void*)(&letter_t))
112 
113 
114 #define SECTION_HEAD	0
115 #define SECTION_BODY	1
116 #define SECTION_FOOT	2
117 
118 /* These numbers need to be the same as with -n option in option string */
119 #define FORMAT_LN	-101
120 #define FORMAT_RN	-102
121 #define FORMAT_RZ	-103
122 
donl(Nl_t * pp,Sfio_t * in,Sfio_t * out)123 static int donl(Nl_t *pp, Sfio_t *in, Sfio_t *out)
124 {
125 	register char	*cp;
126 	register int	n,line=pp->startnum, outline, sectnum=SECTION_BODY;
127 	int		blank=0, width=pp->width+strlen(pp->sep);
128 	char		format[20];
129 	if(pp->format==FORMAT_LN)
130 		sfsprintf(format,sizeof(format),"%%-%dd%%s",pp->width);
131 	else if(pp->format==FORMAT_RN)
132 		sfsprintf(format,sizeof(format),"%%%dd%%s",pp->width);
133 	else
134 		sfsprintf(format,sizeof(format),"%%0%dd%%s",pp->width);
135 	while(cp=sfgetr(in, '\n', 0))
136 	{
137 		outline = 0;
138 		if(*cp!='\n')
139 			blank = 0;
140 		else
141 			blank++;
142 		n = sfvalue(in);
143 		if(*cp==pp->delim1 && cp[1]==pp->delim2)
144 		{
145 			if(cp[2]=='\n')
146 			{
147 				sectnum = SECTION_FOOT;
148 				sfputc(out,'\n');
149 				continue;
150 			}
151 			else if(cp[2]==pp->delim1 && cp[3]==pp->delim2)
152 			{
153 				if(cp[4]=='\n')
154 				{
155 					sectnum = SECTION_BODY;
156 					sfputc(out,'\n');
157 					continue;
158 				}
159 				if(cp[4]==pp->delim1 && cp[5]==pp->delim2 && cp[6]=='\n')
160 				{
161 					sectnum = SECTION_HEAD;
162 					if(!pp->pflag)
163 						line = pp->startnum;
164 					sfputc(out,'\n');
165 					continue;
166 				}
167 			}
168 		}
169 		if(pp->section[sectnum]==TYPE_NONE)
170 			;
171 #if 0
172 		{
173 			sfwrite(out, cp, n);
174 			continue;
175 		}
176 #endif
177 		else if(pp->section[sectnum]==TYPE_ALL)
178 		{
179 			if(!blank || blank==pp->blines)
180 			{
181 				outline = 1;
182 				blank = 0;
183 			}
184 		}
185 		else if(pp->section[sectnum]!=TYPE_TEXT)
186 			outline = !regnexec((regex_t*)pp->section[sectnum], cp, n, (size_t)0, NULL, 0);
187 		else if(*cp!='\n')
188 			outline = 1;
189 		if(outline)
190 		{
191 			blank = 0;
192 			sfprintf(out, format, line, pp->sep);
193 			line += pp->incr;
194 		}
195 		else
196 			sfnputc(out,' ',width);
197 		sfwrite(out, cp, n);
198 	}
199 	return(0);
200 }
201 
202 int
b_nl(int argc,char ** argv,Shbltin_t * context)203 b_nl(int argc, char** argv, Shbltin_t* context)
204 {
205 	register int	n,m;
206 	Sfio_t		*in=sfstdin;
207 	Nl_t		nl;
208 	regex_t		re[3];
209 
210 	cmdinit(argc, argv, context, (const char*)0, 0);
211 	nl.width =  6;
212 	nl.startnum = 1;
213 	nl.blines = 1;
214 	nl.incr = 1;
215 	nl.delim1 = '\\';
216 	nl.delim2 = ':';
217 	nl.section[SECTION_BODY] = TYPE_TEXT;
218 	nl.section[SECTION_HEAD] = TYPE_NONE;
219 	nl.section[SECTION_FOOT] = TYPE_NONE;
220 	nl.format = FORMAT_RN;
221 	nl.sep = "\t";
222 	nl.pflag = 0;
223 
224 	while (n = optget(argv, usage)) switch (n)
225 	{
226 	    case 'p':
227 		nl.pflag |= 1;
228 		break;
229 	    case 'd':
230 		nl.delim1 = *opt_info.arg;
231 		if(opt_info.arg[1])
232 			nl.delim2 = opt_info.arg[1];
233 		break;
234 	    case 'f': case 'b': case 'h':
235 		if(n=='h')
236 			m = SECTION_HEAD;
237 		else if(n=='b')
238 			m = SECTION_BODY;
239 		else
240 			m = SECTION_FOOT;
241 		switch(*opt_info.arg)
242 		{
243 		    case 'a':
244 			nl.section[m] = TYPE_ALL;
245 			break;
246 		    case 't':
247 			nl.section[m] = TYPE_TEXT;
248 			break;
249 		    case 'n':
250 			nl.section[m] = TYPE_NONE;
251 			break;
252 		    case 'p':
253 			nl.section[m] = &re[m];
254 			if(regcomp(&re[m], opt_info.arg+1, REG_NOSUB))
255 				error(2, "-%c: invalid basic regular expression %s", n, opt_info.arg+1);
256 			break;
257 		}
258 		break;
259 	    case 'i':
260 		if((nl.incr=opt_info.num) < 0)
261 			error(2, "-%c: option requires non-negative number", n);
262 		break;
263 	    case 'l':
264 		if((nl.blines=opt_info.num) < 0)
265 			error(2, "-%c: option requires non-negative number", n);
266 		break;
267 	    case 'n':
268 		nl.format = opt_info.num;
269 		break;
270 	    case 's':
271 		nl.sep = opt_info.arg;
272 		break;
273 	    case 'v':
274 		nl.startnum = opt_info.num;
275 		break;
276 	    case 'w':
277 		if((nl.width=opt_info.num) < 0)
278 			error(2, "-%c: option requires non-negative number", n);
279 		break;
280 	    case ':':
281 		error(2, "%s", opt_info.arg);
282 		break;
283 	    case '?':
284 		error(ERROR_usage(2), "%s", opt_info.arg);
285 		break;
286 	}
287 	argv += opt_info.index;
288 	argc -= opt_info.index;
289 	if(argc>1 || error_info.errors)
290 		error(ERROR_usage(2),"%s",optusage((char*)0));
291 	if(*argv && !(in = sfopen((Sfio_t*)0, *argv, "r")))
292 		error(ERROR_system(1),"%s: cannot open for reading",*argv);
293 	n = donl(&nl,in,sfstdout);
294 	for(m=0; m < 3; m++)
295 	{
296 		if(nl.section[m] == (void*)&re[m])
297 			regfree(&re[m]);
298 	}
299 	return(n?1:0);
300 }
301