]> git.draconx.ca Git - cdecl99.git/blob - src/cdecl99.c
cdecl99: Fall back to getline, instead of Gnulib's readline.
[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 <localcharset.h>
33 #include <mbswidth.h>
34
35 #include "cdecl99.h"
36 #include "cdecl.h"
37 #include "help.h"
38 #include "xtra.h"
39 #include "copysym.h"
40 #include "options.h"
41
42 #if HAVE_READLINE_READLINE_H
43 #  include <readline/readline.h>
44 #endif
45
46 static const char *progname = "cdecl99";
47 static bool interactive = true;
48
49 void print_error(const char *fmt, ...)
50 {
51         va_list(ap);
52
53         if (!interactive)
54                 fprintf(stderr, "%s: ", progname);
55         fprintf(stderr, "%s", _("error: "));
56
57         va_start(ap, fmt);
58         vfprintf(stderr, fmt, ap);
59         va_end(ap);
60
61         fprintf(stderr, "\n");
62 }
63
64 static void print_version(void)
65 {
66         const char *copysign = copyright_symbol(locale_charset());
67
68         puts(PACKAGE_STRING);
69         printf("Copyright %s 2023 Nick Bowler.\n", copysign);
70         puts("License GPLv3+: GNU GPL version 3 or any later version.");
71         puts("This is free software: you are free to change and redistribute it.");
72         puts("There is NO WARRANTY, to the extent permitted by law.");
73 }
74
75 static void print_usage(FILE *f)
76 {
77         fprintf(f, _("Usage: %s [options]\n"), progname);
78         if (f != stdout)
79                 fprintf(f, _("Try %s --help for more information.\n"),
80                            progname);
81 }
82
83 static void print_help(const struct option *lopts)
84 {
85         struct lopt_help help = {0};
86         const struct option *opt;
87
88         print_usage(stdout);
89
90         puts(_("This is \"cdecl99\": a command-line tool for parsing and constructing\n"
91                "complicated C declarations."));
92         putchar('\n');
93
94         puts(_("Options:"));
95         for (opt = lopts; opt->name; opt++) {
96                 if (!lopt_get_help(opt, &help))
97                         continue;
98
99                 help_print_option(opt, help.arg, help.desc, 20);
100         }
101         putchar('\n');
102
103         puts(_("For more information, see the cdecl99(1) man page."));
104         putchar('\n');
105
106         /*
107          * TRANSLATORS: Please add *another line* indicating where users should
108          * report translation bugs.
109          */
110         printf(_("Report bugs to <%s>.\n"), PACKAGE_BUGREPORT);
111 }
112
113 static int is_blank_line(const char *line)
114 {
115         return !line[strspn(line, " \t")];
116 }
117
118 static int repl_cmdline(unsigned count, char **commands)
119 {
120         int ret = 0;
121         unsigned i;
122
123         for (i = 0; i < count; i++) {
124                 int rc = run_command(commands[i], false);
125                 if (rc < 0)
126                         ret = -1;
127                 else if (rc > 0)
128                         break;
129         }
130
131         return ret;
132 }
133
134 static int do_getline(char **linebuf, size_t *n)
135 {
136         ssize_t rc;
137
138         if ((rc = getline(linebuf, n, stdin)) < 0) {
139                 if (ferror(stdin))
140                         print_error("%s", strerror(errno));
141                 return 0;
142         }
143
144         if (rc-- && (*linebuf)[rc] == '\n')
145                 (*linebuf)[rc] = '\0';
146         return 1;
147 }
148
149 static int do_readline(char **linebuf, size_t *n, int interactive)
150 {
151 #if !HAVE_READLINE
152         if (interactive) {
153                 fputs("> ", stdout);
154                 fflush(stdout);
155         }
156
157         return do_getline(linebuf, n);
158 #else
159         if (!interactive)
160                 return do_getline(linebuf, n);
161
162         free(*linebuf);
163         if (!(*linebuf = readline("> ")))
164                 return 0;
165
166         if (!is_blank_line(*linebuf))
167                 add_history(*linebuf);
168         return 1;
169 #endif
170 }
171
172 static int repl(int interactive)
173 {
174         char *line = NULL;
175         int ret = 0;
176         size_t n;
177
178         while (do_readline(&line, &n, interactive)) {
179                 int rc = run_command(line, interactive);
180                 if (rc > 0)
181                         break;
182                 else if (rc < 0)
183                         ret = -!interactive;
184         }
185
186         free(line);
187         return ret;
188 }
189
190 /* Initialize gettext */
191 static void init_i18n(void)
192 {
193         if (!ENABLE_NLS)
194                 return;
195
196         setlocale(LC_ALL, "");
197         bindtextdomain(PACKAGE, LOCALEDIR);
198         textdomain(PACKAGE);
199 }
200
201 enum {
202         INIT_EXIT_SUCCESS = -1,
203         INIT_EXIT_FAILURE = -2
204 };
205
206 static int initialize(int argc, char **argv)
207 {
208         int i, opt, quiet = 0, execute = 0;
209         const char *filename = NULL;
210
211         XTRA_PACKED_LOPTS(lopts);
212
213         if (argc > 0)
214                 progname = argv[0];
215
216         init_i18n();
217
218         while ((opt = getopt_long(argc, argv, SOPT_STRING, lopts, 0)) != -1) {
219                 switch (opt) {
220                 case 'q':
221                         quiet = 1;
222                         break;
223                 case 'b':
224                         interactive = false;
225                         break;
226                 case 'i':
227                         interactive = true;
228                         break;
229                 case 'f':
230                         filename = optarg;
231                         break;
232                 case 'e':
233                         argv[execute++] = optarg;
234                         break;
235                 case 'V':
236                         print_version();
237                         return INIT_EXIT_SUCCESS;
238                 case 'H':
239                         print_help(lopts);
240                         return INIT_EXIT_SUCCESS;
241                 default:
242                         print_usage(stderr);
243                         return INIT_EXIT_FAILURE;
244                 }
245         }
246
247         if (optind < argc) {
248                 fprintf(stderr, "%s: ", progname);
249                 fprintf(stderr, _("excess command-line arguments:"));
250                 for (i = optind; i < argc; i++) {
251                         fprintf(stderr, " %s", argv[i]);
252                 }
253                 fprintf(stderr, "\n");
254                 print_usage(stderr);
255                 return INIT_EXIT_FAILURE;
256         }
257
258         /* --filename and --execute imply --batch. */
259         if (filename || execute)
260                 interactive = false;
261
262         /* --batch implies --quiet */
263         if (interactive && !quiet)
264                 print_version();
265
266         /* --execute supersedes --filename */
267         if (filename && !execute) {
268                 if (!freopen(filename, "r", stdin)) {
269                         print_error("failed to open %s: %s", filename,
270                                                         strerror(errno));
271                         return INIT_EXIT_FAILURE;
272                 }
273         }
274
275         return execute;
276 }
277
278 int main(int argc, char **argv)
279 {
280         int rc, execute;
281
282         switch ((execute = initialize(argc, argv))) {
283         case INIT_EXIT_SUCCESS: return EXIT_SUCCESS;
284         case INIT_EXIT_FAILURE: return EXIT_FAILURE;
285         }
286
287         if (execute)
288                 rc = repl_cmdline(execute, argv);
289         else
290                 rc = repl(interactive);
291
292         if (rc != 0)
293                 return EXIT_FAILURE;
294         return EXIT_SUCCESS;
295 }