syft/syft/format/table/encoder.go
Carlos Tadeu Panato Junior 2111d4d0e4
chore: upgrade tablewriter dependency to use new API (#3990)
* upgrade tablewriter

Signed-off-by: Carlos Panato <ctadeu@gmail.com>

* remove header line whitespace

Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com>

* fix static analysis

Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com>

---------

Signed-off-by: Carlos Panato <ctadeu@gmail.com>
Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com>
Co-authored-by: Alex Goodman <wagoodman@users.noreply.github.com>
2025-07-01 16:16:16 -04:00

192 lines
4.2 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

package table
import (
"fmt"
"io"
"sort"
"strings"
"github.com/charmbracelet/lipgloss"
"github.com/olekukonko/tablewriter"
"github.com/olekukonko/tablewriter/renderer"
"github.com/olekukonko/tablewriter/tw"
"github.com/anchore/syft/syft/sbom"
)
const ID sbom.FormatID = "syft-table"
type encoder struct {
}
func NewFormatEncoder() sbom.FormatEncoder {
return encoder{}
}
func (e encoder) ID() sbom.FormatID {
return ID
}
func (e encoder) Aliases() []string {
return []string{
"table",
}
}
func (e encoder) Version() string {
return sbom.AnyVersion
}
func (e encoder) Encode(writer io.Writer, s sbom.SBOM) error {
var rows [][]string
columns := []string{"Name", "Version", "Type"}
for _, p := range s.Artifacts.Packages.Sorted() {
row := []string{
p.Name,
p.Version,
string(p.Type),
}
rows = append(rows, row)
}
if len(rows) == 0 {
_, err := fmt.Fprintln(writer, "No packages discovered")
return err
}
// sort by name, version, then type
sort.SliceStable(rows, func(i, j int) bool {
for col := 0; col < len(columns); col++ {
if rows[i][col] != rows[j][col] {
return rows[i][col] < rows[j][col]
}
}
return false
})
columns = append(columns, "") // add a column for duplicate annotations
rows = markDuplicateRows(rows)
table := newTableWriter(writer, columns)
if err := table.Bulk(rows); err != nil {
return fmt.Errorf("failed to add table rows: %w", err)
}
return table.Render()
}
func newTableWriter(writer io.Writer, columns []string) *tablewriter.Table {
// Heres 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 {
seen := map[string]int{}
var result [][]string
for _, v := range items {
key := strings.Join(v, "|")
if _, ok := seen[key]; ok {
// dup!
seen[key]++
continue
}
seen[key] = 1
result = append(result, v)
}
style := lipgloss.NewStyle().Foreground(lipgloss.Color("#777777"))
for i, v := range result {
key := strings.Join(v, "|")
// var name string
var annotation string
switch seen[key] {
case 0, 1:
case 2:
annotation = "(+1 duplicate)"
default:
annotation = fmt.Sprintf("(+%d duplicates)", seen[key]-1)
}
annotation = style.Render(annotation)
result[i] = append(v, annotation)
}
return result
}