GRASS 8 Programmer's Manual  8.5.0dev(2025)-c070206eb1
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  G_strncasecmp("old", age, 3) == 0 &&
119  G_strncasecmp("file", element, 4) == 0) {
120  type = "str | io.StringIO";
121  }
122  }
123 
124  fprintf(file, "%s**%s** : ", indent, opt->key);
125  int tuple_items = G__option_num_tuple_items(opt);
126  if (opt->multiple) {
127  if (tuple_items) {
128  fprintf(file, "list[");
129  print_python_tuple(file, type, tuple_items);
130  fprintf(file, "] | ");
131  print_python_tuple(file, type, tuple_items);
132  fprintf(file, " | list[%s] | str", type);
133  }
134  else {
135  if (strcmp(type, "str")) {
136  // If it is not a string, we also show it can be a string
137  // because that may be more relevant to show that for
138  // lists due to examples (it is possible for single value as
139  // well).
140  fprintf(file, "%s | list[%s] | str", type, type);
141  }
142  else {
143  fprintf(file, "%s | list[%s]", type, type);
144  }
145  }
146  }
147  else if (tuple_items) {
148  print_python_tuple(file, type, tuple_items);
149  fprintf(file, " | list[%s] | str", type);
150  }
151  else {
152  fprintf(file, "%s", type);
153  }
154  if (opt->required) {
155  fprintf(file, ", *required*");
156  }
157  else {
158  fprintf(file, ", *optional*");
159  }
160 
161  fprintf(file, MD_NEWLINE);
162  fprintf(file, "\n");
163  if (opt->label) {
164  fprintf(file, "%s", indent);
165  G__md_print_escaped(file, "\t");
167  }
168  if (opt->description) {
169  if (opt->label) {
170  fprintf(file, MD_NEWLINE);
171  fprintf(file, "\n");
172  }
173  fprintf(file, "%s", indent);
174  G__md_print_escaped(file, "\t");
176  }
177  if (opt->gisprompt || opt->key_desc) {
178  fprintf(file, MD_NEWLINE);
179  fprintf(file, "\n");
180  fprintf(file, "%s", indent);
181  G__md_print_escaped(file, "\t");
182  fprintf(file, "%s: ", _("Used as"));
183  }
184  if (opt->gisprompt) {
185  if (strcmp(age, "new") == 0)
186  fprintf(file, "output, ");
187  else if (strcmp(age, "old") == 0)
188  fprintf(file, "input, ");
189  // While element more strictly expresses how the value will be
190  // used given that the parser may read that information, desc
191  // is meant as a user-facing representation of the same
192  // information.
193  fprintf(file, "%s", prompt_description);
194  }
195  if (opt->gisprompt && opt->key_desc) {
196  fprintf(file, ", ");
197  }
198  if (opt->key_desc) {
199  fprintf(file, "*%s*", opt->key_desc);
200  }
201 
202  if (opt->options) {
203  fprintf(file, MD_NEWLINE);
204  fprintf(file, "\n");
205  fprintf(file, "%s", indent);
206  G__md_print_escaped(file, "\t");
207  fprintf(file, "%s: *", _("Allowed values"));
209  fprintf(file, "*");
210  }
211 
212  if (opt->descs) {
213  int i = 0;
214 
215  while (opt->opts[i]) {
216  if (opt->descs[i]) {
217  fprintf(file, MD_NEWLINE);
218  fprintf(file, "\n");
219  fprintf(file, "%s", indent);
220  char *thumbnails = NULL;
221  if (opt->gisprompt) {
222  if (strcmp(opt->gisprompt, "old,colortable,colortable") ==
223  0)
224  thumbnails = "colortables";
225  else if (strcmp(opt->gisprompt, "old,barscale,barscale") ==
226  0)
227  thumbnails = "barscales";
228  else if (strcmp(opt->gisprompt,
229  "old,northarrow,northarrow") == 0)
230  thumbnails = "northarrows";
231 
232  if (thumbnails) {
233  G__md_print_escaped(file, "\t\t");
234  fprintf(file, "![%s](%s/%s.png) ", opt->opts[i],
235  thumbnails, opt->opts[i]);
236  }
237  else {
238  G__md_print_escaped(file, "\t\t");
239  }
240  }
241  G__md_print_escaped(file, "\t");
242  fprintf(file, "**");
243  G__md_print_escaped(file, opt->opts[i]);
244  fprintf(file, "**: ");
245  G__md_print_escaped(file, opt->descs[i]);
246  }
247  i++;
248  }
249  }
250 
251  if (opt->def) {
252  fprintf(file, MD_NEWLINE);
253  fprintf(file, "\n");
254  fprintf(file, "%s", indent);
255  G__md_print_escaped(file, "\t");
256  fprintf(file, "%s:", _("Default"));
257  fprintf(file, " *");
259  fprintf(file, "*");
260  }
261 }
262 
263 void print_python_example(FILE *file, const char *python_function,
264  const char *output_format_default, const char *indent,
265  bool tools_api)
266 {
267  fprintf(file, "\n%sExample:\n", indent);
268 
269  fprintf(file, "\n%s```python\n", indent);
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  }
282 
283  const struct Option *first_required_rule_option =
285  const struct Option *opt = NULL;
286  const char *type;
287 
288  if (st->n_opts) {
289  opt = &st->first_option;
290 
291  while (opt != NULL) {
292  if (opt->key_desc != NULL)
293  type = opt->key_desc;
294  else
295  switch (opt->type) {
296  case TYPE_INTEGER:
297  type = "integer";
298  break;
299  case TYPE_DOUBLE:
300  type = "float";
301  break;
302  case TYPE_STRING:
303  type = "string";
304  break;
305  default:
306  type = "string";
307  break;
308  }
309  if (opt->required || first_required_rule_option == opt ||
310  (strcmp(opt->key, "format") == 0 && output_format_default)) {
311  if (first_parameter_printed) {
312  fprintf(file, ", ");
313  }
314  fprintf(file, "%s=", opt->key);
315 
316  char *value = NULL;
317  if (opt->answer) {
318  value = G_store(opt->answer);
319  }
320  else if (opt->options && opt->type == TYPE_STRING) {
321  // Get example value from allowed values, but only for
322  // strings because numbers may have ranges and we don't
323  // want to print a range.
324  // Get allowed values as tokens.
325  char **tokens;
326  char delm[2];
327  delm[0] = ',';
328  delm[1] = '\0';
329  tokens = G_tokenize(opt->options, delm);
330  // We are interested in the first allowed value.
331  if (tokens[0]) {
332  G_chop(tokens[0]);
333  value = G_store(tokens[0]);
334  }
335  G_free_tokens(tokens);
336  }
337 
338  if (output_format_default && strcmp(opt->key, "format") == 0) {
339  fprintf(file, "\"%s\"", output_format_default);
340  }
341  else if (value) {
342  if (opt->type == TYPE_INTEGER || opt->type == TYPE_DOUBLE) {
343  fprintf(file, "%s", value);
344  }
345  else {
346  fprintf(file, "\"%s\"", value);
347  }
348  }
349  else {
350  if (opt->type == TYPE_INTEGER) {
351  fprintf(file, "0");
352  }
353  else if (opt->type == TYPE_DOUBLE) {
354  fprintf(file, "0.0");
355  }
356  else {
357  fprintf(file, "\"%s\"", type);
358  }
359  }
360  first_parameter_printed = true;
361  G_free(value);
362  }
363  opt = opt->next_opt;
364  }
365  }
366  fprintf(file, ")\n%s```\n", indent);
367 }
368 
369 void G__md_print_python_short_version(FILE *file, const char *indent,
370  bool tools_api)
371 {
372  struct Option *opt;
373  struct Flag *flag;
374  int new_prompt = 0;
375  bool output_format_option = false;
376  const char *output_format_default = NULL;
377  bool shell_eval_flag = false;
378  const char *python_function = NULL;
379 
380  new_prompt = G__uses_new_gisprompt();
381 
382  if (st->n_opts) {
383  opt = &st->first_option;
384  while (opt != NULL) {
385  if (strcmp(opt->key, "format") == 0) {
386  if (opt->options) {
387  int i = 0;
388  while (opt->opts[i]) {
389  if (strcmp(opt->opts[i], "csv") == 0)
390  output_format_default = "csv";
391  if (strcmp(opt->opts[i], "json") == 0) {
392  output_format_default = "json";
393  break;
394  }
395  i++;
396  }
397  }
398  if (output_format_default) {
399  output_format_option = true;
400  }
401  break;
402  }
403  opt = opt->next_opt;
404  }
405  }
406  if (st->n_flags) {
407  flag = &st->first_flag;
408  while (st->n_flags && flag != NULL) {
409  if (flag->key == 'g') {
410  shell_eval_flag = true;
411  break;
412  }
413  flag = flag->next_flag;
414  }
415  }
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);
422  }
423  else {
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;
437  }
438 
439  if (st->n_opts) {
440  opt = &st->first_option;
441 
442  while (opt != NULL) {
443  if (first_parameter_printed) {
444  fprintf(file, "%s ", indent);
445  }
446  if (!opt->required && !opt->answer) {
447  fprintf(file, "**%s**=*None*", opt->key);
448  }
449  else {
450  fprintf(file, "**%s**", opt->key);
451  }
452  if (opt->answer) {
453  fprintf(file, "=");
454  int tuple_items = G__option_num_tuple_items(opt);
455  if (!tuple_items &&
456  (opt->type == TYPE_INTEGER || opt->type == TYPE_DOUBLE)) {
457  fprintf(file, "*");
459  fprintf(file, "*");
460  }
461  else {
462  fprintf(file, "*\"");
464  fprintf(file, "\"*");
465  }
466  }
467  fprintf(file, ",\n");
468  first_parameter_printed = true;
469  opt = opt->next_opt;
470  }
471  }
472 
473  if (st->n_flags) {
474  flag = &st->first_flag;
475  fprintf(file, "%s **flags**=*None*,\n", indent);
476  }
477 
478  const char *flag_default = "*None*";
479  if (new_prompt)
480  fprintf(file, "%s **overwrite**=%s,\n", indent, flag_default);
481 
482  fprintf(file, "%s **verbose**=%s,\n", indent, flag_default);
483  fprintf(file, "%s **quiet**=%s,\n", indent, flag_default);
484  fprintf(file, "%s **superquiet**=%s)\n", indent, flag_default);
485 
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  }
494 }
495 
496 void G__md_print_python_long_version(FILE *file, const char *indent,
497  bool tools_api)
498 {
499  struct Option *opt;
500  struct Flag *flag;
501  int new_prompt = 0;
502 
503  new_prompt = G__uses_new_gisprompt();
504 
505  // Options (key-value parameters)
506  if (st->n_opts) {
507  opt = &st->first_option;
508  while (opt != NULL) {
509  print_python_option(file, opt, indent, tools_api);
510  opt = opt->next_opt;
511  fprintf(file, MD_NEWLINE);
512  fprintf(file, "\n");
513  }
514  }
515 
516  // Short (one-letter) flags and tool-specific long flags
517  if (st->n_flags) {
518  fprintf(file, "%s**flags** : str, *optional*", indent);
519  fprintf(file, MD_NEWLINE);
520  fprintf(file, "\n");
521  fprintf(file, "%s", indent);
522  G__md_print_escaped(file, "\t");
523  fprintf(file, "Allowed values: ");
524  flag = &st->first_flag;
525  while (st->n_flags && flag != NULL) {
526  fprintf(file, "*%s*", &flag->key);
527  flag = flag->next_flag;
528  if (flag != NULL)
529  fprintf(file, ", ");
530  }
531  fprintf(file, MD_NEWLINE);
532  fprintf(file, "\n");
533  flag = &st->first_flag;
534  while (st->n_flags && flag != NULL) {
535  print_python_short_flag(file, &flag->key, flag->label,
536  flag->description, indent);
537  fprintf(file, MD_NEWLINE);
538  fprintf(file, "\n");
539  flag = flag->next_flag;
540  }
541  }
542  if (new_prompt) {
543  print_python_long_flag(
544  file, "overwrite", NULL,
545  _("Allow output files to overwrite existing files"), indent);
546  fprintf(file, MD_NEWLINE);
547  fprintf(file, "\n");
548  }
549  // Pre-defined long flags
550  print_python_long_flag(file, "verbose", NULL, _("Verbose module output"),
551  indent);
552  fprintf(file, MD_NEWLINE);
553  fprintf(file, "\n");
554  print_python_long_flag(file, "quiet", NULL, _("Quiet module output"),
555  indent);
556  fprintf(file, MD_NEWLINE);
557  fprintf(file, "\n");
558  print_python_long_flag(file, "superquiet", NULL,
559  _("Very quiet module output"), indent);
560  fprintf(file, MD_NEWLINE);
561  fprintf(file, "\n");
562 }
#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