From fc9f4ef308dbc0e80557d25c6f640c25822390ea Mon Sep 17 00:00:00 2001 From: Vaclav Petras Date: Tue, 1 Apr 2025 17:44:08 -0400 Subject: [PATCH 1/6] docs: Add Python to generated Markdown tool doc This completely revamps the generated header for tool documentation pages, and it adds Python syntax for the tool and Python-oriented parameter description. The Python part is similar, but not the same to what is generated by Sphinx autodoc. I tried to make like it while accomodating the metadata we have (key desc strings, label and description for flags), while working with the Markdown syntax (we generate pure and simple Markdown which is only later translated to HTML in generic way, not knowing about the context or language). Most of the headings are removed for both CLI and Python, key-value parameters go before flags for both, and the idea makes heavy use of synchronized tabs. --- lib/gis/parser_md.c | 621 +++++++++++++++++++++++++++++++++--------- man/mkdocs/mkdocs.yml | 1 - 2 files changed, 489 insertions(+), 133 deletions(-) diff --git a/lib/gis/parser_md.c b/lib/gis/parser_md.c index da10cdf59a6..97603263360 100644 --- a/lib/gis/parser_md.c +++ b/lib/gis/parser_md.c @@ -9,6 +9,7 @@ (>=v2). Read the file COPYING that comes with GRASS for details. \author Martin Landa + \author Vaclav Petras */ #include #include @@ -20,25 +21,28 @@ #define MD_NEWLINE " " -static void print_flag(const char *key, const char *label, - const char *description); -void print_option(const struct Option *opt); +static void print_cli_flag(const char *key, const char *label, + const char *description, const char *indent); +static void print_python_short_flag(const char *key, const char *label, + const char *description, + const char *indent); +static void print_python_long_flag(const char *key, const char *label, + const char *description, const char *indent); +static void print_cli_option(const struct Option *opt, const char *indent); +static void print_python_option(const struct Option *opt, const char *indent); static void print_escaped(FILE *f, const char *str); static void print_escaped_for_md(FILE *f, const char *str); static void print_escaped_for_md_options(FILE *f, const char *str); +static void print_cli_short_version(FILE *file, const char *indent); +static void print_python_short_version(FILE *file, const char *indent); +static void print_cli_long_version(const char *indent); +static void print_python_long_version(const char *indent); /*! \brief Print module usage description in Markdown format. */ void G__usage_markdown(void) { - struct Option *opt; - struct Flag *flag; - const char *type; - int new_prompt = 0; - - new_prompt = G__uses_new_gisprompt(); - if (!st->pgm_name) st->pgm_name = G_program_name(); if (!st->pgm_name) @@ -56,15 +60,7 @@ void G__usage_markdown(void) /* main header */ fprintf(stdout, "# %s\n\n", st->pgm_name); - /* header - GRASS module */ - fprintf(stdout, "## "); - fprintf(stdout, "%s\n", _("NAME")); - fprintf(stdout, "\n"); - fprintf(stdout, "***%s***", st->pgm_name); - - if (st->module_info.label || st->module_info.description) - fprintf(stdout, " - "); - + /* header */ if (st->module_info.label) fprintf(stdout, "%s\n", st->module_info.label); @@ -73,147 +69,90 @@ void G__usage_markdown(void) fprintf(stdout, "\n"); fprintf(stdout, "%s\n", st->module_info.description); } - fprintf(stdout, "\n"); - fprintf(stdout, "### "); - fprintf(stdout, "\n"); - fprintf(stdout, "### "); - fprintf(stdout, "%s\n", _("SYNOPSIS")); - fprintf(stdout, "\n"); - fprintf(stdout, "**%s**", st->pgm_name); - fprintf(stdout, MD_NEWLINE); - fprintf(stdout, "\n"); - fprintf(stdout, "**%s --help**", st->pgm_name); - fprintf(stdout, MD_NEWLINE); - fprintf(stdout, "\n"); - fprintf(stdout, "**%s**", st->pgm_name); - /* print short version first */ - if (st->n_flags) { - flag = &st->first_flag; - fprintf(stdout, " [**-"); - while (flag != NULL) { - fprintf(stdout, "%c", flag->key); - flag = flag->next_flag; - } - fprintf(stdout, "**] "); - } - else - fprintf(stdout, " "); + const char *tab_indent = " "; - if (st->n_opts) { - opt = &st->first_option; + /* short version */ + fprintf(stdout, "\n=== \"Command line (Bash)\"\n\n"); + print_cli_short_version(stdout, tab_indent); + fprintf(stdout, "\n=== \"Python (grass.script)\"\n\n"); + print_python_short_version(stdout, tab_indent); - while (opt != NULL) { - if (opt->key_desc != NULL) - type = opt->key_desc; - else - switch (opt->type) { - case TYPE_INTEGER: - type = "integer"; - break; - case TYPE_DOUBLE: - type = "float"; - break; - case TYPE_STRING: - type = "string"; - break; - default: - type = "string"; - break; - } - fprintf(stdout, " "); - if (!opt->required) - fprintf(stdout, "["); - fprintf(stdout, "**%s**=", opt->key); - fprintf(stdout, "*%s*", type); - if (opt->multiple) { - fprintf(stdout, " [,"); - fprintf(stdout, "*%s*,...]", type); - } - if (!opt->required) - fprintf(stdout, "]"); - fprintf(stdout, "\n"); + fprintf(stdout, "\n## %s\n", _("Parameters")); - opt = opt->next_opt; - } - } - if (new_prompt) - fprintf(stdout, " [**--overwrite**] "); - - fprintf(stdout, " [**--verbose**] "); - fprintf(stdout, " [**--quiet**] "); - fprintf(stdout, " [**--ui**]\n"); + /* long version */ + fprintf(stdout, "\n=== \"Command line (Bash)\"\n\n"); + print_cli_long_version(tab_indent); + fprintf(stdout, "\n=== \"Python (grass.script)\"\n\n"); + print_python_long_version(tab_indent); +} - /* now long version */ +void print_cli_flag(const char *key, const char *label, const char *description, + const char *indent) +{ + fprintf(stdout, "%s**", indent); + if (strlen(key) > 1) + fprintf(stdout, "-"); + fprintf(stdout, "-%s**", key); + fprintf(stdout, MD_NEWLINE); fprintf(stdout, "\n"); - if (st->n_flags || new_prompt) { - flag = &st->first_flag; - fprintf(stdout, "#### "); - fprintf(stdout, "%s\n", _("Flags")); + if (label != NULL) { + fprintf(stdout, "%s", indent); + print_escaped(stdout, "\t"); + print_escaped(stdout, label); + fprintf(stdout, MD_NEWLINE); fprintf(stdout, "\n"); - while (st->n_flags && flag != NULL) { - print_flag(&flag->key, flag->label, flag->description); - fprintf(stdout, MD_NEWLINE); - fprintf(stdout, "\n"); - flag = flag->next_flag; - } - if (new_prompt) { - print_flag("overwrite", NULL, - _("Allow output files to overwrite existing files")); - fprintf(stdout, MD_NEWLINE); - fprintf(stdout, "\n"); - } } - print_flag("help", NULL, _("Print usage summary")); - fprintf(stdout, MD_NEWLINE); - fprintf(stdout, "\n"); - print_flag("verbose", NULL, _("Verbose module output")); - fprintf(stdout, MD_NEWLINE); - fprintf(stdout, "\n"); - print_flag("quiet", NULL, _("Quiet module output")); + if (description != NULL) { + fprintf(stdout, "%s", indent); + print_escaped(stdout, "\t"); + print_escaped(stdout, description); + } +} + +void print_python_short_flag(const char *key, const char *label, + const char *description, const char *indent) +{ + fprintf(stdout, "%s", indent); + print_escaped(stdout, "\t"); + fprintf(stdout, "**%s**", key); fprintf(stdout, MD_NEWLINE); fprintf(stdout, "\n"); - print_flag("ui", NULL, _("Force launching GUI dialog")); - fprintf(stdout, "\n"); - - if (st->n_opts) { - fprintf(stdout, "\n"); - opt = &st->first_option; - fprintf(stdout, "#### "); - fprintf(stdout, "%s\n", _("Parameters")); + if (label != NULL) { + fprintf(stdout, "%s", indent); + print_escaped(stdout, "\t\t"); + print_escaped(stdout, label); + fprintf(stdout, MD_NEWLINE); fprintf(stdout, "\n"); - while (opt != NULL) { - print_option(opt); - opt = opt->next_opt; - if (opt != NULL) { - fprintf(stdout, MD_NEWLINE); - } - fprintf(stdout, "\n"); - } + } + if (description != NULL) { + fprintf(stdout, "%s", indent); + print_escaped(stdout, "\t\t"); + print_escaped(stdout, description); } } -void print_flag(const char *key, const char *label, const char *description) +void print_python_long_flag(const char *key, const char *label, + const char *description, const char *indent) { - fprintf(stdout, "**"); - if (strlen(key) > 1) - fprintf(stdout, "-"); - fprintf(stdout, "-%s**", key); + fprintf(stdout, "%s**%s**: bool, default False", indent, key); fprintf(stdout, MD_NEWLINE); fprintf(stdout, "\n"); if (label != NULL) { + fprintf(stdout, "%s", indent); print_escaped(stdout, "\t"); print_escaped(stdout, label); fprintf(stdout, MD_NEWLINE); fprintf(stdout, "\n"); } if (description != NULL) { + fprintf(stdout, "%s", indent); print_escaped(stdout, "\t"); print_escaped(stdout, description); } } -void print_option(const struct Option *opt) +void print_cli_option(const struct Option *opt, const char *indent) { const char *type; @@ -235,7 +174,7 @@ void print_option(const struct Option *opt) type = "string"; break; } - fprintf(stdout, "**%s**=", opt->key); + fprintf(stdout, "%s**%s**=", indent, opt->key); fprintf(stdout, "*%s*", type); if (opt->multiple) { fprintf(stdout, " [,"); @@ -248,6 +187,7 @@ void print_option(const struct Option *opt) fprintf(stdout, MD_NEWLINE); fprintf(stdout, "\n"); if (opt->label) { + fprintf(stdout, "%s", indent); print_escaped(stdout, "\t"); print_escaped(stdout, opt->label); } @@ -256,6 +196,7 @@ void print_option(const struct Option *opt) fprintf(stdout, MD_NEWLINE); fprintf(stdout, "\n"); } + fprintf(stdout, "%s", indent); print_escaped(stdout, "\t"); print_escaped(stdout, opt->description); } @@ -263,6 +204,7 @@ void print_option(const struct Option *opt) if (opt->options) { fprintf(stdout, MD_NEWLINE); fprintf(stdout, "\n"); + fprintf(stdout, "%s", indent); print_escaped(stdout, "\t"); fprintf(stdout, "%s: *", _("Options")); print_escaped_for_md_options(stdout, opt->options); @@ -272,6 +214,123 @@ void print_option(const struct Option *opt) if (opt->def) { fprintf(stdout, MD_NEWLINE); fprintf(stdout, "\n"); + fprintf(stdout, "%s", indent); + print_escaped(stdout, "\t"); + fprintf(stdout, "%s:", _("Default")); + /* TODO check if value is empty + if (!opt->def.empty()){ */ + fprintf(stdout, " *"); + print_escaped(stdout, opt->def); + fprintf(stdout, "*"); + } + + if (opt->descs) { + int i = 0; + + while (opt->opts[i]) { + if (opt->descs[i]) { + fprintf(stdout, MD_NEWLINE); + fprintf(stdout, "\n"); + fprintf(stdout, "%s", indent); + char *thumbnails = NULL; + if (opt->gisprompt) { + if (strcmp(opt->gisprompt, "old,colortable,colortable") == + 0) + thumbnails = "colortables"; + else if (strcmp(opt->gisprompt, "old,barscale,barscale") == + 0) + thumbnails = "barscales"; + else if (strcmp(opt->gisprompt, + "old,northarrow,northarrow") == 0) + thumbnails = "northarrows"; + + if (thumbnails) { + print_escaped(stdout, "\t\t"); + fprintf(stdout, "![%s](%s/%s.png) ", opt->opts[i], + thumbnails, opt->opts[i]); + } + else { + print_escaped(stdout, "\t\t"); + } + } + print_escaped(stdout, "\t"); + fprintf(stdout, "**"); + print_escaped(stdout, opt->opts[i]); + fprintf(stdout, "**: "); + print_escaped(stdout, opt->descs[i]); + } + i++; + } + } +} + +void print_python_option(const struct Option *opt, const char *indent) +{ + const char *type; + + switch (opt->type) { + case TYPE_INTEGER: + type = "int"; + break; + case TYPE_DOUBLE: + type = "float"; + break; + case TYPE_STRING: + type = "str"; + break; + default: + type = "str"; + break; + } + fprintf(stdout, "%s**%s** : ", indent, opt->key); + if (opt->multiple) { + fprintf(stdout, "Iterable, list[%s]", type); + } + else { + fprintf(stdout, "%s", type); + } + if (opt->key_desc) { + fprintf(stdout, ", `%s`", opt->key_desc); + } + /* fprintf(stdout, "*"); */ + if (opt->required) { + fprintf(stdout, ", required"); + } + else { + fprintf(stdout, ", optional"); + } + + fprintf(stdout, MD_NEWLINE); + fprintf(stdout, "\n"); + if (opt->label) { + fprintf(stdout, "%s", indent); + print_escaped(stdout, "\t"); + print_escaped(stdout, opt->label); + } + if (opt->description) { + if (opt->label) { + fprintf(stdout, MD_NEWLINE); + fprintf(stdout, "\n"); + } + fprintf(stdout, "%s", indent); + print_escaped(stdout, "\t"); + print_escaped(stdout, opt->description); + } + + if (opt->options) { + fprintf(stdout, MD_NEWLINE); + fprintf(stdout, "\n"); + fprintf(stdout, "%s", indent); + print_escaped(stdout, "\t"); + fprintf(stdout, "%s: *", _("Allowed values")); + print_escaped_for_md_options(stdout, opt->options); + fprintf(stdout, "*"); + } + + if (opt->def) { + fprintf(stdout, MD_NEWLINE); + fprintf(stdout, "\n"); + fprintf(stdout, "%s", indent); print_escaped(stdout, "\t"); fprintf(stdout, "%s:", _("Default")); /* TODO check if value is empty @@ -288,6 +347,7 @@ void print_option(const struct Option *opt) if (opt->descs[i]) { fprintf(stdout, MD_NEWLINE); fprintf(stdout, "\n"); + fprintf(stdout, "%s", indent); char *thumbnails = NULL; if (opt->gisprompt) { if (strcmp(opt->gisprompt, "old,colortable,colortable") == @@ -364,4 +424,301 @@ void print_escaped_for_md_options(FILE *f, const char *str) } } +void print_cli_short_version(FILE *file, const char *indent) +{ + struct Option *opt; + struct Flag *flag; + const char *type; + int new_prompt = 0; + + new_prompt = G__uses_new_gisprompt(); + + fprintf(stdout, "%s**%s**", indent, st->pgm_name); + fprintf(stdout, "\n"); + + /* print short version first */ + if (st->n_flags) { + flag = &st->first_flag; + fprintf(stdout, "%s[**-", indent); + while (flag != NULL) { + fprintf(stdout, "%c", flag->key); + flag = flag->next_flag; + } + fprintf(stdout, "**]"); + } + fprintf(stdout, "\n"); + + if (st->n_opts) { + opt = &st->first_option; + + while (opt != NULL) { + if (opt->key_desc != NULL) + type = opt->key_desc; + else + switch (opt->type) { + case TYPE_INTEGER: + type = "integer"; + break; + case TYPE_DOUBLE: + type = "float"; + break; + case TYPE_STRING: + type = "string"; + break; + default: + type = "string"; + break; + } + fprintf(stdout, "%s", indent); + if (!opt->required) + fprintf(stdout, "["); + fprintf(stdout, "**%s**=", opt->key); + fprintf(stdout, "*%s*", type); + if (opt->multiple) { + fprintf(stdout, " [,"); + fprintf(stdout, "*%s*,...]", type); + } + if (!opt->required) + fprintf(stdout, "]"); + fprintf(stdout, "\n"); + + opt = opt->next_opt; + } + } + if (new_prompt) + fprintf(stdout, "%s[**--overwrite**]\n", indent); + + fprintf(stdout, "%s[**--verbose**]\n", indent); + fprintf(stdout, "%s[**--quiet**]\n", indent); + fprintf(stdout, "%s[**--ui**]\n", indent); +} + +void print_python_short_version(FILE *file, const char *indent) +{ + struct Option *opt; + struct Flag *flag; + const char *type; + int new_prompt = 0; + + new_prompt = G__uses_new_gisprompt(); + + if (!new_prompt) { + fprintf(stdout, "%s*grass.script.run_command*(\"***%s***\",", indent, + st->pgm_name); + } + else { + fprintf(stdout, "%s*grass.script.parse_command*(\"***%s***\",", indent, + st->pgm_name); + } + fprintf(stdout, "\n"); + + if (st->n_opts) { + opt = &st->first_option; + + while (opt != NULL) { + fprintf(stdout, "%s ", indent); + if (!opt->required && !opt->answer) { + fprintf(stdout, "**%s**=*None*", opt->key); + } + else { + fprintf(stdout, "**%s**", opt->key); + } + if (opt->answer) { + fprintf(stdout, "=", opt->key); + if (opt->type == TYPE_INTEGER || opt->type == TYPE_DOUBLE) { + fprintf(stdout, "*%s*", opt->answer); + } + else { + fprintf(stdout, "*\"%s\"*", opt->answer); + } + } + fprintf(stdout, ",\n"); + + opt = opt->next_opt; + } + } + + if (st->n_flags) { + flag = &st->first_flag; + fprintf(stdout, "%s **flags**=*None*,", indent); + } + fprintf(stdout, "\n"); + + if (new_prompt) + fprintf(stdout, "%s **overwrite**=*False*,\n", indent); + + fprintf(stdout, "%s **verbose**=*False*,\n", indent); + fprintf(stdout, "%s **quiet**=*False*,\n", indent); + fprintf(stdout, "%s **superquiet**=*False*)\n", indent); + + fprintf(stdout, "\n%sExample:\n", indent); + + fprintf(stdout, "\n%s```python\n", indent); + fprintf(stdout, "%sgs.run_command(\"%s\",", indent, st->pgm_name); + fprintf(stdout, " "); + if (st->n_opts) { + opt = &st->first_option; + + while (opt != NULL) { + if (opt->key_desc != NULL) + type = opt->key_desc; + else + switch (opt->type) { + case TYPE_INTEGER: + type = "integer"; + break; + case TYPE_DOUBLE: + type = "float"; + break; + case TYPE_STRING: + type = "string"; + break; + default: + type = "string"; + break; + } + if (opt->required) { + fprintf(stdout, "%s", opt->key); + fprintf(stdout, "=", opt->key); + if (opt->answer) { + if (opt->type == TYPE_INTEGER || opt->type == TYPE_DOUBLE) { + fprintf(stdout, "%s", opt->answer); + } + else { + fprintf(stdout, "\"%s\"", opt->answer); + } + } + else { + if (opt->type == TYPE_INTEGER || opt->type == TYPE_DOUBLE) { + fprintf(stdout, "%s", type); + } + else { + fprintf(stdout, "\"%s\"", type); + } + } + fprintf(stdout, ", "); + } + opt = opt->next_opt; + } + } + fprintf(stdout, ")\n%s```\n", indent); +} + +void print_cli_long_version(const char *indent) +{ + struct Option *opt; + struct Flag *flag; + const char *type; + int new_prompt = 0; + + new_prompt = G__uses_new_gisprompt(); + + // Options (key-value parameters) + if (st->n_opts) { + opt = &st->first_option; + while (opt != NULL) { + print_cli_option(opt, indent); + opt = opt->next_opt; + if (opt != NULL) { + fprintf(stdout, MD_NEWLINE); + } + fprintf(stdout, "\n"); + } + } + + // Short (one-letter) flags and tool-specific long flags + if (st->n_flags || new_prompt) { + flag = &st->first_flag; + while (st->n_flags && flag != NULL) { + print_cli_flag(&flag->key, flag->label, flag->description, indent); + fprintf(stdout, MD_NEWLINE); + fprintf(stdout, "\n"); + flag = flag->next_flag; + } + if (new_prompt) { + print_cli_flag("overwrite", NULL, + _("Allow output files to overwrite existing files"), + indent); + fprintf(stdout, MD_NEWLINE); + fprintf(stdout, "\n"); + } + } + // Pre-defined long flags + print_cli_flag("help", NULL, _("Print usage summary"), indent); + fprintf(stdout, MD_NEWLINE); + fprintf(stdout, "\n"); + print_cli_flag("verbose", NULL, _("Verbose module output"), indent); + fprintf(stdout, MD_NEWLINE); + fprintf(stdout, "\n"); + print_cli_flag("quiet", NULL, _("Quiet module output"), indent); + fprintf(stdout, MD_NEWLINE); + fprintf(stdout, "\n"); + print_cli_flag("ui", NULL, _("Force launching GUI dialog"), indent); + fprintf(stdout, "\n"); +} + +void print_python_long_version(const char *indent) +{ + struct Option *opt; + struct Flag *flag; + const char *type; + int new_prompt = 0; + + new_prompt = G__uses_new_gisprompt(); + + // Options (key-value parameters) + if (st->n_opts) { + opt = &st->first_option; + while (opt != NULL) { + print_python_option(opt, indent); + opt = opt->next_opt; + fprintf(stdout, MD_NEWLINE); + fprintf(stdout, "\n"); + } + } + + // Short (one-letter) flags and tool-specific long flags + if (st->n_flags) { + fprintf(stdout, "%s**flags** : str", indent); + fprintf(stdout, MD_NEWLINE); + fprintf(stdout, "\n"); + fprintf(stdout, "%s", indent); + print_escaped(stdout, "\t"); + fprintf(stdout, "Allowed values: "); + flag = &st->first_flag; + while (st->n_flags && flag != NULL) { + fprintf(stdout, "*%s*, ", &flag->key); + flag = flag->next_flag; + } + fprintf(stdout, MD_NEWLINE); + fprintf(stdout, "\n"); + flag = &st->first_flag; + while (st->n_flags && flag != NULL) { + print_python_short_flag(&flag->key, flag->label, flag->description, + indent); + fprintf(stdout, MD_NEWLINE); + fprintf(stdout, "\n"); + flag = flag->next_flag; + } + } + if (new_prompt) { + print_python_long_flag( + "overwrite", NULL, + _("Allow output files to overwrite existing files"), indent); + fprintf(stdout, MD_NEWLINE); + fprintf(stdout, "\n"); + } + // Pre-defined long flags + print_python_long_flag("verbose", NULL, _("Verbose module output"), indent); + fprintf(stdout, MD_NEWLINE); + fprintf(stdout, "\n"); + print_python_long_flag("quiet", NULL, _("Quiet module output"), indent); + fprintf(stdout, MD_NEWLINE); + fprintf(stdout, "\n"); + print_python_long_flag("superquiet", NULL, _("Very quiet module output"), + indent); + fprintf(stdout, MD_NEWLINE); + fprintf(stdout, "\n"); +} + #undef do_escape diff --git a/man/mkdocs/mkdocs.yml b/man/mkdocs/mkdocs.yml index 09d9864ba8e..3d32b48b7bd 100644 --- a/man/mkdocs/mkdocs.yml +++ b/man/mkdocs/mkdocs.yml @@ -98,7 +98,6 @@ markdown_extensions: - pymdownx.superfences - pymdownx.tasklist - pymdownx.snippets - - pymdownx.tabbed - pymdownx.magiclink - attr_list - md_in_html From 66a60b56a3112765e990deb366bb0b7e3cb35689 Mon Sep 17 00:00:00 2001 From: Vaclav Petras Date: Wed, 2 Apr 2025 17:34:48 -0400 Subject: [PATCH 2/6] Generate Python examples based on format=json|csv (or -g without file output aka new prompt) and show first parameter from a group of 'at least one required'. Fix C warnings. --- lib/gis/parser_dependencies.c | 24 ++++++ lib/gis/parser_local_proto.h | 1 + lib/gis/parser_md.c | 152 ++++++++++++++++++++++------------ 3 files changed, 124 insertions(+), 53 deletions(-) diff --git a/lib/gis/parser_dependencies.c b/lib/gis/parser_dependencies.c index 256b44bafa7..14f53732f43 100644 --- a/lib/gis/parser_dependencies.c +++ b/lib/gis/parser_dependencies.c @@ -491,6 +491,30 @@ int G__has_required_rule(void) return FALSE; } +const struct Option *G__first_required_option_from_rules(void) +{ + size_t i; + + for (i = 0; i < rules.count; i++) { + const struct rule *rule = &((const struct rule *)rules.data)[i]; + + if (rule->type == RULE_REQUIRED) { + if (rule->count < 0) + G_fatal_error( + _("Internal error: the number of options is < 0")); + size_t j; + for (j = 0; j < (unsigned int)rule->count; j++) { + void *p = rule->opts[j]; + if (is_flag(p)) + continue; + else + return (const struct Option *)p; + } + } + } + return NULL; +} + static const char *const rule_types[] = {"exclusive", "required", "requires", "requires-all", "excludes", "collective"}; diff --git a/lib/gis/parser_local_proto.h b/lib/gis/parser_local_proto.h index 8b75a3bac2b..4503d6d2676 100644 --- a/lib/gis/parser_local_proto.h +++ b/lib/gis/parser_local_proto.h @@ -66,6 +66,7 @@ void G__split_gisprompt(const char *, char *, char *, char *); void G__check_option_rules(void); void G__describe_option_rules(void); int G__has_required_rule(void); +const struct Option *G__first_required_option_from_rules(void); void G__describe_option_rules_xml(FILE *); #endif diff --git a/lib/gis/parser_md.c b/lib/gis/parser_md.c index 97603263360..2f600bd85b4 100644 --- a/lib/gis/parser_md.c +++ b/lib/gis/parser_md.c @@ -433,20 +433,20 @@ void print_cli_short_version(FILE *file, const char *indent) new_prompt = G__uses_new_gisprompt(); - fprintf(stdout, "%s**%s**", indent, st->pgm_name); - fprintf(stdout, "\n"); + fprintf(file, "%s**%s**", indent, st->pgm_name); + fprintf(file, "\n"); /* print short version first */ if (st->n_flags) { flag = &st->first_flag; - fprintf(stdout, "%s[**-", indent); + fprintf(file, "%s[**-", indent); while (flag != NULL) { - fprintf(stdout, "%c", flag->key); + fprintf(file, "%c", flag->key); flag = flag->next_flag; } - fprintf(stdout, "**]"); + fprintf(file, "**]"); } - fprintf(stdout, "\n"); + fprintf(file, "\n"); if (st->n_opts) { opt = &st->first_option; @@ -469,28 +469,28 @@ void print_cli_short_version(FILE *file, const char *indent) type = "string"; break; } - fprintf(stdout, "%s", indent); + fprintf(file, "%s", indent); if (!opt->required) - fprintf(stdout, "["); - fprintf(stdout, "**%s**=", opt->key); - fprintf(stdout, "*%s*", type); + fprintf(file, "["); + fprintf(file, "**%s**=", opt->key); + fprintf(file, "*%s*", type); if (opt->multiple) { - fprintf(stdout, " [,"); - fprintf(stdout, "*%s*,...]", type); + fprintf(file, " [,"); + fprintf(file, "*%s*,...]", type); } if (!opt->required) - fprintf(stdout, "]"); - fprintf(stdout, "\n"); + fprintf(file, "]"); + fprintf(file, "\n"); opt = opt->next_opt; } } if (new_prompt) - fprintf(stdout, "%s[**--overwrite**]\n", indent); + fprintf(file, "%s[**--overwrite**]\n", indent); - fprintf(stdout, "%s[**--verbose**]\n", indent); - fprintf(stdout, "%s[**--quiet**]\n", indent); - fprintf(stdout, "%s[**--ui**]\n", indent); + fprintf(file, "%s[**--verbose**]\n", indent); + fprintf(file, "%s[**--quiet**]\n", indent); + fprintf(file, "%s[**--ui**]\n", indent); } void print_python_short_version(FILE *file, const char *indent) @@ -499,40 +499,85 @@ void print_python_short_version(FILE *file, const char *indent) struct Flag *flag; const char *type; int new_prompt = 0; + bool output_format_option = false; + const char *output_format_default = NULL; + bool shell_eval_flag = false; + const char *python_function = NULL; new_prompt = G__uses_new_gisprompt(); - if (!new_prompt) { - fprintf(stdout, "%s*grass.script.run_command*(\"***%s***\",", indent, - st->pgm_name); + if (st->n_opts) { + opt = &st->first_option; + while (opt != NULL) { + if (strcmp(opt->key, "format") == 0) { + if (opt->options) { + int i = 0; + while (opt->opts[i]) { + if (strcmp(opt->opts[i], "csv") == 0) + output_format_default = "csv"; + if (strcmp(opt->opts[i], "json") == 0) { + output_format_default = "json"; + break; + } + i++; + } + } + if (output_format_default) { + output_format_option = true; + } + break; + } + opt = opt->next_opt; + } + } + if (st->n_flags) { + flag = &st->first_flag; + while (st->n_flags && flag != NULL) { + if (flag->key == 'g') { + shell_eval_flag = true; + break; + } + flag = flag->next_flag; + } + } + if (output_format_option || (!new_prompt && shell_eval_flag)) { + python_function = "parse_command"; + // We know this is can be parsed, but we can't detect just plain stdout + // because we can't distinguish between plain text outputs and + // modifications of data. } else { - fprintf(stdout, "%s*grass.script.parse_command*(\"***%s***\",", indent, - st->pgm_name); + python_function = "run_command"; } - fprintf(stdout, "\n"); + fprintf(file, "%s*grass.script.%s*(\"***%s***\",", indent, python_function, + st->pgm_name); + fprintf(file, "\n"); if (st->n_opts) { opt = &st->first_option; while (opt != NULL) { - fprintf(stdout, "%s ", indent); + fprintf(file, "%s ", indent); if (!opt->required && !opt->answer) { - fprintf(stdout, "**%s**=*None*", opt->key); + fprintf(file, "**%s**=*None*", opt->key); } else { - fprintf(stdout, "**%s**", opt->key); + fprintf(file, "**%s**", opt->key); } if (opt->answer) { - fprintf(stdout, "=", opt->key); + fprintf(file, "="); if (opt->type == TYPE_INTEGER || opt->type == TYPE_DOUBLE) { - fprintf(stdout, "*%s*", opt->answer); + fprintf(file, "*"); + print_escaped(file, opt->answer); + fprintf(file, "*"); } else { - fprintf(stdout, "*\"%s\"*", opt->answer); + fprintf(file, "*\""); + print_escaped(file, opt->answer); + fprintf(file, "\"*"); } } - fprintf(stdout, ",\n"); + fprintf(file, ",\n"); opt = opt->next_opt; } @@ -540,22 +585,24 @@ void print_python_short_version(FILE *file, const char *indent) if (st->n_flags) { flag = &st->first_flag; - fprintf(stdout, "%s **flags**=*None*,", indent); + fprintf(file, "%s **flags**=*None*,\n", indent); } - fprintf(stdout, "\n"); if (new_prompt) - fprintf(stdout, "%s **overwrite**=*False*,\n", indent); + fprintf(file, "%s **overwrite**=*False*,\n", indent); + + fprintf(file, "%s **verbose**=*False*,\n", indent); + fprintf(file, "%s **quiet**=*False*,\n", indent); + fprintf(file, "%s **superquiet**=*False*)\n", indent); + + fprintf(file, "\n%sExample:\n", indent); - fprintf(stdout, "%s **verbose**=*False*,\n", indent); - fprintf(stdout, "%s **quiet**=*False*,\n", indent); - fprintf(stdout, "%s **superquiet**=*False*)\n", indent); + fprintf(file, "\n%s```python\n", indent); + fprintf(file, "%sgs.%s(\"%s\"", indent, python_function, st->pgm_name); - fprintf(stdout, "\n%sExample:\n", indent); + const struct Option *first_required_rule_option = + G__first_required_option_from_rules(); - fprintf(stdout, "\n%s```python\n", indent); - fprintf(stdout, "%sgs.run_command(\"%s\",", indent, st->pgm_name); - fprintf(stdout, " "); if (st->n_opts) { opt = &st->first_option; @@ -577,38 +624,38 @@ void print_python_short_version(FILE *file, const char *indent) type = "string"; break; } - if (opt->required) { - fprintf(stdout, "%s", opt->key); - fprintf(stdout, "=", opt->key); - if (opt->answer) { + if (opt->required || first_required_rule_option == opt) { + fprintf(file, ", %s=", opt->key); + if (output_format_default && strcmp(opt->key, "format") == 0) { + fprintf(file, "\"%s\"", output_format_default); + } + else if (opt->answer) { if (opt->type == TYPE_INTEGER || opt->type == TYPE_DOUBLE) { - fprintf(stdout, "%s", opt->answer); + fprintf(file, "%s", opt->answer); } else { - fprintf(stdout, "\"%s\"", opt->answer); + fprintf(file, "\"%s\"", opt->answer); } } else { if (opt->type == TYPE_INTEGER || opt->type == TYPE_DOUBLE) { - fprintf(stdout, "%s", type); + fprintf(file, "%s", type); } else { - fprintf(stdout, "\"%s\"", type); + fprintf(file, "\"%s\"", type); } } - fprintf(stdout, ", "); } opt = opt->next_opt; } } - fprintf(stdout, ")\n%s```\n", indent); + fprintf(file, ")\n%s```\n", indent); } void print_cli_long_version(const char *indent) { struct Option *opt; struct Flag *flag; - const char *type; int new_prompt = 0; new_prompt = G__uses_new_gisprompt(); @@ -661,7 +708,6 @@ void print_python_long_version(const char *indent) { struct Option *opt; struct Flag *flag; - const char *type; int new_prompt = 0; new_prompt = G__uses_new_gisprompt(); From fa91ea5872baa47fb216e9d797124cc4bf879afe Mon Sep 17 00:00:00 2001 From: Vaclav Petras Date: Wed, 2 Apr 2025 17:35:12 -0400 Subject: [PATCH 3/6] Remove Bash from tab name --- lib/gis/parser_md.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/gis/parser_md.c b/lib/gis/parser_md.c index 2f600bd85b4..4248868c0e6 100644 --- a/lib/gis/parser_md.c +++ b/lib/gis/parser_md.c @@ -73,7 +73,7 @@ void G__usage_markdown(void) const char *tab_indent = " "; /* short version */ - fprintf(stdout, "\n=== \"Command line (Bash)\"\n\n"); + fprintf(stdout, "\n=== \"Command line\"\n\n"); print_cli_short_version(stdout, tab_indent); fprintf(stdout, "\n=== \"Python (grass.script)\"\n\n"); print_python_short_version(stdout, tab_indent); @@ -81,7 +81,7 @@ void G__usage_markdown(void) fprintf(stdout, "\n## %s\n", _("Parameters")); /* long version */ - fprintf(stdout, "\n=== \"Command line (Bash)\"\n\n"); + fprintf(stdout, "\n=== \"Command line\"\n\n"); print_cli_long_version(tab_indent); fprintf(stdout, "\n=== \"Python (grass.script)\"\n\n"); print_python_long_version(tab_indent); From 705e0bea90bd0f320d1529460303404238116f73 Mon Sep 17 00:00:00 2001 From: Vaclav Petras Date: Fri, 4 Apr 2025 14:19:10 -0400 Subject: [PATCH 4/6] Split markdown code into four files: main, CLI, Python, common. Add example for CLI. Add function parameter file instead of hardcoding stdout. Fix CLI synopsis for tools without flags. --- lib/gis/parser_local_proto.h | 10 + lib/gis/parser_md.c | 709 +---------------------------------- lib/gis/parser_md_cli.c | 334 +++++++++++++++++ lib/gis/parser_md_common.c | 60 +++ lib/gis/parser_md_python.c | 421 +++++++++++++++++++++ 5 files changed, 829 insertions(+), 705 deletions(-) create mode 100644 lib/gis/parser_md_cli.c create mode 100644 lib/gis/parser_md_common.c create mode 100644 lib/gis/parser_md_python.c diff --git a/lib/gis/parser_local_proto.h b/lib/gis/parser_local_proto.h index 4503d6d2676..96bae350956 100644 --- a/lib/gis/parser_local_proto.h +++ b/lib/gis/parser_local_proto.h @@ -48,12 +48,22 @@ struct state { extern struct state *st; +#define MD_NEWLINE " " + /* functions which are used by several parser functions in different files */ void G__usage_xml(void); void G__usage_html(void); void G__usage_rest(void); + void G__usage_markdown(void); +void G__md_print_cli_short_version(FILE *file, const char *indent); +void G__md_print_python_short_version(FILE *file, const char *indent); +void G__md_print_cli_long_version(FILE *file, const char *indent); +void G__md_print_python_long_version(FILE *file, const char *indent); +void G__md_print_escaped(FILE *f, const char *str); +void G__md_print_escaped_for_options(FILE *f, const char *str); + void G__usage_text(void); void G__script(void); char *G__json(void); diff --git a/lib/gis/parser_md.c b/lib/gis/parser_md.c index 4248868c0e6..0bb8b4a31a4 100644 --- a/lib/gis/parser_md.c +++ b/lib/gis/parser_md.c @@ -19,25 +19,6 @@ #include "parser_local_proto.h" -#define MD_NEWLINE " " - -static void print_cli_flag(const char *key, const char *label, - const char *description, const char *indent); -static void print_python_short_flag(const char *key, const char *label, - const char *description, - const char *indent); -static void print_python_long_flag(const char *key, const char *label, - const char *description, const char *indent); -static void print_cli_option(const struct Option *opt, const char *indent); -static void print_python_option(const struct Option *opt, const char *indent); -static void print_escaped(FILE *f, const char *str); -static void print_escaped_for_md(FILE *f, const char *str); -static void print_escaped_for_md_options(FILE *f, const char *str); -static void print_cli_short_version(FILE *file, const char *indent); -static void print_python_short_version(FILE *file, const char *indent); -static void print_cli_long_version(const char *indent); -static void print_python_long_version(const char *indent); - /*! \brief Print module usage description in Markdown format. */ @@ -74,697 +55,15 @@ void G__usage_markdown(void) /* short version */ fprintf(stdout, "\n=== \"Command line\"\n\n"); - print_cli_short_version(stdout, tab_indent); + G__md_print_cli_short_version(stdout, tab_indent); fprintf(stdout, "\n=== \"Python (grass.script)\"\n\n"); - print_python_short_version(stdout, tab_indent); + G__md_print_python_short_version(stdout, tab_indent); fprintf(stdout, "\n## %s\n", _("Parameters")); /* long version */ fprintf(stdout, "\n=== \"Command line\"\n\n"); - print_cli_long_version(tab_indent); + G__md_print_cli_long_version(stdout, tab_indent); fprintf(stdout, "\n=== \"Python (grass.script)\"\n\n"); - print_python_long_version(tab_indent); -} - -void print_cli_flag(const char *key, const char *label, const char *description, - const char *indent) -{ - fprintf(stdout, "%s**", indent); - if (strlen(key) > 1) - fprintf(stdout, "-"); - fprintf(stdout, "-%s**", key); - fprintf(stdout, MD_NEWLINE); - fprintf(stdout, "\n"); - if (label != NULL) { - fprintf(stdout, "%s", indent); - print_escaped(stdout, "\t"); - print_escaped(stdout, label); - fprintf(stdout, MD_NEWLINE); - fprintf(stdout, "\n"); - } - if (description != NULL) { - fprintf(stdout, "%s", indent); - print_escaped(stdout, "\t"); - print_escaped(stdout, description); - } -} - -void print_python_short_flag(const char *key, const char *label, - const char *description, const char *indent) -{ - fprintf(stdout, "%s", indent); - print_escaped(stdout, "\t"); - fprintf(stdout, "**%s**", key); - fprintf(stdout, MD_NEWLINE); - fprintf(stdout, "\n"); - if (label != NULL) { - fprintf(stdout, "%s", indent); - print_escaped(stdout, "\t\t"); - print_escaped(stdout, label); - fprintf(stdout, MD_NEWLINE); - fprintf(stdout, "\n"); - } - if (description != NULL) { - fprintf(stdout, "%s", indent); - print_escaped(stdout, "\t\t"); - print_escaped(stdout, description); - } -} - -void print_python_long_flag(const char *key, const char *label, - const char *description, const char *indent) -{ - fprintf(stdout, "%s**%s**: bool, default False", indent, key); - fprintf(stdout, MD_NEWLINE); - fprintf(stdout, "\n"); - if (label != NULL) { - fprintf(stdout, "%s", indent); - print_escaped(stdout, "\t"); - print_escaped(stdout, label); - fprintf(stdout, MD_NEWLINE); - fprintf(stdout, "\n"); - } - if (description != NULL) { - fprintf(stdout, "%s", indent); - print_escaped(stdout, "\t"); - print_escaped(stdout, description); - } -} - -void print_cli_option(const struct Option *opt, const char *indent) -{ - const char *type; - - /* TODO: make this a enumeration type? */ - if (opt->key_desc != NULL) - type = opt->key_desc; - else - switch (opt->type) { - case TYPE_INTEGER: - type = "integer"; - break; - case TYPE_DOUBLE: - type = "float"; - break; - case TYPE_STRING: - type = "string"; - break; - default: - type = "string"; - break; - } - fprintf(stdout, "%s**%s**=", indent, opt->key); - fprintf(stdout, "*%s*", type); - if (opt->multiple) { - fprintf(stdout, " [,"); - fprintf(stdout, "*%s*,...]", type); - } - /* fprintf(stdout, "*"); */ - if (opt->required) { - fprintf(stdout, " **[required]**"); - } - fprintf(stdout, MD_NEWLINE); - fprintf(stdout, "\n"); - if (opt->label) { - fprintf(stdout, "%s", indent); - print_escaped(stdout, "\t"); - print_escaped(stdout, opt->label); - } - if (opt->description) { - if (opt->label) { - fprintf(stdout, MD_NEWLINE); - fprintf(stdout, "\n"); - } - fprintf(stdout, "%s", indent); - print_escaped(stdout, "\t"); - print_escaped(stdout, opt->description); - } - - if (opt->options) { - fprintf(stdout, MD_NEWLINE); - fprintf(stdout, "\n"); - fprintf(stdout, "%s", indent); - print_escaped(stdout, "\t"); - fprintf(stdout, "%s: *", _("Options")); - print_escaped_for_md_options(stdout, opt->options); - fprintf(stdout, "*"); - } - - if (opt->def) { - fprintf(stdout, MD_NEWLINE); - fprintf(stdout, "\n"); - fprintf(stdout, "%s", indent); - print_escaped(stdout, "\t"); - fprintf(stdout, "%s:", _("Default")); - /* TODO check if value is empty - if (!opt->def.empty()){ */ - fprintf(stdout, " *"); - print_escaped(stdout, opt->def); - fprintf(stdout, "*"); - } - - if (opt->descs) { - int i = 0; - - while (opt->opts[i]) { - if (opt->descs[i]) { - fprintf(stdout, MD_NEWLINE); - fprintf(stdout, "\n"); - fprintf(stdout, "%s", indent); - char *thumbnails = NULL; - if (opt->gisprompt) { - if (strcmp(opt->gisprompt, "old,colortable,colortable") == - 0) - thumbnails = "colortables"; - else if (strcmp(opt->gisprompt, "old,barscale,barscale") == - 0) - thumbnails = "barscales"; - else if (strcmp(opt->gisprompt, - "old,northarrow,northarrow") == 0) - thumbnails = "northarrows"; - - if (thumbnails) { - print_escaped(stdout, "\t\t"); - fprintf(stdout, "![%s](%s/%s.png) ", opt->opts[i], - thumbnails, opt->opts[i]); - } - else { - print_escaped(stdout, "\t\t"); - } - } - print_escaped(stdout, "\t"); - fprintf(stdout, "**"); - print_escaped(stdout, opt->opts[i]); - fprintf(stdout, "**: "); - print_escaped(stdout, opt->descs[i]); - } - i++; - } - } -} - -void print_python_option(const struct Option *opt, const char *indent) -{ - const char *type; - - switch (opt->type) { - case TYPE_INTEGER: - type = "int"; - break; - case TYPE_DOUBLE: - type = "float"; - break; - case TYPE_STRING: - type = "str"; - break; - default: - type = "str"; - break; - } - fprintf(stdout, "%s**%s** : ", indent, opt->key); - if (opt->multiple) { - fprintf(stdout, "Iterable, list[%s]", type); - } - else { - fprintf(stdout, "%s", type); - } - if (opt->key_desc) { - fprintf(stdout, ", `%s`", opt->key_desc); - } - /* fprintf(stdout, "*"); */ - if (opt->required) { - fprintf(stdout, ", required"); - } - else { - fprintf(stdout, ", optional"); - } - - fprintf(stdout, MD_NEWLINE); - fprintf(stdout, "\n"); - if (opt->label) { - fprintf(stdout, "%s", indent); - print_escaped(stdout, "\t"); - print_escaped(stdout, opt->label); - } - if (opt->description) { - if (opt->label) { - fprintf(stdout, MD_NEWLINE); - fprintf(stdout, "\n"); - } - fprintf(stdout, "%s", indent); - print_escaped(stdout, "\t"); - print_escaped(stdout, opt->description); - } - - if (opt->options) { - fprintf(stdout, MD_NEWLINE); - fprintf(stdout, "\n"); - fprintf(stdout, "%s", indent); - print_escaped(stdout, "\t"); - fprintf(stdout, "%s: *", _("Allowed values")); - print_escaped_for_md_options(stdout, opt->options); - fprintf(stdout, "*"); - } - - if (opt->def) { - fprintf(stdout, MD_NEWLINE); - fprintf(stdout, "\n"); - fprintf(stdout, "%s", indent); - print_escaped(stdout, "\t"); - fprintf(stdout, "%s:", _("Default")); - /* TODO check if value is empty - if (!opt->def.empty()){ */ - fprintf(stdout, " *"); - print_escaped(stdout, opt->def); - fprintf(stdout, "*"); - } - - if (opt->descs) { - int i = 0; - - while (opt->opts[i]) { - if (opt->descs[i]) { - fprintf(stdout, MD_NEWLINE); - fprintf(stdout, "\n"); - fprintf(stdout, "%s", indent); - char *thumbnails = NULL; - if (opt->gisprompt) { - if (strcmp(opt->gisprompt, "old,colortable,colortable") == - 0) - thumbnails = "colortables"; - else if (strcmp(opt->gisprompt, "old,barscale,barscale") == - 0) - thumbnails = "barscales"; - else if (strcmp(opt->gisprompt, - "old,northarrow,northarrow") == 0) - thumbnails = "northarrows"; - - if (thumbnails) { - print_escaped(stdout, "\t\t"); - fprintf(stdout, "![%s](%s/%s.png) ", opt->opts[i], - thumbnails, opt->opts[i]); - } - else { - print_escaped(stdout, "\t\t"); - } - } - print_escaped(stdout, "\t"); - fprintf(stdout, "**"); - print_escaped(stdout, opt->opts[i]); - fprintf(stdout, "**: "); - print_escaped(stdout, opt->descs[i]); - } - i++; - } - } -} - -/*! - * \brief Format text for Markdown output - */ -#define do_escape(c, escaped) \ - case c: \ - fputs(escaped, f); \ - break - -void print_escaped(FILE *f, const char *str) -{ - print_escaped_for_md(f, str); -} - -void print_escaped_for_md(FILE *f, const char *str) -{ - const char *s; - - for (s = str; *s; s++) { - switch (*s) { - do_escape('\n', "\\\n"); - do_escape('\t', "    "); - do_escape('<', "<"); - do_escape('>', ">"); - do_escape('*', "\\*"); - default: - fputc(*s, f); - } - } + G__md_print_python_long_version(stdout, tab_indent); } - -void print_escaped_for_md_options(FILE *f, const char *str) -{ - const char *s; - - for (s = str; *s; s++) { - switch (*s) { - do_escape('\n', "\n\n"); - do_escape(',', ", "); - default: - fputc(*s, f); - } - } -} - -void print_cli_short_version(FILE *file, const char *indent) -{ - struct Option *opt; - struct Flag *flag; - const char *type; - int new_prompt = 0; - - new_prompt = G__uses_new_gisprompt(); - - fprintf(file, "%s**%s**", indent, st->pgm_name); - fprintf(file, "\n"); - - /* print short version first */ - if (st->n_flags) { - flag = &st->first_flag; - fprintf(file, "%s[**-", indent); - while (flag != NULL) { - fprintf(file, "%c", flag->key); - flag = flag->next_flag; - } - fprintf(file, "**]"); - } - fprintf(file, "\n"); - - if (st->n_opts) { - opt = &st->first_option; - - while (opt != NULL) { - if (opt->key_desc != NULL) - type = opt->key_desc; - else - switch (opt->type) { - case TYPE_INTEGER: - type = "integer"; - break; - case TYPE_DOUBLE: - type = "float"; - break; - case TYPE_STRING: - type = "string"; - break; - default: - type = "string"; - break; - } - fprintf(file, "%s", indent); - if (!opt->required) - fprintf(file, "["); - fprintf(file, "**%s**=", opt->key); - fprintf(file, "*%s*", type); - if (opt->multiple) { - fprintf(file, " [,"); - fprintf(file, "*%s*,...]", type); - } - if (!opt->required) - fprintf(file, "]"); - fprintf(file, "\n"); - - opt = opt->next_opt; - } - } - if (new_prompt) - fprintf(file, "%s[**--overwrite**]\n", indent); - - fprintf(file, "%s[**--verbose**]\n", indent); - fprintf(file, "%s[**--quiet**]\n", indent); - fprintf(file, "%s[**--ui**]\n", indent); -} - -void print_python_short_version(FILE *file, const char *indent) -{ - struct Option *opt; - struct Flag *flag; - const char *type; - int new_prompt = 0; - bool output_format_option = false; - const char *output_format_default = NULL; - bool shell_eval_flag = false; - const char *python_function = NULL; - - new_prompt = G__uses_new_gisprompt(); - - if (st->n_opts) { - opt = &st->first_option; - while (opt != NULL) { - if (strcmp(opt->key, "format") == 0) { - if (opt->options) { - int i = 0; - while (opt->opts[i]) { - if (strcmp(opt->opts[i], "csv") == 0) - output_format_default = "csv"; - if (strcmp(opt->opts[i], "json") == 0) { - output_format_default = "json"; - break; - } - i++; - } - } - if (output_format_default) { - output_format_option = true; - } - break; - } - opt = opt->next_opt; - } - } - if (st->n_flags) { - flag = &st->first_flag; - while (st->n_flags && flag != NULL) { - if (flag->key == 'g') { - shell_eval_flag = true; - break; - } - flag = flag->next_flag; - } - } - if (output_format_option || (!new_prompt && shell_eval_flag)) { - python_function = "parse_command"; - // We know this is can be parsed, but we can't detect just plain stdout - // because we can't distinguish between plain text outputs and - // modifications of data. - } - else { - python_function = "run_command"; - } - fprintf(file, "%s*grass.script.%s*(\"***%s***\",", indent, python_function, - st->pgm_name); - fprintf(file, "\n"); - - if (st->n_opts) { - opt = &st->first_option; - - while (opt != NULL) { - fprintf(file, "%s ", indent); - if (!opt->required && !opt->answer) { - fprintf(file, "**%s**=*None*", opt->key); - } - else { - fprintf(file, "**%s**", opt->key); - } - if (opt->answer) { - fprintf(file, "="); - if (opt->type == TYPE_INTEGER || opt->type == TYPE_DOUBLE) { - fprintf(file, "*"); - print_escaped(file, opt->answer); - fprintf(file, "*"); - } - else { - fprintf(file, "*\""); - print_escaped(file, opt->answer); - fprintf(file, "\"*"); - } - } - fprintf(file, ",\n"); - - opt = opt->next_opt; - } - } - - if (st->n_flags) { - flag = &st->first_flag; - fprintf(file, "%s **flags**=*None*,\n", indent); - } - - if (new_prompt) - fprintf(file, "%s **overwrite**=*False*,\n", indent); - - fprintf(file, "%s **verbose**=*False*,\n", indent); - fprintf(file, "%s **quiet**=*False*,\n", indent); - fprintf(file, "%s **superquiet**=*False*)\n", indent); - - fprintf(file, "\n%sExample:\n", indent); - - fprintf(file, "\n%s```python\n", indent); - fprintf(file, "%sgs.%s(\"%s\"", indent, python_function, st->pgm_name); - - const struct Option *first_required_rule_option = - G__first_required_option_from_rules(); - - if (st->n_opts) { - opt = &st->first_option; - - while (opt != NULL) { - if (opt->key_desc != NULL) - type = opt->key_desc; - else - switch (opt->type) { - case TYPE_INTEGER: - type = "integer"; - break; - case TYPE_DOUBLE: - type = "float"; - break; - case TYPE_STRING: - type = "string"; - break; - default: - type = "string"; - break; - } - if (opt->required || first_required_rule_option == opt) { - fprintf(file, ", %s=", opt->key); - if (output_format_default && strcmp(opt->key, "format") == 0) { - fprintf(file, "\"%s\"", output_format_default); - } - else if (opt->answer) { - if (opt->type == TYPE_INTEGER || opt->type == TYPE_DOUBLE) { - fprintf(file, "%s", opt->answer); - } - else { - fprintf(file, "\"%s\"", opt->answer); - } - } - else { - if (opt->type == TYPE_INTEGER || opt->type == TYPE_DOUBLE) { - fprintf(file, "%s", type); - } - else { - fprintf(file, "\"%s\"", type); - } - } - } - opt = opt->next_opt; - } - } - fprintf(file, ")\n%s```\n", indent); -} - -void print_cli_long_version(const char *indent) -{ - struct Option *opt; - struct Flag *flag; - int new_prompt = 0; - - new_prompt = G__uses_new_gisprompt(); - - // Options (key-value parameters) - if (st->n_opts) { - opt = &st->first_option; - while (opt != NULL) { - print_cli_option(opt, indent); - opt = opt->next_opt; - if (opt != NULL) { - fprintf(stdout, MD_NEWLINE); - } - fprintf(stdout, "\n"); - } - } - - // Short (one-letter) flags and tool-specific long flags - if (st->n_flags || new_prompt) { - flag = &st->first_flag; - while (st->n_flags && flag != NULL) { - print_cli_flag(&flag->key, flag->label, flag->description, indent); - fprintf(stdout, MD_NEWLINE); - fprintf(stdout, "\n"); - flag = flag->next_flag; - } - if (new_prompt) { - print_cli_flag("overwrite", NULL, - _("Allow output files to overwrite existing files"), - indent); - fprintf(stdout, MD_NEWLINE); - fprintf(stdout, "\n"); - } - } - // Pre-defined long flags - print_cli_flag("help", NULL, _("Print usage summary"), indent); - fprintf(stdout, MD_NEWLINE); - fprintf(stdout, "\n"); - print_cli_flag("verbose", NULL, _("Verbose module output"), indent); - fprintf(stdout, MD_NEWLINE); - fprintf(stdout, "\n"); - print_cli_flag("quiet", NULL, _("Quiet module output"), indent); - fprintf(stdout, MD_NEWLINE); - fprintf(stdout, "\n"); - print_cli_flag("ui", NULL, _("Force launching GUI dialog"), indent); - fprintf(stdout, "\n"); -} - -void print_python_long_version(const char *indent) -{ - struct Option *opt; - struct Flag *flag; - int new_prompt = 0; - - new_prompt = G__uses_new_gisprompt(); - - // Options (key-value parameters) - if (st->n_opts) { - opt = &st->first_option; - while (opt != NULL) { - print_python_option(opt, indent); - opt = opt->next_opt; - fprintf(stdout, MD_NEWLINE); - fprintf(stdout, "\n"); - } - } - - // Short (one-letter) flags and tool-specific long flags - if (st->n_flags) { - fprintf(stdout, "%s**flags** : str", indent); - fprintf(stdout, MD_NEWLINE); - fprintf(stdout, "\n"); - fprintf(stdout, "%s", indent); - print_escaped(stdout, "\t"); - fprintf(stdout, "Allowed values: "); - flag = &st->first_flag; - while (st->n_flags && flag != NULL) { - fprintf(stdout, "*%s*, ", &flag->key); - flag = flag->next_flag; - } - fprintf(stdout, MD_NEWLINE); - fprintf(stdout, "\n"); - flag = &st->first_flag; - while (st->n_flags && flag != NULL) { - print_python_short_flag(&flag->key, flag->label, flag->description, - indent); - fprintf(stdout, MD_NEWLINE); - fprintf(stdout, "\n"); - flag = flag->next_flag; - } - } - if (new_prompt) { - print_python_long_flag( - "overwrite", NULL, - _("Allow output files to overwrite existing files"), indent); - fprintf(stdout, MD_NEWLINE); - fprintf(stdout, "\n"); - } - // Pre-defined long flags - print_python_long_flag("verbose", NULL, _("Verbose module output"), indent); - fprintf(stdout, MD_NEWLINE); - fprintf(stdout, "\n"); - print_python_long_flag("quiet", NULL, _("Quiet module output"), indent); - fprintf(stdout, MD_NEWLINE); - fprintf(stdout, "\n"); - print_python_long_flag("superquiet", NULL, _("Very quiet module output"), - indent); - fprintf(stdout, MD_NEWLINE); - fprintf(stdout, "\n"); -} - -#undef do_escape diff --git a/lib/gis/parser_md_cli.c b/lib/gis/parser_md_cli.c new file mode 100644 index 00000000000..3f4968855c6 --- /dev/null +++ b/lib/gis/parser_md_cli.c @@ -0,0 +1,334 @@ +/*! + \file lib/gis/parser_md_cli.c + + \brief GIS Library - Argument parsing functions (Markdown output - CLI) + + (C) 2025 by the GRASS Development Team + + This program is free software under the GNU General Public License + (>=v2). Read the file COPYING that comes with GRASS for details. + + \author Vaclav Petras + */ +#include +#include + +#include +#include + +#include "parser_local_proto.h" + +static void print_cli_flag(FILE *file, const char *key, const char *label, + const char *description, const char *indent); +static void print_cli_option(FILE *file, const struct Option *opt, + const char *indent); +static void print_cli_example(FILE *file, const char *indent); + +void print_cli_flag(FILE *file, const char *key, const char *label, + const char *description, const char *indent) +{ + fprintf(file, "%s**", indent); + if (strlen(key) > 1) + fprintf(file, "-"); + fprintf(file, "-%s**", key); + fprintf(file, MD_NEWLINE); + fprintf(file, "\n"); + if (label != NULL) { + fprintf(file, "%s", indent); + G__md_print_escaped(file, "\t"); + G__md_print_escaped(file, label); + fprintf(file, MD_NEWLINE); + fprintf(file, "\n"); + } + if (description != NULL) { + fprintf(file, "%s", indent); + G__md_print_escaped(file, "\t"); + G__md_print_escaped(file, description); + } +} + +void print_cli_option(FILE *file, const struct Option *opt, const char *indent) +{ + const char *type; + + if (opt->key_desc != NULL) + type = opt->key_desc; + else + switch (opt->type) { + case TYPE_INTEGER: + type = "integer"; + break; + case TYPE_DOUBLE: + type = "float"; + break; + case TYPE_STRING: + type = "string"; + break; + default: + type = "string"; + break; + } + fprintf(file, "%s**%s**=", indent, opt->key); + fprintf(file, "*%s*", type); + if (opt->multiple) { + fprintf(file, " [,"); + fprintf(file, "*%s*,...]", type); + } + /* fprintf(file, "*"); */ + if (opt->required) { + fprintf(file, " **[required]**"); + } + fprintf(file, MD_NEWLINE); + fprintf(file, "\n"); + if (opt->label) { + fprintf(file, "%s", indent); + G__md_print_escaped(file, "\t"); + G__md_print_escaped(file, opt->label); + } + if (opt->description) { + if (opt->label) { + fprintf(file, MD_NEWLINE); + fprintf(file, "\n"); + } + fprintf(file, "%s", indent); + G__md_print_escaped(file, "\t"); + G__md_print_escaped(file, opt->description); + } + + if (opt->options) { + fprintf(file, MD_NEWLINE); + fprintf(file, "\n"); + fprintf(file, "%s", indent); + G__md_print_escaped(file, "\t"); + fprintf(file, "%s: *", _("Allowed values")); + G__md_print_escaped_for_options(file, opt->options); + fprintf(file, "*"); + } + + if (opt->def) { + fprintf(file, MD_NEWLINE); + fprintf(file, "\n"); + fprintf(file, "%s", indent); + G__md_print_escaped(file, "\t"); + fprintf(file, "%s:", _("Default")); + /* TODO check if value is empty + if (!opt->def.empty()){ */ + fprintf(file, " *"); + G__md_print_escaped(file, opt->def); + fprintf(file, "*"); + } + + if (opt->descs) { + int i = 0; + + while (opt->opts[i]) { + if (opt->descs[i]) { + fprintf(file, MD_NEWLINE); + fprintf(file, "\n"); + fprintf(file, "%s", indent); + char *thumbnails = NULL; + if (opt->gisprompt) { + if (strcmp(opt->gisprompt, "old,colortable,colortable") == + 0) + thumbnails = "colortables"; + else if (strcmp(opt->gisprompt, "old,barscale,barscale") == + 0) + thumbnails = "barscales"; + else if (strcmp(opt->gisprompt, + "old,northarrow,northarrow") == 0) + thumbnails = "northarrows"; + + if (thumbnails) { + G__md_print_escaped(file, "\t\t"); + fprintf(file, "![%s](%s/%s.png) ", opt->opts[i], + thumbnails, opt->opts[i]); + } + else { + G__md_print_escaped(file, "\t\t"); + } + } + G__md_print_escaped(file, "\t"); + fprintf(file, "**"); + G__md_print_escaped(file, opt->opts[i]); + fprintf(file, "**: "); + G__md_print_escaped(file, opt->descs[i]); + } + i++; + } + } +} + +void print_cli_example(FILE *file, const char *indent) +{ + fprintf(file, "\n%sExample:\n", indent); + + fprintf(file, "\n%s```sh\n", indent); + fprintf(file, "%s%s", indent, st->pgm_name); + + const struct Option *first_required_rule_option = + G__first_required_option_from_rules(); + const struct Option *opt = NULL; + const char *type; + + if (st->n_opts) { + opt = &st->first_option; + + while (opt != NULL) { + if (opt->key_desc != NULL) + type = opt->key_desc; + else + switch (opt->type) { + case TYPE_INTEGER: + type = "integer"; + break; + case TYPE_DOUBLE: + type = "float"; + break; + case TYPE_STRING: + type = "string"; + break; + default: + type = "string"; + break; + } + if (opt->required || first_required_rule_option == opt) { + fprintf(file, " "); + fprintf(file, "%s=", opt->key); + if (opt->answer) { + fprintf(file, "%s", opt->answer); + } + else { + fprintf(file, "%s", type); + } + } + opt = opt->next_opt; + } + } + fprintf(file, "\n%s```\n", indent); +} + +void G__md_print_cli_short_version(FILE *file, const char *indent) +{ + struct Option *opt; + struct Flag *flag; + const char *type; + int new_prompt = 0; + + new_prompt = G__uses_new_gisprompt(); + + fprintf(file, "%s**%s**", indent, st->pgm_name); + fprintf(file, "\n"); + + /* print short version first */ + if (st->n_flags) { + flag = &st->first_flag; + fprintf(file, "%s[**-", indent); + while (flag != NULL) { + fprintf(file, "%c", flag->key); + flag = flag->next_flag; + } + fprintf(file, "**]"); + fprintf(file, "\n"); + } + + if (st->n_opts) { + opt = &st->first_option; + + while (opt != NULL) { + if (opt->key_desc != NULL) + type = opt->key_desc; + else + switch (opt->type) { + case TYPE_INTEGER: + type = "integer"; + break; + case TYPE_DOUBLE: + type = "float"; + break; + case TYPE_STRING: + type = "string"; + break; + default: + type = "string"; + break; + } + fprintf(file, "%s", indent); + if (!opt->required) + fprintf(file, "["); + fprintf(file, "**%s**=", opt->key); + fprintf(file, "*%s*", type); + if (opt->multiple) { + fprintf(file, " [,"); + fprintf(file, "*%s*,...]", type); + } + if (!opt->required) + fprintf(file, "]"); + fprintf(file, "\n"); + + opt = opt->next_opt; + } + } + if (new_prompt) + fprintf(file, "%s[**--overwrite**]\n", indent); + + fprintf(file, "%s[**--verbose**]\n", indent); + fprintf(file, "%s[**--quiet**]\n", indent); + fprintf(file, "%s[**--qq**]\n", indent); + fprintf(file, "%s[**--ui**]\n", indent); + + print_cli_example(file, indent); +} + +void G__md_print_cli_long_version(FILE *file, const char *indent) +{ + struct Option *opt; + struct Flag *flag; + int new_prompt = 0; + + new_prompt = G__uses_new_gisprompt(); + + // Options (key-value parameters) + if (st->n_opts) { + opt = &st->first_option; + while (opt != NULL) { + print_cli_option(file, opt, indent); + opt = opt->next_opt; + fprintf(file, MD_NEWLINE); + fprintf(file, "\n"); + } + } + + // Short (one-letter) flags and tool-specific long flags + if (st->n_flags || new_prompt) { + flag = &st->first_flag; + while (st->n_flags && flag != NULL) { + print_cli_flag(file, &flag->key, flag->label, flag->description, + indent); + fprintf(file, MD_NEWLINE); + fprintf(file, "\n"); + flag = flag->next_flag; + } + if (new_prompt) { + print_cli_flag(file, "overwrite", NULL, + _("Allow output files to overwrite existing files"), + indent); + fprintf(file, MD_NEWLINE); + fprintf(file, "\n"); + } + } + // Pre-defined long flags + print_cli_flag(file, "help", NULL, _("Print usage summary"), indent); + fprintf(file, MD_NEWLINE); + fprintf(file, "\n"); + print_cli_flag(file, "verbose", NULL, _("Verbose module output"), indent); + fprintf(file, MD_NEWLINE); + fprintf(file, "\n"); + print_cli_flag(file, "quiet", NULL, _("Quiet module output"), indent); + fprintf(file, MD_NEWLINE); + fprintf(file, "\n"); + print_cli_flag(file, "qq", NULL, _("Very quiet module output"), indent); + fprintf(file, MD_NEWLINE); + fprintf(file, "\n"); + print_cli_flag(file, "ui", NULL, _("Force launching GUI dialog"), indent); + fprintf(file, "\n"); +} diff --git a/lib/gis/parser_md_common.c b/lib/gis/parser_md_common.c new file mode 100644 index 00000000000..49a8c97f234 --- /dev/null +++ b/lib/gis/parser_md_common.c @@ -0,0 +1,60 @@ +/*! + \file lib/gis/parser_md_common.c + + \brief GIS Library - Argument parsing functions (Markdown output) + + (C) 2023-2025 by the GRASS Development Team + + This program is free software under the GNU General Public License + (>=v2). Read the file COPYING that comes with GRASS for details. + + \author Martin Landa + */ +#include +#include + +#include +#include + +#include "parser_local_proto.h" + +/*! + * \brief Format text for Markdown output + */ +#define do_escape(c, escaped) \ + case c: \ + fputs(escaped, f); \ + break + +void G__md_print_escaped(FILE *f, const char *str) +{ + const char *s; + + for (s = str; *s; s++) { + switch (*s) { + do_escape('\n', "\\\n"); + do_escape('\t', "    "); + do_escape('<', "<"); + do_escape('>', ">"); + do_escape('*', "\\*"); + default: + fputc(*s, f); + } + } +} + +void G__md_print_escaped_for_options(FILE *f, const char *str) +{ + const char *s; + + for (s = str; *s; s++) { + switch (*s) { + do_escape('\n', "\n\n"); + do_escape(',', ", "); + default: + fputc(*s, f); + } + } +} + +#undef do_escape diff --git a/lib/gis/parser_md_python.c b/lib/gis/parser_md_python.c new file mode 100644 index 00000000000..5dccf231174 --- /dev/null +++ b/lib/gis/parser_md_python.c @@ -0,0 +1,421 @@ +/*! + \file lib/gis/parser_md_python.c + + \brief GIS Library - Argument parsing functions (Markdown output - Python) + + (C) 2025 by the GRASS Development Team + + This program is free software under the GNU General Public License + (>=v2). Read the file COPYING that comes with GRASS for details. + + \author Vaclav Petras + */ +#include +#include + +#include +#include + +#include "parser_local_proto.h" + +static void print_python_short_flag(FILE *file, const char *key, + const char *label, const char *description, + const char *indent); +static void print_python_long_flag(FILE *file, const char *key, + const char *label, const char *description, + const char *indent); +static void print_python_option(FILE *file, const struct Option *opt, + const char *indent); +static void print_python_example(FILE *file, const char *python_function, + const char *output_format_default, + const char *indent); + +void print_python_short_flag(FILE *file, const char *key, const char *label, + const char *description, const char *indent) +{ + fprintf(file, "%s", indent); + G__md_print_escaped(file, "\t"); + fprintf(file, "**%s**", key); + fprintf(file, MD_NEWLINE); + fprintf(file, "\n"); + if (label != NULL) { + fprintf(file, "%s", indent); + G__md_print_escaped(file, "\t\t"); + G__md_print_escaped(file, label); + fprintf(file, MD_NEWLINE); + fprintf(file, "\n"); + } + if (description != NULL) { + fprintf(file, "%s", indent); + G__md_print_escaped(file, "\t\t"); + G__md_print_escaped(file, description); + } +} + +void print_python_long_flag(FILE *file, const char *key, const char *label, + const char *description, const char *indent) +{ + fprintf(file, "%s**%s**: bool, default False", indent, key); + fprintf(file, MD_NEWLINE); + fprintf(file, "\n"); + if (label != NULL) { + fprintf(file, "%s", indent); + G__md_print_escaped(file, "\t"); + G__md_print_escaped(file, label); + fprintf(file, MD_NEWLINE); + fprintf(file, "\n"); + } + if (description != NULL) { + fprintf(file, "%s", indent); + G__md_print_escaped(file, "\t"); + G__md_print_escaped(file, description); + } +} + +void print_python_option(FILE *file, const struct Option *opt, + const char *indent) +{ + const char *type; + + switch (opt->type) { + case TYPE_INTEGER: + type = "int"; + break; + case TYPE_DOUBLE: + type = "float"; + break; + case TYPE_STRING: + type = "str"; + break; + default: + type = "str"; + break; + } + fprintf(file, "%s**%s** : ", indent, opt->key); + if (opt->multiple) { + fprintf(file, "%s, list[%s]", type, type); + } + else { + fprintf(file, "%s", type); + } + if (opt->key_desc) { + fprintf(file, ", `%s`", opt->key_desc); + } + /* fprintf(file, "*"); */ + if (opt->required) { + fprintf(file, ", required"); + } + else { + fprintf(file, ", optional"); + } + + fprintf(file, MD_NEWLINE); + fprintf(file, "\n"); + if (opt->label) { + fprintf(file, "%s", indent); + G__md_print_escaped(file, "\t"); + G__md_print_escaped(file, opt->label); + } + if (opt->description) { + if (opt->label) { + fprintf(file, MD_NEWLINE); + fprintf(file, "\n"); + } + fprintf(file, "%s", indent); + G__md_print_escaped(file, "\t"); + G__md_print_escaped(file, opt->description); + } + + if (opt->options) { + fprintf(file, MD_NEWLINE); + fprintf(file, "\n"); + fprintf(file, "%s", indent); + G__md_print_escaped(file, "\t"); + fprintf(file, "%s: *", _("Allowed values")); + G__md_print_escaped_for_options(file, opt->options); + fprintf(file, "*"); + } + + if (opt->def) { + fprintf(file, MD_NEWLINE); + fprintf(file, "\n"); + fprintf(file, "%s", indent); + G__md_print_escaped(file, "\t"); + fprintf(file, "%s:", _("Default")); + /* TODO check if value is empty + if (!opt->def.empty()){ */ + fprintf(file, " *"); + G__md_print_escaped(file, opt->def); + fprintf(file, "*"); + } + + if (opt->descs) { + int i = 0; + + while (opt->opts[i]) { + if (opt->descs[i]) { + fprintf(file, MD_NEWLINE); + fprintf(file, "\n"); + fprintf(file, "%s", indent); + char *thumbnails = NULL; + if (opt->gisprompt) { + if (strcmp(opt->gisprompt, "old,colortable,colortable") == + 0) + thumbnails = "colortables"; + else if (strcmp(opt->gisprompt, "old,barscale,barscale") == + 0) + thumbnails = "barscales"; + else if (strcmp(opt->gisprompt, + "old,northarrow,northarrow") == 0) + thumbnails = "northarrows"; + + if (thumbnails) { + G__md_print_escaped(file, "\t\t"); + fprintf(file, "![%s](%s/%s.png) ", opt->opts[i], + thumbnails, opt->opts[i]); + } + else { + G__md_print_escaped(file, "\t\t"); + } + } + G__md_print_escaped(file, "\t"); + fprintf(file, "**"); + G__md_print_escaped(file, opt->opts[i]); + fprintf(file, "**: "); + G__md_print_escaped(file, opt->descs[i]); + } + i++; + } + } +} + +void print_python_example(FILE *file, const char *python_function, + const char *output_format_default, const char *indent) +{ + fprintf(file, "\n%sExample:\n", indent); + + fprintf(file, "\n%s```python\n", indent); + fprintf(file, "%sgs.%s(\"%s\"", indent, python_function, st->pgm_name); + + const struct Option *first_required_rule_option = + G__first_required_option_from_rules(); + const struct Option *opt = NULL; + const char *type; + + if (st->n_opts) { + opt = &st->first_option; + + while (opt != NULL) { + if (opt->key_desc != NULL) + type = opt->key_desc; + else + switch (opt->type) { + case TYPE_INTEGER: + type = "integer"; + break; + case TYPE_DOUBLE: + type = "float"; + break; + case TYPE_STRING: + type = "string"; + break; + default: + type = "string"; + break; + } + if (opt->required || first_required_rule_option == opt) { + fprintf(file, ", %s=", opt->key); + if (output_format_default && strcmp(opt->key, "format") == 0) { + fprintf(file, "\"%s\"", output_format_default); + } + else if (opt->answer) { + if (opt->type == TYPE_INTEGER || opt->type == TYPE_DOUBLE) { + fprintf(file, "%s", opt->answer); + } + else { + fprintf(file, "\"%s\"", opt->answer); + } + } + else { + if (opt->type == TYPE_INTEGER || opt->type == TYPE_DOUBLE) { + fprintf(file, "%s", type); + } + else { + fprintf(file, "\"%s\"", type); + } + } + } + opt = opt->next_opt; + } + } + fprintf(file, ")\n%s```\n", indent); +} + +void G__md_print_python_short_version(FILE *file, const char *indent) +{ + struct Option *opt; + struct Flag *flag; + int new_prompt = 0; + bool output_format_option = false; + const char *output_format_default = NULL; + bool shell_eval_flag = false; + const char *python_function = NULL; + + new_prompt = G__uses_new_gisprompt(); + + if (st->n_opts) { + opt = &st->first_option; + while (opt != NULL) { + if (strcmp(opt->key, "format") == 0) { + if (opt->options) { + int i = 0; + while (opt->opts[i]) { + if (strcmp(opt->opts[i], "csv") == 0) + output_format_default = "csv"; + if (strcmp(opt->opts[i], "json") == 0) { + output_format_default = "json"; + break; + } + i++; + } + } + if (output_format_default) { + output_format_option = true; + } + break; + } + opt = opt->next_opt; + } + } + if (st->n_flags) { + flag = &st->first_flag; + while (st->n_flags && flag != NULL) { + if (flag->key == 'g') { + shell_eval_flag = true; + break; + } + flag = flag->next_flag; + } + } + if (output_format_option || (!new_prompt && shell_eval_flag)) { + python_function = "parse_command"; + // We know this is can be parsed, but we can't detect just plain file + // because we can't distinguish between plain text outputs and + // modifications of data. + } + else { + python_function = "run_command"; + } + fprintf(file, "%s*grass.script.%s*(\"***%s***\",", indent, python_function, + st->pgm_name); + fprintf(file, "\n"); + + if (st->n_opts) { + opt = &st->first_option; + + while (opt != NULL) { + fprintf(file, "%s ", indent); + if (!opt->required && !opt->answer) { + fprintf(file, "**%s**=*None*", opt->key); + } + else { + fprintf(file, "**%s**", opt->key); + } + if (opt->answer) { + fprintf(file, "="); + if (opt->type == TYPE_INTEGER || opt->type == TYPE_DOUBLE) { + fprintf(file, "*"); + G__md_print_escaped(file, opt->answer); + fprintf(file, "*"); + } + else { + fprintf(file, "*\""); + G__md_print_escaped(file, opt->answer); + fprintf(file, "\"*"); + } + } + fprintf(file, ",\n"); + + opt = opt->next_opt; + } + } + + if (st->n_flags) { + flag = &st->first_flag; + fprintf(file, "%s **flags**=*None*,\n", indent); + } + + if (new_prompt) + fprintf(file, "%s **overwrite**=*False*,\n", indent); + + fprintf(file, "%s **verbose**=*False*,\n", indent); + fprintf(file, "%s **quiet**=*False*,\n", indent); + fprintf(file, "%s **superquiet**=*False*)\n", indent); + + print_python_example(file, python_function, output_format_default, indent); +} + +void G__md_print_python_long_version(FILE *file, const char *indent) +{ + struct Option *opt; + struct Flag *flag; + int new_prompt = 0; + + new_prompt = G__uses_new_gisprompt(); + + // Options (key-value parameters) + if (st->n_opts) { + opt = &st->first_option; + while (opt != NULL) { + print_python_option(file, opt, indent); + opt = opt->next_opt; + fprintf(file, MD_NEWLINE); + fprintf(file, "\n"); + } + } + + // Short (one-letter) flags and tool-specific long flags + if (st->n_flags) { + fprintf(file, "%s**flags** : str", indent); + fprintf(file, MD_NEWLINE); + fprintf(file, "\n"); + fprintf(file, "%s", indent); + G__md_print_escaped(file, "\t"); + fprintf(file, "Allowed values: "); + flag = &st->first_flag; + while (st->n_flags && flag != NULL) { + fprintf(file, "*%s*, ", &flag->key); + flag = flag->next_flag; + } + fprintf(file, MD_NEWLINE); + fprintf(file, "\n"); + flag = &st->first_flag; + while (st->n_flags && flag != NULL) { + print_python_short_flag(file, &flag->key, flag->label, + flag->description, indent); + fprintf(file, MD_NEWLINE); + fprintf(file, "\n"); + flag = flag->next_flag; + } + } + if (new_prompt) { + print_python_long_flag( + file, "overwrite", NULL, + _("Allow output files to overwrite existing files"), indent); + fprintf(file, MD_NEWLINE); + fprintf(file, "\n"); + } + // Pre-defined long flags + print_python_long_flag(file, "verbose", NULL, _("Verbose module output"), + indent); + fprintf(file, MD_NEWLINE); + fprintf(file, "\n"); + print_python_long_flag(file, "quiet", NULL, _("Quiet module output"), + indent); + fprintf(file, MD_NEWLINE); + fprintf(file, "\n"); + print_python_long_flag(file, "superquiet", NULL, + _("Very quiet module output"), indent); + fprintf(file, MD_NEWLINE); + fprintf(file, "\n"); +} From edd761d6d3db8943de2650f141a14e089f4d71d2 Mon Sep 17 00:00:00 2001 From: Vaclav Petras Date: Fri, 4 Apr 2025 15:53:51 -0400 Subject: [PATCH 5/6] Use key_desc to determine if the type is tuple. Parser treats the parameter as tuple, so the basic type is actually wrong (e.g. string or iterable of ints rather than int). Even the synopsis for Python is wrong without this info as ints or floats can just be comma-separated without that info because default answer (values) will include multiple values for tuples. Now tuples in default values are represented as strings in synopsis (aka short version) which is the simpler solution, but I used the more complicated code only for the actual type in the parameters section (aka long version). The key_desc to tuple comes from parser.c code which is little different and requires a separate change. --- lib/gis/parser_local_proto.h | 1 + lib/gis/parser_md_common.c | 34 ++++++++++++++++++++++++++++++++++ lib/gis/parser_md_python.c | 23 +++++++++++++++++++++-- 3 files changed, 56 insertions(+), 2 deletions(-) diff --git a/lib/gis/parser_local_proto.h b/lib/gis/parser_local_proto.h index 96bae350956..aab43ea9f5e 100644 --- a/lib/gis/parser_local_proto.h +++ b/lib/gis/parser_local_proto.h @@ -63,6 +63,7 @@ void G__md_print_cli_long_version(FILE *file, const char *indent); void G__md_print_python_long_version(FILE *file, const char *indent); void G__md_print_escaped(FILE *f, const char *str); void G__md_print_escaped_for_options(FILE *f, const char *str); +int G__option_num_tuple_items(const struct Option *opt); void G__usage_text(void); void G__script(void); diff --git a/lib/gis/parser_md_common.c b/lib/gis/parser_md_common.c index 49a8c97f234..a26c50d76c9 100644 --- a/lib/gis/parser_md_common.c +++ b/lib/gis/parser_md_common.c @@ -58,3 +58,37 @@ void G__md_print_escaped_for_options(FILE *f, const char *str) } #undef do_escape + +// This can eventually go to a file with functions related to Option, +// but for now it is here until parser.c is refactored. +/** + * \brief Get number of tuple items if option is a tuple + * + * Note that parser code generally does not consider tuples with only one + * item, so this function never returns 1. + * + * The number of items is determined by counting commas in the option key + * description. + * + * \param opt Option definition + * \return Number of items or zero if not a tuple + */ +int G__option_num_tuple_items(const struct Option *opt) +{ + // If empty, it cannot be considered a tuple. + if (!opt->key_desc) + return 0; + + int n_items = 1; + const char *ptr; + + for (ptr = opt->key_desc; *ptr != '\0'; ptr++) + if (*ptr == ',') + n_items++; + + // Only one item is not considered a tuple. + if (n_items == 1) + return 0; + // Only two and more items are a tuple. + return n_items; +} diff --git a/lib/gis/parser_md_python.c b/lib/gis/parser_md_python.c index 5dccf231174..c248c06b85b 100644 --- a/lib/gis/parser_md_python.c +++ b/lib/gis/parser_md_python.c @@ -92,8 +92,25 @@ void print_python_option(FILE *file, const struct Option *opt, break; } fprintf(file, "%s**%s** : ", indent, opt->key); + int tuple_items = G__option_num_tuple_items(opt); if (opt->multiple) { - fprintf(file, "%s, list[%s]", type, type); + if (tuple_items) { + fprintf(file, "list[tuple[%s", type); + for (int i = 1; i < tuple_items; i++) { + fprintf(file, ", %s", type); + } + fprintf(file, "]], list[%s], str", type); + } + else { + fprintf(file, "%s, list[%s]", type, type); + } + } + else if (tuple_items) { + fprintf(file, "tuple[%s", type); + for (int i = 1; i < tuple_items; i++) { + fprintf(file, ", %s", type); + } + fprintf(file, "], list[%s], str", type); } else { fprintf(file, "%s", type); @@ -323,7 +340,9 @@ void G__md_print_python_short_version(FILE *file, const char *indent) } if (opt->answer) { fprintf(file, "="); - if (opt->type == TYPE_INTEGER || opt->type == TYPE_DOUBLE) { + int tuple_items = G__option_num_tuple_items(opt); + if (!tuple_items && + (opt->type == TYPE_INTEGER || opt->type == TYPE_DOUBLE)) { fprintf(file, "*"); G__md_print_escaped(file, opt->answer); fprintf(file, "*"); From f841e6396efd2de5640973b2356ab13ba392b40e Mon Sep 17 00:00:00 2001 From: Vaclav Petras Date: Mon, 7 Apr 2025 22:24:13 -0400 Subject: [PATCH 6/6] Add standalone tuple print function to print tuple in three different contexts for precise tuple-multiple cases. Separate types by pipe. Make optional/required in italics to make it more distinct. Introduce a 'Used as' line which shows age (as input/output) and desc/prompt from gisprompt followed by key_desc in italics. Make flags parameter and long flags more like the other parameters. --- lib/gis/parser_md_python.c | 107 ++++++++++++++++++++++++++----------- 1 file changed, 76 insertions(+), 31 deletions(-) diff --git a/lib/gis/parser_md_python.c b/lib/gis/parser_md_python.c index c248c06b85b..9bc80031c52 100644 --- a/lib/gis/parser_md_python.c +++ b/lib/gis/parser_md_python.c @@ -29,6 +29,7 @@ static void print_python_option(FILE *file, const struct Option *opt, static void print_python_example(FILE *file, const char *python_function, const char *output_format_default, const char *indent); +static void print_python_tuple(FILE *file, const char *type, int num_items); void print_python_short_flag(FILE *file, const char *key, const char *label, const char *description, const char *indent) @@ -55,7 +56,7 @@ void print_python_short_flag(FILE *file, const char *key, const char *label, void print_python_long_flag(FILE *file, const char *key, const char *label, const char *description, const char *indent) { - fprintf(file, "%s**%s**: bool, default False", indent, key); + fprintf(file, "%s**%s**: bool, *optional*", indent, key); fprintf(file, MD_NEWLINE); fprintf(file, "\n"); if (label != NULL) { @@ -69,7 +70,21 @@ void print_python_long_flag(FILE *file, const char *key, const char *label, fprintf(file, "%s", indent); G__md_print_escaped(file, "\t"); G__md_print_escaped(file, description); + fprintf(file, MD_NEWLINE); + fprintf(file, "\n"); } + fprintf(file, "%s", indent); + G__md_print_escaped(file, "\t"); + fprintf(file, "Default: *False*"); +} + +void print_python_tuple(FILE *file, const char *type, int num_items) +{ + fprintf(file, "tuple[%s", type); + for (int i = 1; i < num_items; i++) { + fprintf(file, ", %s", type); + } + fprintf(file, "]"); } void print_python_option(FILE *file, const struct Option *opt, @@ -95,35 +110,37 @@ void print_python_option(FILE *file, const struct Option *opt, int tuple_items = G__option_num_tuple_items(opt); if (opt->multiple) { if (tuple_items) { - fprintf(file, "list[tuple[%s", type); - for (int i = 1; i < tuple_items; i++) { - fprintf(file, ", %s", type); - } - fprintf(file, "]], list[%s], str", type); + fprintf(file, "list["); + print_python_tuple(file, type, tuple_items); + fprintf(file, "] | "); + print_python_tuple(file, type, tuple_items); + fprintf(file, " | list[%s] | str", type); } else { - fprintf(file, "%s, list[%s]", type, type); + if (strcmp(type, "str")) { + // If it is not a string, we also show it can be a string + // because that may be more relevant to show that for + // lists due to examples (it is possible for single value as + // well). + fprintf(file, "%s | list[%s] | str", type, type); + } + else { + fprintf(file, "%s | list[%s]", type, type); + } } } else if (tuple_items) { - fprintf(file, "tuple[%s", type); - for (int i = 1; i < tuple_items; i++) { - fprintf(file, ", %s", type); - } - fprintf(file, "], list[%s], str", type); + print_python_tuple(file, type, tuple_items); + fprintf(file, " | list[%s] | str", type); } else { fprintf(file, "%s", type); } - if (opt->key_desc) { - fprintf(file, ", `%s`", opt->key_desc); - } - /* fprintf(file, "*"); */ if (opt->required) { - fprintf(file, ", required"); + fprintf(file, ", *required*"); } else { - fprintf(file, ", optional"); + fprintf(file, ", *optional*"); } fprintf(file, MD_NEWLINE); @@ -142,27 +159,42 @@ void print_python_option(FILE *file, const struct Option *opt, G__md_print_escaped(file, "\t"); G__md_print_escaped(file, opt->description); } - - if (opt->options) { + if (opt->gisprompt || opt->key_desc) { fprintf(file, MD_NEWLINE); fprintf(file, "\n"); fprintf(file, "%s", indent); G__md_print_escaped(file, "\t"); - fprintf(file, "%s: *", _("Allowed values")); - G__md_print_escaped_for_options(file, opt->options); - fprintf(file, "*"); + fprintf(file, "%s: ", _("Used as")); + } + if (opt->gisprompt) { + char age[KEYLENGTH]; + char element[KEYLENGTH]; + char desc[KEYLENGTH]; + G__split_gisprompt(opt->gisprompt, age, element, desc); + if (strcmp(age, "new") == 0) + fprintf(file, "output, "); + else if (strcmp(age, "old") == 0) + fprintf(file, "input, "); + // While element more strictly expresses how the value will be + // used given that the parser may read that information, desc + // is meant as a user-facing representation of the same + // information. + fprintf(file, "%s", desc); + } + if (opt->gisprompt && opt->key_desc) { + fprintf(file, ", "); + } + if (opt->key_desc) { + fprintf(file, "*%s*", opt->key_desc); } - if (opt->def) { + if (opt->options) { fprintf(file, MD_NEWLINE); fprintf(file, "\n"); fprintf(file, "%s", indent); G__md_print_escaped(file, "\t"); - fprintf(file, "%s:", _("Default")); - /* TODO check if value is empty - if (!opt->def.empty()){ */ - fprintf(file, " *"); - G__md_print_escaped(file, opt->def); + fprintf(file, "%s: *", _("Allowed values")); + G__md_print_escaped_for_options(file, opt->options); fprintf(file, "*"); } @@ -204,6 +236,17 @@ void print_python_option(FILE *file, const struct Option *opt, i++; } } + + if (opt->def) { + fprintf(file, MD_NEWLINE); + fprintf(file, "\n"); + fprintf(file, "%s", indent); + G__md_print_escaped(file, "\t"); + fprintf(file, "%s:", _("Default")); + fprintf(file, " *"); + G__md_print_escaped(file, opt->def); + fprintf(file, "*"); + } } void print_python_example(FILE *file, const char *python_function, @@ -395,7 +438,7 @@ void G__md_print_python_long_version(FILE *file, const char *indent) // Short (one-letter) flags and tool-specific long flags if (st->n_flags) { - fprintf(file, "%s**flags** : str", indent); + fprintf(file, "%s**flags** : str, *optional*", indent); fprintf(file, MD_NEWLINE); fprintf(file, "\n"); fprintf(file, "%s", indent); @@ -403,8 +446,10 @@ void G__md_print_python_long_version(FILE *file, const char *indent) fprintf(file, "Allowed values: "); flag = &st->first_flag; while (st->n_flags && flag != NULL) { - fprintf(file, "*%s*, ", &flag->key); + fprintf(file, "*%s*", &flag->key); flag = flag->next_flag; + if (flag != NULL) + fprintf(file, ", "); } fprintf(file, MD_NEWLINE); fprintf(file, "\n");