Skip to content
This repository was archived by the owner on Oct 31, 2023. It is now read-only.
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,9 @@ Supported configurations:
"gopkg.in/foo/bar.v2" to "github.com/foo/bar". If the map key starts and
ends with `/` then it is treated as a regular expression. In this case,
the map value can use `\1`, `\2`, etc. to reference capture groups.
* `outputColumns` (`array<string>`) - A list of column names in custom order.
Available Column Names: Dependency, Version, SPDX ID, License, Allowed, License Text
Default: `["Dependency", "Version", "SPDX ID", "License", "Allowed"]`

### GitHub Authentication

Expand Down
7 changes: 7 additions & 0 deletions config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,13 @@ type Config struct {
// For example, "gopkg.in/(.*)" => "github.com/\1" would translate
// gopkg into github (incorrectly, but the example would work).
Translate map[string]string `hcl:"translate,optional"`

// OutputColumns list the order for rendering output as xlsx
// Available Column Names:
// Dependency, Version, SPDX ID, License, Allowed, License Text
// Default: ['Dependency', 'Version', 'SPDX ID', 'License', 'Allowed']
// Not matching column names will be represented as empty columns
OutputColumns []string `hcl:"outputColumns,optional"`
}

// Allowed returns the allowed state of a license given the configuration.
Expand Down
2 changes: 1 addition & 1 deletion config/parse.go
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ func Parse(r io.Reader, filename, format string) (*Config, error) {
return parseJSON(r, filename)

default:
return nil, fmt.Errorf("Format must be either 'hcl' or 'json'")
return nil, fmt.Errorf("format must be either 'hcl' or 'json' (%s / %s)", filename, format)
}
}

Expand Down
1 change: 1 addition & 0 deletions license/github/detect.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ func detect(rl *github.RepositoryLicense) (*license.License, error) {
return &license.License{
Name: lic.Name,
SPDX: lic.ID,
Text: lic.Text,
}, nil
}

Expand Down
1 change: 1 addition & 0 deletions license/github/repo_api.go
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@ FETCH_RETRY:
return &license.License{
Name: rl.GetLicense().GetName(),
SPDX: rl.GetLicense().GetSPDXID(),
Text: rl.GetLicense().GetBody(),
}, nil
}

Expand Down
1 change: 1 addition & 0 deletions license/license.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ package license
type License struct {
Name string // Name is a human-friendly name like "MIT License"
SPDX string // SPDX ID of the license, blank if unknown or unavailable
Text string // Text of the license
}

func (l *License) String() string {
Expand Down
6 changes: 5 additions & 1 deletion license/mapper/finder.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,5 +28,9 @@ func (f *Finder) License(ctx context.Context, m module.Module) (*license.License
return nil, fmt.Errorf("Override license %q SPDX lookup error: %s", v, err)
}

return &license.License{Name: lic.Name, SPDX: lic.ID}, nil
return &license.License{
Name: lic.Name,
SPDX: lic.ID,
Text: lic.Text,
}, nil
}
185 changes: 108 additions & 77 deletions output_xlsx.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"fmt"
"sort"
"strconv"
"strings"
"sync"

"github.com/360EntSecGroup-Skylar/excelize"
Expand Down Expand Up @@ -55,101 +56,131 @@ func (o *XLSXOutput) Close() error {
const s = "Sheet1"
f := excelize.NewFile()

// Headers
f.SetCellValue(s, "A1", "Dependency")
f.SetCellValue(s, "B1", "Version")
f.SetCellValue(s, "C1", "SPDX ID")
f.SetCellValue(s, "D1", "License")
f.SetCellValue(s, "E1", "Allowed")
f.SetColWidth(s, "A", "A", 40)
f.SetColWidth(s, "B", "B", 20)
f.SetColWidth(s, "C", "C", 20)
f.SetColWidth(s, "D", "D", 40)
f.SetColWidth(s, "E", "E", 10)

// Create all our styles
redStyle, _ := f.NewStyle(`{"fill":{"type":"pattern","pattern":1,"color":["#FFCCCC"]}}`)
yellowStyle, _ := f.NewStyle(`{"fill":{"type":"pattern","pattern":1,"color":["#FFC107"]}}`)
greenStyle, _ := f.NewStyle(`{"fill":{"type":"pattern","pattern":1,"color":["#9CCC65"]}}`)

// Sort the modules by name
keys := make([]string, 0, len(o.modules))
columns := []string{"Dependency", "Version", "SPDX ID", "License", "Allowed"}
if len(o.Config.OutputColumns) > 0 {
columns = o.Config.OutputColumns
}

// Headers
for i, k := range columns {
colID := string(i + 65)
f.SetCellValue(s, colID+"1", k)
f.SetColWidth(s, colID, colID, 40)
}

keys, index := o.sortModulesByName(o.modules)

// Go through each module and output it into the spreadsheet
for rowIdx, rowKey := range keys {
rowID := strconv.Itoa(rowIdx + 2)

module := index[rowKey]
rawModule := o.modules[module]

for colIdx, colKey := range columns {
cellID := string(colIdx+65) + rowID

f.SetCellValue(s, cellID, o.getModuleValue(module, rawModule, colKey))
f.SetCellStyle(s, cellID, cellID, o.getModuleStyle(module, rawModule, redStyle, yellowStyle, greenStyle))
}
}

// Save
if err := f.SaveAs(o.Path); err != nil {
return err
}

return nil
}

func (o *XLSXOutput) sortModulesByName(modules map[*module.Module]interface{}) ([]string, map[string]*module.Module) {
keys := make([]string, 0, len(modules))
index := map[string]*module.Module{}
for m := range o.modules {
for m := range modules {
keys = append(keys, m.Path)
index[m.Path] = m
}
sort.Strings(keys)
return keys, index
}

// Go through each module and output it into the spreadsheet
for i, k := range keys {
row := strconv.FormatInt(int64(i+2), 10)

m := index[k]
f.SetCellValue(s, "A"+row, m.Path)
f.SetCellValue(s, "B"+row, m.Version)
f.SetCellValue(s, "E"+row, "unknown")
f.SetCellStyle(s, "A"+row, "A"+row, yellowStyle)
f.SetCellStyle(s, "B"+row, "B"+row, yellowStyle)
f.SetCellStyle(s, "C"+row, "C"+row, yellowStyle)
f.SetCellStyle(s, "D"+row, "D"+row, yellowStyle)
f.SetCellStyle(s, "E"+row, "E"+row, yellowStyle)

raw := o.modules[m]
if raw == nil {
f.SetCellValue(s, "D"+row, "no")
f.SetCellStyle(s, "A"+row, "A"+row, redStyle)
f.SetCellStyle(s, "B"+row, "B"+row, redStyle)
f.SetCellStyle(s, "C"+row, "C"+row, redStyle)
f.SetCellStyle(s, "D"+row, "D"+row, redStyle)
f.SetCellStyle(s, "E"+row, "E"+row, redStyle)
continue
}
func (o *XLSXOutput) getModuleValue(module *module.Module, rawModule interface{}, key string) string {
compareKey := strings.ToLower(key)
// keys from module
switch compareKey {
case "dependency":
return module.Path
case "version":
return module.Version
}

// If the value is an error, then note the error
if err, ok := raw.(error); ok {
f.SetCellValue(s, "D"+row, fmt.Sprintf("ERROR: %s", err))
f.SetCellValue(s, "E"+row, "no")
f.SetCellStyle(s, "A"+row, "A"+row, redStyle)
f.SetCellStyle(s, "B"+row, "B"+row, redStyle)
f.SetCellStyle(s, "C"+row, "C"+row, redStyle)
f.SetCellStyle(s, "D"+row, "D"+row, redStyle)
f.SetCellStyle(s, "E"+row, "E"+row, redStyle)
continue
// license key
if compareKey == "license" {
if rawModule == nil {
return "no"
}
if err, ok := rawModule.(error); ok {
return fmt.Sprintf("ERROR: %s", err)
}
if lic, ok := rawModule.(*license.License); ok {
return lic.String()
}
return ""
}

// If the value is a license, then mark the license
if lic, ok := raw.(*license.License); ok {
if lic != nil {
f.SetCellValue(s, fmt.Sprintf("C%d", i+2), lic.SPDX)
}
f.SetCellValue(s, fmt.Sprintf("D%d", i+2), lic.String())
if o.Config != nil {
switch o.Config.Allowed(lic) {
case config.StateAllowed:
f.SetCellValue(s, fmt.Sprintf("E%d", i+2), "yes")
f.SetCellStyle(s, "A"+row, "A"+row, greenStyle)
f.SetCellStyle(s, "B"+row, "B"+row, greenStyle)
f.SetCellStyle(s, "C"+row, "C"+row, greenStyle)
f.SetCellStyle(s, "D"+row, "D"+row, greenStyle)
f.SetCellStyle(s, "E"+row, "E"+row, greenStyle)

case config.StateDenied:
f.SetCellValue(s, fmt.Sprintf("E%d", i+2), "no")
f.SetCellStyle(s, "A"+row, "A"+row, redStyle)
f.SetCellStyle(s, "B"+row, "B"+row, redStyle)
f.SetCellStyle(s, "C"+row, "C"+row, redStyle)
f.SetCellStyle(s, "D"+row, "D"+row, redStyle)
f.SetCellStyle(s, "E"+row, "E"+row, redStyle)
}
// allowed key
if compareKey == "allowed" {
if rawModule == nil {
return "no"
}
if _, ok := rawModule.(error); ok {
return "no"
}
if lic, ok := rawModule.(*license.License); ok && o.Config != nil {
switch o.Config.Allowed(lic) {
case config.StateAllowed:
return "yes"
case config.StateDenied:
return "no"
}
}
return "unknown"
}

// Save
if err := f.SaveAs(o.Path); err != nil {
return err
// other keys
if rawModule == nil {
return ""
}
if lic, ok := rawModule.(*license.License); ok {
switch compareKey {
case "spdx id":
return lic.SPDX
case "license text":
return lic.Text
}
}
return ""
}

return nil
func (o *XLSXOutput) getModuleStyle(module *module.Module, rawModule interface{}, deny, indecisive, allow int) int {
if rawModule == nil {
return deny
}
if _, ok := rawModule.(error); ok {
return deny
}
if lic, ok := rawModule.(*license.License); ok && o.Config != nil {
switch o.Config.Allowed(lic) {
case config.StateAllowed:
return allow
case config.StateDenied:
return deny
}
}
return indecisive
}