diff --git a/pkg/list/list_stacks.go b/pkg/list/list_stacks.go index 5283146aa0..f79fcef63d 100644 --- a/pkg/list/list_stacks.go +++ b/pkg/list/list_stacks.go @@ -260,7 +260,7 @@ func FilterAndListStacks(stacksMap map[string]any, component string, listConfig } return string(jsonBytes) + utils.GetLineEnding(), nil - case FormatCSV: + case FormatCSV, FormatTSV: // Only include columns that have values var nonEmptyHeaders []string var nonEmptyColumnIndexes []int @@ -280,15 +280,46 @@ func FilterAndListStacks(stacksMap map[string]any, component string, listConfig } } + // Set appropriate delimiter based on format + fileDelimiter := delimiter + if delimiter == "\t" || delimiter == "" { + switch format { + case FormatCSV: + fileDelimiter = "," + case FormatTSV: + fileDelimiter = "\t" + } + } + var output strings.Builder - output.WriteString(strings.Join(nonEmptyHeaders, delimiter) + utils.GetLineEnding()) + // Helper function to escape values + escapeValue := func(s string) string { + // For TSV, just replace tabs with spaces to maintain format + if format == FormatTSV { + return strings.ReplaceAll(s, "\t", " ") + } + // For CSV, use standard CSV escaping + if strings.Contains(s, fileDelimiter) || strings.Contains(s, "\n") || strings.Contains(s, "\"") { + return "\""+strings.ReplaceAll(s, "\"", "\"\"")+"\"" + } + return s + } + + // Write headers + escapedHeaders := make([]string, len(nonEmptyHeaders)) + for i, header := range nonEmptyHeaders { + escapedHeaders[i] = escapeValue(header) + } + output.WriteString(strings.Join(escapedHeaders, fileDelimiter) + utils.GetLineEnding()) + + // Write rows for _, row := range rows { - var nonEmptyRow []string + var escapedRow []string for _, i := range nonEmptyColumnIndexes { - nonEmptyRow = append(nonEmptyRow, row[i]) + escapedRow = append(escapedRow, escapeValue(row[i])) } - output.WriteString(strings.Join(nonEmptyRow, delimiter) + utils.GetLineEnding()) + output.WriteString(strings.Join(escapedRow, fileDelimiter) + utils.GetLineEnding()) } return output.String(), nil diff --git a/pkg/list/list_workflows.go b/pkg/list/list_workflows.go index 54de87cdd8..12920d8484 100644 --- a/pkg/list/list_workflows.go +++ b/pkg/list/list_workflows.go @@ -23,6 +23,7 @@ const ( FormatTable = "table" FormatJSON = "json" FormatCSV = "csv" + FormatTSV = "tsv" ) // ValidateFormat checks if the given format is supported @@ -30,7 +31,7 @@ func ValidateFormat(format string) error { if format == "" { return nil } - validFormats := []string{FormatTable, FormatJSON, FormatCSV} + validFormats := []string{FormatTable, FormatJSON, FormatCSV, FormatTSV} for _, f := range validFormats { if format == f { return nil