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