xref: /dragonfly/usr.bin/seq/seq.c (revision 67640b13)
1 /*
2  * Copyright (c) 2005 The DragonFly Project.  All rights reserved.
3  *
4  * This code is derived from software contributed to The DragonFly Project
5  * by Brian Ginsbach.
6  *
7  * Redistribution and use in source and binary forms, with or without
8  * modification, are permitted provided that the following conditions
9  * are met:
10  * 1. Redistributions of source code must retain the above copyright
11  *    notice, this list of conditions and the following disclaimer.
12  * 2. Redistributions in binary form must reproduce the above copyright
13  *    notice, this list of conditions and the following disclaimer in the
14  *    documentation and/or other materials provided with the distribution.
15  * 3. Neither the name of The DragonFly Project nor the names of its
16  *    contributors may be used to endorse or promote products derived
17  *    from this software without specific prior written permission.
18  *
19  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
20  * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
21  * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
22  * FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE
23  * COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
24  * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
25  * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
26  * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
27  * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
28  * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
29  * THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
30  * SUCH DAMAGE.
31  *
32  * $DragonFly: src/usr.bin/seq/seq.c,v 1.1 2005/01/22 19:09:40 joerg Exp $
33  */
34 
35 #include <ctype.h>
36 #include <err.h>
37 #include <errno.h>
38 #include <math.h>
39 #include <locale.h>
40 #include <stdio.h>
41 #include <stdlib.h>
42 #include <string.h>
43 #include <unistd.h>
44 
45 #define ZERO	'0'
46 #define SPACE	' '
47 
48 #define MAX(a, b)	(((a) < (b))? (b) : (a))
49 #define ISSIGN(c)	((int)(c) == '-' || (int)(c) == '+')
50 #define ISEXP(c)	((int)(c) == 'e' || (int)(c) == 'E')
51 #define ISODIGIT(c)	((int)(c) >= '0' && (int)(c) <= '7')
52 
53 /* Globals */
54 
55 const char *decimal_point = ".";	/* default */
56 char default_format[] = { "%g" };	/* default */
57 
58 /* Prototypes */
59 
60 double	e_atof(const char *);
61 
62 int	decimal_places(const char *);
63 int	numeric(const char *);
64 int	valid_format(const char *);
65 
66 char	*generate_format(double, double, double, int, char);
67 char	*unescape(char *);
68 
69 /*
70  * The seq command will print out a numeric sequence from 1, the default,
71  * to a user specified upper limit by 1.  The lower bound and increment
72  * maybe indicated by the user on the command line.  The sequence can
73  * be either whole, the default, or decimal numbers.
74  */
75 int
76 main(int argc, char **argv)
77 {
78 	int c = 0, errflg = 0;
79 	int equalize = 0;
80 	double first = 1.0;
81 	double last = 0.0;
82 	double incr = 0.0;
83 	struct lconv *locale;
84 	char *fmt = NULL;
85 	const char *sep = "\n";
86 	const char *term = NULL;
87 	char pad = ZERO;
88 
89 	/* Determine the locale's decimal point. */
90 	locale = localeconv();
91 	if (locale && locale->decimal_point && locale->decimal_point[0] != '\0')
92 		decimal_point = locale->decimal_point;
93 
94 	/*
95          * Process options, but handle negative numbers separately
96          * least they trip up getopt(3).
97          */
98 	while ((optind < argc) && !numeric(argv[optind]) &&
99 	    (c = getopt(argc, argv, "f:hs:t:w")) != -1) {
100 
101 		switch (c) {
102 		case 'f':	/* format (plan9) */
103 			fmt = optarg;
104 			equalize = 0;
105 			break;
106 		case 's':	/* separator (GNU) */
107 			sep = unescape(optarg);
108 			break;
109 		case 't':	/* terminator (new) */
110 			term = unescape(optarg);
111 			break;
112 		case 'w':	/* equal width (plan9) */
113 			if (!fmt)
114 				if (equalize++)
115 					pad = SPACE;
116 			break;
117 		case 'h':	/* help (GNU) */
118 		default:
119 			errflg++;
120 			break;
121 		}
122 	}
123 
124 	argc -= optind;
125 	argv += optind;
126 	if (argc < 1 || argc > 3)
127 		errflg++;
128 
129 	if (errflg) {
130 		fprintf(stderr,
131 		    "usage: %s [-f format] [-s str] [-t str] [-w] [first [incr]] last\n",
132 		    getprogname());
133 		exit(1);
134 	}
135 
136 	last = e_atof(argv[argc - 1]);
137 
138 	if (argc > 1)
139 		first = e_atof(argv[0]);
140 
141 	if (argc > 2) {
142 		incr = e_atof(argv[1]);
143 		/* Plan 9/GNU don't do zero */
144 		if (incr == 0.0)
145 			errx(1, "zero %screment", (first < last)? "in" : "de");
146 	}
147 
148 	/* default is one for Plan 9/GNU work alike */
149 	if (incr == 0.0)
150 		incr = (first < last) ? 1.0 : -1.0;
151 
152 	if (incr <= 0.0 && first < last)
153 		errx(1, "needs positive increment");
154 
155 	if (incr >= 0.0 && first > last)
156 		errx(1, "needs negative decrement");
157 
158 	if (fmt != NULL) {
159 		if (!valid_format(fmt))
160 			errx(1, "invalid format string: `%s'", fmt);
161 		fmt = unescape(fmt);
162 		/*
163 	         * XXX to be bug for bug compatible with Plan 9 add a
164 		 * newline if none found at the end of the format string.
165 		 */
166 	} else
167 		fmt = generate_format(first, incr, last, equalize, pad);
168 
169 	if (incr > 0) {
170 		for (; first <= last; first += incr) {
171 			printf(fmt, first);
172 			fputs(sep, stdout);
173 		}
174 	} else {
175 		for (; first >= last; first += incr) {
176 			printf(fmt, first);
177 			fputs(sep, stdout);
178 		}
179 	}
180 	if (term != NULL)
181 		fputs(term, stdout);
182 
183 	return(0);
184 }
185 
186 /*
187  * numeric - verify that string is numeric
188  */
189 int
190 numeric(const char *s)
191 {
192 	int seen_decimal_pt, decimal_pt_len;
193 
194 	/* skip any sign */
195 	if (ISSIGN((unsigned char)*s))
196 		s++;
197 
198 	seen_decimal_pt = 0;
199 	decimal_pt_len = strlen(decimal_point);
200 	while (*s != '\0') {
201 		if (!isdigit((unsigned char)*s)) {
202 			if (!seen_decimal_pt &&
203 			    strncmp(s, decimal_point, decimal_pt_len) == 0) {
204 				s += decimal_pt_len;
205 				seen_decimal_pt = 1;
206 				continue;
207 			}
208 			if (ISEXP((unsigned char)*s)) {
209 				s++;
210 				if (ISSIGN((unsigned char)*s)) {
211 					s++;
212 					continue;
213 				}
214 			}
215 			break;
216 		}
217 		s++;
218 	}
219 	return(*s == '\0');
220 }
221 
222 /*
223  * valid_format - validate user specified format string
224  */
225 int
226 valid_format(const char *fmt)
227 {
228 	int conversions = 0;
229 
230 	while (*fmt != '\0') {
231 		/* scan for conversions */
232 		if (*fmt != '\0' && *fmt != '%') {
233 			do {
234 				fmt++;
235 			} while (*fmt != '\0' && *fmt != '%');
236 		}
237 		/* scan a conversion */
238 		if (*fmt != '\0') {
239 			do {
240 				fmt++;
241 
242 				/* ok %% */
243 				if (*fmt == '%') {
244 					fmt++;
245 					break;
246 				}
247 				/* valid conversions */
248 				if (strchr("eEfgG", *fmt) &&
249 				    conversions++ < 1) {
250 					fmt++;
251 					break;
252 				}
253 				/* flags, width and precsision */
254 				if (isdigit((unsigned char)*fmt) ||
255 				    strchr("+- 0#.", *fmt))
256 					continue;
257 
258 				/* oops! bad conversion format! */
259 				return(0);
260 			} while (*fmt != '\0');
261 		}
262 	}
263 
264 	return(conversions <= 1);
265 }
266 
267 /*
268  * unescape - handle C escapes in a string
269  */
270 char *
271 unescape(char *orig)
272 {
273 	char c, *cp, *new = orig;
274 	int i;
275 
276 	for (cp = orig; (*orig = *cp) != '\0'; cp++, orig++) {
277 		if (*cp != '\\')
278 			continue;
279 
280 		switch (*++cp) {
281 		case 'a':	/* alert (bell) */
282 			*orig = '\a';
283 			continue;
284 		case 'b':	/* backspace */
285 			*orig = '\b';
286 			continue;
287 		case 'e':	/* escape */
288 			*orig = '\e';
289 			continue;
290 		case 'f':	/* formfeed */
291 			*orig = '\f';
292 			continue;
293 		case 'n':	/* newline */
294 			*orig = '\n';
295 			continue;
296 		case 'r':	/* carriage return */
297 			*orig = '\r';
298 			continue;
299 		case 't':	/* horizontal tab */
300 			*orig = '\t';
301 			continue;
302 		case 'v':	/* vertical tab */
303 			*orig = '\v';
304 			continue;
305 		case '\\':	/* backslash */
306 			*orig = '\\';
307 			continue;
308 		case '\'':	/* single quote */
309 			*orig = '\'';
310 			continue;
311 		case '\"':	/* double quote */
312 			*orig = '"';
313 			continue;
314 		case '0':
315 		case '1':
316 		case '2':
317 		case '3':	/* octal */
318 		case '4':
319 		case '5':
320 		case '6':
321 		case '7':	/* number */
322 			for (i = 0, c = 0;
323 			     ISODIGIT((unsigned char)*cp) && i < 3;
324 			     i++, cp++) {
325 				c <<= 3;
326 				c |= (*cp - '0');
327 			}
328 			*orig = c;
329 			continue;
330 		case 'x':	/* hexidecimal number */
331 			cp++;	/* skip 'x' */
332 			for (i = 0, c = 0;
333 			     isxdigit((unsigned char)*cp) && i < 2;
334 			     i++, cp++) {
335 				c <<= 4;
336 				if (isdigit((unsigned char)*cp))
337 					c |= (*cp - '0');
338 				else
339 					c |= ((toupper((unsigned char)*cp) -
340 					    'A') + 10);
341 			}
342 			*orig = c;
343 			continue;
344 		default:
345 			--cp;
346 			break;
347 		}
348 	}
349 
350 	return(new);
351 }
352 
353 /*
354  * e_atof - convert an ASCII string to a double
355  *	exit if string is not a valid double, or if converted value would
356  *	cause overflow or underflow
357  */
358 double
359 e_atof(const char *num)
360 {
361 	char *endp;
362 	double dbl;
363 
364 	errno = 0;
365 	dbl = strtod(num, &endp);
366 
367 	if (errno == ERANGE)
368 		/* under or overflow */
369 		err(2, "%s", num);
370 	else if (*endp != '\0')
371 		/* "junk" left in number */
372 		errx(2, "invalid floating point argument: %s", num);
373 
374 	/* zero shall have no sign */
375 	if (dbl == -0.0)
376 		dbl = 0;
377 	return(dbl);
378 }
379 
380 /*
381  * decimal_places - count decimal places in a number (string)
382  */
383 int
384 decimal_places(const char *number)
385 {
386 	int places = 0;
387 	char *dp;
388 
389 	/* look for a decimal point */
390 	if ((dp = strstr(number, decimal_point)) != NULL) {
391 		dp += strlen(decimal_point);
392 
393 		while (isdigit((unsigned char)*dp++))
394 			places++;
395 	}
396 	return(places);
397 }
398 
399 /*
400  * generate_format - create a format string
401  *
402  * XXX to be bug for bug compatable with Plan9 and GNU return "%g"
403  * when "%g" prints as "%e" (this way no width adjustments are made)
404  */
405 char *
406 generate_format(double first, double incr, double last, int equalize, char pad)
407 {
408 	static char buf[256];
409 	char cc = '\0';
410 	int precision, width1, width2, places;
411 
412 	if (equalize == 0)
413 		return(default_format);
414 
415 	/* figure out "last" value printed */
416 	if (first > last)
417 		last = first - incr * floor((first - last) / incr);
418 	else
419 		last = first + incr * floor((last - first) / incr);
420 
421 	snprintf(buf, sizeof(buf), "%g", incr);
422 	if (strchr(buf, 'e'))
423 		cc = 'e';
424 	precision = decimal_places(buf);
425 
426 	width1 = snprintf(buf, sizeof(buf), "%g", first);
427 	if (strchr(buf, 'e'))
428 		cc = 'e';
429 	if ((places = decimal_places(buf)))
430 		width1 -= (places + strlen(decimal_point));
431 
432 	precision = MAX(places, precision);
433 
434 	width2 = snprintf(buf, sizeof(buf), "%g", last);
435 	if (strchr(buf, 'e'))
436 		cc = 'e';
437 	if ((places = decimal_places(buf)))
438 		width2 -= (places + strlen(decimal_point));
439 
440 	if (precision) {
441 		snprintf(buf, sizeof(buf), "%%%c%d.%d%c", pad,
442 		    MAX(width1, width2) + (int) strlen(decimal_point) +
443 		    precision, precision, (cc) ? cc : 'f');
444 	} else {
445 		snprintf(buf, sizeof(buf), "%%%c%d%c", pad,
446 		    MAX(width1, width2), (cc) ? cc : 'g');
447 	}
448 
449 	return(buf);
450 }
451