5e8e0ed53c05f2ef9b51ce897440bae308367f47
chmalee
  Wed Feb 9 14:45:54 2022 -0800
Add csv output option to table browser to support opening files in
Excel, refs #27623. Also change the output filename message to hint
that Excel can auto-recognize .csv extensions and behave appropriately.
Lastly, fix output dropdown selection to properly warn on update
when using GTF output option or a snp table.

diff --git src/hg/hgTables/custom.c src/hg/hgTables/custom.c
index 4a48f98..1b85598 100644
--- src/hg/hgTables/custom.c
+++ src/hg/hgTables/custom.c
@@ -256,42 +256,44 @@
 	    hPrintf("%u,", bed->expIds[i]);
 	}
     else if (sameString(type, "expScores"))
 	{
 	unsigned i;
 	for (i=0; i<bed->expCount; ++i)
 	    hPrintf("%f,", bed->expScores[i]);
 	}
     else
         errAbort("Unrecognized bed field %s", type);
     }
 hPrintf("\n");
 }
 #endif /* UNUSED */
 
-static void tabBedRowFile(struct bed *bed, struct slName *fieldList, FILE *f)
+static void tabBedRowFile(struct bed *bed, struct slName *fieldList, FILE *f, char outSep)
 /* Print out to a file named fields from bed. */
 {
 struct slName *field;
 boolean needTab = FALSE;
 for (field = fieldList; field != NULL; field = field->next)
     {
     char *type = field->name;
     if (needTab)
-        fprintf(f, "\t");
+        fprintf(f, "%c", outSep);
     else
         needTab = TRUE;
+    if (outSep == ',')
+        fputc('"', f);
     if (sameString(type, "chrom"))
         fprintf(f, "%s", bed->chrom);
     else if (sameString(type, "chromStart"))
         fprintf(f, "%u", bed->chromStart);
     else if (sameString(type, "chromEnd"))
         fprintf(f, "%u", bed->chromEnd);
     else if (sameString(type, "name"))
         fprintf(f, "%s", bed->name);
     else if (sameString(type, "score"))
         fprintf(f, "%d", bed->score);
     else if (sameString(type, "strand"))
         fprintf(f, "%s", bed->strand);
     else if (sameString(type, "thickStart"))
         fprintf(f, "%u", bed->thickStart);
     else if (sameString(type, "thickEnd"))
@@ -320,30 +322,32 @@
         fprintf(f, "%u", bed->expCount);
     else if (sameString(type, "expIds"))
 	{
 	unsigned i;
 	for (i=0; i<bed->expCount; ++i)
 	    fprintf(f, "%u,", bed->expIds[i]);
 	}
     else if (sameString(type, "expScores"))
 	{
 	unsigned i;
 	for (i=0; i<bed->expCount; ++i)
 	    fprintf(f, "%f,", bed->expScores[i]);
 	}
     else
         errAbort("Unrecognized bed field %s", type);
+    if (outSep == ',')
+        fputc('"', f);
     }
 fprintf(f, "\n");
 }
 
 struct bedFilter *bedFilterForCustomTrack(char *ctName)
 /* If the user specified constraints, then translate them to a bedFilter. */
 {
 struct hashEl *var, *varList = cartFindPrefix(cart, hgtaFilterVarPrefix);
 int prefixSize = strlen(hgtaFilterVarPrefix);
 struct bedFilter *bf = NULL;
 int *trash;
 
 for (var = varList; var != NULL; var = var->next)
     {
     char *dbTrackFieldType = cloneString(var->name + prefixSize);
@@ -538,88 +542,90 @@
 
     /* Grab filtered beds for each region. */
     for (region = regionList; region != NULL; region = region->next)
 	customTrackFilteredBedOnRegion(region, ct, idHash, bf, lm, &bedList);
 
     /* clean up. */
     hashFree(&idHash);
     slReverse(&bedList);
     }
 if (retFieldCount != NULL)
     *retFieldCount = fieldCount;
 return bedList;
 }
 
 static void doTabOutBedLike(struct customTrack *ct, char *table,
-	struct sqlConnection *conn, char *fields, FILE *f)
+	struct sqlConnection *conn, char *fields, FILE *f, char outSep)
 /* Print out selected fields from a bed-like custom track.  If fields
  * is NULL, then print out all fields. */
 {
 struct region *regionList = getRegions(), *region;
 struct slName *chosenFields, *field;
 int count = 0;
 if (fields == NULL)
     chosenFields = getBedFields(ct->fieldCount);
 else
     chosenFields = commaSepToSlNames(fields);
 
 if (f == NULL)
     f = stdout;
 fprintf(f, "#");
 for (field = chosenFields; field != NULL; field = field->next)
     {
     if (field != chosenFields)
-	fprintf(f, "\t");
+        fprintf(f, "%c", outSep);
+    if (outSep == ',') fputc('"', f);
     fprintf(f, "%s", field->name);
+    if (outSep == ',') fputc('"', f);
     }
 fprintf(f, "\n");
 
 for (region = regionList; region != NULL; region = region->next)
     {
     struct lm *lm = lmInit(64*1024);
     struct bed *bed, *bedList = cookedBedList(conn, table,
 	region, lm, NULL);
     for (bed = bedList; bed != NULL; bed = bed->next)
 	{
-	tabBedRowFile(bed, chosenFields, f);
+	tabBedRowFile(bed, chosenFields, f, outSep);
 	++count;
 	}
     lmCleanup(&lm);
     }
 if (count == 0)
     explainWhyNoResults(f);
 }
 
 void doTabOutCustomTracks(char *db, char *table, struct sqlConnection *conn,
-	char *fields, FILE *f)
+	char *fields, FILE *f, char outSep)
 /* Print out selected fields from custom track.  If fields
  * is NULL, then print out all fields. */
 {
 struct customTrack *ct = ctLookupName(table);
 char *type = ct->tdb->type;
 if (startsWithWord("makeItems", type) || 
         sameWord("bedDetail", type) || 
         sameWord("barChart", type) ||
         sameWord("interact", type) ||
         sameWord("pgSnp", type))
     {
     struct sqlConnection *conn = hAllocConn(CUSTOM_TRASH);
-    doTabOutDb(CUSTOM_TRASH, db, ct->dbTableName, table, f, conn, fields);
+    doTabOutDb(CUSTOM_TRASH, db, ct->dbTableName, table, f, conn, fields, outSep);
     hFreeConn(&conn);
     }
 else
-    doTabOutBedLike(ct, table, conn, fields, f);
+    doTabOutBedLike(ct, table, conn, fields, f, outSep);
 }
 
 
 void removeNamedCustom(struct customTrack **pList, char *name)
 /* Remove named custom track from list if it's on there. */
 {
 struct customTrack *newList = NULL, *ct, *next;
 for (ct = *pList; ct != NULL; ct = next)
     {
     next = ct->next;
     if (!sameString(ct->tdb->table, name))
         {
 	slAddHead(&newList, ct);
 	}
     }