xref: /netbsd-src/usr.bin/indent/args.c (revision d2caba7df8c7d91aaf7bcceecb7e545265b3fcac)
1 /*	$NetBSD: args.c,v 1.88 2024/12/12 05:51:50 rillig Exp $	*/
2 
3 /*-
4  * SPDX-License-Identifier: BSD-4-Clause
5  *
6  * Copyright (c) 1985 Sun Microsystems, Inc.
7  * Copyright (c) 1980, 1993
8  *	The Regents of the University of California.  All rights reserved.
9  * All rights reserved.
10  *
11  * Redistribution and use in source and binary forms, with or without
12  * modification, are permitted provided that the following conditions
13  * are met:
14  * 1. Redistributions of source code must retain the above copyright
15  *    notice, this list of conditions and the following disclaimer.
16  * 2. Redistributions in binary form must reproduce the above copyright
17  *    notice, this list of conditions and the following disclaimer in the
18  *    documentation and/or other materials provided with the distribution.
19  * 3. All advertising materials mentioning features or use of this software
20  *    must display the following acknowledgement:
21  *	This product includes software developed by the University of
22  *	California, Berkeley and its contributors.
23  * 4. Neither the name of the University nor the names of its contributors
24  *    may be used to endorse or promote products derived from this software
25  *    without specific prior written permission.
26  *
27  * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
28  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
29  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
30  * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
31  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
32  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
33  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
34  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
35  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
36  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
37  * SUCH DAMAGE.
38  */
39 
40 #include <sys/cdefs.h>
41 __RCSID("$NetBSD: args.c,v 1.88 2024/12/12 05:51:50 rillig Exp $");
42 
43 /* Read options from profile files and from the command line. */
44 
45 #include <err.h>
46 #include <limits.h>
47 #include <stddef.h>
48 #include <stdio.h>
49 #include <stdlib.h>
50 #include <string.h>
51 
52 #include "indent.h"
53 
54 #if __STDC_VERSION__ >= 201112L
55 #define get_offset(name, type) \
56 	_Generic((&opt.name), type *: offsetof(struct options, name))
57 #else
58 #define get_offset(name, type) (offsetof(struct options, name))
59 #endif
60 
61 #define bool_option(name, value, var) \
62 	{name, true, false, value, 0, 0, get_offset(var, bool)}
63 #define bool_options(name, var) \
64 	{name, true, true, false, 0, 0, get_offset(var, bool)}
65 #define int_option(name, var, min, max) \
66 	{name, false, false, false, min, max, get_offset(var, int)}
67 
68 /* See set_special_option for special options. */
69 static const struct pro {
70 	const char p_name[5];	/* e.g. "bl", "cli" */
71 	bool p_is_bool:1;
72 	bool p_may_negate:1;
73 	bool p_bool_value:1;	/* only relevant if !p_may_negate */
74 	short i_min;
75 	short i_max;
76 	unsigned short opt_offset;	/* the associated variable */
77 } pro[] = {
78 	bool_options("bacc", blank_line_around_conditional_compilation),
79 	bool_options("bad", blank_line_after_decl),
80 	bool_options("badp", blank_line_after_decl_at_top),
81 	bool_options("bap", blank_line_after_proc),
82 	bool_options("bbb", blank_line_before_block_comment),
83 	bool_options("bc", break_after_comma),
84 	bool_option("bl", false, brace_same_line),
85 	bool_option("br", true, brace_same_line),
86 	bool_options("bs", blank_after_sizeof),
87 	int_option("c", comment_column, 1, 999),
88 	int_option("cd", decl_comment_column, 1, 999),
89 	bool_options("cdb", comment_delimiter_on_blank_line),
90 	bool_options("ce", cuddle_else),
91 	int_option("ci", continuation_indent, 0, 999),
92 	/* "cli" is special */
93 	bool_options("cs", space_after_cast),
94 	int_option("d", unindent_displace, -999, 999),
95 	int_option("di", decl_indent, 0, 999),
96 	bool_options("dj", left_justify_decl),
97 	bool_options("eei", extra_expr_indent),
98 	bool_options("ei", else_if_in_same_line),
99 	bool_options("fbs", function_brace_split),
100 	bool_options("fc1", format_col1_comments),
101 	bool_options("fcb", format_block_comments),
102 	int_option("i", indent_size, 1, 80),
103 	bool_options("ip", indent_parameters),
104 	int_option("l", max_line_length, 1, 999),
105 	int_option("lc", block_comment_max_line_length, 1, 999),
106 	int_option("ldi", local_decl_indent, 0, 999),
107 	bool_options("lp", lineup_to_parens),
108 	bool_options("lpl", lineup_to_parens_always),
109 	/* "npro" is special */
110 	/* "P" is special */
111 	bool_options("pcs", proc_calls_space),
112 	bool_options("psl", procnames_start_line),
113 	bool_options("sc", star_comment_cont),
114 	bool_options("sob", swallow_optional_blank_lines),
115 	/* "st" is special */
116 	bool_option("ta", true, auto_typedefs),
117 	/* "T" is special */
118 	int_option("ts", tabsize, 1, 80),
119 	/* "U" is special */
120 	bool_options("ut", use_tabs),
121 	bool_options("v", verbose),
122 	/* "-version" is special */
123 };
124 
125 
126 static void
127 add_typedefs_from_file(const char *fname)
128 {
129 	FILE *file;
130 	char line[BUFSIZ];
131 
132 	if ((file = fopen(fname, "r")) == NULL) {
133 		(void)fprintf(stderr, "indent: cannot open file %s\n", fname);
134 		exit(1);
135 	}
136 	while ((fgets(line, sizeof(line), file)) != NULL) {
137 		/* Only keep the first word of the line. */
138 		line[strcspn(line, " \t\n\r")] = '\0';
139 		register_typename(line);
140 	}
141 	(void)fclose(file);
142 }
143 
144 static bool
145 set_special_option(const char *arg, const char *option_source)
146 {
147 	const char *arg_end;
148 
149 	if (strcmp(arg, "-version") == 0) {
150 		printf("NetBSD indent 2.1\n");
151 		exit(0);
152 	}
153 
154 	if (arg[0] == 'P' || strcmp(arg, "npro") == 0)
155 		return true;	/* see load_profiles */
156 
157 	if (strncmp(arg, "cli", 3) == 0) {
158 		arg_end = arg + 3;
159 		if (arg_end[0] == '\0')
160 			goto need_arg;
161 		char *end;
162 		opt.case_indent = (float)strtod(arg_end, &end);
163 		if (*end != '\0')
164 			errx(1, "%s: argument \"%s\" to option \"-%.*s\" "
165 			    "must be numeric",
166 			    option_source, arg_end, (int)(arg_end - arg), arg);
167 		return true;
168 	}
169 
170 	if (strcmp(arg, "st") == 0) {
171 		if (in.f == NULL)
172 			in.f = stdin;
173 		if (output == NULL)
174 			output = stdout;
175 		return true;
176 	}
177 
178 	if (arg[0] == 'T') {
179 		arg_end = arg + 1;
180 		if (arg_end[0] == '\0')
181 			goto need_arg;
182 		register_typename(arg_end);
183 		return true;
184 	}
185 
186 	if (arg[0] == 'U') {
187 		arg_end = arg + 1;
188 		if (arg_end[0] == '\0')
189 			goto need_arg;
190 		add_typedefs_from_file(arg_end);
191 		return true;
192 	}
193 
194 	return false;
195 
196 need_arg:
197 	errx(1, "%s: option \"-%.*s\" requires an argument",
198 	    option_source, (int)(arg_end - arg), arg);
199 	/* NOTREACHED */
200 }
201 
202 static const char *
203 skip_over(const char *s, bool may_negate, const char *prefix)
204 {
205 	if (may_negate && s[0] == 'n')
206 		s++;
207 	while (*prefix != '\0') {
208 		if (*prefix++ != *s++)
209 			return NULL;
210 	}
211 	return s;
212 }
213 
214 void
215 set_option(const char *arg, const char *option_source)
216 {
217 	const struct pro *p;
218 	const char *arg_arg;
219 
220 	arg++;			/* skip leading '-' */
221 	if (set_special_option(arg, option_source))
222 		return;
223 
224 	for (p = pro + array_length(pro); p-- != pro;) {
225 		arg_arg = skip_over(arg, p->p_may_negate, p->p_name);
226 		if (arg_arg != NULL)
227 			goto found;
228 	}
229 	errx(1, "%s: unknown option \"-%s\"", option_source, arg);
230 found:
231 
232 	if (p->p_is_bool) {
233 		if (arg_arg[0] != '\0')
234 			errx(1, "%s: unknown option \"-%s\"",
235 			    option_source, arg);
236 
237 		*(bool *)((unsigned char *)(void *)&opt + p->opt_offset) =
238 		    p->p_may_negate ? arg[0] != 'n' : p->p_bool_value;
239 		return;
240 	}
241 
242 	char *end;
243 	long num = strtol(arg_arg, &end, 10);
244 	if (*end != '\0')
245 		errx(1, "%s: argument \"%s\" to option \"-%s\" "
246 		    "must be an integer",
247 		    option_source, arg_arg, p->p_name);
248 
249 	if (!(ch_isdigit(*arg_arg) && p->i_min <= num && num <= p->i_max))
250 		errx(1,
251 		    "%s: argument \"%s\" to option \"-%s\" "
252 		    "must be between %d and %d",
253 		    option_source, arg_arg, p->p_name, p->i_min, p->i_max);
254 
255 	*(int *)((unsigned char *)(void *)&opt + p->opt_offset) = (int)num;
256 }
257 
258 static void
259 load_profile(const char *fname, bool must_exist)
260 {
261 	FILE *f;
262 
263 	if ((f = fopen(fname, "r")) == NULL) {
264 		if (must_exist)
265 			err(EXIT_FAILURE, "profile %s", fname);
266 		return;
267 	}
268 
269 	for (;;) {
270 		char buf[BUFSIZ];
271 		size_t n = 0;
272 		int ch, comment_ch = -1;
273 
274 		while ((ch = getc(f)) != EOF) {
275 			if (ch == '*' && comment_ch == -1
276 			    && n > 0 && buf[n - 1] == '/') {
277 				n--;
278 				comment_ch = '*';
279 			} else if (comment_ch != -1) {
280 				comment_ch = ch == '/' && comment_ch == '*'
281 				    ? -1 : ch;
282 			} else if (ch_isspace((char)ch)) {
283 				break;
284 			} else if (n >= array_length(buf) - 2) {
285 				errx(1, "buffer overflow in %s, "
286 				    "starting with '%.10s'",
287 				    fname, buf);
288 			} else
289 				buf[n++] = (char)ch;
290 		}
291 
292 		if (n > 0) {
293 			buf[n] = '\0';
294 			if (opt.verbose)
295 				(void)fprintf(stderr, "profile: %s\n", buf);
296 			if (buf[0] != '-')
297 				errx(1,
298 				    "%s: option \"%s\" must start with '-'",
299 				    fname, buf);
300 			set_option(buf, fname);
301 		}
302 		if (ch == EOF)
303 			break;
304 	}
305 	(void)fclose(f);
306 }
307 
308 void
309 load_profile_files(const char *path)
310 {
311 	if (path != NULL)
312 		load_profile(path, true);
313 	else {
314 		const char *home = getenv("HOME");
315 		if (home != NULL) {
316 			char fname[PATH_MAX];
317 			snprintf(fname, sizeof(fname), "%s/.indent.pro", home);
318 			load_profile(fname, false);
319 		}
320 	}
321 	load_profile(".indent.pro", false);
322 }
323