1/*	$NetBSD: rs.c,v 1.17 2023/08/10 20:36:29 mrg 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\
35 The Regents of the University of California.  All rights reserved.");
36#endif /* not lint */
37
38#ifndef lint
39#if 0
40static char sccsid[] = "@(#)rs.c	8.1 (Berkeley) 6/6/93";
41#else
42__RCSID("$NetBSD: rs.c,v 1.17 2023/08/10 20:36:29 mrg Exp $");
43#endif
44#endif /* not lint */
45
46/*
47 *	rs - reshape a data array
48 *	Author:  John Kunze, Office of Comp. Affairs, UCB
49 *		BEWARE: lots of unfinished edges
50 */
51
52#include <ctype.h>
53#include <err.h>
54#include <stdio.h>
55#include <stdlib.h>
56#include <string.h>
57#include <stdarg.h>
58
59static long	flags;
60#define	TRANSPOSE	000001
61#define	MTRANSPOSE	000002
62#define	ONEPERLINE	000004
63#define	ONEISEPONLY	000010
64#define	ONEOSEPONLY	000020
65#define	NOTRIMENDCOL	000040
66#define	SQUEEZE		000100
67#define	SHAPEONLY	000200
68#define	DETAILSHAPE	000400
69#define	RIGHTADJUST	001000
70#define	NULLPAD		002000
71#define	RECYCLE		004000
72#define	SKIPPRINT	010000
73#define	ICOLBOUNDS	020000
74#define	OCOLBOUNDS	040000
75#define ONEPERCHAR	0100000
76#define NOARGS		0200000
77
78static short	*colwidths;
79static short	*cord;
80static short	*icbd;
81static short	*ocbd;
82static int	nelem;
83static char	**elem;
84static char	**endelem;
85static char	*curline;
86static int	allocsize = BUFSIZ;
87static int	curlen;
88static int	irows, icols;
89static int	orows, ocols;
90static int	maxlen;
91static int	skip;
92static int	propgutter;
93static char	isep = ' ', osep = ' ';
94static int	owidth = 80, gutter = 2;
95
96static void	  usage(const char *, ...) __dead __printflike(1, 2);
97static void	  getargs(int, char *[]);
98static void	  getfile(void);
99static int	  get_line(void);
100static char	 *getlist(short **, char *);
101static char	 *getnum(int *, char *, int);
102static char	**getptrs(char **);
103static void	  prepfile(void);
104static void	  prints(char *, int);
105static void	  putfile(void);
106
107#define INCR(ep) do {			\
108	if (++ep >= endelem)		\
109		ep = getptrs(ep);	\
110} while(0)
111
112int
113main(int argc, char *argv[])
114{
115	getargs(argc, argv);
116	getfile();
117	if (flags & SHAPEONLY) {
118		printf("%d %d\n", irows, icols);
119		exit(0);
120	}
121	prepfile();
122	putfile();
123	exit(0);
124}
125
126static void
127getfile(void)
128{
129	char empty[1] = { '\0' };
130	char *p;
131	char *endp;
132	char **ep = 0;
133	int multisep = (flags & ONEISEPONLY ? 0 : 1);
134	int nullpad = flags & NULLPAD;
135	char **padto;
136
137	while (skip--) {
138		get_line();
139		if (flags & SKIPPRINT)
140			puts(curline);
141	}
142	get_line();
143	if (flags & NOARGS && curlen < owidth)
144		flags |= ONEPERLINE;
145	if (flags & ONEPERLINE)
146		icols = 1;
147	else				/* count cols on first line */
148		for (p = curline, endp = curline + curlen; p < endp; p++) {
149			if (*p == isep && multisep)
150				continue;
151			icols++;
152			while (*p && *p != isep)
153				p++;
154		}
155	ep = getptrs(elem);
156	p = curline;
157	do {
158		if (flags & ONEPERLINE) {
159			*ep = curline;
160			INCR(ep);		/* prepare for next entry */
161			if (maxlen < curlen)
162				maxlen = curlen;
163			irows++;
164			continue;
165		}
166		for (p = curline, endp = curline + curlen; p < endp; p++) {
167			if (*p == isep && multisep)
168				continue;	/* eat up column separators */
169			if (*p == isep)		/* must be an empty column */
170				*ep = empty;
171			else			/* store column entry */
172				*ep = p;
173			while (p < endp && *p != isep)
174				p++;		/* find end of entry */
175			*p = '\0';		/* mark end of entry */
176			if (maxlen < p - *ep)	/* update maxlen */
177				maxlen = p - *ep;
178			INCR(ep);		/* prepare for next entry */
179		}
180		irows++;			/* update row count */
181		if (nullpad) {			/* pad missing entries */
182			padto = elem + irows * icols;
183			while (ep < padto) {
184				*ep = empty;
185				INCR(ep);
186			}
187		}
188	} while (get_line() != EOF);
189	*ep = 0;				/* mark end of pointers */
190	nelem = ep - elem;
191}
192
193static void
194putfile(void)
195{
196	char **ep;
197	int i, j, n;
198
199	ep = elem;
200	if (flags & TRANSPOSE) {
201		for (i = 0; i < orows; i++) {
202			for (j = i; j < nelem; j += orows)
203				prints(ep[j], (j - i) / orows);
204			putchar('\n');
205		}
206	} else {
207		for (n = 0, i = 0; i < orows && n < nelem; i++) {
208			for (j = 0; j < ocols; j++) {
209				if (n++ >= nelem)
210					break;
211				prints(*ep++, j);
212			}
213			putchar('\n');
214		}
215	}
216}
217
218static void
219prints(char *s, int col)
220{
221	int n;
222	char *p = s;
223
224	while (*p)
225		p++;
226	n = (flags & ONEOSEPONLY ? 1 : colwidths[col] - (p - s));
227	if (flags & RIGHTADJUST)
228		while (n-- > 0)
229			putchar(osep);
230	for (p = s; *p; p++)
231		putchar(*p);
232	while (n-- > 0)
233		putchar(osep);
234}
235
236static void
237usage(const char *msg, ...)
238{
239	va_list ap;
240
241	va_start(ap, msg);
242	vwarnx(msg, ap);
243	va_end(ap);
244	fprintf(stderr,
245"usage:  rs [ -[csCS][x][kKgGw][N]tTeEnyjhHm ] [ rows [ cols ] ]\n");
246	exit(1);
247}
248
249static void
250prepfile(void)
251{
252	char **ep;
253	int  i;
254	int  j;
255	char **lp;
256	int colw;
257	int max = 0;
258	int n;
259
260	ep = NULL;
261	if (!nelem)
262		exit(0);
263	gutter += maxlen * propgutter / 100.0;
264	colw = maxlen + gutter;
265	if (flags & MTRANSPOSE) {
266		orows = icols;
267		ocols = irows;
268	}
269	else if (orows == 0 && ocols == 0) {	/* decide rows and cols */
270		ocols = owidth / colw;
271		if (ocols == 0) {
272			warnx("Display width %d is less than column width %d", owidth, colw);
273			ocols = 1;
274		}
275		if (ocols > nelem)
276			ocols = nelem;
277		orows = nelem / ocols + (nelem % ocols ? 1 : 0);
278	}
279	else if (orows == 0)			/* decide on rows */
280		orows = nelem / ocols + (nelem % ocols ? 1 : 0);
281	else if (ocols == 0)			/* decide on cols */
282		ocols = nelem / orows + (nelem % orows ? 1 : 0);
283	lp = elem + orows * ocols;
284	while (lp > endelem) {
285		getptrs(elem + nelem);
286		lp = elem + orows * ocols;
287	}
288	if (flags & RECYCLE) {
289		for (ep = elem + nelem; ep < lp; ep++)
290			*ep = *(ep - nelem);
291		nelem = lp - elem;
292	}
293	if (!(colwidths = (short *) malloc(ocols * sizeof(short))))
294		errx(1, "malloc:  No gutter space");
295	if (flags & SQUEEZE) {
296		if (flags & TRANSPOSE)
297			for (ep = elem, i = 0; i < ocols; i++) {
298				for (j = 0; j < orows; j++)
299					if ((n = strlen(*ep++)) > max)
300						max = n;
301				colwidths[i] = max + gutter;
302			}
303		else
304			for (ep = elem, i = 0; i < ocols; i++) {
305				for (j = i; j < nelem; j += ocols)
306					if ((n = strlen(ep[j])) > max)
307						max = n;
308				colwidths[i] = max + gutter;
309			}
310	}
311	/*	for (i = 0; i < orows; i++) {
312			for (j = i; j < nelem; j += orows)
313				prints(ep[j], (j - i) / orows);
314			putchar('\n');
315		}
316	else
317		for (i = 0; i < orows; i++) {
318			for (j = 0; j < ocols; j++)
319				prints(*ep++, j);
320			putchar('\n');
321		}*/
322	else
323		for (i = 0; i < ocols; i++)
324			colwidths[i] = colw;
325	if (!(flags & NOTRIMENDCOL)) {
326		if (flags & RIGHTADJUST)
327			colwidths[0] -= gutter;
328		else
329			colwidths[ocols - 1] = 0;
330	}
331	n = orows * ocols;
332	if (n > nelem && (flags & RECYCLE))
333		nelem = n;
334	/*for (i = 0; i < ocols; i++)
335		fprintf(stderr, "%d ",colwidths[i]);
336	fprintf(stderr, "is colwidths, nelem %d\n", nelem);*/
337}
338
339#define	BSIZE	2048
340char	ibuf[BSIZE];		/* two screenfuls should do */
341
342static int
343get_line(void)	/* get line; maintain curline, curlen; manage storage */
344{
345	static	int putlength;
346	static	char *endblock = ibuf + BSIZE;
347	char *p;
348	int c, i;
349
350	if (!irows) {
351		curline = ibuf;
352		putlength = flags & DETAILSHAPE;
353	}
354	else if (skip <= 0) {			/* don't waste storage */
355		curline += curlen + 1;
356		if (putlength)		/* print length, recycle storage */
357			printf(" %d line %d\n", curlen, irows);
358	}
359	if (!putlength && endblock - curline < BUFSIZ) {   /* need storage */
360		/*ww = endblock-curline; tt += ww;*/
361		/*printf("#wasted %d total %d\n",ww,tt);*/
362		if (!(curline = (char *) malloc(BSIZE)))
363			errx(1, "File too large");
364		endblock = curline + BSIZE;
365		/*printf("#endb %d curline %d\n",endblock,curline);*/
366	}
367	for (p = curline, i = 1; i < BUFSIZ; *p++ = c, i++)
368		if ((c = getchar()) == EOF || c == '\n')
369			break;
370	*p = '\0';
371	curlen = i - 1;
372	return(c);
373}
374
375static char **
376getptrs(char **sp)
377{
378	char **p;
379	ptrdiff_t off;
380
381	allocsize += allocsize;
382	off = sp - elem;
383	p = (char **)realloc(elem, allocsize * sizeof(char *));
384	if (p == (char **)0)
385		err(1, "no memory");
386
387	sp = p + off;
388	endelem = (elem = p) + allocsize;
389	return(sp);
390}
391
392static void
393getargs(int ac, char *av[])
394{
395	char *p;
396
397	if (ac == 1) {
398		flags |= NOARGS | TRANSPOSE;
399	}
400	while (--ac && **++av == '-')
401		for (p = *av+1; *p; p++)
402			switch (*p) {
403			case 'T':
404				flags |= MTRANSPOSE;
405				/* FALLTHROUGH */
406			case 't':
407				flags |= TRANSPOSE;
408				break;
409			case 'c':		/* input col. separator */
410				flags |= ONEISEPONLY;
411				/* FALLTHROUGH */
412			case 's':		/* one or more allowed */
413				if (p[1])
414					isep = *++p;
415				else
416					isep = '\t';	/* default is ^I */
417				break;
418			case 'C':
419				flags |= ONEOSEPONLY;
420				/* FALLTHROUGH */
421			case 'S':
422				if (p[1])
423					osep = *++p;
424				else
425					osep = '\t';	/* default is ^I */
426				break;
427			case 'w':		/* window width, default 80 */
428				p = getnum(&owidth, p, 0);
429				if (owidth <= 0)
430				usage("Width must be a positive integer");
431				break;
432			case 'K':			/* skip N lines */
433				flags |= SKIPPRINT;
434				/* FALLTHROUGH */
435			case 'k':			/* skip, do not print */
436				p = getnum(&skip, p, 0);
437				if (!skip)
438					skip = 1;
439				break;
440			case 'm':
441				flags |= NOTRIMENDCOL;
442				break;
443			case 'g':		/* gutter space */
444				p = getnum(&gutter, p, 0);
445				break;
446			case 'G':
447				p = getnum(&propgutter, p, 0);
448				break;
449			case 'e':		/* each line is an entry */
450				flags |= ONEPERLINE;
451				break;
452			case 'E':
453				flags |= ONEPERCHAR;
454				break;
455			case 'j':			/* right adjust */
456				flags |= RIGHTADJUST;
457				break;
458			case 'n':	/* null padding for missing values */
459				flags |= NULLPAD;
460				break;
461			case 'y':
462				flags |= RECYCLE;
463				break;
464			case 'H':			/* print shape only */
465				flags |= DETAILSHAPE;
466				/* FALLTHROUGH */
467			case 'h':
468				flags |= SHAPEONLY;
469				break;
470			case 'z':			/* squeeze col width */
471				flags |= SQUEEZE;
472				break;
473			/*case 'p':
474				ipagespace = atoi(++p);	(default is 1)
475				break;*/
476			case 'o':			/* col order */
477				p = getlist(&cord, p);
478				break;
479			case 'b':
480				flags |= ICOLBOUNDS;
481				p = getlist(&icbd, p);
482				break;
483			case 'B':
484				flags |= OCOLBOUNDS;
485				p = getlist(&ocbd, p);
486				break;
487			default:
488				usage("Bad flag:  %.1s", p);
489			}
490	/*if (!osep)
491		osep = isep;*/
492	switch (ac) {
493	/*case 3:
494		opages = atoi(av[2]);*/
495		/* FALLTHROUGH */
496	case 2:
497		ocols = atoi(av[1]);
498		/* FALLTHROUGH */
499	case 1:
500		orows = atoi(av[0]);
501		/* FALLTHROUGH */
502	case 0:
503		break;
504	default:
505		usage("Too many arguments.");
506	}
507}
508
509static char *
510getlist(short **list, char *p)
511{
512	int count = 1;
513	char *t;
514
515	for (t = p + 1; *t; t++) {
516		if (!isdigit((unsigned char)*t))
517			usage("Option %.1s requires a list of unsigned numbers separated by commas", t);
518		count++;
519		while (*t && isdigit((unsigned char)*t))
520			t++;
521		if (*t != ',')
522			break;
523	}
524	if (!(*list = (short *) malloc(count * sizeof(short))))
525		errx(1, "No list space");
526	count = 0;
527	for (t = p + 1; *t; t++) {
528		(*list)[count++] = atoi(t);
529		printf("++ %d ", (*list)[count-1]);
530		fflush(stdout);
531		while (*t && isdigit((unsigned char)*t))
532			t++;
533		if (*t != ',')
534			break;
535	}
536	(*list)[count] = 0;
537	return(t - 1);
538}
539
540static char *
541getnum(int *num, char *p, int strict)	/* num = number p points to; if (strict) complain */
542					/* returns pointer to end of num */
543{
544	char *t = p;
545
546	if (!isdigit((unsigned char)*++t)) {
547		if (strict || *t == '-' || *t == '+')
548			usage("Option %.1s requires an unsigned integer", p);
549		*num = 0;
550		return(p);
551	}
552	*num = atoi(t);
553	while (*++t)
554		if (!isdigit((unsigned char)*t))
555			break;
556	return(--t);
557}
558