GRASS 8 Programmer's Manual  8.5.0dev(2025)-9b240c893d
parser_md_python.c
Go to the documentation of this file.
1 /*!
2  \file lib/gis/parser_md_python.c
3 
4  \brief GIS Library - Argument parsing functions (Markdown output - Python)
5 
6  (C) 2025 by the GRASS Development Team
7 
8  This program is free software under the GNU General Public License
9  (>=v2). Read the file COPYING that comes with GRASS for details.
10 
11  \author Vaclav Petras
12  */
13 #include <stdbool.h>
14 #include <stdio.h>
15 #include <string.h>
16 
17 #include <grass/gis.h>
18 #include <grass/glocale.h>
19 
20 #include "parser_local_proto.h"
21 
22 static void print_python_short_flag(FILE *file, const char *key,
23  const char *label, const char *description,
24  const char *indent);
25 static void print_python_long_flag(FILE *file, const char *key,
26  const char *label, const char *description,
27  const char *indent);
28 static void print_python_option(FILE *file, const struct Option *opt,
29  const char *indent, bool tools_api);
30 static void print_python_example(FILE *file, const char *python_function,
31  const char *output_format_default,
32  const char *indent, bool tools_api);
33 static void print_python_tuple(FILE *file, const char *type, int num_items);
34 
35 void print_python_short_flag(FILE *file, const char *key, const char *label,
36  const char *description, const char *indent)
37 {
38  fprintf(file, "%s", indent);
40  fprintf(file, "**%s**", key);
41  fprintf(file, MD_NEWLINE);
42  fprintf(file, "\n");
43  if (label != NULL) {
44  fprintf(file, "%s", indent);
45  G__md_print_escaped(file, "\t\t");
46  G__md_print_escaped(file, label);
47  fprintf(file, MD_NEWLINE);
48  fprintf(file, "\n");
49  }
50  if (description != NULL) {
51  fprintf(file, "%s", indent);
52  G__md_print_escaped(file, "\t\t");
53  G__md_print_escaped(file, description);
54  }
55 }
56 
57 void print_python_long_flag(FILE *file, const char *key, const char *label,
58  const char *description, const char *indent)
59 {
60  fprintf(file, "%s**%s** : bool, *optional*", indent, key);
61  fprintf(file, MD_NEWLINE);
62  fprintf(file, "\n");
63  if (label != NULL) {
64  fprintf(file, "%s", indent);
66  G__md_print_escaped(file, label);
67  fprintf(file, MD_NEWLINE);
68  fprintf(file, "\n");
69  }
70  if (description != NULL) {
71  fprintf(file, "%s", indent);
73  G__md_print_escaped(file, description);
74  fprintf(file, MD_NEWLINE);
75  fprintf(file, "\n");
76  }
77  fprintf(file, "%s", indent);
79  const char *flag_default = "*None*";
80  fprintf(file, "Default: %s", flag_default);
81 }
82 
83 void print_python_tuple(FILE *file, const char *type, int num_items)
84 {
85  fprintf(file, "tuple[%s", type);
86  for (int i = 1; i < num_items; i++) {
87  fprintf(file, ", %s", type);
88  }
89  fprintf(file, "]");
90 }
91 
92 void print_python_option(FILE *file, const struct Option *opt,
93  const char *indent, bool tools_api)
94 {
95  const char *type;
96 
97  switch (opt->type) {
98  case TYPE_INTEGER:
99  type = "int";
100  break;
101  case TYPE_DOUBLE:
102  type = "float";
103  break;
104  case TYPE_STRING:
105  type = "str";
106  break;
107  default:
108  type = "str";
109  break;
110  }
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  if (G_strncasecmp("old", age, 3) == 0 &&
119  G_strncasecmp("file", element, 4) == 0) {
120  type = "str | io.StringIO";
121  }
122  if (G_strncasecmp("old", age, 3) == 0 &&
123  G_strncasecmp("cell", element, 4) == 0) {
124  type = "str | np.ndarray";
125  }
126  if (G_strncasecmp("new", age, 3) == 0 &&
127  G_strncasecmp("cell", element, 4) == 0) {
128  type = "str | type(np.ndarray) | type(np.array) | "
129  "type(gs.array.array)";
130  }
131  }
132  }
133 
134  fprintf(file, "%s**%s** : ", indent, opt->key);
135  int tuple_items = G__option_num_tuple_items(opt);
136  if (opt->multiple) {
137  if (tuple_items) {
138  fprintf(file, "list[");
139  print_python_tuple(file, type, tuple_items);
140  fprintf(file, "] | ");
141  print_python_tuple(file, type, tuple_items);
142  fprintf(file, " | list[%s] | str", type);
143  }
144  else {
145  if (strcmp(type, "str")) {
146  // If it is not a string, we also show it can be a string
147  // because that may be more relevant to show that for
148  // lists due to examples (it is possible for single value as
149  // well).
150  fprintf(file, "%s | list[%s] | str", type, type);
151  }
152  else {
153  fprintf(file, "%s | list[%s]", type, type);
154  }
155  }
156  }
157  else if (tuple_items) {
158  print_python_tuple(file, type, tuple_items);
159  fprintf(file, " | list[%s] | str", type);
160  }
161  else {
162  fprintf(file, "%s", type);
163  }
164  if (opt->required) {
165  fprintf(file, ", *required*");
166  }
167  else {
168  fprintf(file, ", *optional*");
169  }
170 
171  fprintf(file, MD_NEWLINE);
172  fprintf(file, "\n");
173  if (opt->label) {
174  fprintf(file, "%s", indent);
175  G__md_print_escaped(file, "\t");
177  }
178  if (opt->description) {
179  if (opt->label) {
180  fprintf(file, MD_NEWLINE);
181  fprintf(file, "\n");
182  }
183  fprintf(file, "%s", indent);
184  G__md_print_escaped(file, "\t");
186  }
187  if (opt->gisprompt || opt->key_desc) {
188  fprintf(file, MD_NEWLINE);
189  fprintf(file, "\n");
190  fprintf(file, "%s", indent);
191  G__md_print_escaped(file, "\t");
192  fprintf(file, "%s: ", _("Used as"));
193  }
194  if (opt->gisprompt) {
195  if (strcmp(age, "new") == 0)
196  fprintf(file, "output, ");
197  else if (strcmp(age, "old") == 0)
198  fprintf(file, "input, ");
199  // While element more strictly expresses how the value will be
200  // used given that the parser may read that information, desc
201  // is meant as a user-facing representation of the same
202  // information.
203  fprintf(file, "%s", prompt_description);
204  }
205  if (opt->gisprompt && opt->key_desc) {
206  fprintf(file, ", ");
207  }
208  if (opt->key_desc) {
209  fprintf(file, "*%s*", opt->key_desc);
210  }
211 
212  if (opt->options) {
213  fprintf(file, MD_NEWLINE);
214  fprintf(file, "\n");
215  fprintf(file, "%s", indent);
216  G__md_print_escaped(file, "\t");
217  fprintf(file, "%s: *", _("Allowed values"));
219  fprintf(file, "*");
220  }
221 
222  if (opt->descs) {
223  int i = 0;
224 
225  while (opt->opts[i]) {
226  if (opt->descs[i]) {
227  fprintf(file, MD_NEWLINE);
228  fprintf(file, "\n");
229  fprintf(file, "%s", indent);
230  char *thumbnails = NULL;
231  if (opt->gisprompt) {
232  if (strcmp(opt->gisprompt, "old,colortable,colortable") ==
233  0)
234  thumbnails = "colortables";
235  else if (strcmp(opt->gisprompt, "old,barscale,barscale") ==
236  0)
237  thumbnails = "barscales";
238  else if (strcmp(opt->gisprompt,
239  "old,northarrow,northarrow") == 0)
240  thumbnails = "northarrows";
241 
242  if (thumbnails) {
243  G__md_print_escaped(file, "\t\t");
244  fprintf(file, "![%s](%s/%s.png) ", opt->opts[i],
245  thumbnails, opt->opts[i]);
246  }
247  else {
248  G__md_print_escaped(file, "\t\t");
249  }
250  }
251  G__md_print_escaped(file, "\t");
252  fprintf(file, "**");
253  G__md_print_escaped(file, opt->opts[i]);
254  fprintf(file, "**: ");
255  G__md_print_escaped(file, opt->descs[i]);
256  }
257  i++;
258  }
259  }
260 
261  if (opt->def && opt->def[0] != '\0') {
262  fprintf(file, MD_NEWLINE);
263  fprintf(file, "\n");
264  fprintf(file, "%s", indent);
265  G__md_print_escaped(file, "\t");
266  fprintf(file, "%s:", _("Default"));
267  fprintf(file, " *");
269  fprintf(file, "*");
270  }
271 }
272 
273 void print_python_example(FILE *file, const char *python_function,
274  const char *output_format_default, const char *indent,
275  bool tools_api)
276 {
277  fprintf(file, "\n%sExample:\n", indent);
278 
279  fprintf(file, "\n%s```python\n", indent);
280  bool first_parameter_printed = false;
281  if (tools_api) {
282  char *tool_name = G_store(st->pgm_name);
283  G_strchg(tool_name, '.', '_');
284  fprintf(file, "%stools = Tools()\n", indent);
285  fprintf(file, "%stools.%s(", indent, tool_name);
286  G_free(tool_name);
287  }
288  else {
289  fprintf(file, "%sgs.%s(\"%s\"", indent, python_function, st->pgm_name);
290  first_parameter_printed = true;
291  }
292 
293  const struct Option *first_required_rule_option =
295  const struct Option *opt = NULL;
296  const char *type;
297 
298  if (st->n_opts) {
299  opt = &st->first_option;
300 
301  while (opt != NULL) {
302  if (opt->key_desc != NULL)
303  type = opt->key_desc;
304  else
305  switch (opt->type) {
306  case TYPE_INTEGER:
307  type = "integer";
308  break;
309  case TYPE_DOUBLE:
310  type = "float";
311  break;
312  case TYPE_STRING:
313  type = "string";
314  break;
315  default:
316  type = "string";
317  break;
318  }
319  if (opt->required || first_required_rule_option == opt ||
320  (strcmp(opt->key, "format") == 0 && output_format_default)) {
321  if (first_parameter_printed) {
322  fprintf(file, ", ");
323  }
324  fprintf(file, "%s=", opt->key);
325 
326  char *value = NULL;
327  if (opt->answer) {
328  value = G_store(opt->answer);
329  }
330  else if (opt->options && opt->type == TYPE_STRING) {
331  // Get example value from allowed values, but only for
332  // strings because numbers may have ranges and we don't
333  // want to print a range.
334  // Get allowed values as tokens.
335  char **tokens;
336  char delm[2];
337  delm[0] = ',';
338  delm[1] = '\0';
339  tokens = G_tokenize(opt->options, delm);
340  // We are interested in the first allowed value.
341  if (tokens[0]) {
342  G_chop(tokens[0]);
343  value = G_store(tokens[0]);
344  }
345  G_free_tokens(tokens);
346  }
347 
348  if (output_format_default && strcmp(opt->key, "format") == 0) {
349  fprintf(file, "\"%s\"", output_format_default);
350  }
351  else if (value) {
352  if (opt->type == TYPE_INTEGER || opt->type == TYPE_DOUBLE) {
353  fprintf(file, "%s", value);
354  }
355  else {
356  fprintf(file, "\"%s\"", value);
357  }
358  }
359  else {
360  if (opt->type == TYPE_INTEGER) {
361  fprintf(file, "0");
362  }
363  else if (opt->type == TYPE_DOUBLE) {
364  fprintf(file, "0.0");
365  }
366  else {
367  fprintf(file, "\"%s\"", type);
368  }
369  }
370  first_parameter_printed = true;
371  G_free(value);
372  }
373  opt = opt->next_opt;
374  }
375  }
376  fprintf(file, ")\n%s```\n", indent);
377 }
378 
379 void G__md_print_python_short_version(FILE *file, const char *indent,
380  bool tools_api)
381 {
382  struct Option *opt;
383  struct Flag *flag;
384  int new_prompt = 0;
385  bool output_format_option = false;
386  const char *output_format_default = NULL;
387  bool shell_eval_flag = false;
388  const char *python_function = NULL;
389 
390  new_prompt = G__uses_new_gisprompt();
391 
392  if (st->n_opts) {
393  opt = &st->first_option;
394  while (opt != NULL) {
395  if (strcmp(opt->key, "format") == 0) {
396  if (opt->options) {
397  int i = 0;
398  while (opt->opts[i]) {
399  if (strcmp(opt->opts[i], "csv") == 0)
400  output_format_default = "csv";
401  if (strcmp(opt->opts[i], "json") == 0) {
402  output_format_default = "json";
403  break;
404  }
405  i++;
406  }
407  }
408  if (output_format_default) {
409  output_format_option = true;
410  }
411  break;
412  }
413  opt = opt->next_opt;
414  }
415  }
416  if (st->n_flags) {
417  flag = &st->first_flag;
418  while (st->n_flags && flag != NULL) {
419  if (flag->key == 'g') {
420  shell_eval_flag = true;
421  break;
422  }
423  flag = flag->next_flag;
424  }
425  }
426  bool first_parameter_printed = false;
427  if (tools_api) {
428  char *tool_name = G_store(st->pgm_name);
429  G_strchg(tool_name, '.', '_');
430  fprintf(file, "%s*grass.tools.Tools.%s*(", indent, tool_name);
431  G_free(tool_name);
432  }
433  else {
434  if (output_format_option || (!new_prompt && shell_eval_flag)) {
435  python_function = "parse_command";
436  // We know this can be parsed, but we don't detect just plain
437  // text output to use read_command because we can't distinguish
438  // between plain text outputs and modifications of data.
439  }
440  else {
441  python_function = "run_command";
442  }
443  fprintf(file, "%s*grass.script.%s*(\"***%s***\",", indent,
444  python_function, st->pgm_name);
445  fprintf(file, "\n");
446  first_parameter_printed = true;
447  }
448 
449  if (st->n_opts) {
450  opt = &st->first_option;
451 
452  while (opt != NULL) {
453  if (first_parameter_printed) {
454  fprintf(file, "%s ", indent);
455  }
456  if (!opt->required && !opt->answer) {
457  fprintf(file, "**%s**=*None*", opt->key);
458  }
459  else {
460  fprintf(file, "**%s**", opt->key);
461  }
462  if (opt->answer) {
463  fprintf(file, "=");
464  int tuple_items = G__option_num_tuple_items(opt);
465  if (!tuple_items &&
466  (opt->type == TYPE_INTEGER || opt->type == TYPE_DOUBLE)) {
467  fprintf(file, "*");
469  fprintf(file, "*");
470  }
471  else {
472  fprintf(file, "*\"");
474  fprintf(file, "\"*");
475  }
476  }
477  fprintf(file, ",\n");
478  first_parameter_printed = true;
479  opt = opt->next_opt;
480  }
481  }
482 
483  if (st->n_flags) {
484  flag = &st->first_flag;
485  fprintf(file, "%s **flags**=*None*,\n", indent);
486  }
487 
488  const char *flag_default = "*None*";
489  if (new_prompt)
490  fprintf(file, "%s **overwrite**=%s,\n", indent, flag_default);
491 
492  fprintf(file, "%s **verbose**=%s,\n", indent, flag_default);
493  fprintf(file, "%s **quiet**=%s,\n", indent, flag_default);
494  fprintf(file, "%s **superquiet**=%s)\n", indent, flag_default);
495 
496  print_python_example(file, python_function, output_format_default, indent,
497  tools_api);
498  if (tools_api) {
499  fprintf(file,
500  "\n%sThis grass.tools API is experimental in version 8.5 "
501  "and expected to be stable in version 8.6.\n",
502  indent);
503  }
504 }
505 
506 void G__md_print_python_long_version(FILE *file, const char *indent,
507  bool tools_api)
508 {
509  struct Option *opt;
510  struct Flag *flag;
511  int new_prompt = 0;
512 
513  new_prompt = G__uses_new_gisprompt();
514 
515  // Options (key-value parameters)
516  if (st->n_opts) {
517  opt = &st->first_option;
518  while (opt != NULL) {
519  print_python_option(file, opt, indent, tools_api);
520  opt = opt->next_opt;
521  fprintf(file, MD_NEWLINE);
522  fprintf(file, "\n");
523  }
524  }
525 
526  // Short (one-letter) flags and tool-specific long flags
527  if (st->n_flags) {
528  fprintf(file, "%s**flags** : str, *optional*", indent);
529  fprintf(file, MD_NEWLINE);
530  fprintf(file, "\n");
531  fprintf(file, "%s", indent);
532  G__md_print_escaped(file, "\t");
533  fprintf(file, "Allowed values: ");
534  flag = &st->first_flag;
535  while (st->n_flags && flag != NULL) {
536  fprintf(file, "*%s*", &flag->key);
537  flag = flag->next_flag;
538  if (flag != NULL)
539  fprintf(file, ", ");
540  }
541  fprintf(file, MD_NEWLINE);
542  fprintf(file, "\n");
543  flag = &st->first_flag;
544  while (st->n_flags && flag != NULL) {
545  print_python_short_flag(file, &flag->key, flag->label,
546  flag->description, indent);
547  fprintf(file, MD_NEWLINE);
548  fprintf(file, "\n");
549  flag = flag->next_flag;
550  }
551  }
552  if (new_prompt) {
553  print_python_long_flag(
554  file, "overwrite", NULL,
555  _("Allow output files to overwrite existing files"), indent);
556  fprintf(file, MD_NEWLINE);
557  fprintf(file, "\n");
558  }
559  // Pre-defined long flags
560  print_python_long_flag(file, "verbose", NULL, _("Verbose module output"),
561  indent);
562  fprintf(file, MD_NEWLINE);
563  fprintf(file, "\n");
564  print_python_long_flag(file, "quiet", NULL, _("Quiet module output"),
565  indent);
566  fprintf(file, MD_NEWLINE);
567  fprintf(file, "\n");
568  print_python_long_flag(file, "superquiet", NULL,
569  _("Very quiet module output"), indent);
570  fprintf(file, MD_NEWLINE);
571  fprintf(file, "\n");
572 
573  if (!tools_api)
574  return;
575 
576  fprintf(file, "\n%sReturns:\n\n", indent);
577 
578  bool outputs_arrays = false;
579  char age[KEYLENGTH];
580  char element[KEYLENGTH];
581  char prompt_description[KEYLENGTH];
582  if (st->n_opts) {
583  opt = &st->first_option;
584  while (opt != NULL) {
585  if (opt->gisprompt) {
587  prompt_description);
588  if (tools_api && !opt->multiple && opt->type == TYPE_STRING) {
589  if (G_strncasecmp("new", age, 3) == 0 &&
590  G_strncasecmp("cell", element, 4) == 0) {
591  outputs_arrays = true;
592  }
593  }
594  }
595  opt = opt->next_opt;
596  }
597  }
598 
599  fprintf(file, "%s**result** : ", indent);
600  fprintf(file, "grass.tools.support.ToolResult");
601  if (outputs_arrays) {
602  fprintf(file, " | np.ndarray | tuple[np.ndarray]");
603  }
604  fprintf(file, " | None");
605  fprintf(file, MD_NEWLINE);
606  fprintf(file, "\n%s", indent);
607  fprintf(file, "If the tool produces text as standard output, a "
608  "*ToolResult* object will be returned. "
609  "Otherwise, `None` will be returned.");
610  if (outputs_arrays) {
611  fprintf(file, " If an array type (e.g., *np.ndarray*) is used for one "
612  "of the raster outputs, "
613  "the result will be an array and will have the shape "
614  "corresponding to the computational region. "
615  "If an array type is used for more than one raster "
616  "output, the result will be a tuple of arrays.");
617  }
618  fprintf(file, "\n");
619 
620  fprintf(file, "\n%sRaises:\n\n", indent);
621  fprintf(file,
622  "%s*grass.tools.ToolError*: When the tool ended with an error.\n",
623  indent);
624 }
#define NULL
Definition: ccmath.h:32
void G_free(void *)
Free allocated memory.
Definition: gis/alloc.c:147
void G_free_tokens(char **)
Free memory allocated to tokens.
Definition: gis/token.c:197
char * G_strchg(char *, char, char)
Replace all occurrences of character in string bug with new.
Definition: strings.c:160
int G_strncasecmp(const char *, const char *, int)
String compare ignoring case (upper or lower) - limited number of characters.
Definition: strings.c:69
char * G_chop(char *)
Chop leading and trailing white spaces.
Definition: strings.c:332
char * G_store(const char *)
Copy string to allocated memory.
Definition: strings.c:87
char ** G_tokenize(const char *, const char *)
Tokenize string.
Definition: gis/token.c:47
#define TYPE_STRING
Definition: gis.h:191
#define TYPE_INTEGER
Definition: gis.h:189
#define TYPE_DOUBLE
Definition: gis.h:190
#define _(str)
Definition: glocale.h:10
#define file
int G__uses_new_gisprompt(void)
Definition: parser.c:892
struct state * st
Definition: parser.c:104
void G__split_gisprompt(const char *gisprompt, char *age, char *element, char *desc)
Definition: parser.c:1775
const struct Option * G__first_required_option_from_rules(void)
void G__md_print_escaped(FILE *f, const char *str)
int G__option_num_tuple_items(const struct Option *opt)
Get number of tuple items if option is a tuple.
void G__md_print_escaped_for_options(FILE *f, const char *str)
void G__md_print_python_long_version(FILE *file, const char *indent, bool tools_api)
void G__md_print_python_short_version(FILE *file, const char *indent, bool tools_api)
Structure that stores flag info.
Definition: gis.h:594
struct Flag * next_flag
Definition: gis.h:603
const char * description
Definition: gis.h:600
char key
Definition: gis.h:595
const char * label
Definition: gis.h:599
Structure that stores option information.
Definition: gis.h:563
const char * key
Definition: gis.h:564
struct Option * next_opt
Definition: gis.h:580
const char * key_desc
Definition: gis.h:570
const char ** opts
Definition: gis.h:569
const char * gisprompt
Definition: gis.h:581
const char * label
Definition: gis.h:571
int type
Definition: gis.h:565
const char * def
Definition: gis.h:578
const char * description
Definition: gis.h:572
char * answer
Definition: gis.h:577
int required
Definition: gis.h:566
const char ** descs
Definition: gis.h:575
const char * options
Definition: gis.h:568
int multiple
Definition: gis.h:567
Definition: lidar.h:85