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  }