]> git.draconx.ca Git - cdecl99.git/blob - src/cdecl99.c
Don't parse command-line options more than once.
[cdecl99.git] / src / cdecl99.c
1 /*
2  * Command line utility for making sense of C declarations.
3  * Copyright © 2011-2012, 2020-2023 Nick Bowler
4  *
5  * This program is free software: you can redistribute it and/or modify
6  * it under the terms of the GNU General Public License as published by
7  * the Free Software Foundation, either version 3 of the License, or
8  * (at your option) any later version.
9  *
10  * This program is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13  * GNU General Public License for more details.
14  *
15  * You should have received a copy of the GNU General Public License
16  * along with this program.  If not, see <https://www.gnu.org/licenses/>.
17  */
18
19 #include <config.h>
20 #include <stdio.h>
21 #include <stdlib.h>
22 #include <stdbool.h>
23 #include <string.h>
24 #include <ctype.h>
25 #include <errno.h>
26 #include <locale.h>
27 #include <assert.h>
28 #include <stdarg.h>
29
30 #include <getopt.h>
31 #include <gettext.h>
32 #include <readline.h>
33 #include <striconv.h>
34 #include <localcharset.h>
35 #include <mbswidth.h>
36
37 #include "cdecl99.h"
38 #include "cdecl.h"
39 #include "help.h"
40
41 static const char *progname = "cdecl99";
42 static bool interactive = true;
43
44 #include "options.h"
45 static const char sopts[] = SOPT_STRING;
46 static const struct option lopts[] = {
47         LOPTS_INITIALIZER,
48         {0}
49 };
50
51 void print_error(const char *fmt, ...)
52 {
53         va_list(ap);
54
55         if (!interactive)
56                 fprintf(stderr, "%s: ", progname);
57         fprintf(stderr, "%s", _("error: "));
58
59         va_start(ap, fmt);
60         vfprintf(stderr, fmt, ap);
61         va_end(ap);
62
63         fprintf(stderr, "\n");
64 }
65
66 static void print_version(void)
67 {
68         const char *copysign = "(C)";
69         void *convsign = NULL;
70
71         if (ENABLE_NLS) {
72                 convsign = str_iconv("\xc2\xa9", "UTF-8", locale_charset());
73                 if (convsign)
74                         copysign = convsign;
75         }
76
77         puts(PACKAGE_STRING);
78         printf("Copyright %s 2023 Nick Bowler.\n", copysign);
79         puts("License GPLv3+: GNU GPL version 3 or any later version.");
80         puts("This is free software: you are free to change and redistribute it.");
81         puts("There is NO WARRANTY, to the extent permitted by law.");
82
83         free(convsign);
84 }
85
86 static void print_usage(FILE *f)
87 {
88         fprintf(f, _("Usage: %s [options]\n"), progname);
89         if (f != stdout)
90                 fprintf(f, _("Try %s --help for more information.\n"),
91                            progname);
92 }
93
94 static void print_help(void)
95 {
96         const struct option *opt;
97
98         print_usage(stdout);
99
100         puts(_("This is \"cdecl99\": a command-line tool for parsing and constructing\n"
101                "complicated C declarations."));
102         putchar('\n');
103
104         puts(_("Options:"));
105         for (opt = lopts; opt->name; opt++) {
106                 struct lopt_help help;
107
108                 if (!lopt_get_help(opt, &help))
109                         continue;
110
111                 help_print_option(opt, help.arg, help.desc, 20);
112         }
113         putchar('\n');
114
115         puts(_("For more information, see the cdecl99(1) man page."));
116         putchar('\n');
117
118         /*
119          * TRANSLATORS: Please add *another line* indicating where users should
120          * report translation bugs.
121          */
122         printf(_("Report bugs to <%s>.\n"), PACKAGE_BUGREPORT);
123 }
124
125 static bool is_blank_line(const char *line)
126 {
127         for (size_t i = 0; line[i]; i++) {
128                 if (!isblank((unsigned char)line[i]))
129                         return false;
130         }
131
132         return true;
133 }
134
135 static int repl(void)
136 {
137         char *line;
138
139         for (; (line = readline("> ")); free(line)) {
140                 if (!is_blank_line(line))
141                         add_history(line);
142
143                 if (run_command(line, true) > 0)
144                         break;
145         }
146
147         free(line);
148         return 0;
149 }
150
151 static int repl_cmdline(unsigned count, char **commands)
152 {
153         int ret = 0;
154         unsigned i;
155
156         for (i = 0; i < count; i++) {
157                 int rc = run_command(commands[i], false);
158                 if (rc < 0)
159                         ret = -1;
160                 else if (rc > 0)
161                         break;
162         }
163
164         return ret;
165 }
166
167 static int repl_noninteractive(void)
168 {
169         int rc, ret = 0, saved_errno;
170         char *line = NULL;
171         size_t n;
172
173         while (getline(&line, &n, stdin) >= 0) {
174                 char *c = strchr(line, '\n');
175                 if (c)
176                         *c = '\0';
177
178                 rc = run_command(line, false);
179                 if (rc < 0)
180                         ret = -1;
181                 else if (rc > 0)
182                         break;
183         }
184
185         saved_errno = errno;
186         free(line);
187
188         if (ferror(stdin)) {
189                 print_error("%s", strerror(saved_errno));
190                 return -1;
191         }
192
193         return ret;
194 }
195
196 /* Initialize gettext */
197 static void init_i18n(void)
198 {
199         if (!ENABLE_NLS)
200                 return;
201
202         setlocale(LC_ALL, "");
203         bindtextdomain(PACKAGE, LOCALEDIR);
204         textdomain(PACKAGE);
205 }
206
207 int main(int argc, char **argv)
208 {
209         bool show_intro = true;
210         const char *filename = NULL;
211         unsigned execute = 0;
212         int i, opt, rc;
213
214         if (argc > 0)
215                 progname = argv[0];
216
217         init_i18n();
218
219         while ((opt = getopt_long(argc, argv, sopts, lopts, NULL)) != -1) {
220                 switch (opt) {
221                 case 'q':
222                         show_intro = false;
223                         break;
224                 case 'b':
225                         interactive = false;
226                         break;
227                 case 'i':
228                         interactive = true;
229                         break;
230                 case 'f':
231                         filename = optarg;
232                         break;
233                 case 'e':
234                         argv[execute++] = optarg;
235                         break;
236                 case 'V':
237                         print_version();
238                         return EXIT_SUCCESS;
239                 case 'H':
240                         print_help();
241                         return EXIT_SUCCESS;
242                 default:
243                         print_usage(stderr);
244                         return EXIT_FAILURE;
245                 }
246         }
247
248         if (optind < argc) {
249                 fprintf(stderr, "%s: ", progname);
250                 fprintf(stderr, _("excess command-line arguments:"));
251                 for (i = optind; i < argc; i++) {
252                         fprintf(stderr, " %s", argv[i]);
253                 }
254                 fprintf(stderr, "\n");
255                 print_usage(stderr);
256                 return EXIT_FAILURE;
257         }
258
259         /* --filename and --execute imply --batch. */
260         if (filename || execute)
261                 interactive = false;
262
263         /* --batch implies --quiet */
264         if (interactive && show_intro)
265                 print_version();
266
267         /* --execute supersedes --filename */
268         if (filename && !execute) {
269                 if (!freopen(filename, "r", stdin)) {
270                         print_error("failed to open %s: %s\n", filename,
271                                                         strerror(errno));
272                         return EXIT_FAILURE;
273                 }
274         }
275
276         if (interactive)
277                 rc = repl();
278         else if (execute)
279                 rc = repl_cmdline(execute, argv);
280         else
281                 rc = repl_noninteractive();
282
283         if (rc != 0)
284                 return EXIT_FAILURE;
285         return EXIT_SUCCESS;
286 }