args.c revision 1.88 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