github.com/anchore/syft@v1.4.2-0.20240516191711-1bec1fc5d397/syft/format/table/encoder.go (about) 1 package table 2 3 import ( 4 "fmt" 5 "io" 6 "sort" 7 "strings" 8 9 "github.com/charmbracelet/lipgloss" 10 "github.com/olekukonko/tablewriter" 11 12 "github.com/anchore/syft/syft/sbom" 13 ) 14 15 const ID sbom.FormatID = "syft-table" 16 17 type encoder struct { 18 } 19 20 func NewFormatEncoder() sbom.FormatEncoder { 21 return encoder{} 22 } 23 24 func (e encoder) ID() sbom.FormatID { 25 return ID 26 } 27 28 func (e encoder) Aliases() []string { 29 return []string{ 30 "table", 31 } 32 } 33 34 func (e encoder) Version() string { 35 return sbom.AnyVersion 36 } 37 38 func (e encoder) Encode(writer io.Writer, s sbom.SBOM) error { 39 var rows [][]string 40 41 columns := []string{"Name", "Version", "Type"} 42 for _, p := range s.Artifacts.Packages.Sorted() { 43 row := []string{ 44 p.Name, 45 p.Version, 46 string(p.Type), 47 } 48 rows = append(rows, row) 49 } 50 51 if len(rows) == 0 { 52 _, err := fmt.Fprintln(writer, "No packages discovered") 53 return err 54 } 55 56 // sort by name, version, then type 57 sort.SliceStable(rows, func(i, j int) bool { 58 for col := 0; col < len(columns); col++ { 59 if rows[i][col] != rows[j][col] { 60 return rows[i][col] < rows[j][col] 61 } 62 } 63 return false 64 }) 65 66 columns = append(columns, "") // add a column for duplicate annotations 67 rows = markDuplicateRows(rows) 68 69 table := tablewriter.NewWriter(writer) 70 71 table.SetHeader(columns) 72 table.SetHeaderLine(false) 73 table.SetBorder(false) 74 table.SetAutoWrapText(false) 75 table.SetAutoFormatHeaders(true) 76 table.SetHeaderAlignment(tablewriter.ALIGN_LEFT) 77 table.SetAlignment(tablewriter.ALIGN_LEFT) 78 table.SetCenterSeparator("") 79 table.SetColumnSeparator("") 80 table.SetRowSeparator("") 81 table.SetTablePadding(" ") 82 table.SetNoWhiteSpace(true) 83 84 table.AppendBulk(rows) 85 table.Render() 86 87 return nil 88 } 89 90 func markDuplicateRows(items [][]string) [][]string { 91 seen := map[string]int{} 92 var result [][]string 93 94 for _, v := range items { 95 key := strings.Join(v, "|") 96 if _, ok := seen[key]; ok { 97 // dup! 98 seen[key]++ 99 continue 100 } 101 102 seen[key] = 1 103 result = append(result, v) 104 } 105 106 style := lipgloss.NewStyle().Foreground(lipgloss.Color("#777777")) 107 for i, v := range result { 108 key := strings.Join(v, "|") 109 // var name string 110 var annotation string 111 switch seen[key] { 112 case 0, 1: 113 case 2: 114 annotation = "(+1 duplicate)" 115 default: 116 annotation = fmt.Sprintf("(+%d duplicates)", seen[key]-1) 117 } 118 119 annotation = style.Render(annotation) 120 result[i] = append(v, annotation) 121 } 122 123 return result 124 }