xref: /openbsd-src/usr.bin/lam/lam.c (revision de8cc8edbc71bd3e3bc7fbffa27ba0e564c37d8b)
1 /*	$OpenBSD: lam.c,v 1.22 2018/07/29 11:27:14 schwarze Exp $	*/
2 /*	$NetBSD: lam.c,v 1.2 1994/11/14 20:27:42 jtc Exp $	*/
3 
4 /*-
5  * Copyright (c) 1993
6  *	The Regents of the University of California.  All rights reserved.
7  *
8  * Redistribution and use in source and binary forms, with or without
9  * modification, are permitted provided that the following conditions
10  * are met:
11  * 1. Redistributions of source code must retain the above copyright
12  *    notice, this list of conditions and the following disclaimer.
13  * 2. Redistributions in binary form must reproduce the above copyright
14  *    notice, this list of conditions and the following disclaimer in the
15  *    documentation and/or other materials provided with the distribution.
16  * 3. Neither the name of the University nor the names of its contributors
17  *    may be used to endorse or promote products derived from this software
18  *    without specific prior written permission.
19  *
20  * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
21  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
22  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
23  * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
24  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
25  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
26  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
27  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
28  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
29  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
30  * SUCH DAMAGE.
31  */
32 
33 /*
34  *	lam - laminate files
35  *	Author:  John Kunze, UCB
36  */
37 
38 #include <sys/param.h>	/* NOFILE_MAX */
39 
40 #include <ctype.h>
41 #include <err.h>
42 #include <locale.h>
43 #include <stdio.h>
44 #include <stdlib.h>
45 #include <string.h>
46 #include <unistd.h>
47 
48 #define	BIGBUFSIZ	5 * BUFSIZ
49 
50 struct	openfile {		/* open file structure */
51 	FILE	*fp;		/* file pointer */
52 	int	minwidth;	/* pad this column to this width */
53 	int	maxwidth;	/* truncate this column */
54 	short	eof;		/* eof flag */
55 	short	pad;		/* pad flag for missing columns */
56 	char	eol;		/* end of line character */
57 	char	align;		/* '0' for zero fill, '-' for left align */
58 	char	*sepstring;	/* string to print before each line */
59 }	input[NOFILE_MAX + 1];	/* last one is for the last -s arg. */
60 #define INPUTSIZE sizeof(input) / sizeof(*input)
61 
62 int	numfiles;		/* number of open files */
63 int	nofinalnl;		/* normally append \n to each output line */
64 char	line[BIGBUFSIZ];
65 char	*linep;
66 
67 int	 mbswidth_truncate(char *, int);  /* utf8.c */
68 
69 void	 usage(void);
70 char	*gatherline(struct openfile *);
71 void	 getargs(int, char *[]);
72 char	*pad(struct openfile *);
73 
74 int
75 main(int argc, char *argv[])
76 {
77 	int i;
78 
79 	setlocale(LC_CTYPE, "");
80 
81 	if (pledge("stdio rpath", NULL) == -1)
82 		err(1, "pledge");
83 
84 	/* Process arguments, set numfiles to file argument count. */
85 	getargs(argc, argv);
86 	if (numfiles == 0)
87 		usage();
88 
89 	if (pledge("stdio", NULL) == -1)
90 		err(1, "pledge");
91 
92 	/* Concatenate lines from each file, then print. */
93 	for (;;) {
94 		linep = line;
95 		/*
96 		 * For each file that has a line to print, numfile is
97 		 * incremented.  Thus if numfiles is 0, we are done.
98 		 */
99 		numfiles = 0;
100 		for (i = 0; i < INPUTSIZE - 1 && input[i].fp != NULL; i++)
101 			linep = gatherline(&input[i]);
102 		if (numfiles == 0)
103 			exit(0);
104 		fputs(line, stdout);
105 		/* Print terminating -s argument. */
106 		fputs(input[i].sepstring, stdout);
107 		if (!nofinalnl)
108 			putchar('\n');
109 	}
110 }
111 
112 void
113 getargs(int argc, char *argv[])
114 {
115 	struct openfile *ip = input;
116 	const char *errstr;
117 	char *p, *q;
118 	int ch, P, S, F, T;
119 
120 	P = S = F = T = 0;		/* capitalized options */
121 	while (optind < argc) {
122 		switch (ch = getopt(argc, argv, "F:f:P:p:S:s:T:t:")) {
123 		case 'P': case 'p':
124 			P = (ch == 'P');
125 			ip->pad = 1;
126 			/* FALLTHROUGH */
127 		case 'F': case 'f':
128 			F = (ch == 'F');
129 			/* Validate format string argument. */
130 			p = optarg;
131 			if (*p == '0' || *p == '-')
132 				ip->align = *p++;
133 			else
134 				ip->align = ' ';
135 			if ((q = strchr(p, '.')) != NULL)
136 				*q++ = '\0';
137 			if (*p != '\0') {
138 				ip->minwidth = strtonum(p, 1, INT_MAX,
139 				    &errstr);
140 				if (errstr != NULL)
141 					errx(1, "minimum width is %s: %s",
142 					    errstr, p);
143 			}
144 			if (q != NULL) {
145 				ip->maxwidth = strtonum(q, 1, INT_MAX,
146 				    &errstr);
147 				if (errstr != NULL)
148 					errx(1, "maximum width is %s: %s",
149 					    errstr, q);
150 			} else
151 				ip->maxwidth = INT_MAX;
152 			break;
153 		case 'S': case 's':
154 			S = (ch == 'S');
155 			ip->sepstring = optarg;
156 			break;
157 		case 'T': case 't':
158 			T = (ch == 'T');
159 			if (strlen(optarg) != 1)
160 				usage();
161 			ip->eol = optarg[0];
162 			nofinalnl = 1;
163 			break;
164 		case -1:
165 			if (optind >= argc)
166 				break;		/* to support "--" */
167 			/* This is a file, not a flag. */
168 			++numfiles;
169 			if (numfiles >= INPUTSIZE)
170 				errx(1, "too many files");
171 			if (strcmp(argv[optind], "-") == 0)
172 				ip->fp = stdin;
173 			else if ((ip->fp = fopen(argv[optind], "r")) == NULL)
174 				err(1, "%s", argv[optind]);
175 			ip->pad = P;
176 			if (ip->sepstring == NULL)
177 				ip->sepstring = S ? (ip-1)->sepstring : "";
178 			if (ip->eol == '\0')
179 				ip->eol = T ? (ip-1)->eol : '\n';
180 			if (ip->align == '\0') {
181 				if (F || P) {
182 					ip->align = (ip-1)->align;
183 					ip->minwidth = (ip-1)->minwidth;
184 					ip->maxwidth = (ip-1)->maxwidth;
185 				} else
186 					ip->maxwidth = INT_MAX;
187 			}
188 			ip++;
189 			optind++;
190 			break;
191 		default:
192 			usage();
193 			/* NOTREACHED */
194 		}
195 	}
196 	ip->fp = NULL;
197 	if (ip->sepstring == NULL)
198 		ip->sepstring = "";
199 }
200 
201 char *
202 pad(struct openfile *ip)
203 {
204 	size_t n;
205 	char *lp = linep;
206 	int i = 0;
207 
208 	n = strlcpy(lp, ip->sepstring,  line + sizeof(line) - lp);
209 	lp += (n < line + sizeof(line) - lp) ? n : strlen(lp);
210 	if (ip->pad)
211 		while (i++ < ip->minwidth && lp + 1 < line + sizeof(line))
212 			*lp++ = ' ';
213 	*lp = '\0';
214 	return (lp);
215 }
216 
217 /*
218  * Grab line from file, appending to linep.  Increments numfiles if file
219  * is still open.
220  */
221 char *
222 gatherline(struct openfile *ip)
223 {
224 	size_t n;
225 	char s[BUFSIZ];
226 	char *p;
227 	char *lp = linep;
228 	char *end = s + BUFSIZ - 1;
229 	int c, width;
230 
231 	if (ip->eof)
232 		return (pad(ip));
233 	for (p = s; (c = fgetc(ip->fp)) != EOF && p < end; p++)
234 		if ((*p = c) == ip->eol)
235 			break;
236 	*p = '\0';
237 	if (c == EOF) {
238 		ip->eof = 1;
239 		if (ip->fp == stdin)
240 			fclose(stdin);
241 		return (pad(ip));
242 	}
243 	/* Something will be printed. */
244 	numfiles++;
245 	n = strlcpy(lp, ip->sepstring, line + sizeof(line) - lp);
246 	lp += (n < line + sizeof(line) - lp) ? n : strlen(lp);
247 	width = mbswidth_truncate(s, ip->maxwidth);
248 	if (ip->align != '-')
249 		while (width++ < ip->minwidth && lp + 1 < line + sizeof(line))
250 			*lp++ = ip->align;
251 	n = strlcpy(lp, s, line + sizeof(line) - lp);
252 	lp += (n < line + sizeof(line) - lp) ? n : strlen(lp);
253 	if (ip->align == '-')
254 		while (width++ < ip->minwidth && lp + 1 < line + sizeof(line))
255 			*lp++ = ' ';
256 	*lp = '\0';
257 	return (lp);
258 }
259 
260 void
261 usage(void)
262 {
263 	extern char *__progname;
264 
265 	fprintf(stderr,
266 	    "usage: %s [-f min.max] [-p min.max] [-s sepstring] [-t c] file ...\n",
267 	    __progname);
268 	exit(1);
269 }
270