licensed 0.11.1 → 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.gitignore +13 -4
- data/.rubocop.yml +3 -0
- data/.ruby-version +1 -0
- data/CHANGELOG.md +13 -0
- data/CODE_OF_CONDUCT.md +14 -12
- data/CONTRIBUTING.md +51 -0
- data/Gemfile +2 -1
- data/{LICENSE.txt → LICENSE} +1 -1
- data/README.md +55 -76
- data/Rakefile +3 -2
- data/docs/configuration.md +131 -0
- data/docs/sources/bower.md +5 -0
- data/docs/sources/bundler.md +7 -0
- data/docs/sources/cabal.md +39 -0
- data/docs/sources/go.md +12 -0
- data/docs/sources/manifests.md +26 -0
- data/docs/sources/npm.md +3 -0
- data/docs/sources/stack.md +3 -0
- data/exe/licensed +1 -0
- data/lib/licensed.rb +9 -5
- data/lib/licensed/cli.rb +22 -14
- data/lib/licensed/command/cache.rb +46 -29
- data/lib/licensed/command/list.rb +17 -9
- data/lib/licensed/command/status.rb +78 -0
- data/lib/licensed/configuration.rb +127 -25
- data/lib/licensed/dependency.rb +8 -2
- data/lib/licensed/git.rb +39 -0
- data/lib/licensed/license.rb +1 -0
- data/lib/licensed/shell.rb +28 -0
- data/lib/licensed/source/bower.rb +4 -0
- data/lib/licensed/source/bundler.rb +4 -0
- data/lib/licensed/source/cabal.rb +72 -24
- data/lib/licensed/source/go.rb +23 -36
- data/lib/licensed/source/manifest.rb +26 -23
- data/lib/licensed/source/npm.rb +19 -8
- data/lib/licensed/ui/shell.rb +2 -1
- data/lib/licensed/version.rb +2 -1
- data/licensed.gemspec +9 -5
- data/{bin/setup → script/bootstrap} +13 -8
- data/script/cibuild +7 -0
- data/{bin → script}/console +1 -0
- metadata +53 -158
- data/.bowerrc +0 -3
- data/exe/licensor +0 -5
- data/lib/licensed/command/verify.rb +0 -73
- data/lib/licensed/source/stack.rb +0 -66
@@ -0,0 +1,7 @@
|
|
1
|
+
# Bundler (rubygem)
|
2
|
+
|
3
|
+
The bundler source will detect dependencies when the source is enabled, `Gemfile` and `Gemfile.lock` files are found at an apps `source_path`. The source uses the `Bundler` API to enumerate dependencies from `Gemfile` and `Gemfile.lock`.
|
4
|
+
|
5
|
+
The bundler source will exclude gems in the `:development` and `:test` groups. Be aware that if you have a local
|
6
|
+
bundler configuration (e.g. `.bundle`), that configuration will be respected as well. For example, if you have a local
|
7
|
+
configuration set for `without: [':server']`, the bundler source will exclude all gems in the `:server` group.
|
@@ -0,0 +1,39 @@
|
|
1
|
+
# Cabal
|
2
|
+
|
3
|
+
The cabal source uses the `ghc-pkg` command to enumerate dependencies and provide metadata. It is un-opinionated on GHC packagedb locations and requires some configuration to ensure that all packages are properly found.
|
4
|
+
|
5
|
+
The rubygem source will detect dependencies when the source is enabled and a `.cabal` file is found at an apps `source_path`.
|
6
|
+
|
7
|
+
### Specifying GHC packagedb locations through environment
|
8
|
+
You can configure the `cabal` source to use specific packagedb locations by setting the `GHC_PACKAGE_PATH` environment variable before running `licensed`.
|
9
|
+
|
10
|
+
For example, the following is a useful configuration for use with `cabal new-build`.
|
11
|
+
```bash
|
12
|
+
ghc_version="$(ghc --numeric-version)"
|
13
|
+
CABAL_STORE_PACKAGE_DB="$(cd ~/.cabal/store/ghc-$ghc_version/package.db && pwd)"
|
14
|
+
LOCAL_PACKAGE_DB="$ROOT/dist-newstyle/packagedb/ghc-$ghc_version"
|
15
|
+
GLOBAL_PACKAGE_DB="$(ghc-pkg list --global | head -n 1)"
|
16
|
+
export GHC_PACKAGE_PATH="$LOCAL_PACKAGE_DB:$CABAL_STORE_PACKAGE_DB:$GLOBAL_PACKAGE_DB:$GHC_PACKAGE_PATH"
|
17
|
+
|
18
|
+
bundle exec licensed <args>
|
19
|
+
```
|
20
|
+
|
21
|
+
### Specifying GHC packagedb locations through configuration
|
22
|
+
Alternatively, the `cabal` source can use packagedb locations set in the app configuration. The following is an example configuration identical to the above environment configuration.
|
23
|
+
|
24
|
+
```yml
|
25
|
+
cabal:
|
26
|
+
ghc_package_db:
|
27
|
+
- ~/.cabal/store/ghc-<ghc_version>/package.db
|
28
|
+
- dist-newstyle/packagedb/ghc-<ghc_version>
|
29
|
+
- global
|
30
|
+
```
|
31
|
+
|
32
|
+
Ordering is preserved when evaluating the configuration values. Paths are expanded from the root of the `git` repository and used as values for CLI `--package-db="<path>"` arguments. Additionally, in all specified paths, `<ghc_version>` will be replaced with the result of `ghc --numeric-version`.
|
33
|
+
|
34
|
+
The `global` and `user` keywords are supported and will expand to the `--global` and `--user` CLI arguments, respectively.
|
35
|
+
|
36
|
+
Like most other settings, the `cabal` configuration can be specified for the root configuration, or per-app. If specified at root, it will be inherited by all apps unless explicitly overridden.
|
37
|
+
|
38
|
+
### Stack sandboxes
|
39
|
+
The current recommended way to use `licensed` with stack is to run the `licensed` command with `stack exec`: `stack exec -- bundle exec licensed <args>`. This will allow stack to control the GHC packagedb locations.
|
data/docs/sources/go.md
ADDED
@@ -0,0 +1,12 @@
|
|
1
|
+
# Go
|
2
|
+
|
3
|
+
The go source uses `go` CLI commands to enumerate dependencies and properties. It is expected that `go` projects have been built, and that `GO_PATH` and `GO_ROOT` are properly set before running `licensed`.
|
4
|
+
|
5
|
+
Source paths for go projects should point to a location that contains an entrypoint to the executable or library.
|
6
|
+
|
7
|
+
An example usage might see a configuration like:
|
8
|
+
```YAML
|
9
|
+
source_path: go/path/src/github.com/BurntSushi/toml/cmd/tomlv
|
10
|
+
```
|
11
|
+
|
12
|
+
Note that this configuration points directly to the tomlv command source, which contains `func main`.
|
@@ -0,0 +1,26 @@
|
|
1
|
+
# Manifests
|
2
|
+
|
3
|
+
The manifest source can be used when no package managers are available.
|
4
|
+
|
5
|
+
Manifest files are used to match source files with their corresponding packages to find package dependencies. Manifest file paths can be specified in the app configuration with the following setting:
|
6
|
+
```yml
|
7
|
+
manifest:
|
8
|
+
path: 'path/to/manifest.json'
|
9
|
+
```
|
10
|
+
|
11
|
+
If a manifest path is not specified for an app, the file will be looked for at the apps `<cache_path>/manifest.json`.
|
12
|
+
|
13
|
+
The manifest can be a JSON or YAML file with a single root object and properties mapping file paths to package names.
|
14
|
+
```JSON
|
15
|
+
{
|
16
|
+
"file1": "package1",
|
17
|
+
"path/to/file2": "package1",
|
18
|
+
"other/file3": "package2"
|
19
|
+
}
|
20
|
+
```
|
21
|
+
|
22
|
+
File paths are relative to the git repository root. Package names will be used for the metadata file names at `path/to/cache/manifest/<package>.txt`
|
23
|
+
|
24
|
+
If multiple source files map to a single package and they share a common path under the git repository root, that directory will be used to find license information, if available.
|
25
|
+
|
26
|
+
It is the responsibility of the repository owner to maintain the manifest file.
|
data/docs/sources/npm.md
ADDED
data/exe/licensed
CHANGED
data/lib/licensed.rb
CHANGED
@@ -1,16 +1,18 @@
|
|
1
|
+
# frozen_string_literal: true
|
1
2
|
require "licensed/version"
|
3
|
+
require "licensed/shell"
|
2
4
|
require "licensed/configuration"
|
3
5
|
require "licensed/license"
|
4
6
|
require "licensed/dependency"
|
7
|
+
require "licensed/git"
|
5
8
|
require "licensed/source/bundler"
|
6
9
|
require "licensed/source/bower"
|
7
10
|
require "licensed/source/manifest"
|
8
11
|
require "licensed/source/npm"
|
9
|
-
require "licensed/source/stack"
|
10
12
|
require "licensed/source/go"
|
11
13
|
require "licensed/source/cabal"
|
12
14
|
require "licensed/command/cache"
|
13
|
-
require "licensed/command/
|
15
|
+
require "licensed/command/status"
|
14
16
|
require "licensed/command/list"
|
15
17
|
require "licensed/ui/shell"
|
16
18
|
require "octokit"
|
@@ -25,18 +27,20 @@ module Licensed
|
|
25
27
|
GITHUB_URL = %r{\Ahttps://github.com/([a-z0-9]+(-[a-z0-9]+)*/(\w|\.|\-)+)}
|
26
28
|
LICENSE_CONTENT_TYPE = "application/vnd.github.drax-preview+json"
|
27
29
|
|
30
|
+
# Load license content from a GitHub url. Returns nil if the url does not point
|
31
|
+
# to a GitHub repository, or if the license content is not found
|
28
32
|
def self.from_github(url)
|
29
33
|
return unless use_github && match = GITHUB_URL.match(url)
|
30
34
|
|
31
35
|
license_url = Octokit::Repository.path(match[1]) + "/license"
|
32
|
-
response = octokit.get license_url, :
|
33
|
-
content = Base64.decode64(response["content"]).force_encoding(
|
36
|
+
response = octokit.get license_url, accept: LICENSE_CONTENT_TYPE
|
37
|
+
content = Base64.decode64(response["content"]).force_encoding("UTF-8")
|
34
38
|
Licensee::ProjectFiles::LicenseFile.new(content, response["name"])
|
35
39
|
rescue Octokit::NotFound
|
36
40
|
nil
|
37
41
|
end
|
38
42
|
|
39
43
|
def self.octokit
|
40
|
-
@octokit ||= Octokit::Client.new(:
|
44
|
+
@octokit ||= Octokit::Client.new(access_token: ENV["GITHUB_TOKEN"])
|
41
45
|
end
|
42
46
|
end
|
data/lib/licensed/cli.rb
CHANGED
@@ -1,3 +1,4 @@
|
|
1
|
+
# frozen_string_literal: true
|
1
2
|
require "licensed"
|
2
3
|
require "thor"
|
3
4
|
|
@@ -5,35 +6,42 @@ module Licensed
|
|
5
6
|
class CLI < Thor
|
6
7
|
|
7
8
|
desc "cache", "Cache the licenses of dependencies"
|
8
|
-
method_option :force, :
|
9
|
-
:
|
10
|
-
method_option :offline, :
|
11
|
-
:
|
12
|
-
method_option "
|
13
|
-
:
|
9
|
+
method_option :force, type: :boolean,
|
10
|
+
desc: "Overwrite licenses even if version has not changed."
|
11
|
+
method_option :offline, type: :boolean,
|
12
|
+
desc: "Do not make network calls."
|
13
|
+
method_option :config, aliases: "-c", type: :string,
|
14
|
+
desc: "Path to licensed configuration file"
|
14
15
|
def cache
|
15
16
|
Licensed.use_github = false if options[:offline]
|
16
17
|
run Licensed::Command::Cache.new(config), force: options[:force]
|
17
18
|
end
|
18
19
|
|
19
|
-
desc "
|
20
|
-
method_option "
|
21
|
-
:
|
22
|
-
def
|
23
|
-
run Licensed::Command::
|
20
|
+
desc "status", "Check status of dependencies' cached licenses"
|
21
|
+
method_option :config, aliases: "-c", type: :string,
|
22
|
+
desc: "Path to licensed configuration file"
|
23
|
+
def status
|
24
|
+
run Licensed::Command::Status.new(config)
|
24
25
|
end
|
25
26
|
|
26
27
|
desc "list", "List dependencies"
|
27
|
-
method_option "
|
28
|
-
:
|
28
|
+
method_option :config, aliases: "-c", type: :string,
|
29
|
+
desc: "Path to licensed configuration file"
|
29
30
|
def list
|
30
31
|
run Licensed::Command::List.new(config)
|
31
32
|
end
|
32
33
|
|
33
34
|
private
|
34
35
|
|
36
|
+
# Returns a configuration object for the CLI command
|
35
37
|
def config
|
36
|
-
@config ||= Configuration.
|
38
|
+
@config ||= Configuration.load_from(config_path)
|
39
|
+
end
|
40
|
+
|
41
|
+
# Returns a config path from the CLI if set.
|
42
|
+
# Defaults to the current directory if CLI option is not set
|
43
|
+
def config_path
|
44
|
+
options["config"] || Dir.pwd
|
37
45
|
end
|
38
46
|
|
39
47
|
def run(command, *args)
|
@@ -1,3 +1,4 @@
|
|
1
|
+
# frozen_string_literal: true
|
1
2
|
module Licensed
|
2
3
|
module Command
|
3
4
|
class Cache
|
@@ -8,49 +9,65 @@ module Licensed
|
|
8
9
|
end
|
9
10
|
|
10
11
|
def run(force: false)
|
11
|
-
@config.
|
12
|
-
|
12
|
+
summary = @config.apps.flat_map do |app|
|
13
|
+
app_name = app["name"]
|
14
|
+
@config.ui.info "Caching licenes for #{app_name}:"
|
13
15
|
|
14
|
-
|
15
|
-
|
16
|
+
# load the app environment
|
17
|
+
Dir.chdir app.source_path do
|
16
18
|
|
17
|
-
|
18
|
-
|
19
|
+
# map each available app source to it's dependencies
|
20
|
+
app.sources.map do |source|
|
21
|
+
@config.ui.info " #{source.type} dependencies:"
|
19
22
|
|
20
|
-
|
21
|
-
|
22
|
-
@config.ui.info " Using #{dependency["name"]} (#{dependency["version"]})"
|
23
|
-
next
|
24
|
-
end
|
25
|
-
end
|
23
|
+
names = []
|
24
|
+
cache_path = app.cache_path.join(source.type)
|
26
25
|
|
27
|
-
|
26
|
+
# exclude ignored dependencies
|
27
|
+
dependencies = source.dependencies.select { |d| !app.ignored?(d) }
|
28
28
|
|
29
|
-
|
30
|
-
|
31
|
-
|
29
|
+
# ensure each dependency is cached
|
30
|
+
dependencies.each do |dependency|
|
31
|
+
name = dependency["name"]
|
32
|
+
version = dependency["version"]
|
33
|
+
|
34
|
+
names << name
|
35
|
+
filename = cache_path.join("#{name}.txt")
|
32
36
|
|
33
|
-
|
34
|
-
|
37
|
+
if File.exist?(filename)
|
38
|
+
license = Licensed::License.read(filename)
|
35
39
|
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
40
|
+
# Version did not change, no need to re-cache
|
41
|
+
if !force && version == license["version"]
|
42
|
+
@config.ui.info " Using #{name} (#{version})"
|
43
|
+
next
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
@config.ui.info " Caching #{name} (#{version})"
|
48
|
+
|
49
|
+
dependency.detect_license!
|
50
|
+
dependency.save(filename)
|
51
|
+
end
|
52
|
+
|
53
|
+
# Clean up cached files that dont match current dependencies
|
54
|
+
Dir.glob(cache_path.join("**/*.txt")).each do |file|
|
55
|
+
file_path = Pathname.new(file)
|
56
|
+
relative_path = file_path.relative_path_from(cache_path).to_s
|
57
|
+
FileUtils.rm(file) unless names.include?(relative_path.chomp(".txt"))
|
58
|
+
end
|
59
|
+
|
60
|
+
"* #{app_name} #{source.type} dependencies: #{dependencies.size}"
|
61
|
+
end
|
41
62
|
end
|
42
63
|
end
|
43
64
|
|
44
65
|
@config.ui.confirm "License caching complete!"
|
45
|
-
|
46
|
-
@config.ui.confirm
|
66
|
+
summary.each do |message|
|
67
|
+
@config.ui.confirm message
|
47
68
|
end
|
48
69
|
end
|
49
70
|
|
50
|
-
def dependencies(source)
|
51
|
-
source.dependencies.select { |d| !@config.ignored?(d) }
|
52
|
-
end
|
53
|
-
|
54
71
|
def success?
|
55
72
|
true
|
56
73
|
end
|
@@ -1,3 +1,4 @@
|
|
1
|
+
# frozen_string_literal: true
|
1
2
|
module Licensed
|
2
3
|
module Command
|
3
4
|
class List
|
@@ -8,21 +9,28 @@ module Licensed
|
|
8
9
|
end
|
9
10
|
|
10
11
|
def run
|
11
|
-
@config.
|
12
|
-
@config.ui.info "Displaying
|
12
|
+
@config.apps.each do |app|
|
13
|
+
@config.ui.info "Displaying dependencies for #{app['name']}"
|
14
|
+
Dir.chdir app.source_path do
|
15
|
+
app.sources.each do |source|
|
16
|
+
@config.ui.info " #{source.type} dependencies:"
|
13
17
|
|
14
|
-
|
15
|
-
|
16
|
-
|
18
|
+
source_dependencies = dependencies(app, source)
|
19
|
+
source_dependencies.each do |dependency|
|
20
|
+
@config.ui.info " Found #{dependency['name']} (#{dependency['version']})"
|
21
|
+
end
|
17
22
|
|
18
|
-
|
23
|
+
@config.ui.confirm " * #{source.type} dependencies: #{source_dependencies.size}"
|
24
|
+
end
|
25
|
+
end
|
19
26
|
end
|
20
27
|
end
|
21
28
|
|
22
|
-
|
29
|
+
# Returns an apps non-ignored dependencies, sorted by name
|
30
|
+
def dependencies(app, source)
|
23
31
|
source.dependencies
|
24
|
-
.select { |d|
|
25
|
-
.sort_by { |d| d[
|
32
|
+
.select { |d| !app.ignored?(d) }
|
33
|
+
.sort_by { |d| d["name"] }
|
26
34
|
end
|
27
35
|
|
28
36
|
def success?
|
@@ -0,0 +1,78 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
require "yaml"
|
3
|
+
|
4
|
+
module Licensed
|
5
|
+
module Command
|
6
|
+
class Status
|
7
|
+
attr_reader :config
|
8
|
+
|
9
|
+
def initialize(config)
|
10
|
+
@config = config
|
11
|
+
end
|
12
|
+
|
13
|
+
def allowed_or_reviewed?(app, dependency)
|
14
|
+
app.allowed?(dependency) || app.reviewed?(dependency)
|
15
|
+
end
|
16
|
+
|
17
|
+
def app_dependencies(app)
|
18
|
+
app.sources.flat_map(&:dependencies).select { |d| !app.ignored?(d) }
|
19
|
+
end
|
20
|
+
|
21
|
+
def run
|
22
|
+
@results = @config.apps.flat_map do |app|
|
23
|
+
Dir.chdir app.source_path do
|
24
|
+
dependencies = app_dependencies(app)
|
25
|
+
@config.ui.info "Checking licenses for #{app['name']}: #{dependencies.size} dependencies"
|
26
|
+
|
27
|
+
results = dependencies.map do |dependency|
|
28
|
+
filename = app.cache_path.join(dependency["type"], "#{dependency["name"]}.txt")
|
29
|
+
|
30
|
+
warnings = []
|
31
|
+
|
32
|
+
# verify cached license data for dependency
|
33
|
+
if File.exist?(filename)
|
34
|
+
license = License.read(filename)
|
35
|
+
|
36
|
+
if license["version"] != dependency["version"]
|
37
|
+
warnings << "cached license data out of date"
|
38
|
+
end
|
39
|
+
warnings << "missing license text" if license.text.strip.empty?
|
40
|
+
unless allowed_or_reviewed?(app, license)
|
41
|
+
warnings << "license needs reviewed: #{license["license"]}."
|
42
|
+
end
|
43
|
+
else
|
44
|
+
warnings << "cached license data missing"
|
45
|
+
end
|
46
|
+
|
47
|
+
if warnings.size > 0
|
48
|
+
@config.ui.error("F", false)
|
49
|
+
[filename, warnings]
|
50
|
+
else
|
51
|
+
@config.ui.confirm(".", false)
|
52
|
+
nil
|
53
|
+
end
|
54
|
+
end.compact
|
55
|
+
|
56
|
+
unless results.empty?
|
57
|
+
@config.ui.warn "\n\nWarnings:"
|
58
|
+
|
59
|
+
results.each do |filename, warnings|
|
60
|
+
@config.ui.info "\n#{filename}:"
|
61
|
+
warnings.each do |warning|
|
62
|
+
@config.ui.error " - #{warning}"
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
puts "\n#{dependencies.size} dependencies checked, #{results.size} warnings found."
|
68
|
+
results
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
def success?
|
74
|
+
@results.empty?
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
@@ -1,38 +1,50 @@
|
|
1
|
+
# frozen_string_literal: true
|
1
2
|
require "pathname"
|
2
3
|
|
3
4
|
module Licensed
|
4
|
-
class
|
5
|
-
|
6
|
-
|
7
|
-
|
5
|
+
class AppConfiguration < Hash
|
6
|
+
DEFAULT_CACHE_PATH = ".licenses".freeze
|
7
|
+
DEFAULT_CONFIG_FILES = [
|
8
|
+
".licensed.yml".freeze,
|
9
|
+
".licensed.yaml".freeze,
|
10
|
+
".licensed.json".freeze
|
11
|
+
].freeze
|
12
|
+
|
13
|
+
def initialize(options = {}, inherited_options = {})
|
8
14
|
super()
|
9
|
-
|
10
|
-
update
|
15
|
+
|
16
|
+
# update order:
|
17
|
+
# 1. anything inherited from root config
|
18
|
+
# 2. app defaults
|
19
|
+
# 3. explicitly configured app settings
|
20
|
+
update(inherited_options)
|
21
|
+
update(defaults_for(options, inherited_options))
|
22
|
+
update(options)
|
11
23
|
|
12
24
|
self["sources"] ||= {}
|
13
25
|
self["reviewed"] ||= {}
|
14
26
|
self["ignored"] ||= {}
|
15
|
-
self["
|
27
|
+
self["allowed"] ||= []
|
16
28
|
|
17
|
-
|
29
|
+
verify_arg "source_path"
|
30
|
+
verify_arg "cache_path"
|
18
31
|
end
|
19
32
|
|
20
|
-
|
21
|
-
|
33
|
+
# Returns the path to the app cache directory as a Pathname
|
34
|
+
def cache_path
|
35
|
+
Licensed::Git.repository_root.join(self["cache_path"])
|
22
36
|
end
|
23
37
|
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
def config_path
|
29
|
-
path.join("config.yml")
|
38
|
+
# Returns the path to the app source directory as a Pathname
|
39
|
+
def source_path
|
40
|
+
Licensed::Git.repository_root.join(self["source_path"])
|
30
41
|
end
|
31
42
|
|
32
43
|
def pwd
|
33
|
-
Pathname.
|
44
|
+
Pathname.pwd
|
34
45
|
end
|
35
46
|
|
47
|
+
# Returns an array of enabled app sources
|
36
48
|
def sources
|
37
49
|
@sources ||= [
|
38
50
|
Source::Bundler.new(self),
|
@@ -40,16 +52,16 @@ module Licensed
|
|
40
52
|
Source::Cabal.new(self),
|
41
53
|
Source::Go.new(self),
|
42
54
|
Source::Manifest.new(self),
|
43
|
-
Source::NPM.new(self)
|
44
|
-
Source::Stack.new(self)
|
55
|
+
Source::NPM.new(self)
|
45
56
|
].select(&:enabled?)
|
46
57
|
end
|
47
58
|
|
59
|
+
# Returns whether a source type is enabled
|
48
60
|
def enabled?(source_type)
|
49
61
|
self["sources"].fetch(source_type, true)
|
50
62
|
end
|
51
63
|
|
52
|
-
# Is the given dependency
|
64
|
+
# Is the given dependency reviewed?
|
53
65
|
def reviewed?(dependency)
|
54
66
|
Array(self["reviewed"][dependency["type"]]).include?(dependency["name"])
|
55
67
|
end
|
@@ -59,21 +71,111 @@ module Licensed
|
|
59
71
|
Array(self["ignored"][dependency["type"]]).include?(dependency["name"])
|
60
72
|
end
|
61
73
|
|
62
|
-
# Is the license of the dependency
|
63
|
-
def
|
64
|
-
Array(self["
|
74
|
+
# Is the license of the dependency allowed?
|
75
|
+
def allowed?(dependency)
|
76
|
+
Array(self["allowed"]).include?(dependency["license"])
|
65
77
|
end
|
66
78
|
|
79
|
+
# Ignore a dependency
|
67
80
|
def ignore(dependency)
|
68
81
|
(self["ignored"][dependency["type"]] ||= []) << dependency["name"]
|
69
82
|
end
|
70
83
|
|
84
|
+
# Set a dependency as reviewed
|
71
85
|
def review(dependency)
|
72
86
|
(self["reviewed"][dependency["type"]] ||= []) << dependency["name"]
|
73
87
|
end
|
74
88
|
|
75
|
-
|
76
|
-
|
89
|
+
# Set a license as explicitly allowed
|
90
|
+
def allow(license)
|
91
|
+
self["allowed"] << license
|
92
|
+
end
|
93
|
+
|
94
|
+
def defaults_for(options, inherited_options)
|
95
|
+
name = options["name"] || File.basename(options["source_path"])
|
96
|
+
cache_path = inherited_options["cache_path"] || DEFAULT_CACHE_PATH
|
97
|
+
{
|
98
|
+
"name" => name,
|
99
|
+
"cache_path" => File.join(cache_path, name)
|
100
|
+
}
|
101
|
+
end
|
102
|
+
|
103
|
+
def verify_arg(property)
|
104
|
+
return if self[property]
|
105
|
+
raise Licensed::Configuration::LoadError,
|
106
|
+
"App #{self["name"]} is missing required property #{property}"
|
107
|
+
end
|
108
|
+
end
|
109
|
+
|
110
|
+
class Configuration < AppConfiguration
|
111
|
+
class LoadError < StandardError; end
|
112
|
+
|
113
|
+
attr_accessor :ui
|
114
|
+
|
115
|
+
# Loads and returns a Licensed::Configuration object from the given path.
|
116
|
+
# The path can be relative or absolute, and can point at a file or directory.
|
117
|
+
# If the path given is a directory, the directory will be searched for a
|
118
|
+
# `config.yml` file.
|
119
|
+
def self.load_from(path)
|
120
|
+
config_path = Pathname.pwd.join(path)
|
121
|
+
config_path = find_config(config_path) if config_path.directory?
|
122
|
+
Configuration.new(parse_config(config_path))
|
123
|
+
end
|
124
|
+
|
125
|
+
def initialize(options = {})
|
126
|
+
@ui = Licensed::UI::Shell.new
|
127
|
+
|
128
|
+
apps = options.delete("apps") || []
|
129
|
+
super(default_options.merge(options))
|
130
|
+
|
131
|
+
self["apps"] = apps.map { |app| AppConfiguration.new(app, options) }
|
132
|
+
end
|
133
|
+
|
134
|
+
# Returns an array of the applications for this licensed configuration.
|
135
|
+
# If the configuration did not explicitly configure any applications,
|
136
|
+
# return self as an application configuration.
|
137
|
+
def apps
|
138
|
+
return [self] if self["apps"].empty?
|
139
|
+
self["apps"]
|
140
|
+
end
|
141
|
+
|
142
|
+
private
|
143
|
+
|
144
|
+
# Find a default configuration file in the given directory.
|
145
|
+
# File preference is given by the order of elements in DEFAULT_CONFIG_FILES
|
146
|
+
#
|
147
|
+
# Raises Licensed::Configuration::LoadError if a file isn't found
|
148
|
+
def self.find_config(directory)
|
149
|
+
config_file = DEFAULT_CONFIG_FILES.map { |file| directory.join(file) }
|
150
|
+
.find { |file| file.exist? }
|
151
|
+
|
152
|
+
config_file || raise(LoadError, "Licensed configuration not found in #{directory}")
|
153
|
+
end
|
154
|
+
|
155
|
+
# Parses the configuration given at `config_path` and returns the values
|
156
|
+
# as a Hash
|
157
|
+
#
|
158
|
+
# Raises Licensed::Configuration::LoadError if the file type isn't known
|
159
|
+
def self.parse_config(config_path)
|
160
|
+
return {} unless config_path.file?
|
161
|
+
|
162
|
+
extension = config_path.extname.downcase.delete "."
|
163
|
+
case extension
|
164
|
+
when "json"
|
165
|
+
JSON.parse(File.read(config_path))
|
166
|
+
when "yml", "yaml"
|
167
|
+
YAML.load_file(config_path)
|
168
|
+
else
|
169
|
+
raise LoadError, "Unknown file type #{extension} for #{config_path}"
|
170
|
+
end
|
171
|
+
end
|
172
|
+
|
173
|
+
def default_options
|
174
|
+
# manually set a cache path without additional name
|
175
|
+
{
|
176
|
+
"source_path" => Dir.pwd,
|
177
|
+
"cache_path" => DEFAULT_CACHE_PATH
|
178
|
+
}
|
77
179
|
end
|
78
180
|
end
|
79
181
|
end
|