Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Inventory licensezero.json files (close #4) #5

Merged
merged 13 commits into from
Jun 29, 2018
Merged
82 changes: 82 additions & 0 deletions inventory/generic.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
package inventory

import "encoding/json"
import "github.com/yookoala/realpath"
import "io/ioutil"
import "os"
import "path"

type LicenseZeroJSONFile struct {
Version string `json:"version"`
Envelopes []ProjectManifestEnvelope `json:"licensezero"`
}

func ReadLicenseZeroFiles(directoryPath string) ([]Project, error) {
var returned []Project
entries, err := readAndStatDir(directoryPath)
if err != nil {
if os.IsNotExist(err) {
return []Project{}, nil
} else {
return nil, err
}
}
for _, entry := range entries {
name := entry.Name()
if name == "licensezero.json" {
json_file := path.Join(directoryPath, "licensezero.json")
data, err := ioutil.ReadFile(json_file)
if err != nil {
return nil, err
}
var parsed LicenseZeroJSONFile
json.Unmarshal(data, &parsed)
for _, envelope := range parsed.Envelopes {
if alreadyHaveProject(returned, envelope.Manifest.ProjectID) {
continue
}
project := Project{
Path: directoryPath,
Envelope: envelope,
}
realDirectory, err := realpath.Realpath(directoryPath)
if err != nil {
project.Path = realDirectory
} else {
project.Path = directoryPath
}
packageInfo := findPackageInfo(directoryPath)
if packageInfo != nil {
project.Type = packageInfo.Type
project.Name = packageInfo.Name
project.Version = packageInfo.Version
project.Scope = packageInfo.Scope
}
returned = append(returned, project)
}
} else if entry.IsDir() {
directory := path.Join(directoryPath, name)
below, err := ReadLicenseZeroFiles(directory)
if err != nil {
return nil, err
}
returned = append(returned, below...)
}
}
return returned, nil
}

func findPackageInfo(directoryPath string) *Project {
approaches := []func(string) *Project{
findNPMPackageInfo,
findPythonPackageInfo,
findMavenPackageInfo,
}
for _, approach := range approaches {
returned := approach(directoryPath)
if returned != nil {
return returned
}
}
return nil
}
13 changes: 12 additions & 1 deletion inventory/inventory.go
Original file line number Diff line number Diff line change
Expand Up @@ -131,7 +131,18 @@ func OwnProject(project *Project, identity *data.Identity) bool {
}

func ReadProjects(cwd string) ([]Project, error) {
return ReadNPMProjects(cwd)
descenders := []func(string) ([]Project, error){
ReadNPMProjects,
ReadLicenseZeroFiles,
}
returned := []Project{}
for _, descender := range descenders {
projects, err := descender(cwd)
if err == nil {
returned = append(returned, projects...)
}
}
return returned, nil
}

func isSymlink(entry os.FileInfo) bool {
Expand Down
33 changes: 33 additions & 0 deletions inventory/maven.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
package inventory

import "encoding/xml"
import "io/ioutil"
import "path"

type POM struct {
GroupID string `xml:"groupId"`
ArtifactID string `xml:"artifactId"`
Version string `xml:"version"`
}

func findMavenPackageInfo(directoryPath string) *Project {
pom_xml := path.Join(directoryPath, "pom.xml")
data, err := ioutil.ReadFile(pom_xml)
if err != nil {
return nil
}
var parsed POM
xml.Unmarshal(data, &parsed)
if err != nil {
return nil
}
if parsed.ArtifactID == "" {
return nil
}
return &Project{
Type: "maven",
Name: parsed.ArtifactID,
Scope: parsed.GroupID,
Version: parsed.Version,
}
}
32 changes: 32 additions & 0 deletions inventory/npm.go
Original file line number Diff line number Diff line change
Expand Up @@ -112,3 +112,35 @@ func alreadyHaveProject(projects []Project, projectID string) bool {
}

// TODO: Move package.json writing function into inventory.

func findNPMPackageInfo(directoryPath string) *Project {
package_json := path.Join(directoryPath, "package.json")
data, err := ioutil.ReadFile(package_json)
if err != nil {
return nil
}
var parsed struct {
Name string `json:"name"`
Version string `json:"version"`
}
json.Unmarshal(data, &parsed)
if err != nil {
return nil
}
rawName := parsed.Name
var name, scope string
// If the name looks like @scope/name, parse it.
if strings.HasPrefix(rawName, "@") && strings.Index(rawName, "/") != -1 {
index := strings.Index(rawName, "/")
scope = rawName[1 : index-1]
name = rawName[index:]
} else {
name = rawName
}
return &Project{
Type: "npm",
Name: name,
Scope: scope,
Version: parsed.Version,
}
}
31 changes: 31 additions & 0 deletions inventory/python.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
package inventory

import "bytes"
import "os"
import "os/exec"
import "path"
import "strings"

func findPythonPackageInfo(directoryPath string) *Project {
setup := path.Join(directoryPath, "setup.py")
_, err := os.Stat(setup)
if err != nil {
return nil
}
command := exec.Command("python", "setup.py", "--name", "--version")
var stdout bytes.Buffer
command.Stdout = &stdout
err = command.Run()
if err != nil {
return nil
}
output := string(stdout.Bytes())
lines := strings.Split(output, "\n")
name := strings.TrimSpace(lines[0])
version := strings.TrimSpace(lines[1])
return &Project{
Type: "python",
Name: name,
Version: version,
}
}
114 changes: 87 additions & 27 deletions subcommands/license.go
Original file line number Diff line number Diff line change
Expand Up @@ -49,49 +49,68 @@ var License = Subcommand{
if err != nil {
Fail("Error sending license information request.")
}
// Add metadata to package.json.
newEntry := response.Metadata.LicenseZero
package_json := path.Join(paths.CWD, "package.json")
data, err := ioutil.ReadFile(package_json)
if err != nil {
Fail("Could not read package.json.")
checkForLegacyPackageJSON(paths.CWD)
// Add metadata to licensezero.json.
type FileStructure struct {
Version string `json:"version"`
LicenseZero []interface{} `json:"licensezero"`
}
var existingMetadata interface{}
err = json.Unmarshal(data, &existingMetadata)
var newMetadata FileStructure
newEntry := response.Metadata.LicenseZero
licensezero_json := path.Join(paths.CWD, "licensezero.json")
data, err := ioutil.ReadFile(licensezero_json)
if err != nil {
Fail("Error parsing package.json.")
}
itemsMap := existingMetadata.(map[string]interface{})
var entries []interface{}
if _, ok := itemsMap["licensezero"]; ok {
if entries, ok := itemsMap["licensezero"].([]interface{}); ok {
if *stack {
entries = append(entries, newEntry)
} else {
Fail("package.json already has License Zero metadata.\nUse --stack to stack metadata.")
}
if os.IsNotExist(err) {
newMetadata.LicenseZero = []interface{}{newEntry}
} else {
Fail("package.json has an invalid licensezero property.")
Fail("Could not read licensezero.json.")
}
} else {
if *stack {
Fail("Cannot stack License Zero metadata. There is no preexisting metadata.")
var existingMetadata FileStructure
err = json.Unmarshal(data, &existingMetadata)
if err != nil {
Fail("Error parsing licensezero.json.")
}
entries := existingMetadata.LicenseZero
if len(existingMetadata.LicenseZero) != 0 {
if *stack {
// Check if project already listed.
for _, entry := range entries {
if itemsMap, ok := entry.(map[string]interface{}); ok {
if license, ok := itemsMap["license"].(map[string]interface{}); ok {
if otherID, ok := license["projectID"].(string); ok {
if otherID == *projectID {
Fail("Project ID " + *projectID + " already appears in licensezero.json.")
}
}
}
}
}
entries = append(existingMetadata.LicenseZero, newEntry)
} else {
Fail("licensezero.json already has License Zero metadata.\nUse --stack to stack metadata.")
}
} else {
entries = []interface{}{newEntry}
if *stack {
Fail("Cannot stack License Zero metadata. There is no preexisting metadata.")
} else {
entries = []interface{}{newEntry}
}
}
newMetadata.Version = existingMetadata.Version
newMetadata.LicenseZero = entries
}
itemsMap["licensezero"] = entries
serialized := new(bytes.Buffer)
encoder := json.NewEncoder(serialized)
encoder.SetEscapeHTML(false)
encoder.SetIndent("", " ")
err = encoder.Encode(existingMetadata)
err = encoder.Encode(newMetadata)
if err != nil {
Fail("Error serializing new JSON.")
}
err = ioutil.WriteFile(package_json, serialized.Bytes(), 0644)
err = ioutil.WriteFile(licensezero_json, serialized.Bytes(), 0644)
if !*silent {
os.Stdout.WriteString("Added metadata to package.json.\n")
os.Stdout.WriteString("Added metadata to licensezero.json.\n")
}
// Append to LICENSE.
err = writeLICENSE(&response)
Expand All @@ -101,6 +120,17 @@ var License = Subcommand{
if !*silent {
os.Stdout.WriteString("Appended terms to LICENSE.\n")
}
// TODO: Write licensezero.json to package.json files, MANIFEST.in, and similar.
if !*silent {
os.Stdout.WriteString(
"" +
"Make sure to configure your package manager to include licensezero.json\n" +
"in your package distribution.\n\n" +
"npm: Add licensezero.json to the files array of your npm package's\n" +
" package.json file, if you have one.\n\n" +
"Python: Add licensezero.json to MANIFEST.in.\n",
)
}
os.Exit(0)
},
}
Expand Down Expand Up @@ -139,6 +169,36 @@ func signatureLines(signature string) string {
return signature[0:64] + "\n" + signature[64:]
}

// Earlier versions of `licensezero` wrote License Zero licensing
// metadata to `licensezero` array properties of `package.json` files
// for npm projects, rather than to separate `licenserzero.json` files.
// If we see a `package.json` file with a `licensezero` property, warn
// the user and instruct them to upgrade.
func checkForLegacyPackageJSON(directoryPath string) {
package_json := path.Join(directoryPath, "package.json")
data, err := ioutil.ReadFile(package_json)
if err != nil {
if os.IsNotExist(err) {
return
} else {
Fail("Error reading package.json.")
}
}
var packageData struct {
LegacyMetadata []interface{} `json:"licensezero"`
}
err = json.Unmarshal(data, &packageData)
if len(packageData.LegacyMetadata) != 0 {
Fail(
"" +
"The `licensezero` property in `package.json` is deprecated\n" +
"in favor of `licensezero.json`.\n" +
"Remove the `licensezero` property from `package.json`\n" +
"and run `licensezero license` again.\n",
)
}
}

func licenseUsage() {
usage := licenseDescription + "\n\n" +
"Usage:\n" +
Expand Down
Loading