diff --git a/internal/output/plain.go b/internal/output/plain.go index f6425d1abe..14a0f3c554 100644 --- a/internal/output/plain.go +++ b/internal/output/plain.go @@ -278,6 +278,10 @@ func sprintTable(vertical bool, slice []interface{}) (string, error) { return "", nil } + if vertical { + return sprintVerticalTable(slice) + } + headers := []string{} rows := [][]string{} for _, v := range slice { @@ -333,14 +337,58 @@ func sprintTable(vertical bool, slice []interface{}) (string, error) { } } - if vertical { - t := table.New([]string{"", ""}) - t.AddRow(verticalRows(headers, rows)...) - t.HideHeaders = true - return t.Render(), nil + return table.New(headers).AddRow(rows...).Render(), nil +} + +type verticalRow struct { + header string + content string +} + +func sprintVerticalTable(slice []interface{}) (string, error) { + if len(slice) == 0 { + return "", nil } - return table.New(headers).AddRow(rows...).Render(), nil + rows := [][]verticalRow{} + for _, v := range slice { + meta, err := parseStructMeta(v) + if err != nil { + return "", err + } + + row := []verticalRow{} + for _, field := range meta { + if funk.Contains(field.opts, string(HidePlain)) { + continue + } + + stringValue, err := sprint(field.value) + if err != nil { + return "", err + } + + if funk.Contains(field.opts, string(OmitEmpty)) && (stringValue == "" || stringValue == nilText) { + continue + } + + if funk.Contains(field.opts, string(EmptyNil)) && stringValue == nilText { + stringValue = "" + } + + row = append(row, verticalRow{header: localizedField(field.l10n), content: stringValue}) + } + + if len(row) > 0 { + rows = append(rows, row) + } + } + + t := table.New([]string{"", ""}) + t.AddRow(verticalRows(rows)...) + t.HideHeaders = true + t.Vertical = true + return t.Render(), nil } func asSlice(val interface{}) ([]interface{}, error) { @@ -402,17 +450,12 @@ func columns(offset int, value string) []string { return cols } -func verticalRows(hdrs []string, rows [][]string) [][]string { +func verticalRows(rows [][]verticalRow) [][]string { var vrows [][]string for i, hrow := range rows { - for j, hcol := range hrow { - var header string - if j < len(hdrs) { - header = hdrs[j] - } - - vrow := []string{header, hcol} + for _, hcol := range hrow { + vrow := []string{hcol.header, hcol.content} vrows = append(vrows, vrow) } diff --git a/internal/table/table.go b/internal/table/table.go index f43f08f458..412c03816a 100644 --- a/internal/table/table.go +++ b/internal/table/table.go @@ -32,6 +32,7 @@ type Table struct { rows []row HideHeaders bool + Vertical bool } func New(headers []string) *Table { @@ -103,6 +104,13 @@ func (t *Table) calculateWidth(maxTableWidth int) ([]int, int) { colWidthsCombined += colWidths[n] } + // Capture the width of the vertical header before we equalize the column widths. + // We must respect this width when rescaling the columns. + var verticalHeaderWidth int + if len(colWidths) > 0 && t.Vertical { + verticalHeaderWidth = colWidths[0] + } + if colWidthsCombined >= maxTableWidth { // Equalize widths by 20% of average width. // This is to prevent columns that are much larger than others @@ -115,7 +123,7 @@ func (t *Table) calculateWidth(maxTableWidth int) ([]int, int) { tableWidth = mathutils.MinInt(tableWidth, maxTableWidth) // Now scale back the row sizes according to the max width - rescaleColumns(colWidths, tableWidth) + rescaleColumns(colWidths, tableWidth, t.Vertical, verticalHeaderWidth) logging.Debug("Table column widths: %v, total: %d", colWidths, tableWidth) return colWidths, tableWidth @@ -138,11 +146,13 @@ func equalizeWidths(colWidths []int, percentage int) { } } -func rescaleColumns(colWidths []int, targetTotal int) { +func rescaleColumns(colWidths []int, targetTotal int, vertical bool, verticalHeaderWidth int) { total := float64(mathutils.Total(colWidths...)) multiplier := float64(targetTotal) / total + originalWidths := make([]int, len(colWidths)) for n := range colWidths { + originalWidths[n] = colWidths[n] colWidths[n] = int(float64(colWidths[n]) * multiplier) } @@ -150,6 +160,17 @@ func rescaleColumns(colWidths []int, targetTotal int) { if len(colWidths) > 0 { colWidths[len(colWidths)-1] += targetTotal - mathutils.Total(colWidths...) } + + // If vertical, respect the header width + // verticalHeaderWidth is the width of the header column before we equalized the column widths. + // We compare the current width of the header column with the original width and adjust the other columns accordingly. + if vertical && len(colWidths) > 0 && colWidths[0] < verticalHeaderWidth { + diff := verticalHeaderWidth - colWidths[0] + colWidths[0] += diff + for i := 1; i < len(colWidths); i++ { + colWidths[i] -= diff / (len(colWidths) - 1) + } + } } func renderRow(providedColumns []string, colWidths []int) string { diff --git a/internal/table/table_test.go b/internal/table/table_test.go index 7ba90de0e2..0291231059 100644 --- a/internal/table/table_test.go +++ b/internal/table/table_test.go @@ -28,6 +28,7 @@ func TestTable_colWidths(t1 *testing.T) { {[]string{"1", "2", "3"}}, }, false, + false, }, 100, }, @@ -42,6 +43,7 @@ func TestTable_colWidths(t1 *testing.T) { {[]string{"1", "2", "3"}}, }, false, + false, }, 100, }, @@ -57,6 +59,7 @@ func TestTable_colWidths(t1 *testing.T) { {[]string{"1", "0123456789012345678901234567890123456789"}}, }, false, + false, }, 100, }, @@ -71,6 +74,7 @@ func TestTable_colWidths(t1 *testing.T) { {[]string{"123", "1234", "12345"}}, }, false, + false, }, 100, }, @@ -85,6 +89,7 @@ func TestTable_colWidths(t1 *testing.T) { {[]string{"1", "2", "3"}}, }, false, + false, }, 100, }, @@ -100,6 +105,7 @@ func TestTable_colWidths(t1 *testing.T) { {[]string{strings.Repeat(" ", 100)}}, }, false, + false, }, 100, }, @@ -115,6 +121,7 @@ func TestTable_colWidths(t1 *testing.T) { {[]string{"1", strings.Repeat(" ", 200)}}, }, false, + false, }, 100, }, @@ -333,7 +340,7 @@ func Test_rescaleColumns(t *testing.T) { } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - rescaleColumns(tt.args.colWidths, tt.args.targetTotal) + rescaleColumns(tt.args.colWidths, tt.args.targetTotal, false, tt.args.colWidths[0]) if !reflect.DeepEqual(tt.args.colWidths, tt.want) { t.Errorf("rescaleColumns() got = %v, want %v", tt.args.colWidths, tt.want) }