diff --git a/go.mod b/go.mod index f192eb07e..c6a49b20d 100644 --- a/go.mod +++ b/go.mod @@ -65,7 +65,7 @@ require ( github.com/mholt/archives v0.1.3 github.com/moby/sys/mountinfo v0.7.2 github.com/nix-community/go-nix v0.0.0-20250101154619-4bdde671e0a1 - github.com/olekukonko/tablewriter v0.0.5 + github.com/olekukonko/tablewriter v1.0.7 github.com/opencontainers/go-digest v1.0.0 github.com/pelletier/go-toml v1.9.5 github.com/quasilyte/go-ruleguard/dsl v0.3.22 @@ -137,6 +137,7 @@ require ( github.com/containerd/containerd/api v1.8.0 // indirect github.com/containerd/continuity v0.4.4 // indirect github.com/containerd/errdefs v1.0.0 // indirect + github.com/containerd/errdefs/pkg v0.3.0 // indirect github.com/containerd/fifo v1.1.0 // indirect github.com/containerd/log v0.1.0 // indirect github.com/containerd/platforms v0.2.1 // indirect @@ -155,6 +156,7 @@ require ( github.com/dsnet/compress v0.0.2-0.20230904184137-39efe44ab707 // indirect github.com/emirpasic/gods v1.18.1 // indirect github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f // indirect + github.com/fatih/color v1.17.0 // indirect github.com/felixge/fgprof v0.9.5 // indirect github.com/felixge/httpsnoop v1.0.4 // indirect github.com/fsnotify/fsnotify v1.8.0 // indirect @@ -195,6 +197,7 @@ require ( github.com/mattn/go-localereader v0.0.2-0.20220822084749-2491eb6c1c75 // indirect github.com/mattn/go-runewidth v0.0.16 // indirect github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d // indirect + github.com/mikelolasagasti/xz v1.0.1 // indirect github.com/minio/minlz v1.0.0 // indirect github.com/mitchellh/copystructure v1.2.0 // indirect github.com/mitchellh/go-homedir v1.1.0 // indirect @@ -213,6 +216,8 @@ require ( github.com/ncruces/go-strftime v0.1.9 // indirect github.com/nwaples/rardecode v1.1.3 // indirect github.com/nwaples/rardecode/v2 v2.1.0 // indirect + github.com/olekukonko/errors v0.0.0-20250405072817-4e6d85265da6 // indirect + github.com/olekukonko/ll v0.0.8 // indirect github.com/opencontainers/image-spec v1.1.1 // indirect github.com/opencontainers/runtime-spec v1.1.0 // indirect github.com/opencontainers/selinux v1.11.0 // indirect @@ -285,11 +290,6 @@ require ( modernc.org/memory v1.11.0 // indirect ) -require ( - github.com/containerd/errdefs/pkg v0.3.0 // indirect - github.com/mikelolasagasti/xz v1.0.1 // indirect -) - retract ( v1.25.0 // published with a replace directive (confusing for API users) v0.53.2 diff --git a/go.sum b/go.sum index 034825cb8..c95d7acbe 100644 --- a/go.sum +++ b/go.sum @@ -913,6 +913,8 @@ github.com/facebookincubator/nvdtools v0.1.5/go.mod h1:Kh55SAWnjckS96TBSrXI99KrE github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= github.com/fatih/color v1.9.0/go.mod h1:eQcE1qtQxscV5RaZvpXrrb8Drkc3/DdQ+uUYCNjL+zU= github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk= +github.com/fatih/color v1.17.0 h1:GlRw1BRJxkpqUCBKzKOw098ed57fEsKeNjpTe3cSjK4= +github.com/fatih/color v1.17.0/go.mod h1:YZ7TlrGPkiz6ku9fK3TLD/pl3CpsiFyu8N92HLgmosI= github.com/fatih/set v0.2.1 h1:nn2CaJyknWE/6txyUDGwysr3G5QC6xWB/PtVjPBbeaA= github.com/fatih/set v0.2.1/go.mod h1:+RKtMCH+favT2+3YecHGxcc0b4KyVWA1QWWJUs4E0CI= github.com/felixge/fgprof v0.9.3/go.mod h1:RdbpDgzqYVh/T9fPELJyV7EYJuHB55UTEULNun8eiPw= @@ -1272,7 +1274,6 @@ github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D github.com/mattn/go-localereader v0.0.2-0.20220822084749-2491eb6c1c75 h1:P8UmIzZMYDR+NGImiFvErt6VWfIRPuGM+vyjiEdkmIw= github.com/mattn/go-localereader v0.0.2-0.20220822084749-2491eb6c1c75/go.mod h1:8fBrzywKY7BI3czFoHkuzRoWE9C+EiG4R1k4Cjx5p88= github.com/mattn/go-runewidth v0.0.4/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU= -github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= github.com/mattn/go-runewidth v0.0.16 h1:E5ScNMtiwvlvB5paMFdw9p4kSQzbXFikJ5SQO6TULQc= github.com/mattn/go-runewidth v0.0.16/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= github.com/mattn/go-sqlite3 v1.14.14/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU= @@ -1347,8 +1348,12 @@ github.com/nwaples/rardecode v1.1.3 h1:cWCaZwfM5H7nAD6PyEdcVnczzV8i/JtotnyW/dD9l github.com/nwaples/rardecode v1.1.3/go.mod h1:5DzqNKiOdpKKBH87u8VlvAnPZMXcGRhxWkRpHbbfGS0= github.com/nwaples/rardecode/v2 v2.1.0 h1:JQl9ZoBPDy+nIZGb1mx8+anfHp/LV3NE2MjMiv0ct/U= github.com/nwaples/rardecode/v2 v2.1.0/go.mod h1:7uz379lSxPe6j9nvzxUZ+n7mnJNgjsRNb6IbvGVHRmw= -github.com/olekukonko/tablewriter v0.0.5 h1:P2Ga83D34wi1o9J6Wh1mRuqd4mF/x/lgBS7N7AbDhec= -github.com/olekukonko/tablewriter v0.0.5/go.mod h1:hPp6KlRPjbx+hW8ykQs1w3UBbZlj6HuIJcUGPhkA7kY= +github.com/olekukonko/errors v0.0.0-20250405072817-4e6d85265da6 h1:r3FaAI0NZK3hSmtTDrBVREhKULp8oUeqLT5Eyl2mSPo= +github.com/olekukonko/errors v0.0.0-20250405072817-4e6d85265da6/go.mod h1:ppzxA5jBKcO1vIpCXQ9ZqgDh8iwODz6OXIGKU8r5m4Y= +github.com/olekukonko/ll v0.0.8 h1:sbGZ1Fx4QxJXEqL/6IG8GEFnYojUSQ45dJVwN2FH2fc= +github.com/olekukonko/ll v0.0.8/go.mod h1:En+sEW0JNETl26+K8eZ6/W4UQ7CYSrrgg/EdIYT2H8g= +github.com/olekukonko/tablewriter v1.0.7 h1:HCC2e3MM+2g72M81ZcJU11uciw6z/p82aEnm4/ySDGw= +github.com/olekukonko/tablewriter v1.0.7/go.mod h1:H428M+HzoUXC6JU2Abj9IT9ooRmdq9CxuDmKMtrOCMs= github.com/onsi/gomega v1.34.1 h1:EUMJIKUjM8sKjYbtxQI9A4z2o+rruxnzNvpknOXie6k= github.com/onsi/gomega v1.34.1/go.mod h1:kU1QgUvBDLXBJq618Xvm2LUX6rSAfRaFRTcdOeDLwwY= github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U= diff --git a/syft/format/internal/testutil/snapshot.go b/syft/format/internal/testutil/snapshot.go index bc13fd0aa..0d4b6fab7 100644 --- a/syft/format/internal/testutil/snapshot.go +++ b/syft/format/internal/testutil/snapshot.go @@ -68,15 +68,15 @@ func AssertEncoderAgainstGoldenSnapshot(t *testing.T, cfg EncoderSnapshotTestCon if cfg.IsJSON { require.JSONEq(t, string(expected), string(actual)) } else { - requireEqual(t, expected, actual) + requireEqual(t, string(expected), string(actual)) } } -func requireEqual(t *testing.T, expected any, actual any) { +func requireEqual(t *testing.T, expected string, actual string) { if diff := cmp.Diff(expected, actual); diff != "" { // uncomment to debug - // t.Logf("expected: %s", expected) - // t.Logf("actual: %s", actual) + // t.Logf("expected:\n%s", expected) + // t.Logf("actual:\n%s", actual) t.Fatalf("mismatched output: %s", diff) } } diff --git a/syft/format/table/encoder.go b/syft/format/table/encoder.go index b025acdc5..f0e51063a 100644 --- a/syft/format/table/encoder.go +++ b/syft/format/table/encoder.go @@ -8,6 +8,8 @@ import ( "github.com/charmbracelet/lipgloss" "github.com/olekukonko/tablewriter" + "github.com/olekukonko/tablewriter/renderer" + "github.com/olekukonko/tablewriter/tw" "github.com/anchore/syft/syft/sbom" ) @@ -66,25 +68,90 @@ func (e encoder) Encode(writer io.Writer, s sbom.SBOM) error { columns = append(columns, "") // add a column for duplicate annotations rows = markDuplicateRows(rows) - table := tablewriter.NewWriter(writer) + table := newTableWriter(writer, columns) - table.SetHeader(columns) - table.SetHeaderLine(false) - table.SetBorder(false) - table.SetAutoWrapText(false) - table.SetAutoFormatHeaders(true) - table.SetHeaderAlignment(tablewriter.ALIGN_LEFT) - table.SetAlignment(tablewriter.ALIGN_LEFT) - table.SetCenterSeparator("") - table.SetColumnSeparator("") - table.SetRowSeparator("") - table.SetTablePadding(" ") - table.SetNoWhiteSpace(true) + if err := table.Bulk(rows); err != nil { + return fmt.Errorf("failed to add table rows: %w", err) + } - table.AppendBulk(rows) - table.Render() + return table.Render() +} - return nil +func newTableWriter(writer io.Writer, columns []string) *tablewriter.Table { + // Here’s a simplified diagram of a table with a header, rows, and footer: + // + // [Borders.Top] + // | Header1 | Header2 | (Line below header: Lines.ShowTop) + // [Separators.BetweenRows] + // | Row1 | Row1 | + // [Separators.BetweenRows] + // | Row2 | Row2 | + // [Lines.ShowBottom] + // | Footer1 | Footer2 | + // [Borders.Bottom] + // + // So for example: + // ┌──────┬─────┐ <- Borders.Top + // │ NAME │ AGE │ + // ├──────┼─────┤ <- Lines.ShowTop + // │ Alice│ 25 │ + // ├──────┼─────┤ <- Separators.BetweenRows + // │ Bob │ 30 │ + // ├──────┼─────┤ <- Lines.ShowBottom + // │ Total│ 2 │ + // └──────┴─────┘ <- Borders.Bottom + + return tablewriter.NewTable(writer, + tablewriter.WithHeader(columns), + tablewriter.WithHeaderAutoFormat(tw.On), + tablewriter.WithHeaderAutoWrap(tw.WrapNone), + tablewriter.WithHeaderAlignment(tw.AlignLeft), + tablewriter.WithRowAutoFormat(tw.Off), + tablewriter.WithRowAutoWrap(tw.WrapNone), + tablewriter.WithRowAlignment(tw.AlignLeft), + tablewriter.WithTrimSpace(tw.On), + tablewriter.WithAutoHide(tw.On), + tablewriter.WithRenderer(renderer.NewBlueprint()), + tablewriter.WithBehavior( + tw.Behavior{ + TrimSpace: tw.On, + AutoHide: tw.On, + }, + ), + tablewriter.WithPadding( + tw.Padding{ + Left: "", + Right: " ", + Top: "", + Bottom: "", + }, + ), + tablewriter.WithRendition( + tw.Rendition{ + Symbols: tw.NewSymbols(tw.StyleNone), + Borders: tw.Border{ + Left: tw.Off, + Top: tw.Off, + Right: tw.Off, + Bottom: tw.Off, + }, + Settings: tw.Settings{ + Separators: tw.Separators{ + ShowHeader: tw.Off, + ShowFooter: tw.Off, + BetweenRows: tw.Off, + BetweenColumns: tw.Off, + }, + Lines: tw.Lines{ + ShowTop: tw.Off, + ShowBottom: tw.Off, + ShowHeaderLine: tw.Off, + ShowFooterLine: tw.Off, + }, + }, + }, + ), + ) } func markDuplicateRows(items [][]string) [][]string { diff --git a/syft/format/table/test-fixtures/snapshot/TestTableEncoder.golden b/syft/format/table/test-fixtures/snapshot/TestTableEncoder.golden index f5ca61493..602114bde 100644 --- a/syft/format/table/test-fixtures/snapshot/TestTableEncoder.golden +++ b/syft/format/table/test-fixtures/snapshot/TestTableEncoder.golden @@ -1,3 +1,3 @@ -NAME VERSION TYPE -package-1 1.0.1 python -package-2 2.0.1 deb +NAME VERSION TYPE +package-1 1.0.1 python +package-2 2.0.1 deb