2 * Copyright (C) 2016 Andre Noll <maan@tuebingen.mpg.de>
4 * Licensed under the GPL v3, see http://www.gnu.org/licenses/gpl-3.0.html
16 #include "lopsub-internal.h"
20 * To stringify the result of the *expansion* of a macro argument, we have to
21 * use two levels of macros.
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)
29 static void gen_license_header(FILE *out, const char *cmdline, const char *pfx,
32 fprintf(out, "%sautogenerated by lopsubgen version %s%s\n",
33 pfx, lls_version(), sfx);
35 fprintf(out, "%scommand line:", pfx);
36 fprintf(out, " %s", cmdline);
37 fprintf(out, "%s\n", sfx);
39 fprintf(out, "%sPublic domain, no copyright claims.%s\n",
43 static void inplace_sanitize(char *src)
50 static void inplace_toupper(char *src)
53 *src = toupper((int)*src);
56 static void lsg_init_name(struct lsg_name *name)
59 name->sanitized = strdup(name->orig);
60 inplace_sanitize(name->sanitized);
61 name->capitalized = strdup(name->sanitized);
62 inplace_toupper(name->capitalized);
65 static void print_tabs(int num, FILE *out)
71 static void format_c_text(const char *text, const char *member, int indent,
78 print_tabs(indent, out);
79 fprintf(out, ".%s = (char []) {", member);
80 for (i = 0; text[i]; i++) {
83 print_tabs(indent + 1, out);
85 fprintf(out, "0x%02x,", (unsigned char)text[i]);
87 fprintf(out, "0x00\n");
88 print_tabs(indent, out);
92 static void format_flags(struct lsg_option *opt, FILE *out)
94 fprintf(out, "\t\t\t\t\t.flags = 0");
96 fprintf(out, " | LLS_MULTIPLE");
98 fprintf(out, " | LLS_REQUIRED");
100 fprintf(out, " | LLS_IGNORED");
101 if (opt->default_val)
102 fprintf(out, " | LLS_HAS_DEFAULT");
106 static int string_literal(const char *src, FILE *out)
110 len += fprintf(out, "\"");
111 while (src && *src) {
112 if (*src == '"' || *src == '\\')
113 len += fprintf(out, "\\");
114 len += fprintf(out, "%c", *src);
117 len += fprintf(out, "\"");
121 static void format_man_text(FILE *out, bool bol, const char *text)
127 for (i = 0; text[i]; i++) {
147 fprintf(out, "%c", text[i]);
151 static int format_option_arg(const struct lsg_option *opt, FILE *out,
154 char *typestr = opt->typestr? opt->typestr : "val";
155 int arg_info, len = 0;
161 else if (strcmp(opt->arg_info, "LLS_NO_ARGUMENT") == 0)
163 else if (strcmp(opt->arg_info, "LLS_OPTIONAL_ARGUMENT") == 0)
165 else if (strcmp(opt->arg_info, "LLS_REQUIRED_ARGUMENT") == 0)
171 len += fprintf(out, "[");
173 len += fprintf(out, "=<");
175 len += fprintf(out, "%s", typestr);
177 len += fprintf(out, "\"");
178 len += string_literal(typestr, out);
179 len += fprintf(out, "\"");
184 len += fprintf(out, "]");
188 static void format_synopsis(const struct lsg_command *cmd, FILE *out,
197 format_man_text(out, true, cmd->synopsis);
199 string_literal(cmd->synopsis, 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;
212 if (!man_format && len > 70) {
213 fprintf(out, "\\n\"\n\t\t\t\t\" ");
216 len += fprintf(out, " ");
219 len += fprintf(out, "[");
220 len += fprintf(out, "--%s", opt->name.orig);
221 len += format_option_arg(opt, out, man_format);
223 len += fprintf(out, "]");
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);
237 static void gen_c_options(const struct lsg_command *cmd, FILE *out)
241 if (cmd->num_options == 0)
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",
250 fprintf(out, "\t\t\t\t.short_opt = '%c',\n",
253 fprintf(out, "\t\t\t\t.summary = ");
254 string_literal(opt->summary, out);
258 fprintf(out, "\t\t\t\t.arg_info = %s,\n",
261 fprintf(out, "\t\t\t\t.arg_type = %s,\n",
264 fprintf(out, "\t\t\t\t.typestr = ");
265 string_literal(opt->typestr, out);
268 fprintf(out, "\t\t\t\t.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");
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 = {");
284 fprintf(out, ".uint32_val = ");
286 fprintf(out, ".string_val = ");
287 string_literal(opt->default_val, out);
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");
301 format_c_text(opt->help, "help", 5, out);
302 fprintf(out, "\t\t\t},\n");
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");
308 static void format_command(const struct lsg_command *cmd, FILE *out)
310 if (!cmd->name.orig) {
311 fprintf(out, "{.name = NULL}\n");
314 fprintf(out, "{\n\t\t.name = \"%s\",\n", cmd->name.orig);
315 fprintf(out, "\t\t.purpose = ");
316 string_literal(cmd->purpose, out);
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);
332 static void format_user_data(const struct lsg_command *cmd, FILE *out)
336 fprintf(out, "extern const void *lsg_%s_com_%s_user_data "
337 "__attribute__ ((weak));\n",
338 suite.name.sanitized,
343 static void gen_c(const char *outpath, const char *cmdline)
347 FILE *out = fopen(outpath, "w");
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);
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);
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);
386 static inline bool has_arg(struct lsg_option *opt)
390 return strcmp(opt->arg_info, "LLS_NO_ARGUMENT");
393 static void gen_header(const char *outpath, const char *cmdline)
396 FILE *out = fopen(outpath, "w");
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);
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;
423 name = "SUPERCOMMAND_UNAVAILABLE";
424 fprintf(out, "\tLSG_%s_CMD_%s,\n", suite.name.capitalized, name);
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;
433 ai = suite.aux_info_default;
437 fprintf(out, "\t%s_AUX_INFO(%s) /* %s */ \\\n",
438 suite.name.capitalized, ai, cmd->name.orig?
439 cmd->name.orig : "NO_SUPERCOMMAND");
442 /* generate one option enum per command */
443 for (i = 0; i <= suite.num_subcommands; i++) {
444 struct lsg_command *cmd = suite.commands + i;
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] = "";
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,
461 fprintf(out, "\tLSG_NUM_%s_%s_OPTIONS\n};\n",
462 suite.name.capitalized, cmd->name.capitalized);
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;
470 for (j = 0; j < cmd->num_options; j++) {
471 struct lsg_option *opt = cmd->options + j;
474 fprintf(out, "/* cmd %s, opt %s */\n", cmd->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);
484 for (i = 0; i <= suite.num_subcommands; i++) {
485 struct lsg_command *cmd = suite.commands + i;
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;
494 fprintf(out, "\"-%c%s\"%s", opt->short_opt,
495 has_arg(opt)? "=" : "",
496 j == cmd->num_options - 1? "" : ", ");
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? "" : ", ");
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
519 static void check_option(struct lsg_option *opt)
521 if (!opt->arg_info || !strcmp(opt->arg_info, "no_arg"))
523 if (opt->arg_type && strcmp(opt->arg_type, "none"))
525 fprintf(stderr, "option '%s': inconsistent arg_type/arg_info\n",
530 static void sanity_check(void)
534 if (suite.num_subcommands == 0 && !suite.commands) {
535 fprintf(stderr, "no (sub)commands defined\n");
538 for (i = 0; i <= suite.num_subcommands; i++) {
539 struct lsg_command *cmd = suite.commands + i;
542 for (j = 0; j < cmd->num_options; j++)
543 check_option(cmd->options + j);
547 static void run_yylex(void)
552 if (!suite.name.orig)
553 suite.name.orig = strdup("lopsubgen");
555 lsg_init_name(&suite.name);
556 for (i = 0; i <= suite.num_subcommands; i++) {
557 struct lsg_command *cmd = suite.commands + i;
560 lsg_init_name(&cmd->name);
561 for (j = 0; j < cmd->num_options; j++)
562 lsg_init_name(&cmd->options[j].name);
564 for (i = 0; i < suite.num_sections; i++)
565 lsg_init_name(&suite.sections[i].name);
569 int main(int argc, char **argv)
572 gen_c("lopsubgen.lsg.c", NULL);
573 gen_header("lopsubgen.lsg.h", NULL);
579 #include "lopsubgen.lsg.h"
581 static char *get_output_path(const char *suffix, const char *arg,
582 const struct lls_parse_result *lpr)
585 char *result, *output_dir;
587 output_dir = OPT_STRING_VAL(OUTPUT_DIR, lpr);
589 if (arg && arg[0] == '/') {
590 result = strdup(arg);
594 if (arg) { /* relative path */
595 len = strlen(output_dir) + strlen(arg) + 1;
596 result = malloc(len + 1);
598 sprintf(result, "%s/%s", output_dir, arg);
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);
606 sprintf(result, "%s/%s.lsg.%s", output_dir, suite.name.orig, suffix);
610 static void gen_man(struct lls_parse_result *lpr, const char *cmdline)
616 char *outpath = get_output_path("man",
617 OPT_STRING_VAL(GEN_MAN, lpr), lpr);
619 out = fopen(outpath, "w");
625 gen_license_header(out, cmdline, ".\\\" ", "");
626 if (suite.commands[0].name.orig) {
628 const char *version_string;
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.
637 char *source_date_epoch = getenv("SOURCE_DATE_EPOCH");
638 if (source_date_epoch != NULL)
639 t = strtoll(source_date_epoch, NULL, 10);
647 if (strftime(date, sizeof(date), "%B %Y", tmp) == 0) {
648 fprintf(stderr, "strftime returned 0\n");
652 if (OPT_GIVEN(VERSION_STRING, lpr))
653 version_string = OPT_STRING_VAL(VERSION_STRING, lpr);
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,
662 suite.manual_title? suite.manual_title : "User commands"
665 for (i = 0; i <= suite.num_subcommands; i++) {
666 struct lsg_command *cmd = suite.commands + i;
671 fprintf(out, ".SH NAME\n");
672 fprintf(out, ".B\n%s \\- ", cmd->name.orig);
673 format_man_text(out, false, cmd->purpose);
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);
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);
686 fprintf(out, "\n.P\n");
688 fprintf(out, ".SH SYNOPSIS\n");
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) {
696 fprintf(out, ".SH DESCRIPTION\n");
697 format_man_text(out, true, cmd->description);
699 if (cmd->num_options > 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;
706 fprintf(out, ".SS ");
707 format_man_text(out, false, opt->summary);
710 format_man_text(out, true, opt->help);
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 */);
720 format_man_text(out, true, opt->summary);
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++) {
731 fprintf(out, "%s%s\n", opt->values[n],
732 n == opt->num_values - 1?
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");
742 fprintf(out, ".IP\n");
743 format_man_text(out, true, opt->help);
746 fprintf(out, ".PP\n");
748 char *pfx = suite.aux_info_prefix;
749 fprintf(out, ".RS\n");
751 fprintf(out, "%s ", pfx);
752 fprintf(out, "%s\n.PP\n", cmd->aux_info);
753 fprintf(out, ".RE\n");
756 format_man_text(out, true, cmd->closing);
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);
768 int main(int argc, char **argv)
770 const struct lls_command *cmd = lls_cmd(0, lopsubgen_suite);
771 char *errctx, *outpath, *cmdline;
772 struct lls_parse_result *lpr;
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);
780 for (i = 0, ret = 0; argv[i]; i++)
781 ret += sprintf(cmdline + ret, "%s ", argv[i]);
782 cmdline[ret - 1] = '\0';
784 ret = lls_parse(argc, argv, cmd, &lpr, &errctx);
787 fprintf(stderr, "%s\n", errctx);
788 fprintf(stderr, "%s\n", lls_strerror(-ret));
791 if (OPT_GIVEN(VERSION, lpr)) {
792 printf("lopsubgen-%s\n", lls_version());
795 if (OPT_GIVEN(HELP, lpr)) {
797 if (OPT_GIVEN(HELP, lpr) > 1)
798 help = lls_long_help(cmd);
800 help = lls_short_help(cmd);
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);
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);
818 if (OPT_GIVEN(GEN_MAN, lpr))
819 gen_man(lpr, cmdline);