build: Error out on implicit function declarations.
[lopsub.git] / lsg.c
1 /*
2  * Copyright (C) 2016 Andre Noll <maan@tuebingen.mpg.de>
3  *
4  * Licensed under the GPL v3, see http://www.gnu.org/licenses/gpl-3.0.html
5  */
6
7 #include <stdlib.h>
8 #include <stdio.h>
9 #include <ctype.h>
10 #include <stdbool.h>
11 #include <assert.h>
12 #include <string.h>
13 #include <time.h>
14
15 #include "lsg.h"
16 #include "lopsub-internal.h"
17 #include "lopsub.h"
18
19 /*
20  * To stringify the result of the *expansion* of a macro argument, we have to
21  * use two levels of macros.
22  */
23 #define LLS_STRINGIFY_EXPAND(_arg) LLS_STRINGIFY(_arg)
24 #define LLS_STRINGIFY(_arg) #_arg
25 #define LLS_ABI_VERSION_VAR_STRING LLS_STRINGIFY_EXPAND(LLS_ABI_VERSION_VAR)
26
27 int yylex(void);
28
29 static void gen_license_header(FILE *out, const char *cmdline, const char *pfx,
30                 const char *sfx)
31 {
32         fprintf(out, "%sautogenerated by lopsubgen version %s%s\n",
33                 pfx, lls_version(), sfx);
34         if (cmdline) {
35                 fprintf(out, "%scommand line:", pfx);
36                 fprintf(out, " %s", cmdline);
37                 fprintf(out, "%s\n", sfx);
38         }
39         fprintf(out, "%sPublic domain, no copyright claims.%s\n",
40                 pfx, sfx);
41 }
42
43 static void inplace_sanitize(char *src)
44 {
45         for (; *src; src++)
46                 if (*src == '-')
47                         *src = '_';
48 }
49
50 static void inplace_toupper(char *src)
51 {
52         for (; *src; src++)
53                 *src = toupper((int)*src);
54 }
55
56 static void lsg_init_name(struct lsg_name *name)
57 {
58         assert(name->orig);
59         name->sanitized = strdup(name->orig);
60         inplace_sanitize(name->sanitized);
61         name->capitalized = strdup(name->sanitized);
62         inplace_toupper(name->capitalized);
63 }
64
65 static void print_tabs(int num, FILE *out)
66 {
67         while (--num >= 0)
68                 fprintf(out, "\t");
69 }
70
71 static void format_c_text(const char *text, const char *member, int indent,
72                 FILE *out)
73 {
74         int i;
75
76         if (!text)
77                 return;
78         print_tabs(indent, out);
79         fprintf(out, ".%s = (char []) {", member);
80         for (i = 0; text[i]; i++) {
81                 if ((i % 8) == 0) {
82                         fprintf(out, "\n");
83                         print_tabs(indent + 1, out);
84                 }
85                 fprintf(out, "0x%02x,", (unsigned char)text[i]);
86         }
87         fprintf(out, "0x00\n");
88         print_tabs(indent, out);
89         fprintf(out, "},\n");
90 }
91
92 static void format_flags(struct lsg_option *opt, FILE *out)
93 {
94         fprintf(out, "\t\t\t\t\t.flags = 0");
95         if (opt->multiple)
96                 fprintf(out, " | LLS_MULTIPLE");
97         if (opt->required)
98                 fprintf(out, " | LLS_REQUIRED");
99         if (opt->ignored)
100                 fprintf(out, " | LLS_IGNORED");
101         if (opt->default_val)
102                 fprintf(out, " | LLS_HAS_DEFAULT");
103         fprintf(out, ",\n");
104 }
105
106 static int string_literal(const char *src, FILE *out)
107 {
108         int len = 0;
109
110         len += fprintf(out, "\"");
111         while (src && *src) {
112                 if (*src == '"' || *src == '\\')
113                         len += fprintf(out, "\\");
114                 len += fprintf(out, "%c", *src);
115                 src++;
116         }
117         len += fprintf(out, "\"");
118         return len;
119 }
120
121 static void format_man_text(FILE *out, bool bol, const char *text)
122 {
123         int i;
124
125         if (!text)
126                 return;
127         for (i = 0; text[i]; i++) {
128                 switch (text[i]) {
129                 case '\n':
130                         fprintf(out, "\n");
131                         bol = true;
132                         continue;
133                 case '.':
134                 case '\'':
135                         if (bol)
136                                 fprintf(out, "\\&");
137                         break;
138                 case '\\':
139                         fprintf(out, "\\");
140                         break;
141                 case ' ':
142                 case '\t':
143                         if (bol)
144                                 continue;
145                 }
146                 bol = false;
147                 fprintf(out, "%c", text[i]);
148         }
149 }
150
151 static int format_option_arg(const struct lsg_option *opt, FILE *out,
152                 bool man_format)
153 {
154         char *typestr = opt->typestr? opt->typestr : "val";
155         int arg_info, len = 0;
156
157         if (opt->ignored)
158                 return 0;
159         if (!opt->arg_info)
160                 arg_info = 0;
161         else if (strcmp(opt->arg_info, "LLS_NO_ARGUMENT") == 0)
162                 arg_info = 0;
163         else if (strcmp(opt->arg_info, "LLS_OPTIONAL_ARGUMENT") == 0)
164                 arg_info = -1;
165         else if (strcmp(opt->arg_info, "LLS_REQUIRED_ARGUMENT") == 0)
166                 arg_info = 1;
167         else
168                 assert(0);
169
170         if (arg_info == -1)
171                 len += fprintf(out, "[");
172         if (arg_info != 0) {
173                 len += fprintf(out, "=<");
174                 if (man_format)
175                         len += fprintf(out, "%s", typestr);
176                 else {
177                         len += fprintf(out, "\"");
178                         len += string_literal(typestr, out);
179                         len += fprintf(out, "\"");
180                 }
181                 fprintf(out, ">");
182         }
183         if (arg_info == -1)
184                 len += fprintf(out, "]");
185         return len;
186 }
187
188 static void format_synopsis(const struct lsg_command *cmd, FILE *out,
189                 bool man_format)
190 {
191         int j, len;
192
193         if (man_format)
194                 fprintf(out, "\\&");
195         if (cmd->synopsis) {
196                 if (man_format)
197                         format_man_text(out, true, cmd->synopsis);
198                 else {
199                         string_literal(cmd->synopsis, out);
200                         fprintf(out, ",\n");
201                 }
202                 return;
203         }
204         if (!man_format)
205                 fprintf(out, "\"");
206         len = strlen(cmd->name.orig) + 8;
207         for (j = 0; j < cmd->num_options; j++) {
208                 struct lsg_option *opt = cmd->options + j;
209                 if (opt->ignored)
210                         continue;
211                 if (j > 0) {
212                         if (!man_format && len > 70) {
213                                 fprintf(out, "\\n\"\n\t\t\t\t\"       ");
214                                 len = 8;
215                         } else
216                                 len += fprintf(out, " ");
217                 }
218                 if (!opt->required)
219                         len += fprintf(out, "[");
220                 len += fprintf(out, "--%s", opt->name.orig);
221                 len += format_option_arg(opt, out, man_format);
222                 if (!opt->required)
223                         len += fprintf(out, "]");
224         }
225         if (cmd->non_opts_name) {
226                 len += strlen(cmd->non_opts_name);
227                 if (!man_format && len > 70)
228                         fprintf(out, "\\n\"\n\t\t\t\t\"      ");
229                 if (cmd->num_options > 0)
230                         fprintf(out, " [--]");
231                 fprintf(out, " %s", cmd->non_opts_name);
232         }
233         if (!man_format)
234                 fprintf(out, "\",");
235 }
236
237 static void gen_c_options(const struct lsg_command *cmd, FILE *out)
238 {
239         int i, j;
240
241         if (cmd->num_options == 0)
242                 return;
243         fprintf(out, "\t\t.options = (struct lls_option[]) {\n");
244         for (j = 0; j < cmd->num_options; j++) {
245                 struct lsg_option *opt = cmd->options + j;
246                 fprintf(out, "\t\t\t{\n");
247                 fprintf(out, "\t\t\t\t.name = \"%s\",\n",
248                         opt->name.orig);
249                 if (opt->short_opt)
250                         fprintf(out, "\t\t\t\t.short_opt = '%c',\n",
251                                 opt->short_opt);
252                 if (opt->summary) {
253                         fprintf(out, "\t\t\t\t.summary = ");
254                         string_literal(opt->summary, out);
255                         fprintf(out, ",\n");
256                 }
257                 if (opt->arg_info)
258                         fprintf(out, "\t\t\t\t.arg_info = %s,\n",
259                                 opt->arg_info);
260                 if (opt->arg_type)
261                         fprintf(out, "\t\t\t\t.arg_type = %s,\n",
262                                 opt->arg_type);
263                 if (opt->typestr) {
264                         fprintf(out, "\t\t\t\t.typestr = ");
265                         string_literal(opt->typestr, out);
266                         fprintf(out, ",\n");
267                 }
268                 fprintf(out, "\t\t\t\t.values = ");
269                 if (opt->values) {
270                         fprintf(out, "(union lls_val *)(union lls_val[]) {\n");
271                         for (i = 0; i < opt->num_values; i++)
272                                 fprintf(out, "\t\t\t\t\t{.string_val = %s},\n",
273                                         opt->value_literals[i]);
274                         fprintf(out, "\t\t\t\t\t{.string_val = NULL}\n");
275                         fprintf(out, "\t\t\t\t},\n");
276                 } else
277                         fprintf(out, "NULL,\n");
278                 format_flags(opt, out);
279                 if (opt->default_val) {
280                         bool string = strcmp(opt->arg_type, "LLS_STRING") == 0;
281                         fprintf(out, "\t\t\t\t.default_val = {");
282                         if (string) {
283                                 if (opt->values)
284                                         fprintf(out, ".uint32_val = ");
285                                 else {
286                                         fprintf(out, ".string_val = ");
287                                         string_literal(opt->default_val, out);
288                                 }
289                         } else if (strcmp(opt->arg_type, "LLS_INT32") == 0)
290                                 fprintf(out, ".int32_val = ");
291                         else if (strcmp(opt->arg_type, "LLS_UINT32") == 0)
292                                 fprintf(out, ".uint32_val = ");
293                         else if (strcmp(opt->arg_type, "LLS_INT64") == 0)
294                                 fprintf(out, ".int64_val = ");
295                         else if (strcmp(opt->arg_type, "LLS_UINT64") == 0)
296                                 fprintf(out, ".uint64_val = ");
297                         if (!string || opt->values)
298                                 fprintf(out, "%s", opt->default_val);
299                         fprintf(out, "},\n");
300                 }
301                 format_c_text(opt->help, "help", 5, out);
302                 fprintf(out, "\t\t\t},\n");
303         }
304         fprintf(out, "\t\t\t{\n\t\t\t\t\t.name = NULL\n\t\t\t}\n");
305         fprintf(out, "\t\t}\n");
306 }
307
308 static void format_command(const struct lsg_command *cmd, FILE *out)
309 {
310         if (!cmd->name.orig) {
311                 fprintf(out, "{.name = NULL}\n");
312                 return;
313         }
314         fprintf(out, "{\n\t\t.name = \"%s\",\n", cmd->name.orig);
315         fprintf(out, "\t\t.purpose = ");
316         string_literal(cmd->purpose, out);
317         fprintf(out, ",\n");
318         format_c_text(cmd->description, "description", 3, out);
319         if (cmd->non_opts_name || cmd->synopsis)
320                 fprintf(out, "\t\t.non_opts_name = \"%s\",\n",
321                         cmd->non_opts_name? cmd->non_opts_name : "");
322         fprintf(out, "\t\t.synopsis = ");
323         format_synopsis(cmd, out, false);
324         format_c_text(cmd->closing, "closing", 3, out);
325         fprintf(out, "\n\t\t.user_data = &lsg_%s_com_%s_user_data,\n",
326                 suite.name.sanitized, cmd->name.sanitized);
327         fprintf(out, "\t\t.num_options = %d,\n", cmd->num_options);
328         gen_c_options(cmd, out);
329         fprintf(out, "\t}");
330 }
331
332 static void format_user_data(const struct lsg_command *cmd, FILE *out)
333 {
334         if (!cmd->name.orig)
335                 return;
336         fprintf(out, "extern const void *lsg_%s_com_%s_user_data "
337                 "__attribute__ ((weak));\n",
338                 suite.name.sanitized,
339                 cmd->name.sanitized
340         );
341 }
342
343 static void gen_c(const char *outpath, const char *cmdline)
344 {
345         int i;
346
347         FILE *out = fopen(outpath, "w");
348         if (!out) {
349                 perror("fopen");
350                 exit(EXIT_FAILURE);
351         }
352         gen_license_header(out, cmdline, "/* ", " */");
353         fprintf(out, "#include <stdlib.h>\n");
354         fprintf(out, "#include <inttypes.h>\n");
355         fprintf(out, "#include <lopsub-internal.h>\n");
356         fprintf(out, "#include <lopsub.h>\n");
357         fprintf(out, "__attribute__ ((unused)) "
358                 "static const unsigned *abi_version = &%s;\n",
359                 LLS_ABI_VERSION_VAR_STRING);
360         fprintf(out, "#if LLS_ABI_VERSION - %d\n", LLS_ABI_VERSION);
361         fprintf(out, "#error: \"ABI version mismatch: header version "
362                 "differs from lopsubgen version\"\n");
363         fprintf(out, "#endif\n");
364         for (i = 0; i <= suite.num_subcommands; i++)
365                 format_user_data(suite.commands + i, out);
366         fprintf(out, "static const struct lls_suite the_%s_suite = {\n",
367                 suite.name.sanitized);
368         fprintf(out, "\t.name = \"%s\",\n", suite.name.orig);
369         if (suite.caption)
370                 fprintf(out, "\t.caption = \"%s\",\n", suite.caption);
371         fprintf(out, "\t.num_subcommands = %d,\n", suite.num_subcommands);
372         fprintf(out, "\t.commands = (struct lls_command[]) {\n");
373         for (i = 0; i <= suite.num_subcommands; i++) {
374                 struct lsg_command *cmd = suite.commands + i;
375                 fprintf(out, "%s", i? ", " : "\t\t");
376                 format_command(cmd, out);
377         }
378         fprintf(out, ", {\n\t\t\t.name = NULL\n\t\t}\n");
379         fprintf(out, "\t}\n");
380         fprintf(out, "};\n");
381         fprintf(out, "const struct lls_suite *%s_suite = &the_%s_suite;\n",
382                 suite.name.sanitized, suite.name.sanitized);
383         fclose(out);
384 }
385
386 static inline bool has_arg(struct lsg_option *opt)
387 {
388         if (!opt->arg_info)
389                 return false;
390         return strcmp(opt->arg_info, "LLS_NO_ARGUMENT");
391 }
392
393 static void gen_header(const char *outpath, const char *cmdline)
394 {
395         int i, j, k;
396         FILE *out = fopen(outpath, "w");
397         char *name;
398
399         if (!out) {
400                 perror("fopen");
401                 exit(EXIT_FAILURE);
402         }
403         gen_license_header(out, cmdline, "/* ", " */");
404         /* generate command enum */
405         fprintf(out, "extern const struct lls_suite *%s_suite;\n",
406                 suite.name.sanitized);
407         fprintf(out, "#define LSG_%s_SUBCOMMANDS \\\n", suite.name.capitalized);
408         for (i = 1; i <= suite.num_subcommands; i++)
409                 fprintf(out, "\tLSG_%s_CMD(%s), \\\n", suite.name.capitalized,
410                         suite.commands[i].name.sanitized);
411         fprintf(out, "\n");
412         fprintf(out, "#define LSG_%s_COMMANDS \\\n", suite.name.capitalized);
413         name = suite.commands[0].name.sanitized;
414         fprintf(out, "\tLSG_%s_CMD(%s), \\\n", suite.name.capitalized,
415                 name? name : "SUPERCOMMAND_UNAVAILABLE");
416         fprintf(out, "\tLSG_%s_SUBCOMMANDS\n", suite.name.capitalized);
417         fprintf(out, "enum lsg_%s_command {\n", suite.name.sanitized);
418         for (i = 0; i <= suite.num_subcommands; i++) {
419                 struct lsg_command *cmd = suite.commands + i;
420                 char *name = cmd->name.capitalized;
421
422                 if (!name)
423                         name = "SUPERCOMMAND_UNAVAILABLE";
424                 fprintf(out, "\tLSG_%s_CMD_%s,\n", suite.name.capitalized, name);
425         }
426         fprintf(out, "};\n#define LSG_NUM_%s_SUBCOMMANDS %u\n", suite.name.capitalized,
427                 suite.num_subcommands);
428         fprintf(out, "#define LSG_%s_AUX_INFOS \\\n", suite.name.capitalized);
429         for (i = 0; i <= suite.num_subcommands; i++) {
430                 struct lsg_command *cmd = suite.commands + i;
431                 char *ai = cmd->aux_info;
432                 if (!ai) {
433                         ai = suite.aux_info_default;
434                         if (!ai)
435                                 ai = "0";
436                 }
437                 fprintf(out, "\t%s_AUX_INFO(%s) /* %s */ \\\n",
438                         suite.name.capitalized, ai, cmd->name.orig?
439                                 cmd->name.orig : "NO_SUPERCOMMAND");
440         }
441         fprintf(out, "\n");
442         /* generate one option enum per command */
443         for (i = 0; i <= suite.num_subcommands; i++) {
444                 struct lsg_command *cmd = suite.commands + i;
445                 if (!cmd->name.orig)
446                         continue;
447                 fprintf(out, "enum lsg_%s_%s_option {\n", suite.name.sanitized,
448                         cmd->name.sanitized);
449                 for (j = 0; j < cmd->num_options; j++) {
450                         struct lsg_option *opt = cmd->options + j;
451                         char suffix[20] = "";
452                         if (opt->ignored)
453                                 sprintf(suffix, "%d", j);
454                         fprintf(out, "\tLSG_%s_%s_OPT_%s%s,\n",
455                                 suite.name.capitalized,
456                                 cmd->name.capitalized,
457                                 opt->name.capitalized,
458                                 suffix
459                         );
460                 }
461                 fprintf(out, "\tLSG_NUM_%s_%s_OPTIONS\n};\n",
462                         suite.name.capitalized, cmd->name.capitalized);
463
464         }
465         /* generate enumeration for options of type enum */
466         for (i = 0; i <= suite.num_subcommands; i++) {
467                 struct lsg_command *cmd = suite.commands + i;
468                 if (!cmd->name.orig)
469                         continue;
470                 for (j = 0; j < cmd->num_options; j++) {
471                         struct lsg_option *opt = cmd->options + j;
472                         if (!opt->values)
473                                 continue;
474                         fprintf(out, "/* cmd %s, opt %s */\n", cmd->name.orig,
475                                 opt->name.orig);
476                         fprintf(out, "enum {");
477                         for (k = 0; k < opt->num_values; k++)
478                                 fprintf(out, "%s, ", opt->value_ids[k]);
479                         fprintf(out, "LSG_NUM_%s_%s_%s_VALUES};\n",
480                                 suite.name.capitalized, cmd->name.capitalized,
481                                 opt->name.capitalized);
482                 }
483         }
484         for (i = 0; i <= suite.num_subcommands; i++) {
485                 struct lsg_command *cmd = suite.commands + i;
486                 if (!cmd->name.orig)
487                         continue;
488                 fprintf(out, "#define LSG_%s_%s_SHORT_OPTS ",
489                         suite.name.capitalized, cmd->name.capitalized);
490                 for (j = 0; j < cmd->num_options; j++) {
491                         struct lsg_option *opt = cmd->options + j;
492                         if (!opt->short_opt)
493                                 continue;
494                         fprintf(out, "\"-%c%s\"%s", opt->short_opt,
495                                 has_arg(opt)? "=" : "",
496                                 j == cmd->num_options - 1? "" : ", ");
497                 }
498                 fprintf(out, "\n");
499
500                 fprintf(out, "#define LSG_%s_%s_LONG_OPTS ",
501                         suite.name.capitalized, cmd->name.capitalized);
502                 for (j = 0; j < cmd->num_options; j++) {
503                         struct lsg_option *opt = cmd->options + j;
504                         fprintf(out, "\"--%s%s\"%s", opt->name.orig,
505                                 has_arg(opt)? "=" : "",
506                                 j == cmd->num_options - 1? "" : ", ");
507                 }
508                 fprintf(out, "\n");
509                 fprintf(out, "#define LSG_%s_%s_OPTS "
510                         "LSG_%s_%s_SHORT_OPTS, LSG_%s_%s_LONG_OPTS\n",
511                         suite.name.capitalized, cmd->name.capitalized,
512                         suite.name.capitalized, cmd->name.capitalized,
513                         suite.name.capitalized, cmd->name.capitalized
514                 );
515         }
516         fclose(out);
517 }
518
519 static void check_option(struct lsg_option *opt)
520 {
521         if (!opt->arg_info || !strcmp(opt->arg_info, "no_arg"))
522                 return;
523         if (opt->arg_type && strcmp(opt->arg_type, "none"))
524                 return;
525         fprintf(stderr, "option '%s': inconsistent arg_type/arg_info\n",
526                 opt->name.orig);
527         exit(EXIT_FAILURE);
528 }
529
530 static void sanity_check(void)
531 {
532         int i, j;
533
534         if (suite.num_subcommands == 0 && !suite.commands) {
535                 fprintf(stderr, "no (sub)commands defined\n");
536                 exit(EXIT_FAILURE);
537         }
538         for (i = 0; i <= suite.num_subcommands; i++) {
539                 struct lsg_command *cmd = suite.commands + i;
540                 if (!cmd->name.orig)
541                         continue;
542                 for (j = 0; j < cmd->num_options; j++)
543                         check_option(cmd->options + j);
544         }
545 }
546
547 static void run_yylex(void)
548 {
549         int i, j;
550
551         yylex();
552         if (!suite.name.orig)
553                 suite.name.orig = strdup("lopsubgen");
554         sanity_check();
555         lsg_init_name(&suite.name);
556         for (i = 0; i <= suite.num_subcommands; i++) {
557                 struct lsg_command *cmd = suite.commands + i;
558                 if (!cmd->name.orig)
559                         continue;
560                 lsg_init_name(&cmd->name);
561                 for (j = 0; j < cmd->num_options; j++)
562                         lsg_init_name(&cmd->options[j].name);
563         }
564         for (i = 0; i < suite.num_sections; i++)
565                 lsg_init_name(&suite.sections[i].name);
566 }
567
568 #ifdef STAGE1
569 int main(int argc, char **argv)
570 {
571         run_yylex();
572         gen_c("lopsubgen.lsg.c", NULL);
573         gen_header("lopsubgen.lsg.h", NULL);
574         return EXIT_SUCCESS;
575 }
576
577 #else /* STAGE1 */
578
579 #include "lopsubgen.lsg.h"
580
581 static char *get_output_path(const char *suffix, const char *arg,
582                 const struct lls_parse_result *lpr)
583 {
584         size_t len;
585         char *result, *output_dir;
586
587         output_dir = OPT_STRING_VAL(OUTPUT_DIR, lpr);
588
589         if (arg && arg[0] == '/') {
590                 result = strdup(arg);
591                 assert(result);
592                 return result;
593         }
594         if (arg) { /* relative path */
595                 len = strlen(output_dir) + strlen(arg) + 1;
596                 result = malloc(len + 1);
597                 assert(result);
598                 sprintf(result, "%s/%s", output_dir, arg);
599                 return result;
600         }
601         /* default: suite name plus suffix */
602         len = strlen(output_dir) + 1 /* slash */ + strlen(suite.name.orig)
603                 + 1 /* dot */ + 3 /* "lsg" */ + 1 /* dot */ + strlen(suffix);
604         result = malloc(len + 1);
605         assert(result);
606         sprintf(result, "%s/%s.lsg.%s", output_dir, suite.name.orig, suffix);
607         return result;
608 }
609
610 static void gen_man(struct lls_parse_result *lpr, const char *cmdline)
611 {
612         int i;
613         time_t t = 0;
614         struct tm *tmp;
615         FILE *out;
616         char *outpath = get_output_path("man",
617                 OPT_STRING_VAL(GEN_MAN, lpr), lpr);
618
619         out = fopen(outpath, "w");
620         free(outpath);
621         if (!out) {
622                 perror("fopen");
623                 exit(EXIT_FAILURE);
624         }
625         gen_license_header(out, cmdline, ".\\\" ", "");
626         if (suite.commands[0].name.orig) {
627                 char date[200];
628                 const char *version_string;
629                 if (!suite.date) {
630                         /*
631                          * If the SOURCE_DATE_EPOCH environment variable
632                          * contains a positive integer in the time_t range, use
633                          * that instead of the current time. See:
634                          * <https://reproducible-builds.org/specs/source-date-epoch/>
635                          * for more information.
636                          */
637                         char *source_date_epoch = getenv("SOURCE_DATE_EPOCH");
638                         if (source_date_epoch != NULL)
639                                 t = strtoll(source_date_epoch, NULL, 10);
640                         if (t <= 0)
641                                 t = time(NULL);
642                         tmp = gmtime(&t);
643                         if (tmp == NULL) {
644                                 perror("gmtime");
645                                 exit(EXIT_FAILURE);
646                         }
647                         if (strftime(date, sizeof(date), "%B %Y", tmp) == 0) {
648                                 fprintf(stderr, "strftime returned 0\n");
649                                 exit(EXIT_FAILURE);
650                         }
651                 }
652                 if (OPT_GIVEN(VERSION_STRING, lpr))
653                         version_string = OPT_STRING_VAL(VERSION_STRING, lpr);
654                 else
655                         version_string = suite.version_string?
656                                 suite.version_string : "";
657                 fprintf(out, ".TH %s \"%s\" \"%s\" \"%s\" \"%s\"\n",
658                         suite.title? suite.title : suite.commands[0].name.orig,
659                         suite.mansect? suite.mansect : "1",
660                         suite.date? suite.date : date,
661                         version_string,
662                         suite.manual_title? suite.manual_title : "User commands"
663                 );
664         }
665         for (i = 0; i <= suite.num_subcommands; i++) {
666                 struct lsg_command *cmd = suite.commands + i;
667                 int opt_num;
668                 if (!cmd->name.orig)
669                         continue;
670                 if (i == 0) {
671                         fprintf(out, ".SH NAME\n");
672                         fprintf(out, ".B\n%s \\- ", cmd->name.orig);
673                         format_man_text(out, false, cmd->purpose);
674                 } else {
675                         if (i == 1 && suite.caption) {
676                                 char *caption = strdup(suite.caption);
677                                 inplace_toupper(caption);
678                                 fprintf(out, ".SH %s\n.P\n", caption);
679                                 free(caption);
680                         }
681                         if (i == 1 && suite.introduction)
682                                 format_man_text(out, true, suite.introduction);
683                         fprintf(out, ".SS \n%s \\- ", cmd->name.orig);
684                         format_man_text(out, false, cmd->purpose);
685                 }
686                 fprintf(out, "\n.P\n");
687                 if (i == 0)
688                         fprintf(out, ".SH SYNOPSIS\n");
689                 else
690                         fprintf(out, "Usage: \n");
691                 fprintf(out, ".B %s\n", cmd->name.orig);
692                 format_synopsis(cmd, out, true);
693                 fprintf(out, "\n.P\n");
694                 if (cmd->description) {
695                         if (i == 0)
696                                 fprintf(out, ".SH DESCRIPTION\n");
697                         format_man_text(out, true, cmd->description);
698                 }
699                 if (cmd->num_options > 0)
700                         if (i == 0)
701                                 fprintf(out, ".SH OPTIONS\n");
702                 for (opt_num = 0; opt_num < cmd->num_options; opt_num++) {
703                         struct lsg_option *opt = cmd->options + opt_num;
704
705                         if (opt->ignored) {
706                                 fprintf(out, ".SS ");
707                                 format_man_text(out, false, opt->summary);
708                                 fprintf(out, "\n");
709                                 if (opt->help)
710                                         format_man_text(out, true, opt->help);
711                                 continue;
712
713                         }
714                         fprintf(out, ".TP\n");
715                         if (opt->short_opt != '\0')
716                                 fprintf(out, "\\fB\\-%c\\fR, ", opt->short_opt);
717                         fprintf(out, "\\fB\\-\\-%s\\fR", opt->name.orig);
718                         format_option_arg(opt, out, true /* man_format */);
719                         fprintf(out, "\n");
720                         format_man_text(out, true, opt->summary);
721                         fprintf(out, "\n");
722                         if (opt->values) {
723                                 unsigned n, dflt;
724                                 fprintf(out, ".IP\n");
725                                 fprintf(out, "values:\n");
726                                 dflt = opt->default_val?
727                                         atoi(opt->default_val) : 0;
728                                 for (n = 0; n < opt->num_values; n++) {
729                                         if (n == dflt)
730                                                 fprintf(out, ".B ");
731                                         fprintf(out, "%s%s\n", opt->values[n],
732                                                 n == opt->num_values - 1?
733                                                         "" : ",");
734                                 }
735                         } else if (opt->default_val) {
736                                 fprintf(out, ".IP\n");
737                                 fprintf(out, "default: ");
738                                 format_man_text(out, false, opt->default_val);
739                                 fprintf(out, "\n.P\n");
740                         }
741                         if (opt->help) {
742                                 fprintf(out, ".IP\n");
743                                 format_man_text(out, true, opt->help);
744                         }
745                 }
746                 fprintf(out, ".PP\n");
747                 if (cmd->aux_info) {
748                         char *pfx = suite.aux_info_prefix;
749                         fprintf(out, ".RS\n");
750                         if (pfx)
751                                 fprintf(out, "%s ", pfx);
752                         fprintf(out, "%s\n.PP\n", cmd->aux_info);
753                         fprintf(out, ".RE\n");
754                 }
755                 if (cmd->closing)
756                         format_man_text(out, true, cmd->closing);
757         }
758         if (suite.conclusion)
759                 format_man_text(out, true, suite.conclusion);
760         for (i = 0; i < suite.num_sections; i++) {
761                 struct lsg_section *sec = suite.sections + i;
762                 fprintf(out, "\n.P\n.SH \"%s\"\n", sec->name.capitalized);
763                 fprintf(out, "%s\n.P\n", sec->text);
764         }
765         fclose(out);
766 }
767
768 int main(int argc, char **argv)
769 {
770         const struct lls_command *cmd = lls_cmd(0, lopsubgen_suite);
771         char *errctx, *outpath, *cmdline;
772         struct lls_parse_result *lpr;
773         int i, ret;
774
775         /* Make a copy of the command line because lls_parse() permutes argv[] */
776         for (i = 0, ret = 0; argv[i]; i++)
777                 ret += strlen(argv[i]) + 1;
778         cmdline = malloc(ret + 1);
779         assert(cmdline);
780         for (i = 0, ret = 0; argv[i]; i++)
781                 ret += sprintf(cmdline + ret, "%s ", argv[i]);
782         cmdline[ret - 1] = '\0';
783
784         ret = lls_parse(argc, argv, cmd, &lpr, &errctx);
785         if (ret < 0) {
786                 if (errctx)
787                         fprintf(stderr, "%s\n", errctx);
788                 fprintf(stderr, "%s\n", lls_strerror(-ret));
789                 return EXIT_FAILURE;
790         }
791         if (OPT_GIVEN(VERSION, lpr)) {
792                 printf("lopsubgen-%s\n", lls_version());
793                 return EXIT_SUCCESS;
794         }
795         if (OPT_GIVEN(HELP, lpr)) {
796                 char *help;
797                 if (OPT_GIVEN(HELP, lpr) > 1)
798                         help = lls_long_help(cmd);
799                 else
800                         help = lls_short_help(cmd);
801                 printf("%s", help);
802                 free(help);
803                 return EXIT_SUCCESS;
804         }
805         run_yylex();
806         if (OPT_GIVEN(GEN_C, lpr)) {
807                 outpath = get_output_path("c",
808                         OPT_STRING_VAL(GEN_C, lpr), lpr);
809                 gen_c(outpath, cmdline);
810                 free(outpath);
811         }
812         if (OPT_GIVEN(GEN_HEADER, lpr)) {
813                 outpath = get_output_path("h",
814                         OPT_STRING_VAL(GEN_HEADER, lpr), lpr);
815                 gen_header(outpath, cmdline);
816                 free(outpath);
817         }
818         if (OPT_GIVEN(GEN_MAN, lpr))
819                 gen_man(lpr, cmdline);
820         return EXIT_SUCCESS;
821 }
822 #endif /* STAGE1 */