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