licensed 0.11.1 → 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|