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