diff --git a/README.md b/README.md index 33d8542..76243f5 100644 --- a/README.md +++ b/README.md @@ -124,6 +124,27 @@ screenshot is shown below: ![Excel Report](https://user-images.githubusercontent.com/1299/48667086-84893500-ea83-11e8-925c-7929ed441b1b.png) +### Custom Template output + +It is possible to provide a template to get a custom report. For the template +the [Go text/template](https://golang.org/pkg/text/template/) is used. + +``` +$ golicense -in-template report.tmpl -out-template=report.md ./my-program +``` + +The in-template paramter specifies the template file to use and the out-template +as the filename to write the result. + +A simple example of a template content. + +``` +|Dependency | Version | SPDX| License | Allowed | +|-----------|---------|-----|---------|---------| +{{range . }}| {{.Dependency}} | {{.Version}} | {{.Spdx}} | {{.License}} | {{.Allowed}} | +{{end}} +``` + ## Limitations There are a number of limitations to `golicense` currently. These are fixable diff --git a/main.go b/main.go index b8634cb..a00efce 100644 --- a/main.go +++ b/main.go @@ -37,6 +37,9 @@ func realMain() int { var flagLicense bool var flagOutXLSX string + var flagOutTemplate string + var flagInTemplate string + flags := flag.NewFlagSet(os.Args[0], flag.ExitOnError) flags.BoolVar(&flagLicense, "license", true, "look up and verify license. If false, dependencies are\n"+ @@ -45,6 +48,10 @@ func realMain() int { flags.BoolVar(&termOut.Verbose, "verbose", false, "additional logging to terminal, requires -plain") flags.StringVar(&flagOutXLSX, "out-xlsx", "", "save report in Excel XLSX format to the given path") + flags.StringVar(&flagOutTemplate, "out-template", "", + "save report with in-template in the given path") + flags.StringVar(&flagInTemplate, "in-template", "", + "save report the given template") flags.Parse(os.Args[1:]) args := flags.Args() if len(args) == 0 { @@ -59,6 +66,13 @@ func realMain() int { return 1 } + if flagOutTemplate != "" && flagInTemplate =="" { + fmt.Fprintf(os.Stderr, color.RedString( + "❗️ Path to template file for template output expected.\n\n")) + printHelp(flags) + return 1 + } + // Determine the exe path and parse the configuration if given. var cfg config.Config exePath := args[0] @@ -117,6 +131,19 @@ func realMain() int { }) } + if flagOutTemplate != "" { + if _, err := os.Stat(flagInTemplate); os.IsNotExist(err) { + fmt.Fprintf(os.Stderr, color.RedString(fmt.Sprintf( + "❗️ Error Teamplte file does not exist: %s\n", flagInTemplate))) + return 1 + } + out.Outputs = append(out.Outputs, &TemplateOutput { + Path: flagOutTemplate, + Template: flagInTemplate, + Config: &cfg, + }) + } + // Setup a context. We don't connect this to an interrupt signal or // anything since we just exit immediately on interrupt. No cleanup // necessary. diff --git a/output_template.go b/output_template.go new file mode 100644 index 0000000..7022fb6 --- /dev/null +++ b/output_template.go @@ -0,0 +1,121 @@ +package main + +import ( + "fmt" + "os" + "sort" + "sync" + "text/template" + + "github.com/mitchellh/golicense/config" + "github.com/mitchellh/golicense/license" + "github.com/mitchellh/golicense/module" +) + +// TemplateOutput writes the results of license lookups to a tempalte file. +type TemplateOutput struct { + // Path is the path to the file to write. This will be overwritten if + // it exists. + Path string + + // The template file to use for rendering. + Template string + + // Config is the configuration (if any). This will be used to check + // if a license is allowed or not. + Config *config.Config + + modules map[*module.Module]interface{} + lock sync.Mutex +} + +// Start implements Output +func (o *TemplateOutput) Start(m *module.Module) {} + +// Update implements Output +func (o *TemplateOutput) Update(m *module.Module, t license.StatusType, msg string) {} + +// Finish implements Output +func (o *TemplateOutput) Finish(m *module.Module, l *license.License, err error) { + o.lock.Lock() + defer o.lock.Unlock() + + if o.modules == nil { + o.modules = make(map[*module.Module]interface{}) + } + + o.modules[m] = l + if err != nil { + o.modules[m] = err + } +} + +type libraryEntry struct { + Dependency string + Version string + Spdx string + License string + Allowed string +} + +// Close implements Output +func (o *TemplateOutput) Close() error { + o.lock.Lock() + defer o.lock.Unlock() + + keys := make([]string, 0, len(o.modules)) + index := map[string]*module.Module{} + for m := range o.modules { + keys = append(keys, m.Path) + index[m.Path] = m + } + sort.Strings(keys) + + entries := []*libraryEntry{} + + // Go through each module and output it into the spreadsheet + for _, k := range keys { + m := index[k] + entry := libraryEntry{ + Dependency: m.Path, + Version: m.Version, + Spdx: "", + License: "", + Allowed: "unknown", + } + + raw := o.modules[m] + if raw == nil { + entry.License = "no" + continue + } + + if err, ok := raw.(error); ok { + entry.License = fmt.Sprintf("ERROR: %s", err) + continue + } + + if lic, ok := raw.(*license.License); ok { + if lic != nil { + entry.Spdx = lic.SPDX + } + entry.License = fmt.Sprintf(lic.String()) + + if o.Config != nil { + switch o.Config.Allowed(lic) { + case config.StateAllowed: + entry.Allowed = "yes" + case config.StateDenied: + entry.Allowed = "no" + } + } + } + entries = append(entries, &entry) + } + + t := template.Must(template.ParseFiles(o.Template)) + writer, _ := os.Create(o.Path) + t.Execute(writer, entries) + + return nil +}