xref: /freebsd/usr.bin/rs/rs.cc (revision 19fae0f66023a97a9b464b3beeeabb2081f575b3)
1 /*-
2  * SPDX-License-Identifier: BSD-3-Clause
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 <err.h>
39 #include <ctype.h>
40 #include <limits.h>
41 #include <stdio.h>
42 #include <stdlib.h>
43 #include <string.h>
44 #include <unistd.h>
45 #include <vector>
46 
47 static long	flags;
48 #define	TRANSPOSE	000001
49 #define	MTRANSPOSE	000002
50 #define	ONEPERLINE	000004
51 #define	ONEISEPONLY	000010
52 #define	ONEOSEPONLY	000020
53 #define	NOTRIMENDCOL	000040
54 #define	SQUEEZE		000100
55 #define	SHAPEONLY	000200
56 #define	DETAILSHAPE	000400
57 #define	RIGHTADJUST	001000
58 #define	NULLPAD		002000
59 #define	RECYCLE		004000
60 #define	SKIPPRINT	010000
61 #define	ICOLBOUNDS	020000
62 #define	OCOLBOUNDS	040000
63 #define ONEPERCHAR	0100000
64 #define NOARGS		0200000
65 
66 static short	*colwidths;
67 static std::vector<char *> elem;
68 static char	*curline;
69 static size_t	curlen;
70 static size_t	irows, icols;
71 static size_t	orows = 0, ocols = 0;
72 static size_t	maxlen;
73 static int	skip;
74 static int	propgutter;
75 static char	isep = ' ', osep = ' ';
76 static char	blank[] = "";
77 static size_t	owidth = 80, gutter = 2;
78 
79 static void	  getargs(int, char *[]);
80 static void	  getfile(void);
81 static int	  get_line(void);
82 static long	  getnum(const char *);
83 static void	  prepfile(void);
84 static void	  prints(char *, int);
85 static void	  putfile(void);
86 static void usage(void);
87 
88 int
89 main(int argc, char *argv[])
90 {
91 	getargs(argc, argv);
92 	getfile();
93 	if (flags & SHAPEONLY) {
94 		printf("%zu %zu\n", irows, icols);
95 		exit(0);
96 	}
97 	prepfile();
98 	putfile();
99 	exit(0);
100 }
101 
102 static void
103 getfile(void)
104 {
105 	char *p, *sp;
106 	char *endp;
107 	int c;
108 	int multisep = (flags & ONEISEPONLY ? 0 : 1);
109 	int nullpad = flags & NULLPAD;
110 	size_t len, padto;
111 
112 	while (skip--) {
113 		c = get_line();
114 		if (flags & SKIPPRINT)
115 			puts(curline);
116 		if (c == EOF)
117 			return;
118 	}
119 	get_line();
120 	if (flags & NOARGS && curlen < owidth)
121 		flags |= ONEPERLINE;
122 	if (flags & ONEPERLINE)
123 		icols = 1;
124 	else				/* count cols on first line */
125 		for (p = curline, endp = curline + curlen; p < endp; p++) {
126 			if (*p == isep && multisep)
127 				continue;
128 			icols++;
129 			while (*p && *p != isep)
130 				p++;
131 		}
132 	do {
133 		if (flags & ONEPERLINE) {
134 			elem.push_back(curline);
135 			if (maxlen < curlen)
136 				maxlen = curlen;
137 			irows++;
138 			continue;
139 		}
140 		for (p = curline, endp = curline + curlen; p < endp; p++) {
141 			if (*p == isep && multisep)
142 				continue;	/* eat up column separators */
143 			if (*p == isep)		/* must be an empty column */
144 				elem.push_back(blank);
145 			else			/* store column entry */
146 				elem.push_back(p);
147 			sp = p;
148 			while (p < endp && *p != isep)
149 				p++;		/* find end of entry */
150 			*p = '\0';		/* mark end of entry */
151 			len = p - sp;
152 			if (maxlen < len)	/* update maxlen */
153 				maxlen = len;
154 		}
155 		irows++;			/* update row count */
156 		if (nullpad) {			/* pad missing entries */
157 			padto = irows * icols;
158 			elem.resize(padto, blank);
159 		}
160 	} while (get_line() != EOF);
161 }
162 
163 static void
164 putfile(void)
165 {
166 	size_t i, j, k;
167 
168 	if (flags & TRANSPOSE)
169 		for (i = 0; i < orows; i++) {
170 			for (j = i; j < elem.size(); j += orows)
171 				prints(elem[j], (j - i) / orows);
172 			putchar('\n');
173 		}
174 	else
175 		for (i = k = 0; i < orows; i++) {
176 			for (j = 0; j < ocols; j++, k++)
177 				if (k < elem.size())
178 					prints(elem[k], j);
179 			putchar('\n');
180 		}
181 }
182 
183 static void
184 prints(char *s, int col)
185 {
186 	int n;
187 	char *p = s;
188 
189 	while (*p)
190 		p++;
191 	n = (flags & ONEOSEPONLY ? 1 : colwidths[col] - (p - s));
192 	if (flags & RIGHTADJUST)
193 		while (n-- > 0)
194 			putchar(osep);
195 	for (p = s; *p; p++)
196 		putchar(*p);
197 	while (n-- > 0)
198 		putchar(osep);
199 }
200 
201 static void
202 usage(void)
203 {
204 	fprintf(stderr,
205 		"usage: rs [-[csCS][x][kKgGw][N]tTeEnyjhHmz] [rows [cols]]\n");
206 	exit(1);
207 }
208 
209 static void
210 prepfile(void)
211 {
212 	size_t i, j;
213 	size_t colw, max, n, orig_size, padto;
214 
215 	if (elem.empty())
216 		exit(0);
217 	gutter += maxlen * propgutter / 100.0;
218 	colw = maxlen + gutter;
219 	if (flags & MTRANSPOSE) {
220 		orows = icols;
221 		ocols = irows;
222 	}
223 	else if (orows == 0 && ocols == 0) {	/* decide rows and cols */
224 		ocols = owidth / colw;
225 		if (ocols == 0) {
226 			warnx("display width %zu is less than column width %zu",
227 					owidth, colw);
228 			ocols = 1;
229 		}
230 		if (ocols > elem.size())
231 			ocols = elem.size();
232 		orows = elem.size() / ocols + (elem.size() % ocols ? 1 : 0);
233 	}
234 	else if (orows == 0)			/* decide on rows */
235 		orows = elem.size() / ocols + (elem.size() % ocols ? 1 : 0);
236 	else if (ocols == 0)			/* decide on cols */
237 		ocols = elem.size() / orows + (elem.size() % orows ? 1 : 0);
238 	padto = orows * ocols;
239 	orig_size = elem.size();
240 	if (flags & RECYCLE) {
241 		for (i = 0; elem.size() < padto; i++)
242 			elem.push_back(elem[i % orig_size]);
243 	}
244 	if (!(colwidths = (short *) malloc(ocols * sizeof(short))))
245 		errx(1, "malloc");
246 	if (flags & SQUEEZE) {
247 		if (flags & TRANSPOSE) {
248 			auto it = elem.begin();
249 			for (i = 0; i < ocols; i++) {
250 				max = 0;
251 				for (j = 0; it != elem.end() && j < orows; j++)
252 					if ((n = strlen(*it++)) > max)
253 						max = n;
254 				colwidths[i] = max + gutter;
255 			}
256 		} else {
257 			for (i = 0; i < ocols; i++) {
258 				max = 0;
259 				for (j = i; j < elem.size(); j += ocols)
260 					if ((n = strlen(elem[j])) > max)
261 						max = n;
262 				colwidths[i] = max + gutter;
263 			}
264 		}
265 	}
266 	/*	for (i = 0; i < orows; i++) {
267 			for (j = i; j < elem.size(); j += orows)
268 				prints(elem[j], (j - i) / orows);
269 			putchar('\n');
270 		}
271 	else {
272 		auto it = elem.begin();
273 		for (i = 0; i < orows; i++) {
274 			for (j = 0; j < ocols; j++)
275 				prints(*it++, j);
276 			putchar('\n');
277 		}*/
278 	else
279 		for (i = 0; i < ocols; i++)
280 			colwidths[i] = colw;
281 	if (!(flags & NOTRIMENDCOL)) {
282 		if (flags & RIGHTADJUST)
283 			colwidths[0] -= gutter;
284 		else
285 			colwidths[ocols - 1] = 0;
286 	}
287 	/*for (i = 0; i < ocols; i++)
288 		warnx("%d is colwidths, nelem %zu", colwidths[i], elem.size());*/
289 }
290 
291 #define	BSIZE	(LINE_MAX * 2)
292 static char	ibuf[BSIZE];
293 
294 static int
295 get_line(void)	/* get line; maintain curline, curlen; manage storage */
296 {
297 	static	int putlength;
298 	static	char *endblock = ibuf + BSIZE;
299 	char *p;
300 	int c, i;
301 
302 	if (irows == 0) {
303 		curline = ibuf;
304 		putlength = flags & DETAILSHAPE;
305 	}
306 	else if (skip <= 0) {			/* don't waste storage */
307 		curline += curlen + 1;
308 		if (putlength) {	/* print length, recycle storage */
309 			printf(" %zu line %zu\n", curlen, irows);
310 			curline = ibuf;
311 		}
312 	}
313 	if (!putlength && endblock - curline < LINE_MAX + 1) { /* need storage */
314 		/*ww = endblock-curline; tt += ww;*/
315 		/*printf("#wasted %d total %d\n",ww,tt);*/
316 		if (!(curline = (char *) malloc(BSIZE)))
317 			errx(1, "file too large");
318 		endblock = curline + BSIZE;
319 		/*printf("#endb %d curline %d\n",endblock,curline);*/
320 	}
321 	for (p = curline, i = 0;; *p++ = c, i++) {
322 		if ((c = getchar()) == EOF)
323 			break;
324 		if (i >= LINE_MAX)
325 			errx(1, "maximum line length (%d) exceeded", LINE_MAX);
326 		if (c == '\n')
327 			break;
328 	}
329 	*p = '\0';
330 	curlen = i;
331 	return(c);
332 }
333 
334 static void
335 getargs(int ac, char *av[])
336 {
337 	long val;
338 	int ch;
339 
340 	if (ac == 1) {
341 		flags |= NOARGS | TRANSPOSE;
342 	}
343 
344 	while ((ch = getopt(ac, av, "C::EG:HK:S::Tc::eg:hjk:mns::tw:yz")) != -1)
345 		switch (ch) {
346 		case 'T':
347 			flags |= MTRANSPOSE;
348 			/* FALLTHROUGH */
349 		case 't':
350 			flags |= TRANSPOSE;
351 			break;
352 		case 'c':		/* input col. separator */
353 			flags |= ONEISEPONLY;
354 			/* FALLTHROUGH */
355 		case 's':		/* one or more allowed */
356 			if (optarg != NULL)
357 				isep = *optarg;
358 			else
359 				isep = '\t';	/* default is ^I */
360 			break;
361 		case 'C':
362 			flags |= ONEOSEPONLY;
363 			/* FALLTHROUGH */
364 		case 'S':
365 			if (optarg != NULL)
366 				osep = *optarg;
367 			else
368 				osep = '\t';	/* default is ^I */
369 			break;
370 		case 'w':		/* window width, default 80 */
371 			val = getnum(optarg);
372 			if (val <= 0)
373 				errx(1, "width must be a positive integer");
374 			owidth = val;
375 			break;
376 		case 'K':			/* skip N lines */
377 			flags |= SKIPPRINT;
378 			/* FALLTHROUGH */
379 		case 'k':			/* skip, do not print */
380 			skip = getnum(optarg);
381 			if (skip < 1)
382 				skip = 1;
383 			break;
384 		case 'm':
385 			flags |= NOTRIMENDCOL;
386 			break;
387 		case 'g':		/* gutter space */
388 			gutter = getnum(optarg);
389 			break;
390 		case 'G':
391 			propgutter = getnum(optarg);
392 			break;
393 		case 'e':		/* each line is an entry */
394 			flags |= ONEPERLINE;
395 			break;
396 		case 'E':
397 			flags |= ONEPERCHAR;
398 			break;
399 		case 'j':			/* right adjust */
400 			flags |= RIGHTADJUST;
401 			break;
402 		case 'n':	/* null padding for missing values */
403 			flags |= NULLPAD;
404 			break;
405 		case 'y':
406 			flags |= RECYCLE;
407 			break;
408 		case 'H':			/* print shape only */
409 			flags |= DETAILSHAPE;
410 			/* FALLTHROUGH */
411 		case 'h':
412 			flags |= SHAPEONLY;
413 			break;
414 		case 'z':			/* squeeze col width */
415 			flags |= SQUEEZE;
416 			break;
417 		/*case 'p':
418 			ipagespace = atoi(optarg);	(default is 1)
419 			break;*/
420 		default:
421 			usage();
422 		}
423 
424 	av += optind;
425 	ac -= optind;
426 
427 	/*if (!osep)
428 		osep = isep;*/
429 	switch (ac) {
430 #if 0
431 	case 3:
432 		opages = atoi(av[2]);
433 		/* FALLTHROUGH */
434 #endif
435 	case 2:
436 		val = strtol(av[1], NULL, 10);
437 		if (val >= 0)
438 			ocols = val;
439 		/* FALLTHROUGH */
440 	case 1:
441 		val = strtol(av[0], NULL, 10);
442 		if (val >= 0)
443 			orows = val;
444 		/* FALLTHROUGH */
445 	case 0:
446 		break;
447 	default:
448 		errx(1, "too many arguments");
449 	}
450 }
451 
452 static long
453 getnum(const char *p)
454 {
455 	char *ep;
456 	long val;
457 
458 	val = strtol(p, &ep, 10);
459 	if (*ep != '\0')
460 		errx(1, "invalid integer %s", p);
461 	return (val);
462 }
463