xref: /netbsd-src/usr.bin/jot/jot.c (revision e6d6e05cb173f30287ab619b21120b27baa66ad6)
1 /*	$NetBSD: jot.c,v 1.22 2008/03/02 21:33:42 dsl Exp $	*/
2 
3 /*-
4  * Copyright (c) 1993
5  *	The Regents of the University of California.  All rights reserved.
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 University nor the names of its contributors
16  *    may be used to endorse or promote products derived from this software
17  *    without specific prior written permission.
18  *
19  * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
20  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
21  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
22  * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
23  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
24  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
25  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
26  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
27  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
28  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
29  * SUCH DAMAGE.
30  */
31 
32 #include <sys/cdefs.h>
33 #ifndef lint
34 __COPYRIGHT("@(#) Copyright (c) 1993\n\
35 	The Regents of the University of California.  All rights reserved.\n");
36 #endif /* not lint */
37 
38 #ifndef lint
39 #if 0
40 static char sccsid[] = "@(#)jot.c	8.1 (Berkeley) 6/6/93";
41 #endif
42 __RCSID("$NetBSD: jot.c,v 1.22 2008/03/02 21:33:42 dsl Exp $");
43 #endif /* not lint */
44 
45 /*
46  * jot - print sequential or random data
47  *
48  * Author:  John Kunze, Office of Comp. Affairs, UCB
49  */
50 
51 #include <ctype.h>
52 #include <err.h>
53 #include <limits.h>
54 #include <math.h>
55 #include <stdio.h>
56 #include <stdlib.h>
57 #include <string.h>
58 #include <time.h>
59 #include <unistd.h>
60 
61 #define	REPS_DEF	100
62 #define	BEGIN_DEF	1
63 #define	ENDER_DEF	100
64 #define	STEP_DEF	1
65 
66 #define	is_default(s)	(strcmp((s), "-") == 0)
67 
68 static double	begin = BEGIN_DEF;
69 static double	ender = ENDER_DEF;
70 static double	step = STEP_DEF;
71 static long	reps = REPS_DEF;
72 static int	randomize;
73 static int	boring;
74 static int	prec = -1;
75 static int	dox;
76 static int	chardata;
77 static int	nofinalnl;
78 static const char *sepstring = "\n";
79 static char	format[BUFSIZ];
80 
81 static void	getargs(int, char *[]);
82 static void	getformat(void);
83 static int	getprec(char *);
84 static void	putdata(double, long);
85 static void	usage(void) __dead;
86 
87 int
88 main(int argc, char *argv[])
89 {
90 	double	x;
91 	long	i;
92 
93 	getargs(argc, argv);
94 	if (randomize) {
95 		x = ender - begin;
96 		if (dox == 0)
97 			/*
98 			 * We are printing floating point, generate random
99 			 * number that include both supplied limits.
100 			 * Due to FP routing for display the low and high
101 			 * values are likely to occur half as often as all
102 			 * the others.
103 			 */
104 			x /= (1u << 31) - 1.0;
105 		else {
106 			/*
107 			 * We are printing integers increase the range by
108 			 * one but ensure we never generate it.
109 			 * This makes all the integer values equally likely.
110 			 */
111 			if (ender > begin)
112 				x += 1.0;
113 			else
114 				x -= 1.0;
115 			x /= (1u << 31);
116 		}
117 		srandom((unsigned long) step);
118 		for (i = 1; i <= reps || reps == 0; i++)
119 			putdata(random() * x + begin, reps - i);
120 	} else {
121 		/*
122 		 * If we are going to display as integer, add 0.5 here
123 		 * and use floor(x) later to get sane rounding.
124 		 */
125 		x = begin;
126 		if (dox)
127 			x += 0.5;
128 		for (i = 1; i <= reps || reps == 0; i++, x += step)
129 			putdata(x, reps - i);
130 	}
131 	if (!nofinalnl)
132 		putchar('\n');
133 	exit(0);
134 }
135 
136 static void
137 getargs(int argc, char *argv[])
138 {
139 	unsigned int have = 0;
140 #define BEGIN	1
141 #define	STEP	2	/* seed if -r */
142 #define REPS	4
143 #define	ENDER	8
144 	int n = 0;
145 	long t;
146 	char *ep;
147 
148 	for (;;) {
149 		switch (getopt(argc, argv, "b:cnp:rs:w:")) {
150 		default:
151 			usage();
152 		case -1:
153 			break;
154 		case 'c':
155 			chardata = 1;
156 			continue;
157 		case 'n':
158 			nofinalnl = 1;
159 			continue;
160 		case 'p':
161 			prec = strtol(optarg, &ep, 0);
162 			if (*ep != 0 || prec < 0)
163 				errx(EXIT_FAILURE, "Bad precision value");
164 			continue;
165 		case 'r':
166 			randomize = 1;
167 			continue;
168 		case 's':
169 			sepstring = optarg;
170 			continue;
171 		case 'b':
172 			boring = 1;
173 			/* FALLTHROUGH */
174 		case 'w':
175 			strlcpy(format, optarg, sizeof(format));
176 			continue;
177 		}
178 		break;
179 	}
180 	argc -= optind;
181 	argv += optind;
182 
183 	switch (argc) {	/* examine args right to left, falling thru cases */
184 	case 4:
185 		if (!is_default(argv[3])) {
186 			step = strtod(argv[3], &ep);
187 			if (*ep != 0)
188 				errx(EXIT_FAILURE, "Bad step value:  %s",
189 				    argv[3]);
190 			have |= STEP;
191 		}
192 	case 3:
193 		if (!is_default(argv[2])) {
194 			if (!sscanf(argv[2], "%lf", &ender))
195 				ender = argv[2][strlen(argv[2])-1];
196 			have |= ENDER;
197 			if (prec < 0)
198 				n = getprec(argv[2]);
199 		}
200 	case 2:
201 		if (!is_default(argv[1])) {
202 			if (!sscanf(argv[1], "%lf", &begin))
203 				begin = argv[1][strlen(argv[1])-1];
204 			have |= BEGIN;
205 			if (prec < 0)
206 				prec = getprec(argv[1]);
207 			if (n > prec)		/* maximum precision */
208 				prec = n;
209 		}
210 	case 1:
211 		if (!is_default(argv[0])) {
212 			reps = strtoul(argv[0], &ep, 0);
213 			if (*ep != 0 || reps < 0)
214 				errx(EXIT_FAILURE, "Bad reps value:  %s",
215 				    argv[0]);
216 			have |= REPS;
217 		}
218 		break;
219 	case 0:
220 		usage();
221 		break;
222 	default:
223 		errx(EXIT_FAILURE,
224 		    "Too many arguments.  What do you mean by %s?", argv[4]);
225 	}
226 	getformat();
227 
228 	if (prec == -1)
229 		prec = 0;
230 
231 	if (randomize) {
232 		/* 'step' is the seed here, use pseudo-random default */
233 		if (!(have & STEP))
234 			step = time(NULL) * getpid();
235 		/* Take the default values for everything else */
236 		return;
237 	}
238 
239 	/*
240 	 * The loop we run uses begin/step/reps, so if we have been
241 	 * given an end value (ender) we must use it to replace the
242 	 * default values of the others.
243 	 * We will assume a begin of 0 and step of 1 if necessary.
244 	 */
245 
246 	switch (have) {
247 
248 	case ENDER | STEP:
249 	case ENDER | STEP | BEGIN:
250 		/* Calculate reps */
251 		if (step == 0.0)
252 			reps = 0;	/* ie infinite */
253 		else {
254 			reps = (ender - begin + step) / step;
255 			if (reps <= 0)
256 				errx(EXIT_FAILURE, "Impossible stepsize");
257 		}
258 		break;
259 
260 	case REPS | ENDER:
261 	case REPS | ENDER | STEP:
262 		/* Calculate begin */
263 		if (reps == 0)
264 			errx(EXIT_FAILURE,
265 			    "Must specify begin if reps == 0");
266 		begin = ender - reps * step + step;
267 		break;
268 
269 	case REPS | BEGIN | ENDER:
270 		/* Calculate step */
271 		if (reps == 0)
272 			errx(EXIT_FAILURE,
273 			    "Infinite sequences cannot be bounded");
274 		if (reps == 1)
275 			step = 0.0;
276 		else
277 			step = (ender - begin) / (reps - 1);
278 		break;
279 
280 	case REPS | BEGIN | ENDER | STEP:
281 		/* reps given and implied - take smaller */
282 		if (step == 0.0)
283 			break;
284 		t = (ender - begin + step) / step;
285 		if (t <= 0)
286 			errx(EXIT_FAILURE,
287 			    "Impossible stepsize");
288 		if (t < reps)
289 			reps = t;
290 		break;
291 
292 	default:
293 		/* No values can be calculated, use defaults */
294 		break;
295 	}
296 }
297 
298 static void
299 putdata(double x, long notlast)
300 {
301 
302 	if (boring)				/* repeated word */
303 		printf("%s", format);
304 	else if (dox)				/* scalar */
305 		printf(format, (long)floor(x));
306 	else					/* real */
307 		printf(format, x);
308 	if (notlast != 0)
309 		fputs(sepstring, stdout);
310 }
311 
312 __dead static void
313 usage(void)
314 {
315 	(void)fprintf(stderr, "usage: %s [-cnr] [-b word] [-p precision] "
316 	    "[-s string] [-w word] [reps [begin [end [step | seed]]]]\n",
317 	    getprogname());
318 	exit(1);
319 }
320 
321 static int
322 getprec(char *num_str)
323 {
324 
325 	num_str = strchr(num_str, '.');
326 	if (num_str == NULL)
327 		return 0;
328 	return strspn(num_str + 1, "0123456789");
329 }
330 
331 static void
332 getformat(void)
333 {
334 	char	*p;
335 	size_t	sz;
336 
337 	if (boring)				/* no need to bother */
338 		return;
339 	for (p = format; *p; p++) {		/* look for '%' */
340 		if (*p == '%') {
341 			if (*(p+1) != '%')
342 				break;
343 			p++;		/* leave %% alone */
344 		}
345 	}
346 	sz = sizeof(format) - strlen(format) - 1;
347 	if (!*p) {
348 		if (chardata || prec == 0) {
349 			if (snprintf(p, sz, "%%%s", chardata ? "c" : "ld") >= sz)
350 				errx(EXIT_FAILURE, "-w word too long");
351 			dox = 1;
352 		} else {
353 			if (snprintf(p, sz, "%%.%df", prec) >= (int)sz)
354 				errx(EXIT_FAILURE, "-w word too long");
355 		}
356 	} else if (!*(p+1)) {
357 		if (sz <= 0)
358 			errx(EXIT_FAILURE, "-w word too long");
359 		strcat(format, "%");		/* cannot end in single '%' */
360 	} else {
361 		p++;				/* skip leading % */
362 		for(; *p && !isalpha((unsigned char)*p); p++) {
363 			/* allow all valid printf(3) flags, but deny '*' */
364 			if (!strchr("0123456789#-+. ", *p))
365 				break;
366 		}
367 		/* Allow 'l' prefix, but no other. */
368 		if (*p == 'l')
369 			p++;
370 		switch (*p) {
371 		case 'f': case 'e': case 'g': case '%':
372 		case 'E': case 'G':
373 			break;
374 		case 's':
375 			errx(EXIT_FAILURE,
376 			    "cannot convert numeric data to strings");
377 			break;
378 		case 'd': case 'o': case 'x': case 'u':
379 		case 'D': case 'O': case 'X': case 'U':
380 		case 'c': case 'i':
381 			dox = 1;
382 			break;
383 		default:
384 			errx(EXIT_FAILURE, "unknown or invalid format `%s'",
385 			    format);
386 		}
387 		/* Need to check for trailing stuff to print */
388 		for (; *p; p++)		/* look for '%' */
389 			if (*p == '%') {
390 				if (*(p+1) != '%')
391 					break;
392 				p++;		/* leave %% alone */
393 			}
394 		if (*p)
395 			errx(EXIT_FAILURE, "unknown or invalid format `%s'",
396 			    format);
397 	}
398 }
399