06d7be056190c14b85e71bc12523f18ea6815b5e
markd
  Mon Dec 7 00:50:29 2020 -0800
BLAT mmap index support merge with master

diff --git src/hg/lib/hui.c src/hg/lib/hui.c
index 0794478..fad4bc0 100644
--- src/hg/lib/hui.c
+++ src/hg/lib/hui.c
@@ -9240,39 +9240,39 @@
 	{
 	timep = bbiUpdateTime(bbi);
 	bbiFileClose(&bbi);
 	}
     printBbiUpdateTime(&timep);
     }
 else
     {
     tableName = hTableForTrack(database, tdb->table);
     conn = hAllocConnTrack(database, tdb);
     }
 if (tableName)
     {
     char *date = firstWordInLine(sqlTableUpdate(conn, tableName));
     if (date != NULL)
-	printf("<B>Data last updated:&nbsp;</B>%s<BR>\n", date);
+	printf("<B>Data last updated at UCSC:&nbsp;</B>%s<BR>\n", date);
     }
 hFreeConn(&conn);
 }
 
 void printBbiUpdateTime(time_t *timep)
 /* for bbi files, print out the timep value */
 {
-    printf("<B>Data last updated:&nbsp;</B>%s<BR>\n", sqlUnixTimeToDate(timep, FALSE));
+    printf("<B>Data last updated at UCSC:&nbsp;</B>%s<BR>\n", sqlUnixTimeToDate(timep, FALSE));
 }
 
 static boolean tableDescriptionsExists(struct sqlConnection *conn)
 /* Cache flag for whether tableDescriptions exists in conn, in case we will need to
  * fetch a lot of descriptions from tableDescriptions. */
 {
 static struct hash *hash = NULL;
 if (hash == NULL)
     hash = hashNew(0);
 char *db = sqlGetDatabase(conn);
 int exists =  hashIntValDefault(hash, db, -1);
 if (exists < 0)
     {
     exists = sqlTableExists(conn, "tableDescriptions");
     hashAddInt(hash, db, exists);
@@ -9401,39 +9401,56 @@
 return list;
 }
 
 static struct dyString *subMultiField(char *pattern, int fieldCount,
                                  char *in[], char *out[])
 /* Substitute $in with out values in pattern */
 {
 int i;
 struct dyString *s = newDyString(256), *d = NULL;
 dyStringAppend(s, pattern);
 for (i=0; i<fieldCount; ++i)
     {
     if (out[i]==NULL)
         continue;
 
-    // prefix field with $
+    // If a field is a prefix or suffix to another field, for example 'chrom' and 'chromStart'
+    // we don't want to erroneously sub out the 'chrom' in 'chromStart'. Allow the wrapping
+    // protected fields in ${} to prevent the substitution:
     char *field = in[i];
-    char *spec = needMem(strlen(field) + 2);
+    int fieldLen = strlen(field);
+    char *spec = needMem(fieldLen + 2);
+    char *strictSpec = needMem(fieldLen + 4);
     *spec = '$';
+    *strictSpec = '$';
+    strictSpec[1] = '{';
     strcpy(spec + 1, field);
+    strcpy(strictSpec + 2, field);
+    strictSpec[fieldLen + 2] = '}';
+    strictSpec[fieldLen + 3] = '\0';
 
+    if (stringIn(strictSpec, s->string))
+        {
+        d = dyStringSub(s->string, strictSpec, out[i]);
+        s = d;
+        }
+    // the user may have both a ${} enclosed instance and a non-enclosed one!
     d = dyStringSub(s->string, spec, out[i]);
+
     dyStringFree(&s);
     freeMem(spec);
+    freeMem(strictSpec);
     s = d;
     d = NULL;
     }
 return s;
 }
 
 char *replaceFieldInPattern(char *pattern, int fieldCount, char **fieldNames, char **fieldVals)
 /* Replace $fieldName in pattern with value.  Used in trackDb mouseOver setting */
 {
 struct dyString *ds = subMultiField(pattern, fieldCount, fieldNames, fieldVals);
 return dyStringCannibalize(&ds);
 }
 
 static struct dyString *subMulti(char *orig, int subCount,
                                  char *in[], char *out[])
@@ -9452,32 +9469,32 @@
     dyStringFree(&s);
     s = d;
     d = NULL;
     }
 return s;
 }
 
 char *replaceInUrl(char *url, char *idInUrl, struct cart *cart, char *db, char *seqName, 
                         int winStart, int winEnd, char *track, boolean encode, struct slPair *fields) 
 /* replace $$ in url with idInUrl. Supports many other wildchards, and custom fields $<field>
  * XX Do we have readable docs for these parameters somewhere?
  * Look at http://genome.ucsc.edu/goldenpath/help/trackDb/trackDbHub.html */
 {
 struct dyString *uUrl = NULL;
 struct dyString *eUrl = NULL;
-char startString[64], endString[64];
-char *ins[13], *outs[13];
+char startString[64], endString[64],oneBasedStart[64];
+char *ins[14], *outs[14];
 char *eItem = (encode ? cgiEncode(idInUrl) : cloneString(idInUrl));
 
 char *scName = NULL;
 // try to avoid the mysql query it not necessary
 if (stringIn("$n", url))
     {
     char *tmp = hScientificName(db);
     scName = replaceChars(tmp, " ", "_");
     freeMem(tmp);
     }
 
 char *taxId = NULL;
 // try to avoid the mysql query it not necessary
 if (stringIn("$taxId", url))
     {
@@ -9516,41 +9533,46 @@
     if (nextColon)	/* terminate suffixClone suffix */
         *nextColon = '\0';	/* when next colon is present */
     *suffix = '\0';   /* terminate itemClone prefix */
     outs[7] = itemClone;
     outs[8] = suffixClone;
     /* small memory leak here for these cloned strings */
     /* not important for a one-time operation in a CGI that will exit */
 } else {
     outs[7] = idInUrl;	/* otherwise, these are not expected */
     outs[8] = idInUrl;	/* to be used */
 }
 
 // URL may now contain item boundaries
 ins[9] = "${";
 ins[10] = "$}";
+ins[13] = "$#";
 if (cart!=NULL && cartOptionalString(cart, "o") && cartOptionalString(cart, "t"))
     {
     char *itemBeg = cartString(cart, "o"); // unexpected commas?
     char *itemEnd = cartString(cart, "t");
     outs[9] = itemBeg;
     outs[10] = itemEnd;
+    safef(oneBasedStart, sizeof(oneBasedStart), "%d", cartInt(cart, "o") + 1);
+    outs[13] = oneBasedStart;
     }
 else // should never be but I am unwilling to bet the farm
     {
     outs[9] = startString;
     outs[10] = endString;
+    safef(oneBasedStart, sizeof(oneBasedStart), "%d", winStart + 1);
+    outs[13] = oneBasedStart;
     }
 
 ins[11] = "$n";
 outs[11] = scName;
 
 ins[12] = "$taxId";
 outs[12] = taxId;
 
 uUrl = subMulti(url, ArraySize(ins), ins, outs);
 outs[0] = eItem;
 eUrl = subMulti(url, ArraySize(ins), ins, outs);
 freeDyString(&uUrl);
 freeMem(eItem);
 freeMem(scName);
 
@@ -9609,31 +9631,31 @@
 {
 char *version = checkDataVersion(database, tdb);
 
 if (version == NULL)
     {
     // try the hgFixed.trackVersion table
     struct trackVersion *trackVersion = getTrackVersion(database, tdb->track);
     // try trackVersion table with parent, for composites/superTracks
     if (trackVersion == NULL && tdb->parent != NULL)
         trackVersion = getTrackVersion(database, tdb->parent->track);
     if (trackVersion != NULL)
         version = trackVersion->version;
     }
 
 if (isNotEmpty(version))
-    printf("<B>Data version:</B> %s <BR>\n", version);
+    printf("<B>Source data version:</B> %s <BR>\n", version);
 }
 
 void printRelatedTracks(char *database, struct hash *trackHash, struct trackDb *tdb, struct cart *cart)
 /* Maybe print a "related track" section */
 {
 if (!cfgOption("db.relatedTrack") || trackHubDatabase(database))
     return;
 char *relatedTrackTable = cfgOptionDefault("db.relatedTrack","relatedTrack");
 struct sqlConnection *conn = hAllocConn(database);
 if (!sqlTableExists(conn, relatedTrackTable))
     {
     hFreeConn(&conn);
     return;
     }