Skip to content

Sort -B options under the hood to ensure proper order for parsing #5441

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 6 commits into from
Jul 6, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
86 changes: 63 additions & 23 deletions src/gmt_parse.c
Original file line number Diff line number Diff line change
Expand Up @@ -458,35 +458,72 @@ GMT_LOCAL struct GMT_OPTION *gmtparse_fix_gdal_files (struct GMT_OPTION *opt) {
return opt;
}

struct B_PRIORITY {
unsigned int level;
struct GMT_OPTION *opt;
};

GMT_LOCAL int gmtparse_compare_B (const void *p1, const void *p2) {
const struct B_PRIORITY *a = p1, *b = p2;

if (a->level < b->level) return (-1);
if (a->level > b->level) return (+1);
return (0);
}

/*! */
GMT_LOCAL void gmtparse_ensure_b_options_order (struct GMT_CTRL *GMT, struct GMT_OPTION *options) {
GMT_LOCAL struct GMT_OPTION * gmtparse_ensure_b_options_order (struct GMT_CTRL *GMT, struct GMT_OPTION *options) {
bool do_sort = false;
char *c = NULL;
unsigned int k, n_B = 0, this_priority, priority[12]; /* Worst case is -Bpx, -Bsx, ... */
struct GMT_OPTION *opt;
unsigned int np, n_B = 0, k, j, this_priority;
struct GMT_OPTION *opt, *head = NULL;
struct B_PRIORITY *priority = NULL; /* Worst case is -Bpx, -Bsx, ... */

gmt_M_memset (priority, 12, unsigned int);
if (options == NULL) return NULL; /* Nothing to do */

for (opt = options; opt; opt = opt->next) {
for (np = 0, opt = options; opt; opt = opt->next, np++); /* Count the options */
priority = gmt_M_memory (GMT, NULL, np, struct B_PRIORITY);

for (opt = options, k = 0; opt; opt = opt->next, k++) {
priority[k].opt = opt;
if (opt->option != 'B') continue; /* Only look at -B options here */
if (gmtlib_B_is_frame (GMT, opt->arg)) continue; /* Don't care about the frame setting here */
if ((c = gmt_first_modifier (GMT, opt->arg, "aflLpsSu"))) { /* Option has axis modifier(s) */
n_B++;
if (gmtlib_B_is_frame (GMT, opt->arg)) { /* Do a special check for -Bs which could be confused with secondary annotations */
if (opt->arg[0] == 's' && opt->arg[1] == '\0') /* Place -Bs at the end */
this_priority = 3;
else /* Don't care about the other frame setting here */
this_priority = 2;
}
else if ((c = gmt_first_modifier (GMT, opt->arg, "aflLpsSu"))) { /* Option has axis modifier(s) */
c[0] = '\0'; /* Temporary chop them off */
k = 0; /* Start of option string, then advance past any leading [p|s][x|y|z] */
if (strchr ("ps", opt->arg[k])) k++;
if (strchr ("xyz", opt->arg[k])) k++;
this_priority = (opt->arg[k]) ? 1 : 2; /* If nothing then probably just a label setting, else we have a leading interval setting */
j = 0; /* Start of option string, then advance past any leading [p|s][x|y|z] */
if (strchr ("ps", opt->arg[j])) j++;
if (strchr ("xyz", opt->arg[j])) j++;
this_priority = (opt->arg[j]) ? 1 : 2; /* If nothing then probably just a label setting, else we have a leading interval setting */
c[0] = '+'; /* Restore modifiers */
}
else /* Must be interval setting */
this_priority = 1;
priority[n_B] = this_priority;
n_B++;
priority[k].level = this_priority;
}
for (k = 1; k < np; k++) if (priority[k].level < priority[k-1].level) do_sort = true;
if (n_B <= 1) do_sort = false; /* No point sorting on -B if only one such option */
if (do_sort) mergesort (priority, np, sizeof (struct B_PRIORITY), gmtparse_compare_B);
/* Rebuild options links */
head = opt = GMT_Make_Option (GMT->parent, priority[0].opt->option, priority[0].opt->arg);
for (k = 1; k < np; k++) {
opt->next = GMT_Make_Option (GMT->parent, priority[k].opt->option, priority[k].opt->arg);
opt = opt->next;
}
if (n_B < 2) return; /* Nothing to sort */
for (k = 1; k < n_B; k++) if (priority[k] < priority[k-1]) do_sort = true;
if (!do_sort) return; /* No need to sort */
GMT_Report (GMT->parent, GMT_MSG_WARNING, "GMT_Parse_Options: List interval-setting -B options before other axis -B options to ensure proper parsing.\n");
gmt_M_free (GMT, priority);

if (do_sort) {
char *cmd = GMT_Create_Cmd (GMT->parent, head);
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");
GMT_Report (GMT->parent, GMT_MSG_INFORMATION, "GMT_Parse_Options: New option order: %s\n", cmd);
GMT_Destroy_Cmd (GMT->parent, &cmd);
}
return (head);
}

/*! . */
Expand Down Expand Up @@ -1021,15 +1058,15 @@ int GMT_Delete_Option (void *V_API, struct GMT_OPTION *current, struct GMT_OPTIO
}

/*! . */
int GMT_Parse_Common (void *V_API, const char *given_options, struct GMT_OPTION *options) {
int GMT_Parse_Common (void *V_API, const char *given_options, struct GMT_OPTION *in_options) {
/* GMT_Parse_Common parses the option list for a program and detects the GMT common options.
* These are processed in the order required by GMT regardless of order given.
* The settings will override values set previously by other commands.
* It ignores filenames and only return errors if GMT common options are misused.
* Note: GMT_CRITICAL_OPT_ORDER is defined in gmt_common.h
*/

struct GMT_OPTION *opt = NULL;
struct GMT_OPTION *opt = NULL, *options = NULL;
char list[2] = {0, 0}, critical_opt_order[] = GMT_CRITICAL_OPT_ORDER;
unsigned int i, n_errors;
struct GMTAPI_CTRL *API = NULL;
Expand All @@ -1041,11 +1078,11 @@ int GMT_Parse_Common (void *V_API, const char *given_options, struct GMT_OPTION
* by consulting the current GMT history machinery. If not possible then we have an error to report */

API = gmtparse_get_api_ptr (V_API); /* Cast void pointer to a GMTAPI_CTRL pointer */
if (gmtparse_complete_options (API->GMT, options)) return_error (API, GMT_OPTION_HISTORY_ERROR); /* Replace shorthand failed */
if (gmtparse_complete_options (API->GMT, in_options)) return_error (API, GMT_OPTION_HISTORY_ERROR); /* Replace shorthand failed */

if (API->GMT->common.B.mode == 0) API->GMT->common.B.mode = gmtparse_check_b_options (API->GMT, options); /* Determine the syntax of the -B option(s) */
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) */

gmtparse_ensure_b_options_order (API->GMT, options); /* Avoid parsing axes labels etc before axis increments since we may auto-set increments in the former */
options = gmtparse_ensure_b_options_order (API->GMT, in_options); /* Sorted list to avoid parsing axes labels etc before axis increments since we may auto-set increments in the former */

n_errors = gmtparse_check_extended_R (API->GMT, options); /* Possibly parse -I if required by -R */

Expand All @@ -1068,9 +1105,12 @@ int GMT_Parse_Common (void *V_API, const char *given_options, struct GMT_OPTION
if (opt->option != list[0]) continue;
}
}
if (GMT_Destroy_Options (API, &options)) {
return_error (V_API, GMT_RUNTIME_ERROR); /* Failed to free temp options list */
}

/* Update [read-only] pointer to the current option list */
API->GMT->current.options = options;
API->GMT->current.options = in_options;
if (n_errors) return_error (API, GMT_PARSE_ERROR); /* One or more options failed to parse */
if (gmt_M_is_geographic (API->GMT, GMT_IN)) API->GMT->current.io.warn_geo_as_cartesion = false; /* Don't need this warning */

Expand Down
6 changes: 3 additions & 3 deletions test/grdimage/twogrids.sh
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,12 @@ gmt begin twogrids
gmt subplot begin 3x1 -Fs8c -Baf -R180/190/10/20 -JM7.5c -A+gwhite+p0.25p+o0.2c+jTC -Y1.5c
gmt subplot set -A"GRD2 same resolution [6m]"
gmt grdcut -R180/190/10/20 @earth_age_06m -Gt.grd
gmt grdimage t.grd -Iz.grd+d -B
gmt grdimage t.grd -Iz.grd+d
gmt subplot set -A"GRD2 courser resolution [10m]"
gmt grdcut -R180/190/10/20 @earth_age_10m -Gt.grd
gmt grdimage t.grd -Iz.grd+d -B
gmt grdimage t.grd -Iz.grd+d
gmt subplot set -A"GRD2 finer resolution [2m]"
gmt grdcut -R180/190/10/20 @earth_age_02m -Gt.grd
gmt grdimage t.grd -Iz.grd+d -B
gmt grdimage t.grd -Iz.grd+d
gmt subplot end
gmt end show
Binary file modified test/pscoast/coastclip.ps
Binary file not shown.
6 changes: 3 additions & 3 deletions test/pscoast/coastclip.sh
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
#!/usr/bin/env bash
# Test new +c|C clipping modifiers in coast's -E option
gmt begin coastclip
gmt subplot begin 2x1 -R-10/10/40/52 -Fs15c/14c -JM15c
gmt subplot begin 2x1 -R-10/10/40/52 -Fs15c/14c -JM15c -Baf
# Clip on the inside
gmt basemap -B -c
gmt basemap -c
gmt coast -EFR+c
gmt basemap -B+gdarkgreen
gmt clip -C
# Clip on the outside
gmt basemap -B -c
gmt basemap -c
gmt coast -EFR+C
gmt basemap -B+gorange
gmt clip -C
Expand Down
4 changes: 2 additions & 2 deletions test/psscale/cyclecpt.sh
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@
ps=cyclecpt.ps
gmt makecpt -Ccyclic -T0/180 -Ww > temp_cpt.cpt
# Vertical
gmt psscale -P -K -Ctemp_cpt.cpt -Dx3c/5c+w10c/0.618c+n -By+l"m" -Bxa+l"Cyclic CPT with NaN and unit" -X1i > $ps
gmt psscale -P -K -Ctemp_cpt.cpt -Dx3c/5c+w10c/0.618c+n -Bxa+l"Cyclic CPT with NaN and unit" -By+l"m" -X1i > $ps
gmt psscale -O -K -Ctemp_cpt.cpt -Dx3c/5c+w10c/0.618c+n -Bxa+l"Cyclic CPT with NaN" -X4i >> $ps
# Horizontal
gmt psscale -O -K -Ctemp_cpt.cpt -Dx5c/5c+w10c/0.618c+n+h -By+l"m" -Bxa+l"Cyclic CPT with NaN and unit" -X-4i -Y5i >> $ps
gmt psscale -O -K -Ctemp_cpt.cpt -Dx5c/5c+w10c/0.618c+n+h -Bxa+l"Cyclic CPT with NaN and unit" -By+l"m" -X-4i -Y5i >> $ps
gmt psscale -O -Ctemp_cpt.cpt -Dx5c/5c+w10c/0.618c+n+h -Bxa+l"Cyclic CPT with NaN" -Y1i >> $ps