3a52ad30839586e54377927d8ee87a65d4806a7f galt Thu Oct 23 21:21:22 2025 -0700 hubCheck can now scan for and report with text valid values for VIEWLIMIT VIEWLIMITMAX and DEFAULTVIEWLIMIT. fixes #12098 diff --git src/hg/utils/hubCheck/hubCheck.c src/hg/utils/hubCheck/hubCheck.c index 5d66be2cf4f..fb0d06cdea2 100644 --- src/hg/utils/hubCheck/hubCheck.c +++ src/hg/utils/hubCheck/hubCheck.c @@ -158,30 +158,31 @@ int score = scoreWordMatch(setting, suggest->name, ss); if (score < bestScore) continue; if (score > bestScore) { best = suggest->name; bestScore = score; bestCount = 1; } else { // same score bestCount++; } } + if (bestCount == 1 && bestScore > 15) { verbose(3, "suggest %s score: %d\n", best, bestScore); return best; } return NULL; } struct htmlPage *trackHubVersionSpecMustGet(char *specHost, char *version) /* Open the trackDbHub.html file and attach html reader. Use default version if NULL */ { char *specUrl; char buf[256]; if (version != NULL && startsWith("http", version)) specUrl = version; @@ -821,30 +822,104 @@ { errAbort("error in type line \"%s\" for track \"%s\". " "Only \"+\" or \".\" is allowed after bigBed numFields setting.", type, tdb->track); } } } errCatchEnd(errCatch); if (errCatch->gotError) { trackDbErr(errors, errCatch->message->string, genome, tdb, options->htmlOut); retVal = TRUE; } return retVal; } +boolean isValidDouble(char *s, double *result) +/* Convert string to a double. Assumes all of string is number + * and returns FALSE if double is invalid. + * Does not errAbort. Avoids sqlDouble which does abort. */ +{ +char* end; +double val = strtod(s, &end); + +if ((end == s) || (*end != '\0') + || *s == ' ' + ) + return FALSE; +*result = val; +return TRUE; +} + + +static void parseColonRange(struct trackDb *tdb, char *settingName, char *setting) +/* Parse setting's two colon-separated numbers and detects errors in their value. + * Helpfully shows track and setting name as well as setting value in the error message. + * Does not swap or return min max range values. + * trackDbDoc syntax + viewLimits + */ +{ +char tmp[64]; +safecpy(tmp, sizeof(tmp), setting); +char *words[3]; +if (chopByChar(tmp, ':', words, ArraySize(words)) == 2) + { + char *lower = words[0]; + double lowerVal = 0; + boolean validLower = isValidDouble(lower, &lowerVal); + if (!validLower) + errAbort("Invalid double range lower value '%s' in range '%s' in setting %s track %s", lower, setting, settingName, tdb->track); + + char *upper = words[1]; + double upperVal = 0; + boolean validUpper = isValidDouble(upper, &upperVal); + if (!validUpper) + errAbort("Invalid double range upper value '%s' in range '%s' in setting %s track %s", upper, setting, settingName, tdb->track); + + if (validUpper && validLower) + { + if (upperVal < lowerVal) + warn("upper < lower. Should swap lower and upper range values in %s' in setting %s track %s", setting, settingName, tdb->track); + } + } +else + errAbort("Missing a colon in range value '%s' in setting %s track %s", setting, settingName, tdb->track); +} + +void checkViewLimitsSettings(struct trackDb *tdb) +/* check viewLimits and viewLimitsMax and defaultViewLimits setting values */ +{ +int i; +for(i = 0; i < 3; i++) + { + char *settingName; + if (i == 0) + settingName = VIEWLIMITS; + if (i == 1) + settingName = VIEWLIMITSMAX; + if (i == 2) + settingName = DEFAULTVIEWLIMITS; + + char *setting = trackDbSetting(tdb, settingName); + if (setting) + { + parseColonRange(tdb, settingName, setting); + } + } +} + int hubCheckTrack(struct trackHub *hub, struct trackHubGenome *genome, struct trackDb *tdb, struct trackHubCheckOptions *options, struct dyString *errors) /* Check track settings and optionally, files */ { int retVal = 0; int trackDbErrorCount = 0; if (options->checkSettings && options->settings) { //verbose(3, "Found %d settings to check to spec\n", slCount(settings)); verbose(3, "Checking track: %s\n", tdb->shortLabel); verbose(3, "Found %d settings to check to spec\n", hashNumEntries(tdb->settingsHash)); struct hashEl *hel; struct hashCookie cookie = hashFirst(tdb->settingsHash); while ((hel = hashNext(&cookie)) != NULL) @@ -901,30 +976,33 @@ if (tdbIsSubtrack(tdb)) retVal |= hubCheckSubtrackSettings(genome, tdb, errors, options); // check that type line is syntactically correct regardless of // if we actually want to check the data file itself boolean foundTypeError = checkTypeLine(genome, tdb, errors, options); // No point in checking the data file if the type setting is incorrect, // since hubCheckBigDataUrl will error out early with a less clear message // if the type line is messed up. This has the added benefit of providing // consistent messaging on command line interface vs web interface if (!foundTypeError && options->checkFiles) hubCheckBigDataUrl(hub, genome, tdb); + + checkViewLimitsSettings(tdb); + if (!sameString(tdb->track, "cytoBandIdeo")) { trackHubAddDescription(genome->trackDbFile, tdb); if (!tdb->html) warn("warning: missing description page for track. Add 'html %s.html' line to the '%s' track stanza. ", tdb->track, tdb->track); } if (!trackIsContainer && sameString(trackDbRequiredSetting(tdb, "type"), "bigWig")) { char *autoScaleSetting = trackDbLocalSetting(tdb, "autoScale"); if (autoScaleSetting && !sameString(autoScaleSetting, "off") && !sameString(autoScaleSetting, "on")) { errAbort("track \"%s\" has value \"%s\" for autoScale setting, " "valid autoScale values for individual bigWig tracks are \"off\" or \"on\" only. "