From f46564e491f439043820d00a2c88d1748c1edda5 Mon Sep 17 00:00:00 2001 From: mdrakos Date: Fri, 23 Feb 2024 15:44:12 -0800 Subject: [PATCH 01/13] Fix omitEmpty and align headers --- internal/output/plain.go | 21 ++++++++++++++------- 1 file changed, 14 insertions(+), 7 deletions(-) diff --git a/internal/output/plain.go b/internal/output/plain.go index ad13dcf5f9..229e84a9a2 100644 --- a/internal/output/plain.go +++ b/internal/output/plain.go @@ -266,7 +266,7 @@ func sprintMap(value interface{}) (string, error) { sort.Slice(result, func(i, j int) bool { return result[i] < result[j] }) - return "\n" + strings.Join(result, "\n"), nil + return strings.Join(result, "\n"), nil } // sprintTable will marshal and return the given slice of structs as a string, formatted as a table @@ -299,14 +299,14 @@ func sprintTable(vertical bool, slice []interface{}) (string, error) { return "", err } - if funk.Contains(field.opts, string(OmitEmpty)) && (stringValue == "" || stringValue == nilText) { - continue - } - if firstIteration && !funk.Contains(field.opts, string(SeparateLineOpt)) { headers = append(headers, localizedField(field.l10n)) } + if funk.Contains(field.opts, string(OmitEmpty)) && (stringValue == "" || stringValue == nilText) { + stringValue = "" + } + if funk.Contains(field.opts, string(EmptyNil)) && stringValue == nilText { stringValue = "" } @@ -325,7 +325,8 @@ func sprintTable(vertical bool, slice []interface{}) (string, error) { row = append(row, columns(offset, stringValue)...) } - if len(row) > 0 { + // Append row if vertical so we can align headers later + if len(row) > 0 || vertical { rows = append(rows, row) } } @@ -334,6 +335,7 @@ func sprintTable(vertical bool, slice []interface{}) (string, error) { t := table.New([]string{"", ""}) t.AddRow(verticalRows(headers, rows)...) t.HideHeaders = true + t.Vertical = true return t.Render(), nil } @@ -406,7 +408,12 @@ func verticalRows(hdrs []string, rows [][]string) [][]string { for j, hcol := range hrow { var header string if j < len(hdrs) { - header = hdrs[j] + // Align headers with rows + if hcol == "" { + continue + } else { + header = hdrs[j] + } } vrow := []string{header, hcol} From 72f0998ddaab7e8ef3a303aec1de99c2b1121b6a Mon Sep 17 00:00:00 2001 From: mdrakos Date: Fri, 23 Feb 2024 15:44:40 -0800 Subject: [PATCH 02/13] Fix vertical header spacing --- internal/table/table.go | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/internal/table/table.go b/internal/table/table.go index f43f08f458..f18250d3cc 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,11 @@ func (t *Table) calculateWidth(maxTableWidth int) ([]int, int) { colWidthsCombined += colWidths[n] } + 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 +121,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 +144,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 +158,13 @@ func rescaleColumns(colWidths []int, targetTotal int) { if len(colWidths) > 0 { colWidths[len(colWidths)-1] += targetTotal - mathutils.Total(colWidths...) } + + // If vertical, respect the header width + if vertical && len(colWidths) > 0 && colWidths[0] < verticalHeaderWidth { + diff := verticalHeaderWidth - colWidths[0] + colWidths[0] += diff + colWidths[len(colWidths)-1] -= diff + } } func renderRow(providedColumns []string, colWidths []int) string { From fdeda2ce24557af05e46e4631f0b5b249d7d25d6 Mon Sep 17 00:00:00 2001 From: mdrakos Date: Fri, 23 Feb 2024 15:58:41 -0800 Subject: [PATCH 03/13] Fix unit test --- internal/output/plain_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/output/plain_test.go b/internal/output/plain_test.go index 836e8c0947..cdce06376b 100644 --- a/internal/output/plain_test.go +++ b/internal/output/plain_test.go @@ -78,7 +78,7 @@ func TestPlain_Print(t *testing.T) { "int": int(1), "float": float32(9.1), }}, - "\n float: 9.10 \n int: 1 \n string: hello \n", + " float: 9.10 \n int: 1 \n string: hello \n", "", }, { From f4d6eabf967ac0fbe3074170ca7abb463a961c08 Mon Sep 17 00:00:00 2001 From: mdrakos Date: Fri, 23 Feb 2024 16:12:42 -0800 Subject: [PATCH 04/13] Fix more tests --- internal/table/table_test.go | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) 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) } From 1cc0a891d938a730a19d7ab4295e1d0a5e5addde Mon Sep 17 00:00:00 2001 From: mdrakos Date: Wed, 13 Mar 2024 15:25:51 -0700 Subject: [PATCH 05/13] Use separate vertical table printing function --- internal/output/plain.go | 93 ++++++++++++++++++++++++++++------------ 1 file changed, 65 insertions(+), 28 deletions(-) diff --git a/internal/output/plain.go b/internal/output/plain.go index 229e84a9a2..579acdb4fb 100644 --- a/internal/output/plain.go +++ b/internal/output/plain.go @@ -1,7 +1,6 @@ package output import ( - "errors" "fmt" "io" "reflect" @@ -266,7 +265,7 @@ func sprintMap(value interface{}) (string, error) { sort.Slice(result, func(i, j int) bool { return result[i] < result[j] }) - return strings.Join(result, "\n"), nil + return "\n" + strings.Join(result, "\n"), nil } // sprintTable will marshal and return the given slice of structs as a string, formatted as a table @@ -275,13 +274,13 @@ func sprintTable(vertical bool, slice []interface{}) (string, error) { return "", nil } + if vertical { + return sprintVerticalTable(slice) + } + headers := []string{} rows := [][]string{} for _, v := range slice { - if !isStruct(v) { - return "", errors.New("Tried to sprintTable with slice that doesn't contain all structs") - } - meta, err := parseStructMeta(v) if err != nil { return "", err @@ -322,24 +321,70 @@ func sprintTable(vertical bool, slice []interface{}) (string, error) { break } - row = append(row, columns(offset, stringValue)...) + rowValue := columns(offset, stringValue) + + row = append(row, rowValue...) } - // Append row if vertical so we can align headers later - if len(row) > 0 || vertical { + if len(row) > 0 { rows = append(rows, row) } } - if vertical { - t := table.New([]string{"", ""}) - t.AddRow(verticalRows(headers, rows)...) - t.HideHeaders = true - t.Vertical = true - return t.Render(), nil + return table.New(headers).AddRow(rows...).Render(), nil +} + +type binding struct { + header string + value string +} + +func sprintVerticalTable(slice []interface{}) (string, error) { + if len(slice) == 0 { + return "", nil } - return table.New(headers).AddRow(rows...).Render(), nil + rows := [][]binding{} + for _, v := range slice { + meta, err := parseStructMeta(v) + if err != nil { + return "", err + } + + row := []binding{} + 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) { + stringValue = "" + } + + if funk.Contains(field.opts, string(EmptyNil)) && stringValue == nilText { + stringValue = "" + } + + if stringValue != "" { + row = append(row, binding{header: localizedField(field.l10n), value: 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) { @@ -401,22 +446,14 @@ func columns(offset int, value string) []string { return cols } -func verticalRows(hdrs []string, rows [][]string) [][]string { +func verticalRows(rows [][]binding) [][]string { var vrows [][]string for i, hrow := range rows { - for j, hcol := range hrow { - var header string - if j < len(hdrs) { - // Align headers with rows - if hcol == "" { - continue - } else { - header = hdrs[j] - } - } + for _, hcol := range hrow { + header := hcol.header - vrow := []string{header, hcol} + vrow := []string{header, hcol.value} vrows = append(vrows, vrow) } From b6472f9a787242b201aa641e19525198a831a123 Mon Sep 17 00:00:00 2001 From: mdrakos Date: Wed, 13 Mar 2024 15:36:45 -0700 Subject: [PATCH 06/13] Fix test --- internal/output/plain_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/output/plain_test.go b/internal/output/plain_test.go index cdce06376b..836e8c0947 100644 --- a/internal/output/plain_test.go +++ b/internal/output/plain_test.go @@ -78,7 +78,7 @@ func TestPlain_Print(t *testing.T) { "int": int(1), "float": float32(9.1), }}, - " float: 9.10 \n int: 1 \n string: hello \n", + "\n float: 9.10 \n int: 1 \n string: hello \n", "", }, { From 118a135fc25a93b8d6d3d9bd4a7e2d327ec59c78 Mon Sep 17 00:00:00 2001 From: mdrakos Date: Mon, 18 Mar 2024 11:45:24 -0700 Subject: [PATCH 07/13] Apply diff over remaining columns --- internal/table/table.go | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/internal/table/table.go b/internal/table/table.go index f18250d3cc..ee34324edd 100644 --- a/internal/table/table.go +++ b/internal/table/table.go @@ -163,7 +163,9 @@ func rescaleColumns(colWidths []int, targetTotal int, vertical bool, verticalHea if vertical && len(colWidths) > 0 && colWidths[0] < verticalHeaderWidth { diff := verticalHeaderWidth - colWidths[0] colWidths[0] += diff - colWidths[len(colWidths)-1] -= diff + for i := 1; i < len(colWidths); i++ { + colWidths[i] = diff / (len(colWidths) - 1) + } } } From 402b68525094c5befb5c081ef869da46f62f2ac5 Mon Sep 17 00:00:00 2001 From: mdrakos Date: Mon, 18 Mar 2024 13:11:04 -0700 Subject: [PATCH 08/13] Renaming for clarity --- internal/output/plain.go | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/internal/output/plain.go b/internal/output/plain.go index 579acdb4fb..93a0f69ccb 100644 --- a/internal/output/plain.go +++ b/internal/output/plain.go @@ -334,9 +334,9 @@ func sprintTable(vertical bool, slice []interface{}) (string, error) { return table.New(headers).AddRow(rows...).Render(), nil } -type binding struct { - header string - value string +type verticalRow struct { + header string + content string } func sprintVerticalTable(slice []interface{}) (string, error) { @@ -344,14 +344,14 @@ func sprintVerticalTable(slice []interface{}) (string, error) { return "", nil } - rows := [][]binding{} + rows := [][]verticalRow{} for _, v := range slice { meta, err := parseStructMeta(v) if err != nil { return "", err } - row := []binding{} + row := []verticalRow{} for _, field := range meta { if funk.Contains(field.opts, string(HidePlain)) { continue @@ -371,7 +371,7 @@ func sprintVerticalTable(slice []interface{}) (string, error) { } if stringValue != "" { - row = append(row, binding{header: localizedField(field.l10n), value: stringValue}) + row = append(row, verticalRow{header: localizedField(field.l10n), content: stringValue}) } } @@ -446,14 +446,14 @@ func columns(offset int, value string) []string { return cols } -func verticalRows(rows [][]binding) [][]string { +func verticalRows(rows [][]verticalRow) [][]string { var vrows [][]string for i, hrow := range rows { for _, hcol := range hrow { header := hcol.header - vrow := []string{header, hcol.value} + vrow := []string{header, hcol.content} vrows = append(vrows, vrow) } From 2326e0de2ef74541f6f37da164f9729835dcacfa Mon Sep 17 00:00:00 2001 From: mdrakos Date: Tue, 19 Mar 2024 09:41:58 -0700 Subject: [PATCH 09/13] Revert some changes made for initial implementation --- internal/output/plain.go | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/internal/output/plain.go b/internal/output/plain.go index 93a0f69ccb..53da99eed7 100644 --- a/internal/output/plain.go +++ b/internal/output/plain.go @@ -303,7 +303,7 @@ func sprintTable(vertical bool, slice []interface{}) (string, error) { } if funk.Contains(field.opts, string(OmitEmpty)) && (stringValue == "" || stringValue == nilText) { - stringValue = "" + continue } if funk.Contains(field.opts, string(EmptyNil)) && stringValue == nilText { @@ -321,9 +321,7 @@ func sprintTable(vertical bool, slice []interface{}) (string, error) { break } - rowValue := columns(offset, stringValue) - - row = append(row, rowValue...) + row = append(row, columns(offset, stringValue)...) } if len(row) > 0 { @@ -363,7 +361,7 @@ func sprintVerticalTable(slice []interface{}) (string, error) { } if funk.Contains(field.opts, string(OmitEmpty)) && (stringValue == "" || stringValue == nilText) { - stringValue = "" + continue } if funk.Contains(field.opts, string(EmptyNil)) && stringValue == nilText { From dc58a4cab49085f4e92dcfd958911670107aa2a9 Mon Sep 17 00:00:00 2001 From: mdrakos Date: Tue, 19 Mar 2024 10:25:23 -0700 Subject: [PATCH 10/13] Add comment and fix equalization --- internal/table/table.go | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/internal/table/table.go b/internal/table/table.go index ee34324edd..07cd79c7a3 100644 --- a/internal/table/table.go +++ b/internal/table/table.go @@ -104,6 +104,8 @@ 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] @@ -164,7 +166,7 @@ func rescaleColumns(colWidths []int, targetTotal int, vertical bool, verticalHea diff := verticalHeaderWidth - colWidths[0] colWidths[0] += diff for i := 1; i < len(colWidths); i++ { - colWidths[i] = diff / (len(colWidths) - 1) + colWidths[i] -= diff / (len(colWidths) - 1) } } } From 8393fe3dd64830e3dbb1ae5593917de923ba537f Mon Sep 17 00:00:00 2001 From: mdrakos Date: Tue, 19 Mar 2024 11:46:20 -0700 Subject: [PATCH 11/13] Revert some sprintTable changes --- internal/output/plain.go | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/internal/output/plain.go b/internal/output/plain.go index 53da99eed7..d96cbba775 100644 --- a/internal/output/plain.go +++ b/internal/output/plain.go @@ -1,6 +1,7 @@ package output import ( + "errors" "fmt" "io" "reflect" @@ -281,6 +282,10 @@ func sprintTable(vertical bool, slice []interface{}) (string, error) { headers := []string{} rows := [][]string{} for _, v := range slice { + if !isStruct(v) { + return "", errors.New("Tried to sprintTable with slice that doesn't contain all structs") + } + meta, err := parseStructMeta(v) if err != nil { return "", err @@ -298,14 +303,14 @@ func sprintTable(vertical bool, slice []interface{}) (string, error) { return "", err } - if firstIteration && !funk.Contains(field.opts, string(SeparateLineOpt)) { - headers = append(headers, localizedField(field.l10n)) - } - if funk.Contains(field.opts, string(OmitEmpty)) && (stringValue == "" || stringValue == nilText) { continue } + if firstIteration && !funk.Contains(field.opts, string(SeparateLineOpt)) { + headers = append(headers, localizedField(field.l10n)) + } + if funk.Contains(field.opts, string(EmptyNil)) && stringValue == nilText { stringValue = "" } @@ -368,9 +373,7 @@ func sprintVerticalTable(slice []interface{}) (string, error) { stringValue = "" } - if stringValue != "" { - row = append(row, verticalRow{header: localizedField(field.l10n), content: stringValue}) - } + row = append(row, verticalRow{header: localizedField(field.l10n), content: stringValue}) } if len(row) > 0 { From d3bd42dd3363bba976666444374ab7a3163fe6fd Mon Sep 17 00:00:00 2001 From: mdrakos Date: Tue, 19 Mar 2024 13:02:42 -0700 Subject: [PATCH 12/13] Add comment --- internal/table/table.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/internal/table/table.go b/internal/table/table.go index 07cd79c7a3..412c03816a 100644 --- a/internal/table/table.go +++ b/internal/table/table.go @@ -162,6 +162,8 @@ func rescaleColumns(colWidths []int, targetTotal int, vertical bool, verticalHea } // 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 From b07d5366d84e226147fc7a21e8495205e34acd42 Mon Sep 17 00:00:00 2001 From: mdrakos Date: Tue, 19 Mar 2024 13:38:10 -0700 Subject: [PATCH 13/13] Don't initialize separate header variable --- internal/output/plain.go | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/internal/output/plain.go b/internal/output/plain.go index d96cbba775..4986a871d6 100644 --- a/internal/output/plain.go +++ b/internal/output/plain.go @@ -452,9 +452,7 @@ func verticalRows(rows [][]verticalRow) [][]string { for i, hrow := range rows { for _, hcol := range hrow { - header := hcol.header - - vrow := []string{header, hcol.content} + vrow := []string{hcol.header, hcol.content} vrows = append(vrows, vrow) }