xref: /openbsd/usr.bin/rs/rs.c (revision d415bd75)
1 /*	$OpenBSD: rs.c,v 1.30 2015/12/03 12:23:15 schwarze 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 /*
33  *	rs - reshape a data array
34  *	Author:  John Kunze, Office of Comp. Affairs, UCB
35  *		BEWARE: lots of unfinished edges
36  */
37 
38 #include <ctype.h>
39 #include <err.h>
40 #include <errno.h>
41 #include <limits.h>
42 #include <locale.h>
43 #include <stdio.h>
44 #include <stdlib.h>
45 #include <string.h>
46 #include <unistd.h>
47 
48 struct	entry {
49 	int	 w;  /* Display width. */
50 	char	*s;  /* Multibyte string. */
51 };
52 
53 long	flags;
54 #define	TRANSPOSE	000001
55 #define	MTRANSPOSE	000002
56 #define	ONEPERLINE	000004
57 #define	ONEISEPONLY	000010
58 #define	ONEOSEPONLY	000020
59 #define	NOTRIMENDCOL	000040
60 #define	SQUEEZE		000100
61 #define	SHAPEONLY	000200
62 #define	DETAILSHAPE	000400
63 #define	RIGHTADJUST	001000
64 #define	NULLPAD		002000
65 #define	RECYCLE		004000
66 #define	SKIPPRINT	010000
67 #define ONEPERCHAR	0100000
68 #define NOARGS		0200000
69 
70 short	*colwidths;
71 int	nelem;
72 struct entry *elem;
73 struct entry *endelem;
74 char	*curline;
75 int	allocsize = BUFSIZ;
76 int	irows, icols;
77 int	orows, ocols;
78 int	maxwidth;
79 int	skip;
80 int	propgutter;
81 char	isep = ' ', osep = ' ';
82 int	owidth = 80, gutter = 2;
83 
84 int	  mbsavis(char **, const char *);
85 
86 void	  usage(void);
87 void	  getargs(int, char *[]);
88 void	  getfile(void);
89 int	  get_line(void);
90 struct entry *getptrs(struct entry *);
91 void	  prepfile(void);
92 void	  prints(struct entry *, int);
93 void	  putfile(void);
94 
95 #define INCR(ep) do {			\
96 	if (++ep >= endelem)		\
97 		ep = getptrs(ep);	\
98 } while(0)
99 
100 int
101 main(int argc, char *argv[])
102 {
103 	setlocale(LC_CTYPE, "");
104 
105 	if (pledge("stdio", NULL) == -1)
106 		err(1, "pledge");
107 
108 	getargs(argc, argv);
109 	getfile();
110 	if (flags & SHAPEONLY) {
111 		printf("%d %d\n", irows, icols);
112 		exit(0);
113 	}
114 	prepfile();
115 	putfile();
116 	exit(0);
117 }
118 
119 void
120 getfile(void)
121 {
122 	const char delim[2] = { isep, '\0' };
123 	char *p;
124 	struct entry *ep;
125 	int multisep = (flags & ONEISEPONLY ? 0 : 1);
126 	int nullpad = flags & NULLPAD;
127 	struct entry *padto;
128 
129 	curline = NULL;
130 	while (skip--) {
131 		if (get_line() == EOF)
132 			return;
133 		if (flags & SKIPPRINT)
134 			puts(curline);
135 	}
136 	if (get_line() == EOF)
137 		return;
138 	if (flags & NOARGS && strlen(curline) < (size_t)owidth)
139 		flags |= ONEPERLINE;
140 	if (flags & ONEPERLINE)
141 		icols = 1;
142 	else				/* count cols on first line */
143 		for (p = curline; *p != '\0'; p++) {
144 			if (*p == isep && multisep)
145 				continue;
146 			icols++;
147 			while (*p && *p != isep)
148 				p++;
149 		}
150 	ep = getptrs(NULL);
151 	p = curline;
152 	do {
153 		if (flags & ONEPERLINE) {
154 			ep->w = mbsavis(&ep->s, curline);
155 			if (maxwidth < ep->w)
156 				maxwidth = ep->w;
157 			INCR(ep);		/* prepare for next entry */
158 			irows++;
159 			continue;
160 		}
161 		p = curline;
162 		while (p != NULL && *p != '\0') {
163 			if (*p == isep) {
164 				p++;
165 				if (multisep)
166 					continue;
167 				ep->s = "";	/* empty column */
168 				ep->w = 0;
169 			} else
170 				ep->w = mbsavis(&ep->s, strsep(&p, delim));
171 			if (maxwidth < ep->w)
172 				maxwidth = ep->w;
173 			INCR(ep);		/* prepare for next entry */
174 		}
175 		irows++;			/* update row count */
176 		if (nullpad) {			/* pad missing entries */
177 			padto = elem + irows * icols;
178 			while (ep < padto) {
179 				ep->s = "";
180 				ep->w = 0;
181 				INCR(ep);
182 			}
183 		}
184 	} while (get_line() != EOF);
185 	nelem = ep - elem;
186 }
187 
188 void
189 putfile(void)
190 {
191 	struct entry *ep;
192 	int i, j, n;
193 
194 	ep = elem;
195 	if (flags & TRANSPOSE) {
196 		for (i = 0; i < orows; i++) {
197 			for (j = i; j < nelem; j += orows)
198 				prints(ep + j, (j - i) / orows);
199 			putchar('\n');
200 		}
201 	} else {
202 		for (n = 0, i = 0; i < orows && n < nelem; i++) {
203 			for (j = 0; j < ocols; j++) {
204 				if (n++ >= nelem)
205 					break;
206 				prints(ep++, j);
207 			}
208 			putchar('\n');
209 		}
210 	}
211 }
212 
213 void
214 prints(struct entry *ep, int col)
215 {
216 	int n;
217 
218 	n = (flags & ONEOSEPONLY ? 1 : colwidths[col] - ep->w);
219 	if (flags & RIGHTADJUST)
220 		while (n-- > 0)
221 			putchar(osep);
222 	fputs(ep->s, stdout);
223 	while (n-- > 0)
224 		putchar(osep);
225 }
226 
227 void
228 usage(void)
229 {
230 	extern char *__progname;
231 
232 	fprintf(stderr,
233 	    "usage: %s [-CcSs[x]] [-GgKkw N] [-EeHhjmnTtyz] [rows [cols]]\n",
234 	    __progname);
235 	exit(1);
236 }
237 
238 void
239 prepfile(void)
240 {
241 	struct entry *ep;
242 	int  i;
243 	int  j;
244 	struct entry *lp;
245 	int colw;
246 	int max = 0;
247 	int n;
248 
249 	if (!nelem)
250 		exit(0);
251 	gutter += maxwidth * propgutter / 100.0;
252 	colw = maxwidth + gutter;
253 	if (flags & MTRANSPOSE) {
254 		orows = icols;
255 		ocols = irows;
256 	}
257 	else if (orows == 0 && ocols == 0) {	/* decide rows and cols */
258 		ocols = owidth / colw;
259 		if (ocols == 0) {
260 			warnx("Display width %d is less than column width %d",
261 			    owidth, colw);
262 			ocols = 1;
263 		}
264 		if (ocols > nelem)
265 			ocols = nelem;
266 		orows = nelem / ocols + (nelem % ocols ? 1 : 0);
267 	}
268 	else if (orows == 0)			/* decide on rows */
269 		orows = nelem / ocols + (nelem % ocols ? 1 : 0);
270 	else if (ocols == 0)			/* decide on cols */
271 		ocols = nelem / orows + (nelem % orows ? 1 : 0);
272 	while ((lp = elem + orows * ocols) > endelem)
273 	     (void)getptrs(NULL);
274 	if (flags & RECYCLE) {
275 		for (ep = elem + nelem; ep < lp; ep++)
276 			memcpy(ep, ep - nelem, sizeof(*ep));
277 		nelem = lp - elem;
278 	}
279 	if (!(colwidths = calloc(ocols, sizeof(short))))
280 		errx(1, "malloc:  No gutter space");
281 	if (flags & SQUEEZE) {
282 		for (ep = elem, i = 0; i < ocols; i++) {
283 			max = 0;
284 			if (flags & TRANSPOSE) {
285 				for (j = 0; j < orows; j++, ep++)
286 					if (ep->w > max)
287 						max = ep->w;
288 			} else {
289 				for (j = i; j < nelem; j += ocols)
290 					if (ep[j].w > max)
291 						max = ep[j].w;
292 			}
293 			colwidths[i] = max + gutter;
294 		}
295 	} else {
296 		for (i = 0; i < ocols; i++)
297 			colwidths[i] = colw;
298 	}
299 	if (!(flags & NOTRIMENDCOL)) {
300 		if (flags & RIGHTADJUST)
301 			colwidths[0] -= gutter;
302 		else
303 			colwidths[ocols - 1] = 0;
304 	}
305 	n = orows * ocols;
306 	if (n > nelem && (flags & RECYCLE))
307 		nelem = n;
308 }
309 
310 int
311 get_line(void)
312 {
313 	static	size_t	 cursz;
314 	static	ssize_t	 curlen;
315 
316 	if (irows > 0 && flags & DETAILSHAPE)
317 		printf(" %zd line %d\n", curlen, irows);
318 
319 	if ((curlen = getline(&curline, &cursz, stdin)) == EOF) {
320 		if (ferror(stdin))
321 			err(1, NULL);
322 		return EOF;
323 	}
324 	if (curlen > 0 && curline[curlen - 1] == '\n')
325 		curline[--curlen] = '\0';
326 
327 	return 0;
328 }
329 
330 struct entry *
331 getptrs(struct entry *sp)
332 {
333 	struct entry *p;
334 	int newsize;
335 
336 	newsize = allocsize * 2;
337 	p = reallocarray(elem, newsize, sizeof(*p));
338 	if (p == NULL)
339 		err(1, "no memory");
340 
341 	allocsize = newsize;
342 	sp = sp == NULL ? p : p + (sp - elem);
343 	elem = p;
344 	endelem = elem + allocsize;
345 	return(sp);
346 }
347 
348 void
349 getargs(int ac, char *av[])
350 {
351 	int ch;
352 	const char *errstr;
353 
354 	if (ac == 1)
355 		flags |= NOARGS | TRANSPOSE;
356 	while ((ch = getopt(ac, av, "c::C::s::S::k:K:g:G:w:tTeEnyjhHmz")) != -1) {
357 		switch (ch) {
358 		case 'T':
359 			flags |= MTRANSPOSE;
360 			/* FALLTHROUGH */
361 		case 't':
362 			flags |= TRANSPOSE;
363 			break;
364 		case 'c':		/* input col. separator */
365 			flags |= ONEISEPONLY;
366 			/* FALLTHROUGH */
367 		case 's':		/* one or more allowed */
368 			if (optarg == NULL)
369 				isep = '\t';	/* default is ^I */
370 			else if (optarg[1] != '\0')
371 				usage();	/* single char only */
372 			else
373 				isep = *optarg;
374 			break;
375 		case 'C':
376 			flags |= ONEOSEPONLY;
377 			/* FALLTHROUGH */
378 		case 'S':
379 			if (optarg == NULL)
380 				osep = '\t';	/* default is ^I */
381 			else if (optarg[1] != '\0')
382 				usage();	/* single char only */
383 			else
384 				osep = *optarg;
385 			break;
386 		case 'w':		/* window width, default 80 */
387 			owidth = strtonum(optarg, 1, INT_MAX, &errstr);
388 			if (errstr) {
389 				warnx("width %s", errstr);
390 				usage();
391 			}
392 			break;
393 		case 'K':			/* skip N lines */
394 			flags |= SKIPPRINT;
395 			/* FALLTHROUGH */
396 		case 'k':			/* skip, do not print */
397 			skip = strtonum(optarg, 0, INT_MAX, &errstr);
398 			if (errstr) {
399 				warnx("skip value %s", errstr);
400 				usage();
401 			}
402 			if (skip == 0)
403 				skip = 1;
404 			break;
405 		case 'm':
406 			flags |= NOTRIMENDCOL;
407 			break;
408 		case 'g':		/* gutter width */
409 			gutter = strtonum(optarg, 0, INT_MAX, &errstr);
410 			if (errstr) {
411 				warnx("gutter width %s", errstr);
412 				usage();
413 			}
414 			break;
415 		case 'G':
416 			propgutter = strtonum(optarg, 0, INT_MAX, &errstr);
417 			if (errstr) {
418 				warnx("gutter proportion %s", errstr);
419 				usage();
420 			}
421 			break;
422 		case 'e':		/* each line is an entry */
423 			flags |= ONEPERLINE;
424 			break;
425 		case 'E':
426 			flags |= ONEPERCHAR;
427 			break;
428 		case 'j':			/* right adjust */
429 			flags |= RIGHTADJUST;
430 			break;
431 		case 'n':	/* null padding for missing values */
432 			flags |= NULLPAD;
433 			break;
434 		case 'y':
435 			flags |= RECYCLE;
436 			break;
437 		case 'H':			/* print shape only */
438 			flags |= DETAILSHAPE;
439 			/* FALLTHROUGH */
440 		case 'h':
441 			flags |= SHAPEONLY;
442 			break;
443 		case 'z':			/* squeeze col width */
444 			flags |= SQUEEZE;
445 			break;
446 		default:
447 			usage();
448 		}
449 	}
450 	ac -= optind;
451 	av += optind;
452 
453 	switch (ac) {
454 	case 2:
455 		ocols = strtonum(av[1], 0, INT_MAX, &errstr);
456 		if (errstr) {
457 			warnx("columns value %s", errstr);
458 			usage();
459 		}
460 		/* FALLTHROUGH */
461 	case 1:
462 		orows = strtonum(av[0], 0, INT_MAX, &errstr);
463 		if (errstr) {
464 			warnx("columns value %s", errstr);
465 			usage();
466 		}
467 		/* FALLTHROUGH */
468 	case 0:
469 		break;
470 	default:
471 		usage();
472 	}
473 }
474