98faa189349f1bb9b033f314ffe327541bacf290
chmalee
  Tue Mar 31 15:17:30 2026 -0700
Add a new hgFindSpec setting, searchItemLabel, that allows using hgFindSpec defined labels (with variable substitution) as the label for each line of a search result. This only works for bigBed tracks. refs #37299

diff --git src/lib/common.c src/lib/common.c
index a06ee397ba2..16f4f1317ef 100644
--- src/lib/common.c
+++ src/lib/common.c
@@ -1,22 +1,23 @@
 /* Commonly used routines in a wide range of applications.
  * Strings, singly-linked lists, and a little file i/o.
  *
  * This file is copyright 2002 Jim Kent, but license is hereby
  * granted for all use - public, private or commercial. */
 
 #include "common.h"
+#include "dystring.h"
 #include "errAbort.h"
 #include "portable.h"
 #include "linefile.h"
 #include "hash.h"
 #include "sqlNum.h"
 
 void *cloneMem(void *pt, size_t size)
 /* Allocate a new buffer of given size, and copy pt to it. */
 {
 void *newPt = needLargeMem(size);
 memcpy(newPt, pt, size);
 return newPt;
 }
 
 static char *cloneStringZExt(const char *s, int size, int copySize)
@@ -1808,30 +1809,76 @@
     {
     strLen = ptr - string;
     strcpy(resultPtr, string);
     string = ptr + oldLen;
 
     resultPtr += strLen;
     strcpy(resultPtr, new);
     resultPtr += newLen;
     ptr = strstr(string, old);
     }
 
 strcpy(resultPtr, string);
 return result;
 }
 
+char *replaceFieldInPattern(char *pattern, int fieldCount, char **fieldNames, char **fieldVals)
+/* Given a pattern containing $fieldName or ${fieldName} variable references, replace each
+ * variable with the corresponding value from fieldVals.  The ${} form prevents ambiguity
+ * when one field name is a prefix of another (e.g. "chrom" vs "chromStart"). */
+{
+int i;
+struct dyString *result = dyStringNew(256), *sub = NULL;
+dyStringAppend(result, pattern);
+for (i = 0; i < fieldCount; ++i)
+    {
+    if (fieldVals[i] == NULL || fieldNames[i] == NULL)
+        continue;
+
+    char *field = fieldNames[i];
+    int fieldLen = strlen(field);
+    char *bareSpec = needMem(fieldLen + 2);
+    char *bracedSpec = needMem(fieldLen + 4);
+    *bareSpec = '$';
+    *bracedSpec = '$';
+    bracedSpec[1] = '{';
+    strcpy(bareSpec + 1, field);
+    strcpy(bracedSpec + 2, field);
+    bracedSpec[fieldLen + 2] = '}';
+    bracedSpec[fieldLen + 3] = '\0';
+
+    if (stringIn(bracedSpec, result->string))
+        {
+        sub = dyStringSub(result->string, bracedSpec, fieldVals[i]);
+        dyStringFree(&result);
+        result = sub;
+        sub = NULL;
+        }
+    // the user may have both a ${} enclosed instance and a non-enclosed one
+    // also note that if the value substituted above is the field name itself with
+    // a leading $, then we will substitute again
+    sub = dyStringSub(result->string, bareSpec, fieldVals[i]);
+
+    dyStringFree(&result);
+    freeMem(bareSpec);
+    freeMem(bracedSpec);
+    result = sub;
+    sub = NULL;
+    }
+return dyStringCannibalize(&result);
+}
+
 int strSwapStrs(char *string, int sz,char *oldStr, char *newStr)
 /* Swaps all occurrences of the old with the new in string. Need not be same size
    Swaps in place but restricted by sz.  Returns count of swaps or -1 for sz failure. */
 {
 // WARNING: called at low level, so no errors allowed.
 int count = 0;
 char *p=NULL;
 for(p=strstr(string,oldStr);p!=NULL;p=strstr(p+strlen(oldStr),oldStr))
     count++;
 if (count == 0)
     return 0;
 if((strlen(string)+(count*(strlen(newStr) - strlen(oldStr))))>=sz)
     return -1;
 for(p=strstr(string,oldStr);p!=NULL;p=strstr(p+strlen(newStr),oldStr))
     {