1 /*--------------------------------------------------------------------
2 *
3 * Copyright (c) 1991-2021 by the GMT Team (https://www.generic-mapping-tools.org/team.html)
4 * See LICENSE.TXT file for copying and redistribution conditions.
5 *
6 * This program is free software; you can redistribute it and/or modify
7 * it under the terms of the GNU Lesser General Public License as published by
8 * the Free Software Foundation; version 3 or any later version.
9 *
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU Lesser General Public License for more details.
14 *
15 * Contact info: www.generic-mapping-tools.org
16 *--------------------------------------------------------------------*/
17 /*
18 * Public function prototypes for GMT API option parsing.
19 *
20 * Author: Paul Wessel
21 * Date: 1-JUN-2010
22 * Version: 5
23 *
24 * The API presently consists of 52 documented functions. For a full
25 * description of the API, see the GMT_API documentation.
26 * These functions have Fortran bindings as well, provided you add
27 * -DFORTRAN_API to the C preprocessor flags [in ConfigUserAdvanced.cmake].
28 *
29 * Here lie the 13 public functions used for GMT API command parsing:
30 *
31 * GMT_Create_Options : Convert an array of text args to a linked option list
32 * GMT_Destroy_Options : Delete the linked option list
33 * GMT_Create_Args : Convert a struct option list back to an array of text args
34 * GMT_Destroy_Args : Delete the array of text args
35 * GMT_Create_Cmd : Convert a struct option list to a single command text.
36 * GMT_Destroy_Cmd : Delete the command string
37 * GMT_Make_Option : Create a single option structure given arguments
38 * GMT_Find_Option : Find a specified option in the linked option list
39 * GMT_Update_Option : Update the arguments of the specified option in the list
40 * GMT_Append_Option : Append the given option to the end of the structure list
41 * GMT_Delete_Option : Delete the specified option and adjust the linked list
42 * GMT_Parse_Common : Parse the common GMT options
43 * GMT_Expand_Option : Replace special marker (?) with naemd argument [external API only]
44
45 * This part of the API helps the developer create, manipulate, modify, find, and
46 * update options that will be passed to various GMT_* modules ("The GMT programs").
47 * This involves converting from text arrays (e.g., argc, argv[]) to the linked list
48 * of structures used by these functions, manipulate these lists, and perhaps even
49 * creating text arrays from the linked list. All these functions are pass the
50 * API pointer and if that is NULL then errors will be issued.
51 *
52 * There are no GMT devel functions exported by this file.
53 */
54
55 /*!
56 * \file gmt_parse.c
57 * \brief Public function prototypes for GMT API option parsing.
58 */
59
60 #include "gmt_dev.h"
61 #include "gmt_internals.h"
62
63 static char *GMT_unique_option[GMT_N_UNIQUE] = { /* The common GMT command-line options [ just the subset that accepts arguments (e.g., -O is not listed) ] */
64 #include "gmt_unique.h"
65 };
66
67 /* Some private macros and inline function used in this file */
68 #define ptr_return(API,err,ptr) { gmtlib_report_error(API,err); return (ptr);}
69 #define return_null(API,err) { gmtlib_report_error(API,err); return (NULL);}
70 #define return_error(API,err) { gmtlib_report_error(API,err); return (err);}
71 #define return_value(API,err,val) { gmtlib_report_error(API,err); return (val);}
72
gmtparse_get_api_ptr(struct GMTAPI_CTRL * ptr)73 static inline struct GMTAPI_CTRL *gmtparse_get_api_ptr (struct GMTAPI_CTRL *ptr) {return (ptr);}
74
75 /* Local functions */
76
77 /*! . */
gmtparse_B_arg_inspector(struct GMT_CTRL * GMT,char * in)78 GMT_LOCAL int gmtparse_B_arg_inspector (struct GMT_CTRL *GMT, char *in) {
79 /* Determines if the -B option indicates old GMT4-style switches and flags
80 * or if it follows the GMT 5 specification. This is only called when
81 * compatibility mode has been selected; otherwise we only check GMT 5
82 * style arguments. We return 5 for GMT5 style, 4 for GMT4 style, 9
83 * if no decision could be make and -1 if mixing of GMT4 & 5 styles are
84 * found, which is an error. */
85 size_t k, j, last;
86 int gmt4 = 0, gmt5 = 0, n_digits = 0, n_colons = 0, n_slashes = 0, colon_text = 0, wesn_at_end = 0;
87 bool ignore = false; /* true if inside a colon-separated string under GMT4 style assumption */
88 bool ignore5 = false; /* true if label, title, prefix, suffix */
89 bool custom = false; /* True if -B[p|s][x|y|z]c<filename> was given; then we relax checking for .c (old second) */
90 char mod = 0;
91
92 if (!in || in[0] == 0) return (9); /* Just a safety precaution, 9 means "either" syntax but it is an empty string */
93 last = strlen (in);
94 for (k = 0; k < last; k++) { /* Count digits. If none then it is GMT5 and we can return */
95 if (isdigit (in[k])) n_digits++;
96 }
97 if (n_digits == 0) return (5); /* If no numbers are given then we know it is GMT5 */
98 k = (in[0] == 'p' || in[0] == 's') ? 1 : 0; /* Skip p|s in -Bp|s */
99 if (strchr ("xyz", in[k])) gmt5++; /* Definitively GMT5 */
100 if (k == 0 && !isdigit (in[0]) && strchr ("WESNwesn", in[1])) gmt5++; /* Definitively GMT5 */
101 j = k;
102 while (j < last && (in[j] == 'x' || in[j] == 'y' || in[j] == 'z')) j++;
103 custom = (in[j] == 'c'); /* Got -B[p|s][xyz]c<customfile> */
104 for (k = 0; k < last; k++) {
105 if (k && in[k] == '+' && in[k-1] == '@') { /* Found a @+ PSL sequence, just skip */
106 continue; /* Resume processing */
107 }
108 if (ignore5) continue; /* Don't look inside a GMT5 title, label, suffix, or prefix */
109 if (in[k] == ':') {
110 #ifdef _WIN32 /* Filenames under Windows may be X:\<name> which should not trigger "colon" test */
111 if (!(k && isalpha (in[k-1]) && k < last && in[k+1] == '\\'))
112 #endif
113 if (k && in[k-1] == '@') { /* Found a @:[<font>]: sequence, scan to end */
114 k++; /* Skip past the colon */
115 while (in[k] && in[k] != ':') k++; /* Find the matching colon */
116 continue; /* Resume processing */
117 }
118 ignore = !ignore, n_colons++; /* Possibly stepping into a label/title */
119 if (!ignore) continue; /* End of title or label, skip check for next character */
120 if (k < last && in[k+1] == '.') colon_text++; /* Title */
121 else if (k < last && in[k+1] == '=') colon_text++; /* Annotation prefix */
122 else if (k < last && in[k+1] == ',') colon_text++; /* Annotation suffix */
123 }
124 if (ignore) continue; /* Don't look inside a title or label */
125 switch (in[k]) {
126 case '/': if (mod == 0) n_slashes++; break; /* Only GMT4 uses slashes */
127 case '+': /* Plus, might be a GMT5 modifier switch */
128 if (k < last && in[k+1] == 'u') {mod = 'u'; ignore5 = true; gmt5++;} /* unit (suffix) settings */
129 else if (k < last && in[k+1] == 'b') {mod = 'b'; ignore5 = false; gmt5++;} /* 3-D box settings */
130 else if (k < last && in[k+1] == 'g') {mod = 'g'; ignore5 = false; gmt5++;} /* fill settings */
131 else if (k < last && in[k+1] == 'i') {mod = 'i'; ignore5 = false; gmt5++;} /* internal annotation settings */
132 else if (k < last && in[k+1] == 'n') {mod = 'n'; ignore5 = true; gmt5++;} /* Turn off frames and annotations */
133 else if (k < last && in[k+1] == 'o') {mod = 'o'; ignore5 = false; gmt5++;} /* oblique pole settings */
134 else if (k < last && in[k+1] == 'p') {mod = 'p'; ignore5 = true; gmt5++;} /* prefix settings */
135 else if (k < last && in[k+1] == 'l') {mod = 'l'; ignore5 = true; gmt5++;} /* Label */
136 else if (k < last && in[k+1] == 'L') {mod = 'L'; ignore5 = true; gmt5++;} /* Forced horizontal Label */
137 else if (k < last && in[k+1] == 's') {mod = 's'; ignore5 = true; gmt5++;} /* Secondary label */
138 else if (k < last && in[k+1] == 'S') {mod = 'S'; ignore5 = true; gmt5++;} /* Forced horizontal Secondary lLabel */
139 else if (k < last && in[k+1] == 't') {mod = 't'; ignore5 = true; gmt5++;} /* title */
140 else if (k < last && in[k+1] == 'w') {mod = 'w'; ignore5 = false; gmt5++;} /* Pen for walls */
141 else if (k < last && in[k+1] == 'x') {mod = 'x'; ignore5 = false; gmt5++;} /* Paint for yz plane */
142 else if (k < last && in[k+1] == 'y') {mod = 'y'; ignore5 = false; gmt5++;} /* Paint for xz plane */
143 else if (k < last && in[k+1] == 'z') {mod = 'z'; ignore5 = false; gmt5++;} /* Paint for xy plane */
144 else if (k && (in[k-1] == 'Z' || in[k-1] == 'z')) {ignore5 = false; gmt4++;} /* Z-axis with 3-D box */
145 break;
146 case 'c': /* If following a number this is unit c for seconds in GMT4 */
147 if (!custom && k && (in[k-1] == '.' || isdigit (in[k-1]))) gmt4++; /* Old-style second unit */
148 break;
149 case 'W': case 'E': case 'S': case 'N': case 'Z': case 'w': case 'e': case 'z': /* Not checking s as confusion with seconds and n because of +n */
150 if (k > 1) wesn_at_end++; /* GMT5 has -B<WESNwesn> up front while GMT4 usually has them at the end */
151 break;
152 case 'n': /* Tell this apart from +n */
153 if (!(k && in[k-1] == '+') && k > 1) wesn_at_end++; /* GMT5 has -B<WESNwesn> up front while GMT4 usually has them at the end */
154 break;
155 }
156 }
157 if (!gmt5 && wesn_at_end) gmt4++; /* Presumably got WESNwesn stuff towards the end of the option */
158 if (n_colons && (n_colons % 2) == 0) gmt4++; /* Presumably :labels: in GMT4 style as any +mod would have kicked in above */
159 if (!custom && n_slashes) gmt4++; /* Presumably / to separate axis in GMT4 style */
160 if (colon_text) gmt4++; /* Gave title, suffix, prefix in GMT4 style */
161 GMT_Report (GMT->parent, GMT_MSG_DEBUG, "gmtparse_B_arg_inspector: GMT4 = %d vs. GMT = %d\n", gmt4, gmt5);
162 if (gmt5 && !gmt4) {
163 GMT_Report (GMT->parent, GMT_MSG_DEBUG, "gmtparse_B_arg_inspector: Detected GMT >=5 style elements in -B option\n");
164 return (5);
165 }
166 else if (gmt4 && !gmt5) {
167 GMT_Report (GMT->parent, GMT_MSG_DEBUG, "gmtparse_B_arg_inspector: Detected GMT 4 style elements in -B option\n");
168 return (4);
169 }
170 else if (gmt4 && gmt5) { /* Mixed case is never allowed */
171 GMT_Report (GMT->parent, GMT_MSG_ERROR, "gmtparse_B_arg_inspector: Detected both GMT 4 and >= style elements in -B option. Unable to parse.\n");
172 if (n_slashes) GMT_Report (GMT->parent, GMT_MSG_ERROR, "gmtparse_B_arg_inspector: Slashes no longer separate axis specifications, use -B[xyz] and repeat\n");
173 if (colon_text || n_colons) GMT_Report (GMT->parent, GMT_MSG_ERROR, "gmtparse_B_arg_inspector: Colons no longer used for titles, labels, prefix, and suffix; see +t, +l, +p, +s\n");
174 return (-1);
175 }
176 else {
177 GMT_Report (GMT->parent, GMT_MSG_DEBUG, "gmtparse_B_arg_inspector: Assume new GMT style format in -B option\n");
178 return (9);
179 }
180 }
181
182 /*! . */
gmtparse_check_b_options(struct GMT_CTRL * GMT,struct GMT_OPTION * options)183 GMT_LOCAL int gmtparse_check_b_options (struct GMT_CTRL *GMT, struct GMT_OPTION *options) {
184 /* Determine how many -B options were given and if it is clear we are
185 * dealing with GMT4 or GMT5 syntax just by looking at this information.
186 * We also examine each argument for clues to version compatibility.
187 * We return 5 if it is clear this is GMT5 syntax, 4 if it is clear it
188 * is GMT 4 syntax, 9 if we cannot tell, and -1 if it is a mix of both.
189 * The latter will result in a syntax error.
190 */
191 struct GMT_OPTION *opt = NULL;
192 unsigned int n4_expected = 0, n_B = 0, gmt4 = 0, gmt5 = 0, k;
193 int verdict;
194
195 for (opt = options; opt; opt = opt->next) { /* Loop over all given options */
196 if (opt->option != 'B') continue; /* Skip anything but -B options */
197 n_B++; /* Count how many (max 2 in GMT4 if -Bp|s given) */
198 k = (opt->arg[0] == 'p' || opt->arg[0] == 's') ? 1 : 0; /* Step over any p|s designation */
199 if (k == 1) n4_expected++; /* Count how many -Bp or -Bs were given */
200 verdict = gmtparse_B_arg_inspector (GMT, opt->arg); /* Check this argument, return 5, 4, 1 (undetermined) or 0 (mixing) */
201 if (verdict == 4) gmt4++;
202 if (verdict == 5) gmt5++;
203 if (verdict == -1) gmt4++, gmt5++; /* This is bad and will lead to a syntax error */
204 }
205 if (n4_expected == 0) n4_expected = 1; /* If there are no -Bs|p options then GMT4 expects a single -B option */
206 if (n_B > n4_expected) gmt5++; /* Gave more -B options than expected for GMT4 */
207 if (gmt5 && !gmt4) return 5; /* Matched GMT5 syntax only */
208 if (gmt4 && !gmt5) return 4; /* Matched GMT4 syntax only */
209 if (!gmt4 && !gmt5) return 9; /* Could be either */
210 return (-1); /* Error: Cannot be both */
211 }
212
gmtparse_count_slashes(char * txt)213 GMT_LOCAL unsigned int gmtparse_count_slashes (char *txt) {
214 unsigned int i, n;
215 for (i = n = 0; txt[i]; i++) if (txt[i] == '/') n++;
216 return (n);
217 }
218
219 /*! . */
gmtparse_check_extended_R(struct GMT_CTRL * GMT,struct GMT_OPTION * options)220 GMT_LOCAL unsigned int gmtparse_check_extended_R (struct GMT_CTRL *GMT, struct GMT_OPTION *options) {
221 /* In order to use -R[L|C|R][B|M|T]<lon0>/<lat0>/<n_columns>/<ny> we need access
222 * to grid increments dx/dy, usually given via a -I option. Hence, we here
223 * make sure that if such a -R option is given we first process -I */
224
225 struct GMT_OPTION *opt = NULL;
226 bool got_extended_R = false;
227 for (opt = options; opt; opt = opt->next) {
228 if (opt->option == 'R' && gmtparse_count_slashes (opt->arg) == 3 && strchr ("LCRlcr", opt->arg[0]) && strchr ("TMBtmb", opt->arg[1]))
229 got_extended_R = true;
230 }
231 if (!got_extended_R) return 0; /* No such situation */
232
233 /* Now look for -Idx[/dy] option */
234 for (opt = options; opt; opt = opt->next) {
235 if (opt->option == 'I' && opt->arg[0]) {
236 if (!gmt_getinc (GMT, opt->arg, GMT->common.R.inc)) { /* Successful parsing */
237 GMT->common.R.active[ISET] = true;
238 }
239 }
240 }
241 if (GMT->common.R.active[ISET])
242 return 0;
243 GMT_Report (GMT->parent, GMT_MSG_ERROR, "-R[L|C|R][T|M|B]<x0>/<y0>/<n_columns>/<ny> requires grid spacings via -I\n");
244 return 1;
245 }
246
247 #define Return { GMT_Report (GMT->parent, GMT_MSG_ERROR, "Found no history for option -%s\n", str); return (-1); }
248
249 /*! . */
gmtparse_complete_options(struct GMT_CTRL * GMT,struct GMT_OPTION * options)250 GMT_LOCAL int gmtparse_complete_options (struct GMT_CTRL *GMT, struct GMT_OPTION *options) {
251 /* Go through the given arguments and look for shorthands,
252 * i.e., -B, -J, -R, -X, -x, -Y, -c, -p. given without arguments.
253 * If found, see if we have a matching command line history and then
254 * update that entry in the option list.
255 * Finally, keep the option arguments in the history list.
256 * However, when func_level > GMT_TOP_MODULE, do update the entry, but do not
257 * remember it in history. Note, there are two special cases here:
258 * -J is special since we also need to deal with the sub-species
259 * like -JM, -JX, etc. So these all have separate entries.
260 * -B is special because the option is repeatable for different
261 * aspects of the basemap. We concatenate all of them to store
262 * in the history file and use RS = ASCII 30 as separator.
263 * Also, a single -B in the options may expand to several
264 * separate -B<args> so the linked options list may grow.
265 */
266 int id = 0, k, n_B = 0, B_id, update_id = 0;
267 unsigned int pos = 0, B_replace = 1;
268 bool update, remember, check_B;
269 struct GMT_OPTION *opt = NULL, *opt2 = NULL, *B_next = NULL;
270 char str[3] = {""}, B_string[GMT_BUFSIZ] = {""}, p[GMT_BUFSIZ] = {""}, B_delim[2] = {30, 0}; /* Use ASCII 30 RS Record Separator between -B strings */
271
272 remember = (GMT->hidden.func_level == GMT_TOP_MODULE); /* Only update the history for top level function */
273
274 for (opt = options; opt; opt = opt->next) {
275 if (opt->option == 'B') { /* Do some initial counting of how many -B options and determine if there is just one with no args */
276 if (n_B > 0 || opt->arg[0]) B_replace = 0;
277 n_B++;
278 }
279 }
280 for (k = 0, B_id = GMT_NOTSET; k < GMT_N_UNIQUE && B_id == GMT_NOTSET; k++)
281 if (!strcmp (GMT_unique_option[k], "B")) B_id = k; /* B_id === 0 but just in case this changes we do this search anyway */
282 assert (B_id != GMT_NOTSET); /* Safety valve just in case */
283 check_B = (strncmp (GMT->init.module_name, "psscale", 7U) && strncmp (GMT->init.module_name, "docs", 4U));
284 if (GMT->current.setting.run_mode == GMT_MODERN && n_B && check_B) { /* Write gmt.frame.<fig> file unless module is psscale, overwriting any previous file */
285 char file[PATH_MAX] = {""};
286 FILE *fp = NULL;
287 int fig = gmt_get_current_figure (GMT->parent); /* Get current figure number */
288 snprintf (file, PATH_MAX, "%s/gmt.frame.%d", GMT->parent->gwf_dir, fig);
289 if ((fp = fopen (file, "w")) == NULL) {
290 GMT_Report (GMT->parent, GMT_MSG_DEBUG, "Unable to create file %s\n", file);
291 return (-1);
292 }
293 for (k = 0, opt = options; opt; opt = opt->next) {
294 if (opt->option != 'B') continue;
295 if (k) fprintf (fp, "%s", B_delim);
296 fprintf (fp, "%s", opt->arg);
297 k++;
298 }
299 fprintf (fp, "\n");
300 fclose (fp);
301 }
302 for (opt = options; opt; opt = opt->next) {
303 if (!strchr (GMT_SHORTHAND_OPTIONS, opt->option)) continue; /* Not one of the shorthand options */
304 if (GMT->current.setting.run_mode == GMT_MODERN && opt->option == 'B') continue; /* The -B option is NOT a shorthand option under modern mode */
305 update = false;
306 GMT_Report (GMT->parent, GMT_MSG_DEBUG, "History: Process -%c%s\n", opt->option, opt->arg);
307
308 str[0] = opt->option; str[1] = str[2] = '\0';
309 if (opt->option == 'J') { /* -J is special since it can be -J or -J<code> */
310
311 /* Always look up "J" first. It comes before "J?" and tells what the last -J was */
312 if ((id = gmt_get_option_id (0, str)) == GMT_NOTSET) Return; /* No -J found at all - nothing more to do */
313 if (opt->arg && opt->arg[0]) { /* Gave -J<code>[<args>] so we either use or update history and continue */
314 str[1] = opt->arg[0];
315 /* Remember this last -J<code> for later use as -J, but do not remember it when -Jz|Z */
316 if (remember) {
317 if (str[1] == 'Z' || str[1] == 'z') {
318 int z_id = gmt_get_option_id (0, "Z");
319 gmt_M_str_free (GMT->init.history[z_id]);
320 GMT->init.history[z_id] = strdup (&str[1]);
321 }
322 else {
323 gmt_M_str_free (GMT->init.history[id]);
324 GMT->init.history[id] = strdup (&str[1]);
325 }
326 }
327 if (opt->arg[1]) update = true; /* Gave -J<code><args> so we want to update history and continue */
328 }
329 else {
330 if (!GMT->init.history[id]) Return;
331 str[1] = GMT->init.history[id][0];
332 }
333 /* Continue looking for -J<code> */
334 if ((id = gmt_get_option_id (id + 1, str)) == GMT_NOTSET) Return; /* No -J<code> found */
335 update_id = id;
336 }
337 else if (opt->option == 'B') { /* -B is also special since there may be many of these, or just -B */
338 if (B_replace) { /* Only -B is given and we want to use the history */
339 if (B_replace == 2) continue; /* Already done this */
340 if (!GMT->init.history[B_id]) Return;
341 opt2 = opt; /* Since we don't want to change the opt loop avove */
342 B_next = opt->next; /* Pointer to option following the -B option */
343 gmt_M_str_free (opt2->arg);/* Free previous pointer to arg */
344 if (gmt_strtok (GMT->init.history[B_id], B_delim, &pos, p)) /* Get the first argument */
345 opt2->arg = strdup (p); /* Update arg */
346 while (gmt_strtok (GMT->init.history[B_id], B_delim, &pos, p)) { /* Parse any additional |<component> statements */
347 opt2->next = GMT_Make_Option (GMT->parent, 'B', p); /* Create new struct */
348 opt2->next->previous = opt2;
349 opt2 = opt2->next;
350 }
351 opt2->next = B_next; /* Hook back onto main option list */
352 B_replace = 2; /* Flag to let us know we are done with -B */
353 }
354 else { /* One of possibly several -B<arg> options; concatenate and separate by RS */
355 if (B_string[0]) strcat (B_string, B_delim); /* Add RS separator between args */
356 strncat (B_string, opt->arg, GMT_LEN256-1);
357 }
358 }
359 else { /* Gave -R[<args>], -V[<args>] etc., so we either use or update the history and continue */
360 update_id = id = gmt_get_option_id (0, str); /* If -R then we get id for RP */
361 if (id == GMT_NOTSET) Return; /* Error: user gave shorthand option but there is no record in the history */
362 if (GMT->current.setting.run_mode == GMT_MODERN && opt->option == 'R') { /* Must deal with both RP and RG under modern mode */
363 if (gmtlib_module_may_get_R_from_RP (GMT, GMT->init.module_name)) { /* First check -RP history */
364 if (!GMT->init.history[id]) id++; /* No RP in the history, try RG next */
365 }
366 else { /* Only try RG for non-plotting modules. RG follows RP in gmt_unique.h order [Modern mode only] */
367 id++; /* Go to RG */
368 update_id = id; /* We want to update the RG history since it is not a plotter */
369 }
370 }
371 if (opt->arg && opt->arg[0]) update = true; /* Gave -R<args>, -V<args> etc. so we we want to update history and continue */
372 }
373 if (opt->option != 'B') { /* Do -B separately again after the loop so skip it here */
374 if (id < 0) Return; /* Error: user gave shorthand option but there is no record in the history */
375 if (update) { /* Gave -J<code><args>, -R<args>, -V<args> etc. so we update history and continue */
376 if (remember) {
377 gmt_M_str_free (GMT->init.history[update_id]);
378 GMT->init.history[update_id] = strdup (opt->arg);
379 }
380 }
381 else { /* Gave -J<code>, -R, -J etc. so we complete the option and continue */
382 if (!GMT->init.history[id]) Return;
383 gmt_M_str_free (opt->arg); /* Free previous pointer to arg */
384 opt->arg = strdup (GMT->init.history[id]);
385 if (id != update_id) { /* Happens for -R when we need the RG settings as first-time RP setting via -R */
386 char *c = NULL;
387 if ((c = strstr (opt->arg, "+I")) || (c = strstr (opt->arg, "+G"))) /* Strip off increment/node setting when used for plot domain */
388 c[0] = '\0';
389 if (remember) GMT->init.history[update_id] = strdup (opt->arg);
390 }
391 }
392 }
393 }
394
395 if (B_string[0] && B_id >= 0) { /* Got a concatenated string with one or more individual -B args, now separated by the RS character (ASCII 30) */
396 gmt_M_str_free (GMT->init.history[B_id]);
397 GMT->init.history[B_id] = strdup (B_string);
398 }
399
400 return (GMT_NOERROR);
401 }
402
403 /*----------------------------------------------------------|
404 * Public functions that are part of the GMT API library |
405 *----------------------------------------------------------|
406 */
407 #ifdef DEBUG
408 /*! . */
GMT_List_Args(void * V_API,struct GMT_OPTION * head)409 int GMT_List_Args (void *V_API, struct GMT_OPTION *head) {
410 /* This function dumps the options to stderr for debug purposes.
411 * It is not meant to be part of the API but to assist developers
412 * during the debug phase of development.
413 */
414
415 struct GMT_OPTION *opt = NULL;
416 struct GMTAPI_CTRL *API = gmtparse_get_api_ptr (V_API);
417
418 if (head == NULL) return_error (API, GMT_OPTION_LIST_NULL);
419
420 fprintf (stderr, "Options:");
421 for (opt = head; opt; opt = opt->next) {
422 if (!opt->option) continue; /* Skip all empty options */
423 if (opt != head) fprintf (stderr, " ");
424 if (opt->option == GMT_OPT_SYNOPSIS) /* Produce special - command for synopsis */
425 fprintf (stderr, "-");
426 else if (opt->option == GMT_OPT_INFILE) /* Option for input filename [or number] */
427 fprintf (stderr, "%s", opt->arg);
428 else if (opt->arg && opt->arg[0]) /* Regular -?arg commandline argument */
429 fprintf (stderr, "-%c%s", opt->option, opt->arg);
430 else /* Regular -? commandline argument */
431 fprintf (stderr, "-%c", opt->option);
432
433 }
434 fprintf (stderr, "\n");
435
436 return (GMT_OK); /* No error encountered */
437 }
438 #endif
439
440 /*! . */
gmtparse_fix_gdal_files(struct GMT_OPTION * opt)441 GMT_LOCAL struct GMT_OPTION *gmtparse_fix_gdal_files (struct GMT_OPTION *opt) {
442 char *pch = NULL;
443 if (((pch = strstr (opt->arg, "+b")) != NULL) && !strstr(opt->arg, "=gd")) {
444 /* Here we deal with the case that the filename has embedded a band request for gdalread, as in img.tif+b1
445 However, the issue is that for these cases the machinery is set only to parse the request in the
446 form of img.tif=gd+b1 so the trick is to insert the missing '=gd' in the filename and let t go.
447 JL 29-November 2014
448 */
449 char t[GMT_LEN256] = {""};
450 pch[0] = '\0';
451 strncpy (t, opt->arg, GMT_LEN256-1);
452 strcat (t, "=gd");
453 pch[0] = '+'; /* Restore what we have erased 2 lines above */
454 strncat(t, pch, GMT_LEN256-1);
455 gmt_M_str_free (opt->arg); /* free it so that we can extend it */
456 opt->arg = strdup (t);
457 }
458 return opt;
459 }
460
461 struct B_PRIORITY {
462 unsigned int level;
463 struct GMT_OPTION *opt;
464 };
465
gmtparse_compare_B(const void * p1,const void * p2)466 GMT_LOCAL int gmtparse_compare_B (const void *p1, const void *p2) {
467 const struct B_PRIORITY *a = p1, *b = p2;
468
469 if (a->level < b->level) return (-1);
470 if (a->level > b->level) return (+1);
471 return (0);
472 }
473
474 /*! */
gmtparse_ensure_b_and_dash_options_order(struct GMT_CTRL * GMT,struct GMT_OPTION * options)475 GMT_LOCAL struct GMT_OPTION * gmtparse_ensure_b_and_dash_options_order (struct GMT_CTRL *GMT, struct GMT_OPTION *options) {
476 bool do_sort = false, got_theme = false;
477 char *c = NULL;
478 unsigned int np, n_B = 0, n_par = 0, k, j, this_priority;
479 struct GMT_OPTION *opt, *head = NULL;
480 struct B_PRIORITY *priority = NULL; /* Worst case is -Bpx, -Bsx, ... */
481
482 if (options == NULL) return NULL; /* Nothing to do */
483
484 for (np = 0, opt = options; opt; opt = opt->next, np++); /* Count the options */
485 priority = gmt_M_memory (GMT, NULL, np, struct B_PRIORITY);
486
487 for (opt = options, k = 0; opt; opt = opt->next, k++) {
488 priority[k].opt = opt;
489 if (! (opt->option == 'B' || opt->option == '-')) continue; /* Only look at -B --PAR options here */
490 if (opt->option == '-') { /* --PAR=value */
491 if (!strncmp (opt->arg, "GMT_THEME", 9U)) { /* We need to process GMT_THEME first */
492 this_priority = 4; /* We need to process GMT_THEME first */
493 got_theme = true;
494 }
495 else
496 this_priority = 5; /* The rest can be at the end */
497 n_par++;
498 }
499 else if (gmtlib_B_is_frame (GMT, opt->arg)) { /* Do a special check for -Bs which could be confused with secondary annotations */
500 if (opt->arg[0] == 's' && opt->arg[1] == '\0') /* Place -Bs at the end */
501 this_priority = 3;
502 else /* Don't care about the other frame setting here */
503 this_priority = 2;
504 n_B++;
505 }
506 else if ((c = gmt_first_modifier (GMT, opt->arg, GMT_AXIS_MODIFIERS))) { /* Option has axis modifier(s) */
507 c[0] = '\0'; /* Temporary chop them off */
508 j = 0; /* Start of option string, then advance past any leading [p|s][x|y|z] */
509 if (strchr ("ps", opt->arg[j])) j++;
510 if (strchr ("xyz", opt->arg[j])) j++;
511 this_priority = (opt->arg[j]) ? 1 : 2; /* If nothing then probably just a label setting, else we have a leading interval setting */
512 c[0] = '+'; /* Restore modifiers */
513 n_B++;
514 }
515 else { /* Must be interval setting */
516 this_priority = 1;
517 n_B++;
518 }
519 priority[k].level = this_priority;
520 }
521 for (k = 1; k < np; k++) if (priority[k].level < priority[k-1].level) do_sort = true;
522 if (n_B <= 1 && !(got_theme && n_par > 1)) do_sort = true; /* Sorting needed */
523 if (do_sort) mergesort (priority, np, sizeof (struct B_PRIORITY), gmtparse_compare_B);
524 /* Rebuild options links */
525 head = opt = GMT_Make_Option (GMT->parent, priority[0].opt->option, priority[0].opt->arg);
526 for (k = 1; k < np; k++) {
527 opt->next = GMT_Make_Option (GMT->parent, priority[k].opt->option, priority[k].opt->arg);
528 opt = opt->next;
529 }
530 gmt_M_free (GMT, priority);
531
532 if (do_sort && n_B > 1) {
533 char *cmd = GMT_Create_Cmd (GMT->parent, head);
534 GMT_Report (GMT->parent, GMT_MSG_INFORMATION, "GMT_Parse_Options: Interval-setting -B options were reordered to appear before axis and frame -B options to ensure proper parsing.\n");
535 GMT_Report (GMT->parent, GMT_MSG_INFORMATION, "GMT_Parse_Options: New option order: %s\n", cmd);
536 GMT_Destroy_Cmd (GMT->parent, &cmd);
537 }
538 return (head);
539 }
540
541 /*! . */
GMT_Create_Options(void * V_API,int n_args_in,const void * in)542 struct GMT_OPTION *GMT_Create_Options (void *V_API, int n_args_in, const void *in) {
543 /* This function will loop over the n_args_in supplied command line arguments (in) and
544 * then creates a linked list of GMT_OPTION structures for each program option.
545 * These will in turn will be processed by the module-specific option parsers.
546 * What actually happens is controlled by n_args_in. There are three cases:
547 * n_args_in < 0 : This means that in is already a linked list and we just duplicate and return a copy.
548 * n_args_in == 0: in is a single text string with multiple options (e.g., "-R0/2/0/5 -Jx1 -O -K > file")
549 * and we must first break this command string into separate words.
550 * n_args_in > 0 : in is an array of text strings (e.g., argv[]).
551 *
552 * Note: 1. If argv[0] is the calling program name, make sure to pass argc-1, args+1 instead.
553 * 2. in == NULL is allowed only for n_args_in <= 0 (an empty list of options).
554 *
555 * The linked list returned by GMT_Create_Options should be freed by GMT_Destroy_Options().
556 */
557
558 int error = GMT_OK;
559 unsigned int arg, first_char, append = 0, n_args;
560 char option, **args = NULL, **new_args = NULL, *this_arg = NULL, buffer[GMT_LEN1024] = {""};
561 struct GMT_OPTION *head = NULL, *new_opt = NULL;
562 struct GMT_CTRL *G = NULL;
563 struct GMTAPI_CTRL *API = NULL;
564
565 if (V_API == NULL) return_null (V_API, GMT_NOT_A_SESSION); /* GMT_Create_Session has not been called */
566 if (in == NULL && n_args_in > 1) return_null (V_API, GMT_ARGV_LIST_NULL); /* Gave no argument pointer but said we had at least 1 */
567 if (in == NULL) return (NULL); /* Gave no argument pointer so a null struct is returned */
568 API = gmtparse_get_api_ptr (V_API); /* Convert V_API to a GMTAPI_CTRL pointer */
569 if (n_args_in < 0) { /* Already was given a linked list. Duplicate and return the copy */
570 struct GMT_OPTION *head_in = (struct GMT_OPTION *)in;
571 while (head_in) {
572 if ((new_opt = GMT_Make_Option (API, head_in->option, head_in->arg)) == NULL) /* Make option with the listing name flagged as option -= */
573 return_null (API, API->error); /* Create the new option structure given the args, or return the error */
574 if ((head = GMT_Append_Option (API, new_opt, head)) == NULL) /* Hook new option to the end of the list (or initiate list if new == NULL) */
575 return_null (API, API->error); /* Create the new option structure given the args, or return the error */
576 head_in = head_in->next; /* Go to next input option */
577 }
578 return (head);
579 }
580
581 G = API->GMT; /* GMT control structure */
582 if (n_args_in == 0) { /* Check if a single command line, if so break into tokens */
583 unsigned int pos = 0, new_n_args = 0, k;
584 bool quoted;
585 size_t n_alloc = GMT_SMALL_CHUNK;
586 char p[GMT_BUFSIZ] = {""}, *txt_in = strdup (in); /* Passed a single text string */
587 new_args = gmt_M_memory (G, NULL, n_alloc, char *);
588 /* txt_in can contain options that take multi-word text strings, e.g., -B+t"My title". We avoid the problem of splitting
589 * these items by temporarily replacing spaces inside quoted strings with ASCII 31 US (Unit Separator), do the strtok on
590 * space, and then replace all ASCII 31 with space at the end (we do the same for tab using ASCII 29 GS (group separator) */
591 for (k = 0, quoted = false; txt_in[k]; k++) {
592 if (txt_in[k] == '\"' || txt_in[k] == '\'') quoted = !quoted; /* Initially false, becomes true at start of quote, then false when exit quote */
593 else if (quoted && txt_in[k] == '\t') txt_in[k] = GMT_ASCII_GS;
594 else if (quoted && txt_in[k] == ' ') txt_in[k] = GMT_ASCII_US;
595 }
596 while ((gmt_strtok (txt_in, " ", &pos, p))) { /* Break up string into separate words, and strip off double quotes */
597 unsigned int i, o;
598 for (k = 0; p[k]; k++)
599 if (p[k] == GMT_ASCII_GS) p[k] = '\t'; else if (p[k] == GMT_ASCII_US) p[k] = ' '; /* Replace spaces and tabs masked above */
600 for (i = o = 0; p[i]; i++) if (p[i] != '\"') p[o++] = p[i]; /* Ignore double quotes */
601 p[o] = '\0';
602 new_args[new_n_args++] = strdup (p);
603 if (new_n_args == n_alloc) {
604 n_alloc += GMT_SMALL_CHUNK;
605 new_args = gmt_M_memory (G, new_args, n_alloc, char *);
606 }
607 }
608 for (k = 0; txt_in[k]; k++) /* Restore input string to prestine condition */
609 if (txt_in[k] == GMT_ASCII_GS) txt_in[k] = '\t'; else if (txt_in[k] == GMT_ASCII_US) txt_in[k] = ' '; /* Replace spaces and tabs masked above */
610 args = new_args;
611 n_args = new_n_args;
612 gmt_M_str_free (txt_in);
613 }
614 else {
615 args = (char **)in; /* Gave an argv[] argument */
616 n_args = n_args_in;
617 }
618 if (args == NULL && n_args) return_null (API, GMT_ARGV_LIST_NULL); /* Conflict between # of args and args being NULL */
619
620 for (arg = 0; arg < n_args; arg++) { /* Loop over all command arguments */
621
622 if (!args[arg]) continue; /* Skip any NULL arguments quietly */
623
624 /* Note: The UNIX command line will never see redirections like >, >>, and < pass as arguments, so when we check for these
625 * below it is because command-strings passed from external APIs may contain things like '-Fap -O -K >> plot.ps' */
626
627 if (args[arg][0] == '=' && args[arg][1] && !gmt_access (API->GMT, &args[arg][1], F_OK)) { /* Gave a file list which we must expand into options */
628 char **flist = NULL;
629 uint64_t n_files, f;
630 n_files = gmt_read_list (API->GMT, &args[arg][1], &flist);
631 if ((new_opt = GMT_Make_Option (API, '=', &args[arg][1])) == NULL) /* Make option with the listing name flagged as option -= */
632 return_null (API, error); /* Create the new option structure given the args, or return the error */
633 head = GMT_Append_Option (API, new_opt, head); /* Hook new option to the end of the list (or initiate list if head == NULL) */
634 GMT_Report (API, GMT_MSG_INFORMATION, "GMT_Create_Options: Must expand list file %s\n", args[arg]);
635 for (f = 0; f < n_files; f++) { /* Now expand all the listed files into options */
636 GMT_Report (API, GMT_MSG_DEBUG, "GMT_Create_Options: Adding input file: %s\n", flist[f]);
637 if ((new_opt = GMT_Make_Option (API, GMT_OPT_INFILE, flist[f])) == NULL)
638 return_null (API, error); /* Create the new option structure given the args, or return the error */
639 new_opt = gmtparse_fix_gdal_files (new_opt);
640 head = GMT_Append_Option (API, new_opt, head); /* Hook new option to the end of the list (or initiate list if head == NULL) */
641 }
642 gmt_free_list (API->GMT, flist, n_files);
643 continue;
644 }
645 else if (args[arg][0] == '<' && !args[arg][1] && (arg+1) < n_args && args[arg+1][0] != '-') /* string command with "< file" for input */
646 first_char = 0, option = GMT_OPT_INFILE, arg++;
647 else if (args[arg][0] == '>' && !args[arg][1] && (arg+1) < n_args && args[arg+1][0] != '-') /* string command with "> file" for output */
648 first_char = 0, option = GMT_OPT_OUTFILE, arg++;
649 else if (args[arg][0] == '>' && args[arg][1] == '>' && args[arg][2] == '\0' && (arg+1) < n_args && args[arg+1][0] != '-') /* string command with ">> file" for appended output */
650 first_char = 0, option = GMT_OPT_OUTFILE, append = 1, arg++;
651 else if (args[arg][0] == '+' && !args[arg][1] && n_args == 1) /* extended synopsis + */
652 first_char = 1, option = GMT_OPT_USAGE, G->common.synopsis.extended = true;
653 else if (args[arg][0] == '-' && args[arg][1] == '+' && !args[arg][2] && n_args == 1) /* extended synopsis + */
654 first_char = 1, option = GMT_OPT_USAGE, G->common.synopsis.extended = true;
655 else if (args[arg][0] != '-') /* Probably a file (could also be a gmt/grdmath OPERATOR or number, to be handled later by GMT_Make_Option) */
656 first_char = 0, option = GMT_OPT_INFILE;
657 else if (!args[arg][1]) /* Found the special synopsis option "-" */
658 first_char = 1, option = GMT_OPT_SYNOPSIS;
659 else if (!strcmp(args[arg], "--help")) /* Translate '--help' to '-?' */
660 first_char = 6, option = GMT_OPT_USAGE;
661 else if ((isdigit ((int)args[arg][1]) || args[arg][1] == '.') && !gmt_not_numeric (API->GMT, args[arg])) /* A negative number, most likely; convert to "file" for now */
662 first_char = 0, option = GMT_OPT_INFILE;
663 else { /* Most likely found a regular option flag (e.g., -D45.0/3) */
664 first_char = 2, option = args[arg][1];
665 if (option == ')') option = GMT_OPT_OUTFILE, append = 1; /* -)file is same as ">> file", i.e., append to existing file */
666 }
667
668 if (append) { /* Must prepend ">" to the filename so we select append mode when opening the file later */
669 /* First check that this file exists */
670 if (access (&args[arg][first_char], F_OK)) { /* File does not exist; revert to writing to new output file */
671 GMT_Report (API, GMT_MSG_DEBUG, "GMT_Create_Options: File %s does not exist so append (>>) changed to output (>)\n", &args[arg][first_char]);
672 this_arg = &args[arg][first_char];
673 }
674 else {
675 snprintf (buffer, GMT_LEN1024, ">%s", &args[arg][first_char]);
676 this_arg = buffer;
677 }
678 append = 0;
679 }
680 else
681 this_arg = &args[arg][first_char];
682
683 /* To store -JEPSG:n or even -Jn in history we must prefix it with a '+' sign */
684 if (option == 'J' && (isdigit(this_arg[0]) || !strncmp(this_arg, "EPSG:", 5) || !strncmp(this_arg, "epsg:", 5))) {
685 char *t = malloc (strlen(this_arg)+2);
686 sprintf (t, "+%s", this_arg);
687 if ((new_opt = GMT_Make_Option (API, option, t)) == NULL) {
688 free (t);
689 return_null (API, error); /* Create the new option structure given the args, or return the error */
690 }
691 free (t);
692 }
693 else
694 if ((new_opt = GMT_Make_Option (API, option, this_arg)) == NULL)
695 return_null (API, error); /* Create the new option structure given the args, or return the error */
696
697 if (option == GMT_OPT_INFILE) /* A special check on infiles to see if they refer to GDAL implicitly */
698 new_opt = gmtparse_fix_gdal_files (new_opt);
699
700 head = GMT_Append_Option (API, new_opt, head); /* Hook new option to the end of the list (or initiate list if head == NULL) */
701 }
702 if (n_args_in == 0) { /* Free up temporary arg list */
703 for (arg = 0; arg < n_args; arg++) gmt_M_str_free (new_args[arg]);
704 gmt_M_free (G, new_args);
705 }
706
707 /* Check if we are in presence of a oneliner. If yes that implies MODERN mode. */
708 if (n_args_in == 0) {
709 int k;
710 bool code = false;
711 char figure[GMT_LEN512] = {""}, *c = NULL;
712 struct GMT_OPTION *opt = NULL;
713 for (opt = head; opt; opt = opt->next) { /* Loop over all options */
714 if (opt->option == 'O' || opt->option == 'K') break; /* Cannot be a one-liner if -O or -K are involved */
715 snprintf (figure, GMT_LEN512, "%c%s", opt->option, opt->arg); /* So -png, which would be parse into -p and ng, are reunited to png */
716 if ((c = strchr (figure, ','))) c[0] = 0; /* Chop of more than one format for the id test */
717 if ((k = gmt_get_graphics_id (API->GMT, figure)) == GMT_NOTSET) continue; /* Not a quicky one-liner option */
718 if (opt->next && opt->next->option == GMT_OPT_INFILE) /* Found a -ext[,ext,ext,...] <prefix> pair */
719 code = true;
720 }
721 if (code)
722 API->GMT->current.setting.run_mode = GMT_MODERN;
723 }
724
725 return (head); /* We return the linked list */
726 }
727
728 /*! . */
GMT_Destroy_Options(void * V_API,struct GMT_OPTION ** head)729 int GMT_Destroy_Options (void *V_API, struct GMT_OPTION **head) {
730 /* Delete the entire linked list of options, such as those created by GMT_Create_Options */
731
732 struct GMT_OPTION *current = NULL, *to_delete = NULL;
733 struct GMTAPI_CTRL *API = NULL;
734
735 if (V_API == NULL) return_error (V_API, GMT_NOT_A_SESSION); /* GMT_Create_Session has not been called */
736 API = gmtparse_get_api_ptr (V_API); /* Cast to GMTAPI_CTRL pointer */
737
738 current = *head;
739 if (current && current->option < 0) {
740 GMT_Report(API, GMT_MSG_ERROR, "GMT_Destroy_Options(): GMT_OPTION struct has junk. Returning before crash\n");
741 *head = NULL;
742 return (GMT_OK); /* Should we return an error state instead? */
743 }
744
745 while (current) { /* Start at head and loop over the list and delete the options, one by one. */
746 to_delete = current; /* The one we want to delete */
747 current = current->next; /* But first grab the pointer to the next item */
748 gmt_M_str_free (to_delete->arg); /* The option had a text argument allocated by strdup, so free it first */
749 gmt_M_free (API->GMT, to_delete); /* Then free the structure which was allocated by gmt_M_memory */
750 }
751 *head = NULL; /* Reset head to NULL value since it no longer points to any allocated memory */
752 return (GMT_OK); /* No error encountered */
753 }
754
755 /*! . */
GMT_Duplicate_Options(void * V_API,struct GMT_OPTION * head)756 struct GMT_OPTION *GMT_Duplicate_Options (void *V_API, struct GMT_OPTION *head) {
757 /* Create an identical copy of the linked list of options pointed to by head */
758 struct GMT_OPTION *opt = NULL, *new_opt = NULL, *new_head = NULL;
759 struct GMTAPI_CTRL *API = NULL;
760
761 if (V_API == NULL) return_null (V_API, GMT_NOT_A_SESSION); /* GMT_Create_Session has not been called */
762 if (head == NULL) return_null (V_API, GMT_OPTION_LIST_NULL); /* No list of options was given */
763 API = gmtparse_get_api_ptr (V_API); /* Cast void pointer to a GMTAPI_CTRL pointer */
764
765 for (opt = head; opt; opt = opt->next) { /* Loop over all options in the linked list */
766 if ((new_opt = GMT_Make_Option (API, opt->option, opt->arg)) == NULL)
767 return_null (API, API->error); /* Create the new option structure given the args, or return the error */
768 if ((new_head = GMT_Append_Option (API, new_opt, new_head)) == NULL) /* Hook new option to the end of the list (or initiate list if new == NULL) */
769 return_null (API, API->error); /* Create the new option structure given the args, or return the error */
770 }
771 return (new_head);
772 }
773
774 /*! . */
GMT_Free_Option(void * V_API,struct GMT_OPTION ** opt)775 int GMT_Free_Option (void *V_API, struct GMT_OPTION **opt) {
776 struct GMTAPI_CTRL *API = NULL;
777 /* Free the memory pointed to by *opt */
778 if (V_API == NULL) return_error (V_API, GMT_NOT_A_SESSION); /* GMT_Create_Session has not been called */
779 if (*opt == NULL) return_error (V_API, GMT_OPTION_LIST_NULL); /* No list of options was given */
780 API = gmtparse_get_api_ptr (V_API); /* Cast void pointer to a GMTAPI_CTRL pointer */
781 gmt_M_str_free ((*opt)->arg); /* The option had a text argument allocated by strdup, so free it first */
782 gmt_M_free (API->GMT, *opt); /* Then free the structure which was allocated by gmt_M_memory */
783 *opt = NULL;
784 return (GMT_OK); /* No error encountered */
785 }
786
787 /*! . */
GMT_Create_Args(void * V_API,int * argc,struct GMT_OPTION * head)788 char ** GMT_Create_Args (void *V_API, int *argc, struct GMT_OPTION *head) {
789 /* This function creates a character array with the command line options that
790 * correspond to the linked options provided. It is the inverse of GMT_Create_Options.
791 * The number of array strings is returned via *argc.
792 */
793
794 char **txt = NULL, buffer[GMT_BUFSIZ] = {""};
795 int arg = 0;
796 bool skip_infiles = false;
797 struct GMT_OPTION *opt = NULL;
798 struct GMT_CTRL *G = NULL;
799 struct GMTAPI_CTRL *API = NULL;
800
801 if (V_API == NULL) return_null (V_API, GMT_NOT_A_SESSION); /* GMT_Create_Session has not been called */
802 if (head == NULL) return_null (V_API, GMT_OPTION_LIST_NULL); /* No list of options was given */
803 API = gmtparse_get_api_ptr (V_API); /* Cast void pointer to a GMTAPI_CTRL pointer */
804
805 *argc = 0; /* Start off with no arguments */
806 for (opt = head; opt; opt = opt->next) /* Loop over all options in the linked list */
807 if (opt->option) (*argc)++; /* Found non-empty option */
808 if (*argc == 0) return NULL; /* Found no options, so we are done */
809
810 G = API->GMT; /* GMT control structure */
811 txt = gmt_M_memory (G, NULL, *argc, char *); /* Allocate text arg array of given length */
812
813 for (opt = head; opt; opt = opt->next) { /* Loop over all options in the linked list */
814 if (!opt->option) continue; /* Skip all empty options */
815 if (opt->option == GMT_OPT_SYNOPSIS) /* Produce special - option for synopsis */
816 sprintf (buffer, "-");
817 else if (opt->option == '=' && opt->arg[0]) { /* Start of long list of file args initially given via -=<list> */
818 snprintf (buffer, GMT_BUFSIZ, "=%s", opt->arg);
819 skip_infiles = true;
820 }
821 else if (opt->option == GMT_OPT_INFILE) { /* Option for input filename [or numbers] */
822 if (skip_infiles) continue;
823 snprintf (buffer, GMT_BUFSIZ, "%s", opt->arg);
824 }
825 else if (opt->arg && opt->arg[0]) /* Regular -?arg commandline option with argument for some ? */
826 snprintf (buffer, GMT_BUFSIZ, "-%c%s", opt->option, opt->arg);
827 else /* Regular -? commandline argument without argument */
828 snprintf (buffer, GMT_BUFSIZ, "-%c", opt->option);
829
830 txt[arg] = gmt_M_memory (G, NULL, strlen (buffer)+1, char); /* Get memory for this item */
831
832 /* Copy over the buffer contents */
833 strcpy (txt[arg], buffer);
834 arg++; /* One more option added */
835 }
836 /* OK, done processing all options */
837 if (arg < *argc) { /* Shrink the list to fit */
838 *argc = arg;
839 txt = gmt_M_memory (G, txt, *argc, char *);
840 }
841
842 return (txt); /* Pass back the char** array to the calling module */
843 }
844
845 /*! . */
GMT_Destroy_Args(void * V_API,int argc,char ** args[])846 int GMT_Destroy_Args (void *V_API, int argc, char **args[]) {
847 /* Delete all text arguments, perhaps those created by GMT_Create_Args
848 * Note that a pointer to the args[] array is expected so that we can
849 * set it to NULL afterwards. */
850
851 struct GMTAPI_CTRL *API = NULL;
852 if (V_API == NULL) return_error (V_API, GMT_NOT_A_SESSION); /* GMT_Create_Session has not been called */
853 if (argc == 0 || !args) return_error (V_API, GMT_ARGV_LIST_NULL); /* We were given no args to destroy, so there! */
854 if (argc < 0) return_error (V_API, GMT_COUNTER_IS_NEGATIVE); /* We were given a negative count! */
855 API = gmtparse_get_api_ptr (V_API); /* Cast void pointer to a GMTAPI_CTRL pointer */
856 /* Just deallocate the space taken by the list of arguments */
857 while (argc--) gmt_M_free (API->GMT, (*args)[argc]);
858 gmt_M_free (API->GMT, *args); /* Free the array itself */
859 return (GMT_OK); /* No error encountered */
860 }
861
862 /*! . */
GMT_Create_Cmd(void * V_API,struct GMT_OPTION * head)863 char *GMT_Create_Cmd (void *V_API, struct GMT_OPTION *head) {
864 /* This function creates a single character string with the command line options that
865 * correspond to the linked options provided.
866 */
867
868 char *txt = NULL, *c = NULL, buffer[GMT_BUFSIZ] = {""};
869 bool first = true, skip_infiles = false;
870 int k_data;
871 size_t length = 0, inc, n_alloc = GMT_BUFSIZ;
872 struct GMT_OPTION *opt = NULL;
873 struct GMT_CTRL *G = NULL;
874 struct GMTAPI_CTRL *API = NULL;
875
876 if (V_API == NULL) return_null (V_API, GMT_NOT_A_SESSION); /* GMT_Create_Session has not been called */
877 if (head == NULL) return_null (V_API, GMT_OPTION_LIST_NULL); /* No list of options was given */
878 API = gmtparse_get_api_ptr (V_API); /* Cast void pointer to a GMTAPI_CTRL pointer */
879
880 G = API->GMT; /* GMT control structure */
881 txt = gmt_M_memory (G, NULL, n_alloc, char);
882
883 for (opt = head; opt; opt = opt->next) { /* Loop over all options in the linked list */
884 if (!opt->option) continue; /* Skip all empty options */
885 if (opt->option == GMT_OPT_SYNOPSIS) /* Produce special - option for synopsis */
886 sprintf (buffer, "-");
887 else if (opt->option == '=' && opt->arg[0]) { /* Special list file, add it but not the files that follow */
888 snprintf (buffer, GMT_BUFSIZ, "=%s", opt->arg);
889 skip_infiles = true;
890 }
891 else if (opt->option == GMT_OPT_INFILE) { /* Option for input filename [or numbers] */
892 if (skip_infiles) continue;
893 if (gmt_file_is_tiled_list (API, opt->arg, &k_data, NULL, NULL)) { /* Want to replace the tiled list with the original @remotefile name instead */
894 snprintf (buffer, GMT_BUFSIZ, "@%s", API->remote_info[k_data].file);
895 }
896 else if ((k_data = gmt_remote_dataset_id (API, opt->arg)) != GMT_NOTSET && API->remote_info[k_data].ext[0] && (c = strstr (opt->arg, API->remote_info[k_data].ext))) {
897 c[0] = '\0'; /* Remove extension on remote file */
898 snprintf (buffer, GMT_BUFSIZ, "%s", opt->arg);
899 c[0] = '.';
900 }
901 else
902 snprintf (buffer, GMT_BUFSIZ, "%s", opt->arg);
903 }
904 else if (opt->arg && opt->arg[0]) { /* Regular -?arg commandline option with argument for some ? */
905 if (strchr (opt->arg, ' ')) /* Has a space in the argument, e.g., a title or similar */
906 snprintf (buffer, GMT_BUFSIZ, "-%c\"%s\"", opt->option, opt->arg);
907 else
908 snprintf (buffer, GMT_BUFSIZ, "-%c%s", opt->option, opt->arg);
909 }
910 else /* Regular -? commandline argument without argument */
911 snprintf (buffer, GMT_BUFSIZ, "-%c", opt->option);
912
913 inc = strlen (buffer);
914 if (!first) inc++; /* Count the space between args */
915 if ((length + inc) >= n_alloc) { /* Will need more memory */
916 n_alloc <<= 1;
917 txt = gmt_M_memory (G, txt, n_alloc, char);
918 }
919 if (!first) strcat (txt, " "); /* Add space between args */
920 strcat (txt, buffer);
921 length += inc;
922 first = false;
923 }
924 length++; /* Need space for trailing \0 */
925 /* OK, done processing all options */
926 if (length == 1) /* Found no options, so delete the string we allocated */
927 gmt_M_free (G, txt);
928 else if (length < n_alloc) /* Trim back on the list to fit what we want */
929 txt = gmt_M_memory (G, txt, length, char);
930
931 return (txt); /* Pass back the results to the calling module */
932 }
933
934 /*! Delete string created by GMT_Create_Cmd, pass its address */
GMT_Destroy_Cmd(void * V_API,char ** cmd)935 int GMT_Destroy_Cmd (void *V_API, char **cmd) {
936
937 struct GMTAPI_CTRL *API = NULL;
938 if (V_API == NULL) return_error (V_API, GMT_NOT_A_SESSION); /* GMT_Create_Session has not been called */
939 if (*cmd == NULL) return_error (V_API, GMT_ARG_IS_NULL); /* No command was given */
940 API = gmtparse_get_api_ptr (V_API); /* Cast void pointer to a GMTAPI_CTRL pointer */
941 gmt_M_free (API->GMT, *cmd); /* Free the command string */
942 return (GMT_OK); /* No error encountered */
943 }
944
945 /*! Create an option structure given the option character and the optional argument arg */
GMT_Make_Option(void * V_API,char option,const char * arg)946 struct GMT_OPTION *GMT_Make_Option (void *V_API, char option, const char *arg) {
947 struct GMT_OPTION *new_opt = NULL;
948 struct GMTAPI_CTRL *API = NULL;
949
950 if (V_API == NULL) return_null (V_API, GMT_NOT_A_SESSION); /* GMT_Create_Session has not been called */
951 API = gmtparse_get_api_ptr (V_API); /* Cast void pointer to a GMTAPI_CTRL pointer */
952
953 /* Here we have a program-specific option or a file name. In either case we create a new option structure */
954
955 new_opt = gmt_M_memory (API->GMT, NULL, 1, struct GMT_OPTION); /* Allocate one option structure */
956
957 new_opt->option = option; /* Assign which option character was used */
958 if (!arg) /* If arg is a NULL pointer: */
959 new_opt->arg = strdup (""); /* Copy an empty string, such that new_opt->arg[0] = '\0', which avoids */
960 /* segfaults later on since few functions check for NULL pointers */
961 else { /* If arg is set to something (may be an empty string): */
962 new_opt->arg = strdup (arg); /* Allocate space for the argument and duplicate it in the option structure */
963 gmt_chop (new_opt->arg); /* Get rid of any trailing \n \r from cross-binary use in Cygwin/Windows */
964 }
965
966 return (new_opt); /* Pass back the pointer to the allocated option structure */
967 }
968
969 /*! Search the list for the selected option and return the pointer to the item. Only the first occurrence will be found. */
GMT_Find_Option(void * V_API,char option,struct GMT_OPTION * head)970 struct GMT_OPTION *GMT_Find_Option (void *V_API, char option, struct GMT_OPTION *head) {
971
972 struct GMT_OPTION *current = NULL;
973
974 if (V_API == NULL) return_null (V_API, GMT_NOT_A_SESSION); /* GMT_Create_Session has not been called */
975
976 for (current = head; current && current->option != option; current = current->next); /* Linearly search for the specified option */
977 return (current); /* NULL if not found */
978 }
979
980 /*! Replaces the argument of this option with new arg. */
GMT_Update_Option(void * V_API,struct GMT_OPTION * opt,const char * arg)981 int GMT_Update_Option (void *V_API, struct GMT_OPTION *opt, const char *arg) {
982
983 if (V_API == NULL) return_error (V_API, GMT_NOT_A_SESSION); /* GMT_Create_Session has not been called */
984 if (opt == NULL) return_error (V_API, GMT_OPTION_IS_NULL); /* We passed NULL as the option */
985 if (arg == NULL) return_error (V_API, GMT_ARG_IS_NULL); /* We passed NULL as the argument */
986 gmt_M_str_free (opt->arg);
987 opt->arg = strdup (arg);
988
989 return (GMT_OK); /* No error encountered */
990 }
991
992 /*! Replaces a marker character (?) in the option arg with the replacement argument in arg, except when in quotes. */
GMT_Expand_Option(void * V_API,struct GMT_OPTION * opt,const char * arg)993 int GMT_Expand_Option (void *V_API, struct GMT_OPTION *opt, const char *arg) {
994 char buffer[BUFSIZ] = {""};
995 size_t in = 0, out = 0;
996 size_t s_length;
997 bool quote = false; /* We are outside any quoted text */
998
999 if (V_API == NULL) return_error (V_API, GMT_NOT_A_SESSION); /* GMT_Create_Session has not been called */
1000 if (opt == NULL) return_error (V_API, GMT_OPTION_IS_NULL); /* We passed NULL as the option */
1001 if (arg == NULL) return_error (V_API, GMT_ARG_IS_NULL); /* We passed NULL as the argument */
1002 if (opt->arg == NULL) return_error (V_API, GMT_ARG_IS_NULL); /* We passed NULL as the option argument */
1003 s_length = strlen (arg);
1004 if ((s_length + strlen (opt->arg)) > BUFSIZ) return_error (V_API, GMT_DIM_TOO_LARGE); /* Don't have room */
1005
1006 while (opt->arg[in]) {
1007 if (opt->arg[in] == '\"') quote = !quote;
1008 if (opt->arg[in] == '?' && !quote) { /* Found an unquoted questionmark */
1009 strcat (&buffer[out], arg); /* Insert the given arg instead */
1010 out += s_length; /* Adjust next output location */
1011 }
1012 else /* Regular text, copy one-by-one */
1013 buffer[out++] = opt->arg[in];
1014 in++;
1015 }
1016 gmt_M_str_free (opt->arg);
1017 opt->arg = strdup (buffer);
1018 return (GMT_NOERROR);
1019 }
1020
1021 /*! Append this entry to the end of the linked list */
GMT_Append_Option(void * V_API,struct GMT_OPTION * new_opt,struct GMT_OPTION * head)1022 struct GMT_OPTION *GMT_Append_Option (void *V_API, struct GMT_OPTION *new_opt, struct GMT_OPTION *head) {
1023 struct GMT_OPTION *current = NULL;
1024
1025 if (V_API == NULL) return_null (V_API, GMT_NOT_A_SESSION); /* GMT_Create_Session has not been called */
1026 if (!new_opt) return_null (V_API, GMT_OPTION_IS_NULL); /* No option was passed */
1027 if (!new_opt->arg) return_null (V_API, GMT_ARG_IS_NULL); /* Option argument must not be null pointer */
1028
1029 if (head == NULL) return (new_opt); /* No list yet, let new_opt become the start of the list */
1030
1031 /* Here the list already existed with head != NULL */
1032
1033 if (new_opt->option == GMT_OPT_OUTFILE) { /* Only allow one output file on command line */
1034 /* Search for existing output option */
1035 for (current = head; current->next && current->option != GMT_OPT_OUTFILE; current = current->next);
1036 if (current->option == GMT_OPT_OUTFILE) return_null (V_API, GMT_ONLY_ONE_ALLOWED); /* Cannot have > 1 output file */
1037 /* Here current is at end so no need to loop again */
1038 }
1039 else { /* Not an output file name so just go to end of list */
1040 for (current = head; current->next; current = current->next); /* Go to end of list */
1041 }
1042 /* Append new_opt to end the list */
1043 current->next = new_opt;
1044 new_opt->previous = current;
1045 new_opt->next = NULL; /* Since at end of the list and may have had junk */
1046
1047 return (head); /* Return head of list */
1048 }
1049
1050 /*! . */
GMT_Delete_Option(void * V_API,struct GMT_OPTION * current,struct GMT_OPTION ** head)1051 int GMT_Delete_Option (void *V_API, struct GMT_OPTION *current, struct GMT_OPTION **head) {
1052 /* Remove the specified entry from the linked list. It is assumed that current
1053 * points to the correct option in the linked list. */
1054 struct GMTAPI_CTRL *API = NULL;
1055
1056 if (V_API == NULL) return_error (V_API, GMT_NOT_A_SESSION); /* GMT_Create_Session has not been called */
1057 if (!current) return_error (V_API, GMT_OPTION_IS_NULL); /* No option was passed */
1058 API = gmtparse_get_api_ptr (V_API); /* Cast void pointer to a GMTAPI_CTRL pointer */
1059
1060 /* Remove the current option and bypass via the next/prev pointers in the linked list */
1061 if (current->next) current->next->previous = current->previous;
1062 if (current->previous) /* Reset pointer from previous entry to current's next entry */
1063 current->previous->next = current->next;
1064 else /* current == *head so there is no previous; must update head */
1065 *head = current->next;
1066 gmt_M_str_free (current->arg); /* Option arguments were created by strdup, so we must use free */
1067 gmt_M_free (API->GMT, current); /* Option structure was created by gmt_M_memory, hence gmt_M_free */
1068
1069 return (GMT_OK); /* No error encountered */
1070 }
1071
1072 /*! . */
GMT_Parse_Common(void * V_API,const char * given_options,struct GMT_OPTION * in_options)1073 int GMT_Parse_Common (void *V_API, const char *given_options, struct GMT_OPTION *in_options) {
1074 /* GMT_Parse_Common parses the option list for a program and detects the GMT common options.
1075 * These are processed in the order required by GMT regardless of order given.
1076 * The settings will override values set previously by other commands.
1077 * It ignores filenames and only return errors if GMT common options are misused.
1078 * Note: GMT_CRITICAL_OPT_ORDER is defined in gmt_common.h
1079 */
1080
1081 struct GMT_OPTION *opt = NULL, *options = NULL;
1082 char list[2] = {0, 0}, critical_opt_order[] = GMT_CRITICAL_OPT_ORDER;
1083 unsigned int i, n_errors;
1084 struct GMTAPI_CTRL *API = NULL;
1085 const unsigned int s_length = (unsigned int)strlen(critical_opt_order);
1086
1087 if (V_API == NULL) return_error (V_API, GMT_NOT_A_SESSION); /* GMT_Create_Session has not been called */
1088
1089 /* Check if there are short-hand commands present (e.g., -J with no arguments); if so complete these to full options
1090 * by consulting the current GMT history machinery. If not possible then we have an error to report */
1091
1092 API = gmtparse_get_api_ptr (V_API); /* Cast void pointer to a GMTAPI_CTRL pointer */
1093 if (gmtparse_complete_options (API->GMT, in_options)) return_error (API, GMT_OPTION_HISTORY_ERROR); /* Replace shorthand failed */
1094
1095 if (API->GMT->common.B.mode == 0) API->GMT->common.B.mode = gmtparse_check_b_options (API->GMT, in_options); /* Determine the syntax of the -B option(s) */
1096
1097 /* Need sorted list to avoid parsing axes labels etc before axis increments since we may auto-set increments in the former, and also --GMT_THEME must be parsed before other -- */
1098 options = gmtparse_ensure_b_and_dash_options_order (API->GMT, in_options);
1099
1100 n_errors = gmtparse_check_extended_R (API->GMT, options); /* Possibly parse -I if required by -R */
1101
1102 /* First parse the common options in the order they appear in GMT_CRITICAL_OPT_ORDER */
1103 for (i = 0; i < s_length; i++) { /* These are the GMT options that must be parsed in this particular order, if present */
1104 if (strchr (given_options, critical_opt_order[i]) == NULL) continue; /* Not a selected option */
1105 list[0] = critical_opt_order[i]; /* Just look for this particular option in the linked opt list */
1106 for (opt = options; opt; opt = opt->next) {
1107 if (opt->option != list[0]) continue;
1108 n_errors += gmt_parse_common_options (API->GMT, list, opt->option, opt->arg);
1109 }
1110 }
1111
1112 /* Now any critical option given has been parsed. Next we parse anything NOT a critical GMT option */
1113 for (i = 0; given_options[i]; i++) { /* Loop over all options given */
1114 if (strchr (critical_opt_order, given_options[i])) continue; /* Skip critical option */
1115 list[0] = given_options[i]; /* Just look for this particular option */
1116 for (opt = options; opt; opt = opt->next) {
1117 n_errors += gmt_parse_common_options (API->GMT, list, opt->option, opt->arg);
1118 if (opt->option != list[0]) continue;
1119 }
1120 }
1121 if (GMT_Destroy_Options (API, &options)) {
1122 return_error (V_API, GMT_RUNTIME_ERROR); /* Failed to free temp options list */
1123 }
1124
1125 /* Update [read-only] pointer to the current option list */
1126 API->GMT->current.options = in_options;
1127 if (n_errors) return_error (API, GMT_PARSE_ERROR); /* One or more options failed to parse */
1128 if (gmt_M_is_geographic (API->GMT, GMT_IN)) API->GMT->current.io.warn_geo_as_cartesion = false; /* Don't need this warning */
1129
1130 if (API->GMT->current.io.cycle_col != GMT_NOTSET) {
1131 unsigned int k = 2 * API->GMT->current.io.cycle_col;
1132 API->GMT->current.io.cycle_min = API->GMT->common.R.wesn[k];
1133 API->GMT->current.io.cycle_max = API->GMT->common.R.wesn[k+1];
1134 switch (API->GMT->current.io.cycle_operator) {
1135 case GMT_CYCLE_SEC: /* Return 0.000-0.999999 second */
1136 API->GMT->current.io.cycle_range = 1.0;
1137 break;
1138 case GMT_CYCLE_MIN: /* Return 0.000-59.999999 seconds */
1139 API->GMT->current.io.cycle_range = GMT_MIN2SEC_F;
1140 break;
1141 case GMT_CYCLE_HOUR: /* Return 0.000-59.999999 minutes */
1142 API->GMT->current.io.cycle_range = GMT_HR2MIN_F;
1143 break;
1144 case GMT_CYCLE_DAY: /* Return 0.000-23.999999 hours */
1145 API->GMT->current.io.cycle_range = GMT_DAY2HR_F;
1146 break;
1147 case GMT_CYCLE_WEEK: /* Return 0.00000-6.9999999 days */
1148 API->GMT->current.io.cycle_range = GMT_WEEK2DAY_F;
1149 API->GMT->current.io.cycle_interval = true;
1150 break;
1151 case GMT_CYCLE_ANNUAL: /* Return 0.000000-11.999999 months */
1152 API->GMT->current.io.cycle_range = 12.0;
1153 API->GMT->current.io.cycle_interval = true;
1154 break;
1155 case GMT_CYCLE_YEAR: /* Return 0.00000-0.99999999 years */
1156 API->GMT->current.io.cycle_range = 1.0;
1157 break;
1158 case GMT_CYCLE_CUSTOM: /* Return 0.00000-0.99999999 custom cycle */
1159 API->GMT->current.io.cycle_range = 1.0;
1160 break;
1161 }
1162 }
1163 return (GMT_OK);
1164 }
1165