Skip to content

Commit 8efcc36

Browse files
authored
grass.tools: Add Tools API to generated tools doc (#6015)
This adds grass.tools.Tools from #2923 to the generated documentation of individual tools as a new tab similar to the grass.script tab. The documentation and treatment of parameters is the same at this points in grass.tools as in grass.script except for _io.StringIO_. The _io.StringIO_ type is added based on the gisprompt on a best-guess basis. The current parser metadata don't contain explicit machine-readable information about stdin being allowed. Parser then does not know whether or not a specific tool supports stdin for one of its parameters. The stdin is resolved while opening the file by a library function call at the level of individual tools, but during command line parsing, the parser merely allows for dash being provided instead of a filename. The resulting documentation now contains `"'-' for standard input"` or the like based on what is in each tool. This is slightly misleading for the Tools API because `"-"` won't do anything useful because the only way to supply stdin at this point is _io.StringIO_. However, without extending the parser metadata, I don't see any way how to make it better. The first first parameter spacing and comma in short doc now needs a special handling because before, we always had a first parameter, namely the name of the tool. A care was given to the wording of the experimental part to be clear what part of our API is the experimental one. This also fixes and clarifies the comment about parse_command versus read_command and run_command.
1 parent bc5cb70 commit 8efcc36

File tree

3 files changed

+83
-30
lines changed

3 files changed

+83
-30
lines changed

lib/gis/parser_local_proto.h

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
#define __PARSER_LOCAL_PROTO_H__
33

44
#include <stdio.h>
5+
#include <stdbool.h>
56
#include <grass/gis.h>
67

78
#define KEYLENGTH 64
@@ -58,9 +59,11 @@ void G__usage_rest(void);
5859

5960
void G__usage_markdown(void);
6061
void G__md_print_cli_short_version(FILE *file, const char *indent);
61-
void G__md_print_python_short_version(FILE *file, const char *indent);
62+
void G__md_print_python_short_version(FILE *file, const char *indent,
63+
bool tools_api);
6264
void G__md_print_cli_long_version(FILE *file, const char *indent);
63-
void G__md_print_python_long_version(FILE *file, const char *indent);
65+
void G__md_print_python_long_version(FILE *file, const char *indent,
66+
bool tools_api);
6467
void G__md_print_escaped(FILE *f, const char *str);
6568
void G__md_print_escaped_for_options(FILE *f, const char *str);
6669
int G__option_num_tuple_items(const struct Option *opt);

lib/gis/parser_md.c

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -64,13 +64,17 @@ void G__usage_markdown(void)
6464
fprintf(stdout, "\n=== \"Command line\"\n\n");
6565
G__md_print_cli_short_version(stdout, tab_indent);
6666
fprintf(stdout, "\n=== \"Python (grass.script)\"\n\n");
67-
G__md_print_python_short_version(stdout, tab_indent);
67+
G__md_print_python_short_version(stdout, tab_indent, false);
68+
fprintf(stdout, "\n=== \"Python (grass.tools)\"\n\n");
69+
G__md_print_python_short_version(stdout, tab_indent, true);
6870

6971
fprintf(stdout, "\n## %s\n", _("Parameters"));
7072

7173
/* long version */
7274
fprintf(stdout, "\n=== \"Command line\"\n\n");
7375
G__md_print_cli_long_version(stdout, tab_indent);
7476
fprintf(stdout, "\n=== \"Python (grass.script)\"\n\n");
75-
G__md_print_python_long_version(stdout, tab_indent);
77+
G__md_print_python_long_version(stdout, tab_indent, false);
78+
fprintf(stdout, "\n=== \"Python (grass.tools)\"\n\n");
79+
G__md_print_python_long_version(stdout, tab_indent, true);
7680
}

lib/gis/parser_md_python.c

Lines changed: 72 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -26,10 +26,10 @@ static void print_python_long_flag(FILE *file, const char *key,
2626
const char *label, const char *description,
2727
const char *indent);
2828
static void print_python_option(FILE *file, const struct Option *opt,
29-
const char *indent);
29+
const char *indent, bool tools_api);
3030
static void print_python_example(FILE *file, const char *python_function,
3131
const char *output_format_default,
32-
const char *indent);
32+
const char *indent, bool tools_api);
3333
static void print_python_tuple(FILE *file, const char *type, int num_items);
3434

3535
void print_python_short_flag(FILE *file, const char *key, const char *label,
@@ -90,7 +90,7 @@ void print_python_tuple(FILE *file, const char *type, int num_items)
9090
}
9191

9292
void print_python_option(FILE *file, const struct Option *opt,
93-
const char *indent)
93+
const char *indent, bool tools_api)
9494
{
9595
const char *type;
9696

@@ -108,6 +108,19 @@ void print_python_option(FILE *file, const struct Option *opt,
108108
type = "str";
109109
break;
110110
}
111+
112+
char age[KEYLENGTH];
113+
char element[KEYLENGTH];
114+
char prompt_description[KEYLENGTH];
115+
if (opt->gisprompt) {
116+
G__split_gisprompt(opt->gisprompt, age, element, prompt_description);
117+
if (tools_api && !opt->multiple && opt->type == TYPE_STRING &&
118+
G_strncasecmp("old", age, 3) == 0 &&
119+
G_strncasecmp("file", element, 4) == 0) {
120+
type = "str | io.StringIO";
121+
}
122+
}
123+
111124
fprintf(file, "%s**%s** : ", indent, opt->key);
112125
int tuple_items = G__option_num_tuple_items(opt);
113126
if (opt->multiple) {
@@ -169,10 +182,6 @@ void print_python_option(FILE *file, const struct Option *opt,
169182
fprintf(file, "%s: ", _("Used as"));
170183
}
171184
if (opt->gisprompt) {
172-
char age[KEYLENGTH];
173-
char element[KEYLENGTH];
174-
char desc[KEYLENGTH];
175-
G__split_gisprompt(opt->gisprompt, age, element, desc);
176185
if (strcmp(age, "new") == 0)
177186
fprintf(file, "output, ");
178187
else if (strcmp(age, "old") == 0)
@@ -181,7 +190,7 @@ void print_python_option(FILE *file, const struct Option *opt,
181190
// used given that the parser may read that information, desc
182191
// is meant as a user-facing representation of the same
183192
// information.
184-
fprintf(file, "%s", desc);
193+
fprintf(file, "%s", prompt_description);
185194
}
186195
if (opt->gisprompt && opt->key_desc) {
187196
fprintf(file, ", ");
@@ -252,12 +261,24 @@ void print_python_option(FILE *file, const struct Option *opt,
252261
}
253262

254263
void print_python_example(FILE *file, const char *python_function,
255-
const char *output_format_default, const char *indent)
264+
const char *output_format_default, const char *indent,
265+
bool tools_api)
256266
{
257267
fprintf(file, "\n%sExample:\n", indent);
258268

259269
fprintf(file, "\n%s```python\n", indent);
260-
fprintf(file, "%sgs.%s(\"%s\"", indent, python_function, st->pgm_name);
270+
bool first_parameter_printed = false;
271+
if (tools_api) {
272+
char *tool_name = G_store(st->pgm_name);
273+
G_strchg(tool_name, '.', '_');
274+
fprintf(file, "%stools = Tools()\n", indent);
275+
fprintf(file, "%stools.%s(", indent, tool_name);
276+
G_free(tool_name);
277+
}
278+
else {
279+
fprintf(file, "%sgs.%s(\"%s\"", indent, python_function, st->pgm_name);
280+
first_parameter_printed = true;
281+
}
261282

262283
const struct Option *first_required_rule_option =
263284
G__first_required_option_from_rules();
@@ -287,7 +308,10 @@ void print_python_example(FILE *file, const char *python_function,
287308
}
288309
if (opt->required || first_required_rule_option == opt ||
289310
(strcmp(opt->key, "format") == 0 && output_format_default)) {
290-
fprintf(file, ", %s=", opt->key);
311+
if (first_parameter_printed) {
312+
fprintf(file, ", ");
313+
}
314+
fprintf(file, "%s=", opt->key);
291315

292316
char *value = NULL;
293317
if (opt->answer) {
@@ -333,6 +357,7 @@ void print_python_example(FILE *file, const char *python_function,
333357
fprintf(file, "\"%s\"", type);
334358
}
335359
}
360+
first_parameter_printed = true;
336361
G_free(value);
337362
}
338363
opt = opt->next_opt;
@@ -341,7 +366,8 @@ void print_python_example(FILE *file, const char *python_function,
341366
fprintf(file, ")\n%s```\n", indent);
342367
}
343368

344-
void G__md_print_python_short_version(FILE *file, const char *indent)
369+
void G__md_print_python_short_version(FILE *file, const char *indent,
370+
bool tools_api)
345371
{
346372
struct Option *opt;
347373
struct Flag *flag;
@@ -387,24 +413,36 @@ void G__md_print_python_short_version(FILE *file, const char *indent)
387413
flag = flag->next_flag;
388414
}
389415
}
390-
if (output_format_option || (!new_prompt && shell_eval_flag)) {
391-
python_function = "parse_command";
392-
// We know this is can be parsed, but we can't detect just plain file
393-
// because we can't distinguish between plain text outputs and
394-
// modifications of data.
416+
bool first_parameter_printed = false;
417+
if (tools_api) {
418+
char *tool_name = G_store(st->pgm_name);
419+
G_strchg(tool_name, '.', '_');
420+
fprintf(file, "%s*grass.tools.Tools.%s*(", indent, tool_name);
421+
G_free(tool_name);
395422
}
396423
else {
397-
python_function = "run_command";
424+
if (output_format_option || (!new_prompt && shell_eval_flag)) {
425+
python_function = "parse_command";
426+
// We know this can be parsed, but we don't detect just plain
427+
// text output to use read_command because we can't distinguish
428+
// between plain text outputs and modifications of data.
429+
}
430+
else {
431+
python_function = "run_command";
432+
}
433+
fprintf(file, "%s*grass.script.%s*(\"***%s***\",", indent,
434+
python_function, st->pgm_name);
435+
fprintf(file, "\n");
436+
first_parameter_printed = true;
398437
}
399-
fprintf(file, "%s*grass.script.%s*(\"***%s***\",", indent, python_function,
400-
st->pgm_name);
401-
fprintf(file, "\n");
402438

403439
if (st->n_opts) {
404440
opt = &st->first_option;
405441

406442
while (opt != NULL) {
407-
fprintf(file, "%s ", indent);
443+
if (first_parameter_printed) {
444+
fprintf(file, "%s ", indent);
445+
}
408446
if (!opt->required && !opt->answer) {
409447
fprintf(file, "**%s**=*None*", opt->key);
410448
}
@@ -427,7 +465,7 @@ void G__md_print_python_short_version(FILE *file, const char *indent)
427465
}
428466
}
429467
fprintf(file, ",\n");
430-
468+
first_parameter_printed = true;
431469
opt = opt->next_opt;
432470
}
433471
}
@@ -445,10 +483,18 @@ void G__md_print_python_short_version(FILE *file, const char *indent)
445483
fprintf(file, "%s **quiet**=%s,\n", indent, flag_default);
446484
fprintf(file, "%s **superquiet**=%s)\n", indent, flag_default);
447485

448-
print_python_example(file, python_function, output_format_default, indent);
486+
print_python_example(file, python_function, output_format_default, indent,
487+
tools_api);
488+
if (tools_api) {
489+
fprintf(file,
490+
"\n%sThis grass.tools API is experimental in version 8.5 "
491+
"and expected to be stable in version 8.6.\n",
492+
indent);
493+
}
449494
}
450495

451-
void G__md_print_python_long_version(FILE *file, const char *indent)
496+
void G__md_print_python_long_version(FILE *file, const char *indent,
497+
bool tools_api)
452498
{
453499
struct Option *opt;
454500
struct Flag *flag;
@@ -460,7 +506,7 @@ void G__md_print_python_long_version(FILE *file, const char *indent)
460506
if (st->n_opts) {
461507
opt = &st->first_option;
462508
while (opt != NULL) {
463-
print_python_option(file, opt, indent);
509+
print_python_option(file, opt, indent, tools_api);
464510
opt = opt->next_opt;
465511
fprintf(file, MD_NEWLINE);
466512
fprintf(file, "\n");

0 commit comments

Comments
 (0)