blob: 1aed4634fc4798031dc89014758545bddf69de55 [file] [log] [blame]
// Copyright 2020 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
package checklicenses
import (
"bytes"
"compress/gzip"
"encoding/json"
"fmt"
"io/ioutil"
"sort"
"strings"
"text/template"
"go.fuchsia.dev/fuchsia/tools/check-licenses/templates"
)
// saveToOutputFile writes to output the serialized licenses.
//
// It writes an uncompressed version too if a compressed version is requested.
func saveToOutputFile(path string, licenses *Licenses, config *Config) error {
// Sort the licenses in alphabetical order for consistency.
sort.Slice(licenses.licenses, func(i, j int) bool { return licenses.licenses[i].Category < licenses.licenses[j].Category })
var used []*License
var unused []*License
for _, l := range licenses.licenses {
isUsed := false
for _, m := range l.matches {
if m.Used {
isUsed = true
break
}
}
if isUsed {
used = append(used, l)
} else {
unused = append(unused, l)
}
}
for _, n := range licenses.notices {
used = append(used, n)
}
var output []byte
var err error
switch {
case strings.HasSuffix(path, ".txt") || strings.HasSuffix(path, ".txt.gz"):
output, err = createTextOutput(used, unused, config)
case strings.HasSuffix(path, ".html") || strings.HasSuffix(path, ".html.gz"):
output, err = createHtmlOutput(used, unused, config)
case strings.HasSuffix(path, ".json") || strings.HasSuffix(path, ".json.gz"):
output, err = createJsonOutput(used, unused, config)
default:
err = fmt.Errorf("invalid output suffix %s", path)
}
if err != nil {
return err
}
// Special handling for compressed file.
const gz = ".gz"
if strings.HasSuffix(path, gz) {
// First write uncompressed, then compressed.
if err := ioutil.WriteFile(path[:len(path)-len(gz)], output, 0666); err != nil {
return err
}
d, err := compressGZ(output)
if err != nil {
return err
}
return ioutil.WriteFile(path, d, 0666)
}
return ioutil.WriteFile(path, output, 0666)
}
// The following structs are used to serialize data to JSON.
type Output struct {
Unused UnusedLicenses `json:"unused"`
Used []UsedLicense `json:"used"`
}
type UnusedLicenses struct {
Categories []string `json:"categories"`
}
type UsedLicense struct {
Copyrights []string `json:"copyrights"`
Category string `json:"category"`
Files string `json:"files"`
Text string `json:"text"`
}
func createJsonOutput(usedLicenses []*License, unusedLicenses []*License, config *Config) ([]byte, error) {
output := Output{}
for _, l := range unusedLicenses {
category := getCategory(l)
output.Unused.Categories = append(output.Unused.Categories, category)
}
for _, l := range usedLicenses {
category := getCategory(l)
for _, match := range l.matches {
if !match.Used {
continue
}
copyrights := []string{}
for c := range match.Copyrights {
trim := strings.TrimSpace(c)
if trim != "" {
copyrights = append(copyrights, trim)
}
}
sort.Strings(copyrights)
files := ""
if config.PrintFiles {
files = getFilesFromMatch(match)
}
text := match.Text
licenseOutput := UsedLicense{
Copyrights: copyrights,
Category: category,
Files: files,
Text: text,
}
output.Used = append(output.Used, licenseOutput)
}
}
return json.Marshal(output)
}
func createTextOutput(usedLicenses []*License, unusedLicenses []*License, config *Config) ([]byte, error) {
data := struct {
Used []*License
Unused []*License
}{}
data.Used = usedLicenses
data.Unused = unusedLicenses
buf := bytes.Buffer{}
tmpl := template.Must(template.New("name").Funcs(getFuncMap(config)).Parse(templates.TemplateTxt))
if err := tmpl.Execute(&buf, data); err != nil {
return nil, err
}
return buf.Bytes(), nil
}
// TODO(omerlevran): Use html/template instead of text/template.
// text/template is inherently unsafe to generate HTML.
func createHtmlOutput(usedLicenses []*License, unusedLicenses []*License, config *Config) ([]byte, error) {
data := struct {
Used []*License
Unused []*License
}{}
data.Used = usedLicenses
data.Unused = unusedLicenses
buf := bytes.Buffer{}
tmpl := template.Must(template.New("name").Funcs(getFuncMap(config)).Parse(templates.TemplateHtml))
if err := tmpl.Execute(&buf, data); err != nil {
return nil, err
}
return buf.Bytes(), nil
}
// compressGZ returns the compressed buffer with gzip format.
func compressGZ(d []byte) ([]byte, error) {
buf := bytes.Buffer{}
zw := gzip.NewWriter(&buf)
if _, err := zw.Write(d); err != nil {
return nil, err
}
if err := zw.Close(); err != nil {
return nil, err
}
return buf.Bytes(), nil
}
func getCategory(l *License) string {
return strings.TrimSuffix(l.Category, ".lic")
}
func getAuthors(l *License) []string {
var authors []string
for author := range l.matches {
authors = append(authors, author)
}
sort.Strings(authors)
return authors
}
func getHTMLText(m *Match) string {
txt := m.Text
txt = strings.ReplaceAll(txt, "<", "&lt;")
txt = strings.ReplaceAll(txt, ">", "&gt;")
txt = strings.Replace(txt, "\n", "<br />", -1)
return txt
}
func getMatches(l *License) []*Match {
sortedList := []*Match{}
for _, m := range l.matches {
sortedList = append(sortedList, m)
}
sort.Sort(matchByText(sortedList))
return sortedList
}
func getFilesFromMatch(m *Match) string {
result := ""
if len(m.Files) > 0 {
result += "Files:\n"
sort.Strings(m.Files)
for _, s := range m.Files {
result += " -> " + s + "\n"
}
}
return result
}
func getProjectsFromMatch(m *Match) string {
result := ""
if len(m.Projects) > 0 {
result += "Projects:\n"
sort.Strings(m.Projects)
for _, s := range m.Projects {
result += " -> " + s + "\n"
}
}
return result
}
func getFiles(l *License, author string) []string {
var files []string
for _, file := range l.matches[author].Files {
files = append(files, file)
}
sort.Strings(files)
return files
}
func getEscapedText(l *License, author string) string {
return strings.Replace(l.matches[author].Text, "\"", "\\\"", -1)
}
func getCopyrights(m *Match) string {
sortedList := []string{}
for c := range m.Copyrights {
trim := strings.TrimSpace(c)
if trim != "" {
sortedList = append(sortedList, trim)
}
}
sort.Strings(sortedList)
result := ""
for _, s := range sortedList {
result += s + "\n"
}
return result
}
func getFuncMap(config *Config) template.FuncMap {
return template.FuncMap{
"getPattern": func(l *License) string {
return l.pattern.String()
},
"getText": func(l *License, author string) string {
return l.matches[author].Text
},
"getEscapedText": getEscapedText,
"getCategory": getCategory,
"getFiles": getFiles,
"getAuthors": getAuthors,
"getHTMLText": getHTMLText,
"getMatches": getMatches,
"getFilesFromMatch": func(m *Match) string {
files := ""
if config.PrintFiles {
files = getFilesFromMatch(m)
}
return files
},
"getProjectsFromMatch": func(m *Match) string {
projects := ""
if config.PrintProjects {
projects = getProjectsFromMatch(m)
}
return projects
},
"getCopyrights": getCopyrights,
}
}