]> git.draconx.ca Git - cdecl99.git/blob - src/cdecl99.c
Use gperf to implement command selection.
[cdecl99.git] / src / cdecl99.c
1 /*
2  * Command line utility for making sense of C declarations.
3  * Copyright © 2011-2012, 2020-2021 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 "history.h"
29 #include "cdecl.h"
30
31 #include <getopt.h>
32 #include <gettext.h>
33 #include <readline.h>
34 #include <striconv.h>
35 #include <localcharset.h>
36 #include <mbswidth.h>
37
38 #include "cdecl99.h"
39
40 static const char *progname = "cdecl99";
41
42 #include "options.h"
43 static const char sopts[] = SOPT_STRING;
44 static const struct option lopts[] = {
45         LOPTS_INITIALIZER,
46         {0}
47 };
48
49 static void print_version(void)
50 {
51         const char *copysign = "(C)";
52         void *convsign = NULL;
53
54         if (ENABLE_NLS) {
55                 convsign = str_iconv("\xc2\xa9", "UTF-8", locale_charset());
56                 if (convsign)
57                         copysign = convsign;
58         }
59
60         puts(PACKAGE_STRING);
61         printf("Copyright %s 2021 Nick Bowler.\n", copysign);
62         puts("License GPLv3+: GNU GPL version 3 or any later version.");
63         puts("This is free software: you are free to change and redistribute it.");
64         puts("There is NO WARRANTY, to the extent permitted by law.");
65
66         free(convsign);
67 }
68
69 static void print_usage(FILE *f)
70 {
71         fprintf(f, _("Usage: %s [options]\n"), progname);
72         if (f != stdout)
73                 fprintf(f, _("Try %s --help for more information.\n"),
74                            progname);
75 }
76
77 static int
78 print_optstring(const struct option *opt, const struct lopt_help *help)
79 {
80         char optstring[100];
81         int w;
82
83         if (!ENABLE_NLS)
84                 goto no_translate;
85
86         if (opt->has_arg) {
87                 w = snprintf(optstring, sizeof optstring,
88                              _("  -%c, --%s=%s"), opt->val, opt->name,
89                              pgettext_expr(opt->name, help->arg));
90         } else {
91                 w = snprintf(optstring, sizeof optstring,
92                              _("  -%c, --%s"), opt->val, opt->name);
93         }
94
95         if (w < 0)
96                 goto no_translate;
97
98         w = mbsnwidth(optstring, w, 0);
99         printf("%s", optstring);
100         goto out;
101
102 no_translate:
103         if (opt->has_arg) {
104                 w = printf("  -%c, --%s=%s", opt->val, opt->name, help->arg);
105         } else {
106                 w = printf("  -%c, --%s", opt->val, opt->name);
107         }
108 out:
109         if (w < 0 || w > 18) {
110                 putchar('\n');
111                 return 0;
112         }
113
114         return w;
115 }
116
117 /*
118  * Print a string, with each line indented by i spaces.  The first line
119  * will be indented by w fewer spaces (to account for the cursor being in
120  * some other column).
121  */
122 void print_block(const char *s, int i, int w)
123 {
124         for (; *s; w = 0) {
125                 const char *nl = strchr(s, '\n');
126                 int n = (nl ? nl-s : -1);
127
128                 printf("%*s%.*s\n", i-w, "", n, s);
129                 if (!nl)
130                         break;
131
132                 s = nl+1;
133         }
134 }
135
136 static void print_help(void)
137 {
138         const struct option *opt;
139
140         print_usage(stdout);
141
142         puts(_("This is \"cdecl99\": a command-line tool for parsing and constructing\n"
143                "complicated C declarations."));
144         putchar('\n');
145
146         puts(_("Options:"));
147         for (opt = lopts; opt->name; opt++) {
148                 struct lopt_help help;
149                 const char *line;
150                 int w;
151
152                 if (!lopt_get_help(opt, &help))
153                         continue;
154
155                 w = print_optstring(opt, &help);
156
157                 if (ENABLE_NLS)
158                         help.desc = pgettext_expr(opt->name, help.desc);
159
160                 print_block(help.desc, 20, w);
161         }
162         putchar('\n');
163
164         puts(_("For more information, see the cdecl99(1) man page."));
165         putchar('\n');
166
167         /*
168          * TRANSLATORS: Please add *another line* indicating where users should
169          * report translation bugs.
170          */
171         printf(_("Report bugs to <%s>.\n"), PACKAGE_BUGREPORT);
172 }
173
174 static bool is_blank_line(const char *line)
175 {
176         for (size_t i = 0; line[i]; i++) {
177                 if (!isblank((unsigned char)line[i]))
178                         return false;
179         }
180
181         return true;
182 }
183
184 static int repl(void)
185 {
186         char *line;
187
188         for (; (line = readline("> ")); free(line)) {
189                 if (!is_blank_line(line))
190                         cdecl_add_history(line);
191
192                 if (run_command(line, true) > 0)
193                         break;
194         }
195
196         free(line);
197         return 0;
198 }
199
200 static int repl_cmdline(int argc, char **argv)
201 {
202         int opt, rc, ret = 0;
203
204         optind = 1;
205         while ((opt = getopt_long(argc, argv, sopts, lopts, NULL)) != -1) {
206                 if (opt != 'e')
207                         continue;
208
209                 rc = run_command(optarg, false);
210                 if (rc < 0)
211                         ret = -1;
212                 else if (rc > 0)
213                         break;
214         }
215
216         return ret;
217 }
218
219 static int repl_noninteractive(void)
220 {
221         int rc, ret = 0, saved_errno;
222         char *line = NULL;
223         size_t n;
224
225         while (getline(&line, &n, stdin) >= 0) {
226                 char *c = strchr(line, '\n');
227                 if (c)
228                         *c = '\0';
229
230                 rc = run_command(line, false);
231                 if (rc < 0)
232                         ret = -1;
233                 else if (rc > 0)
234                         break;
235         }
236
237         saved_errno = errno;
238         free(line);
239
240         if (ferror(stdin)) {
241                 errno = saved_errno;
242                 perror("read error");
243                 return -1;
244         }
245
246         return ret;
247 }
248
249 /* Initialize gettext */
250 static void init_i18n(void)
251 {
252         if (!ENABLE_NLS)
253                 return;
254
255         setlocale(LC_ALL, "");
256         bindtextdomain(PACKAGE, LOCALEDIR);
257         textdomain(PACKAGE);
258 }
259
260 int main(int argc, char **argv)
261 {
262         bool show_intro = true, interactive = true, execute = false;
263         const char *filename = NULL;
264         int i, opt, rc;
265
266         if (argc > 0)
267                 progname = argv[0];
268
269         init_i18n();
270
271         while ((opt = getopt_long(argc, argv, sopts, lopts, NULL)) != -1) {
272                 switch (opt) {
273                 case 'q':
274                         show_intro = false;
275                         break;
276                 case 'b':
277                         interactive = false;
278                         break;
279                 case 'i':
280                         interactive = true;
281                         break;
282                 case 'f':
283                         filename = optarg;
284                         break;
285                 case 'e':
286                         execute = true;
287                         break;
288                 case 'V':
289                         print_version();
290                         return EXIT_SUCCESS;
291                 case 'H':
292                         print_help();
293                         return EXIT_SUCCESS;
294                 default:
295                         print_usage(stderr);
296                         return EXIT_FAILURE;
297                 }
298         }
299
300         if (optind < argc) {
301                 fprintf(stderr, _("%s: excess command-line arguments:"),
302                                 progname);
303                 for (i = optind; i < argc; i++) {
304                         fprintf(stderr, " %s", argv[i]);
305                 }
306                 fprintf(stderr, "\n");
307                 print_usage(stderr);
308                 return EXIT_FAILURE;
309         }
310
311         /* --filename and --execute imply --batch. */
312         if (filename || execute)
313                 interactive = false;
314
315         /* --batch implies --quiet */
316         if (interactive && show_intro)
317                 print_version();
318
319         /* --execute supersedes --filename */
320         if (filename && !execute) {
321                 if (!freopen(filename, "r", stdin)) {
322                         perror(filename);
323                         return EXIT_FAILURE;
324                 }
325         }
326
327         if (interactive)
328                 rc = repl();
329         else if (execute)
330                 rc = repl_cmdline(argc, argv);
331         else
332                 rc = repl_noninteractive();
333
334         if (rc != 0)
335                 return EXIT_FAILURE;
336         return EXIT_SUCCESS;
337 }