licensed 1.5.2 → 2.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.
Files changed (51) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +2 -0
  3. data/CHANGELOG.md +22 -1
  4. data/CONTRIBUTING.md +2 -2
  5. data/README.md +17 -24
  6. data/Rakefile +2 -2
  7. data/docs/adding_a_new_source.md +93 -0
  8. data/docs/commands.md +81 -0
  9. data/docs/configuration.md +8 -8
  10. data/docs/migrating_to_newer_versions.md +3 -0
  11. data/docs/reporters.md +174 -0
  12. data/docs/sources/bundler.md +5 -5
  13. data/lib/licensed.rb +5 -14
  14. data/lib/licensed/cli.rb +23 -9
  15. data/lib/licensed/commands.rb +9 -0
  16. data/lib/licensed/commands/cache.rb +82 -0
  17. data/lib/licensed/commands/command.rb +112 -0
  18. data/lib/licensed/commands/list.rb +24 -0
  19. data/lib/licensed/commands/status.rb +49 -0
  20. data/lib/licensed/configuration.rb +3 -8
  21. data/lib/licensed/dependency.rb +116 -58
  22. data/lib/licensed/dependency_record.rb +76 -0
  23. data/lib/licensed/migrations.rb +7 -0
  24. data/lib/licensed/migrations/v2.rb +65 -0
  25. data/lib/licensed/reporters.rb +9 -0
  26. data/lib/licensed/reporters/cache_reporter.rb +76 -0
  27. data/lib/licensed/reporters/list_reporter.rb +69 -0
  28. data/lib/licensed/reporters/reporter.rb +119 -0
  29. data/lib/licensed/reporters/status_reporter.rb +67 -0
  30. data/lib/licensed/shell.rb +8 -10
  31. data/lib/licensed/sources.rb +15 -0
  32. data/lib/licensed/{source → sources}/bower.rb +14 -19
  33. data/lib/licensed/{source → sources}/bundler.rb +73 -48
  34. data/lib/licensed/{source → sources}/cabal.rb +40 -46
  35. data/lib/licensed/{source → sources}/dep.rb +15 -27
  36. data/lib/licensed/{source → sources}/git_submodule.rb +14 -19
  37. data/lib/licensed/{source → sources}/go.rb +28 -35
  38. data/lib/licensed/{source → sources}/manifest.rb +68 -90
  39. data/lib/licensed/{source → sources}/npm.rb +16 -25
  40. data/lib/licensed/{source → sources}/pip.rb +23 -25
  41. data/lib/licensed/sources/source.rb +69 -0
  42. data/lib/licensed/ui/shell.rb +4 -0
  43. data/lib/licensed/version.rb +6 -1
  44. data/licensed.gemspec +4 -4
  45. data/script/source-setup/bundler +1 -1
  46. metadata +32 -18
  47. data/lib/licensed/command/cache.rb +0 -82
  48. data/lib/licensed/command/list.rb +0 -43
  49. data/lib/licensed/command/status.rb +0 -79
  50. data/lib/licensed/command/version.rb +0 -18
  51. data/lib/licensed/license.rb +0 -68
@@ -1,4 +1,4 @@
1
- # Bundler (rubygem)
1
+ # Bundler
2
2
 
3
3
  The bundler source will detect dependencies `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
4
 
@@ -6,13 +6,13 @@ The bundler source will detect dependencies `Gemfile` and `Gemfile.lock` files a
6
6
 
7
7
  **Note** this content only applies to running licensed from an executable. It does not apply when using licensed as a gem.
8
8
 
9
- _It is strongly recommended that ruby is always available when running the licensed executable._
9
+ _It is required that the ruby runtime is available when running the licensed executable._
10
10
 
11
11
  The licensed executable contains and runs a version of ruby. When using the Bundler APIs, a mismatch between the version of ruby built into the licensed executable and the version of licensed used during `bundle install` can occur. This mismatch can lead to licensed raising errors due to not finding dependencies.
12
12
 
13
13
  For example, if `bundle install` was run with ruby 2.5.0 then the bundler specification path would be `<bundle path>/ruby/2.5.0/specifications`. However, if the licensed executable contains ruby 2.4.0, then licensed will be looking for specifications at `<bundle path>/ruby/2.4.0/specifications`. That path may not exist, or it may contain invalid or stale content.
14
14
 
15
- If ruby is available when running licensed, licensed will determine the ruby version used during `bundle install` and prefer that version string over the version contained in the executable. If bundler is also available, then the ruby command will be run from a `bundle exec` context.
15
+ To prevent confusion, licensed uses the local ruby runtime to determine the ruby version for local gems during `bundle install`. If bundler is also available, then the ruby command will be run from a `bundle exec` context.
16
16
 
17
17
  ### Excluding gem groups
18
18
 
@@ -27,14 +27,14 @@ The bundler source determines which gem groups to include or exclude with the fo
27
27
  Strings and string arrays are both :+1:
28
28
 
29
29
  ```yml
30
- rubygem:
30
+ bundler:
31
31
  without: development
32
32
  ```
33
33
 
34
34
  or
35
35
 
36
36
  ```yml
37
- rubygem:
37
+ bundler:
38
38
  without:
39
39
  - build
40
40
  - development
@@ -1,21 +1,12 @@
1
1
  # frozen_string_literal: true
2
2
  require "licensed/version"
3
3
  require "licensed/shell"
4
- require "licensed/license"
4
+ require "licensed/dependency_record"
5
5
  require "licensed/dependency"
6
6
  require "licensed/git"
7
- require "licensed/source/bundler"
8
- require "licensed/source/bower"
9
- require "licensed/source/manifest"
10
- require "licensed/source/npm"
11
- require "licensed/source/go"
12
- require "licensed/source/dep"
13
- require "licensed/source/cabal"
14
- require "licensed/source/pip"
15
- require "licensed/source/git_submodule"
7
+ require "licensed/sources"
16
8
  require "licensed/configuration"
17
- require "licensed/command/cache"
18
- require "licensed/command/status"
19
- require "licensed/command/list"
20
- require "licensed/command/version"
9
+ require "licensed/reporters"
10
+ require "licensed/commands"
21
11
  require "licensed/ui/shell"
12
+ require "licensed/migrations"
@@ -8,33 +8,48 @@ module Licensed
8
8
  desc "cache", "Cache the licenses of dependencies"
9
9
  method_option :force, type: :boolean,
10
10
  desc: "Overwrite licenses even if version has not changed."
11
- method_option :offline, type: :boolean,
12
- desc: "This option is deprecated and will be removed in the next major release."
13
11
  method_option :config, aliases: "-c", type: :string,
14
12
  desc: "Path to licensed configuration file"
15
13
  def cache
16
- run Licensed::Command::Cache.new(config), force: options[:force]
14
+ run Licensed::Commands::Cache.new(config: config), force: options[:force]
17
15
  end
18
16
 
19
17
  desc "status", "Check status of dependencies' cached licenses"
20
18
  method_option :config, aliases: "-c", type: :string,
21
19
  desc: "Path to licensed configuration file"
22
20
  def status
23
- run Licensed::Command::Status.new(config)
21
+ run Licensed::Commands::Status.new(config: config)
24
22
  end
25
23
 
26
24
  desc "list", "List dependencies"
27
25
  method_option :config, aliases: "-c", type: :string,
28
26
  desc: "Path to licensed configuration file"
29
27
  def list
30
- run Licensed::Command::List.new(config)
28
+ run Licensed::Commands::List.new(config: config)
31
29
  end
32
30
 
33
31
  map "-v" => :version
34
32
  map "--version" => :version
35
33
  desc "version", "Show Installed Version of Licensed, [-v, --version]"
36
34
  def version
37
- run Licensed::Command::Version.new
35
+ puts Licensed::VERSION
36
+ end
37
+
38
+ desc "migrate", "Migrate from a previous version of licensed"
39
+ method_option :config, aliases: "-c", type: :string, required: true,
40
+ desc: "Path to licensed configuration file"
41
+ method_option :from, aliases: "-f", type: :string, required: true,
42
+ desc: "Licensed version to migrate from - #{Licensed.previous_major_versions.map { |major| "v#{major}" }.join(", ")}"
43
+ def migrate
44
+ case options["from"]
45
+ when "v1"
46
+ Licensed::Migrations::V2.migrate(options["config"])
47
+ else
48
+ shell = Thor::Base.shell.new
49
+ shell.say "Unrecognized option from=#{options["from"]}", :red
50
+ CLI.command_help(shell, "migrate")
51
+ exit 1
52
+ end
38
53
  end
39
54
 
40
55
  # If an error occurs (e.g. a missing command or argument), exit 1.
@@ -55,9 +70,8 @@ module Licensed
55
70
  options["config"] || Dir.pwd
56
71
  end
57
72
 
58
- def run(command, *args)
59
- command.run(*args)
60
- exit command.success?
73
+ def run(command, **args)
74
+ exit command.run(args)
61
75
  end
62
76
  end
63
77
  end
@@ -0,0 +1,9 @@
1
+ # frozen_string_literal: true
2
+ module Licensed
3
+ module Commands
4
+ require "licensed/commands/command"
5
+ require "licensed/commands/cache"
6
+ require "licensed/commands/status"
7
+ require "licensed/commands/list"
8
+ end
9
+ end
@@ -0,0 +1,82 @@
1
+ # frozen_string_literal: true
2
+ module Licensed
3
+ module Commands
4
+ class Cache < Command
5
+ def initialize(config:, reporter: Licensed::Reporters::CacheReporter.new)
6
+ super(config: config, reporter: reporter)
7
+ end
8
+
9
+ protected
10
+
11
+ # Run the command for all enumerated dependencies found in a dependency source,
12
+ # recording results in a report.
13
+ # Removes any cached records that don't match a current application
14
+ # dependency.
15
+ #
16
+ # app - The application configuration for the source
17
+ # source - A dependency source enumerator
18
+ #
19
+ # Returns whether the command succeeded for the dependency source enumerator
20
+ def run_source(app, source)
21
+ result = super
22
+ clear_stale_cached_records(app, source) if result
23
+ result
24
+ end
25
+
26
+ # Cache dependency record data.
27
+ #
28
+ # app - The application configuration for the dependency
29
+ # source - The dependency source enumerator for the dependency
30
+ # dependency - An application dependency
31
+ # report - A report hash for the command to provide extra data for the report output.
32
+ #
33
+ # Returns true.
34
+ def evaluate_dependency(app, source, dependency, report)
35
+ filename = app.cache_path.join(source.class.type, "#{dependency.name}.#{DependencyRecord::EXTENSION}")
36
+ cached_record = Licensed::DependencyRecord.read(filename)
37
+ if options[:force] || save_dependency_record?(dependency, cached_record)
38
+ # use the cached license value if the license text wasn't updated
39
+ dependency.record["license"] = cached_record["license"] if dependency.record.matches?(cached_record)
40
+ dependency.record.save(filename)
41
+ report["cached"] = true
42
+ end
43
+
44
+ true
45
+ end
46
+
47
+ # Determine if the current dependency's record should be saved.
48
+ # The record should be saved if:
49
+ # 1. there is no cached record
50
+ # 2. the cached record doesn't have a version set
51
+ # 3. the cached record version doesn't match the current dependency version
52
+ #
53
+ # dependency - An application dependency
54
+ # cached_record - A dependency record to compare with the dependency
55
+ #
56
+ # Returns true if dependency's record should be saved
57
+ def save_dependency_record?(dependency, cached_record)
58
+ return true if cached_record.nil?
59
+
60
+ cached_version = cached_record["version"]
61
+ return true if cached_version.nil? || cached_version.empty?
62
+ return true if dependency.version != cached_version
63
+ false
64
+ end
65
+
66
+ # Clean up cached files that dont match current dependencies
67
+ #
68
+ # app - An application configuration
69
+ # source - A dependency source enumerator
70
+ #
71
+ # Returns nothing
72
+ def clear_stale_cached_records(app, source)
73
+ names = source.dependencies.map { |dependency| File.join(source.class.type, dependency.name) }
74
+ Dir.glob(app.cache_path.join(source.class.type, "**/*.#{DependencyRecord::EXTENSION}")).each do |file|
75
+ file_path = Pathname.new(file)
76
+ relative_path = file_path.relative_path_from(app.cache_path).to_s
77
+ FileUtils.rm(file) unless names.include?(relative_path.chomp(".#{DependencyRecord::EXTENSION}"))
78
+ end
79
+ end
80
+ end
81
+ end
82
+ end
@@ -0,0 +1,112 @@
1
+ # frozen_string_literal: true
2
+ module Licensed
3
+ module Commands
4
+ class Command
5
+ attr_reader :config
6
+ attr_reader :reporter
7
+ attr_reader :options
8
+
9
+ def initialize(config:, reporter:)
10
+ @config = config
11
+ @reporter = reporter
12
+ end
13
+
14
+ # Run the command
15
+ #
16
+ # options - Options to run the command with
17
+ #
18
+ # Returns whether the command was a success
19
+ def run(**options)
20
+ @options = options
21
+ begin
22
+ result = reporter.report_run(self) do
23
+ config.apps.map { |app| run_app(app) }.all?
24
+ end
25
+ ensure
26
+ @options = nil
27
+ end
28
+
29
+ result
30
+ end
31
+
32
+ protected
33
+
34
+ # Run the command for all enabled sources for an application configuration,
35
+ # recording results in a report.
36
+ #
37
+ # app - An application configuration
38
+ #
39
+ # Returns whether the command succeeded for the application.
40
+ def run_app(app)
41
+ reporter.report_app(app) do |report|
42
+ Dir.chdir app.source_path do
43
+ begin
44
+ app.sources.select(&:enabled?).map { |source| run_source(app, source) }.all?
45
+ rescue Licensed::Shell::Error => err
46
+ report.errors << err.message
47
+ false
48
+ end
49
+ end
50
+ end
51
+ end
52
+
53
+ # Run the command for all enumerated dependencies found in a dependency source,
54
+ # recording results in a report.
55
+ #
56
+ # app - The application configuration for the source
57
+ # source - A dependency source enumerator
58
+ #
59
+ # Returns whether the command succeeded for the dependency source enumerator
60
+ def run_source(app, source)
61
+ reporter.report_source(source) do |report|
62
+ begin
63
+ source.dependencies.map { |dependency| run_dependency(app, source, dependency) }.all?
64
+ rescue Licensed::Shell::Error => err
65
+ report.errors << err.message
66
+ false
67
+ rescue Licensed::Sources::Source::Error => err
68
+ report.errors << err.message
69
+ false
70
+ end
71
+ end
72
+ end
73
+
74
+ # Run the command for a dependency, evaluating the dependency and
75
+ # recording results in a report. Dependencies that were found with errors
76
+ # are not evaluated and add any errors to the dependency report.
77
+ #
78
+ # app - The application configuration for the dependency
79
+ # source - The dependency source enumerator for the dependency
80
+ # dependency - An application dependency
81
+ #
82
+ # Returns whether the command succeeded for the dependency
83
+ def run_dependency(app, source, dependency)
84
+ reporter.report_dependency(dependency) do |report|
85
+ if dependency.errors?
86
+ report.errors.concat(dependency.errors)
87
+ return false
88
+ end
89
+
90
+ begin
91
+ evaluate_dependency(app, source, dependency, report)
92
+ rescue Licensed::Shell::Error => err
93
+ report.errors << err.message
94
+ false
95
+ end
96
+ end
97
+ end
98
+
99
+ # Evaluate a dependency for the command. Must be implemented by a command implementation.
100
+ #
101
+ # app - The application configuration for the dependency
102
+ # source - The dependency source enumerator for the dependency
103
+ # dependency - An application dependency
104
+ # report - A report hash for the command to provide extra data for the report output.
105
+ #
106
+ # Returns whether the command succeeded for the dependency
107
+ def evaluate_dependency(app, source, dependency, report)
108
+ raise "`evaluate_dependency` must be implemented by a command"
109
+ end
110
+ end
111
+ end
112
+ end
@@ -0,0 +1,24 @@
1
+ # frozen_string_literal: true
2
+ module Licensed
3
+ module Commands
4
+ class List < Command
5
+ def initialize(config:, reporter: Licensed::Reporters::ListReporter.new)
6
+ super(config: config, reporter: reporter)
7
+ end
8
+
9
+ protected
10
+
11
+ # Listing dependencies requires no extra work.
12
+ #
13
+ # app - The application configuration for the dependency
14
+ # source - The dependency source enumerator for the dependency
15
+ # dependency - An application dependency
16
+ # report - A report hash for the command to provide extra data for the report output.
17
+ #
18
+ # Returns true.
19
+ def evaluate_dependency(app, source, dependency, report)
20
+ true
21
+ end
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,49 @@
1
+ # frozen_string_literal: true
2
+ require "yaml"
3
+
4
+ module Licensed
5
+ module Commands
6
+ class Status < Command
7
+ def initialize(config:, reporter: Licensed::Reporters::StatusReporter.new)
8
+ super(config: config, reporter: reporter)
9
+ end
10
+
11
+ protected
12
+
13
+ # Verifies that a cached record exists, is up to date and
14
+ # has license data that complies with the licensed configuration.
15
+ #
16
+ # app - The application configuration for the dependency
17
+ # source - The dependency source enumerator for the dependency
18
+ # dependency - An application dependency
19
+ # report - A report hash for the command to provide extra data for the report output.
20
+ #
21
+ # Returns whether the dependency has a cached record that is compliant
22
+ # with the licensed configuration.
23
+ def evaluate_dependency(app, source, dependency, report)
24
+ filename = app.cache_path.join(source.class.type, "#{dependency.name}.#{DependencyRecord::EXTENSION}")
25
+ report["filename"] = filename
26
+
27
+ cached_record = cached_record(filename)
28
+ if cached_record.nil?
29
+ report.errors << "cached dependency record not found"
30
+ else
31
+ report.errors << "cached dependency record out of date" if cached_record["version"] != dependency.version
32
+ report.errors << "missing license text" if cached_record.licenses.empty?
33
+ report.errors << "license needs review: #{cached_record["license"]}" unless allowed_or_reviewed?(app, cached_record)
34
+ end
35
+
36
+ report.errors.empty?
37
+ end
38
+
39
+ def allowed_or_reviewed?(app, dependency)
40
+ app.allowed?(dependency) || app.reviewed?(dependency)
41
+ end
42
+
43
+ def cached_record(filename)
44
+ return nil unless File.exist?(filename)
45
+ DependencyRecord.read(filename)
46
+ end
47
+ end
48
+ end
49
+ end
@@ -9,15 +9,10 @@ module Licensed
9
9
  ".licensed.yaml".freeze,
10
10
  ".licensed.json".freeze
11
11
  ].freeze
12
- SOURCE_TYPES = Source.constants.map { |c| Source.const_get(c) }.freeze
13
-
14
- attr_reader :ui
15
12
 
16
13
  def initialize(options = {}, inherited_options = {})
17
14
  super()
18
15
 
19
- @ui = Licensed::UI::Shell.new
20
-
21
16
  # update order:
22
17
  # 1. anything inherited from root config
23
18
  # 2. app defaults
@@ -61,9 +56,9 @@ module Licensed
61
56
 
62
57
  # Returns an array of enabled app sources
63
58
  def sources
64
- @sources ||= SOURCE_TYPES.select { |source_class| enabled?(source_class.type) }
65
- .map { |source_class| source_class.new(self) }
66
- .select(&:enabled?)
59
+ @sources ||= Licensed::Sources::Source.sources
60
+ .select { |source_class| enabled?(source_class.type) }
61
+ .map { |source_class| source_class.new(self) }
67
62
  end
68
63
 
69
64
  # Returns whether a source type is enabled
@@ -2,90 +2,148 @@
2
2
  require "licensee"
3
3
 
4
4
  module Licensed
5
- class Dependency < License
6
- LEGAL_FILES = /\A(AUTHORS|NOTICE|LEGAL)(?:\..*)?\z/i
5
+ class Dependency < Licensee::Projects::FSProject
6
+ LEGAL_FILES_PATTERN = /(AUTHORS|NOTICE|LEGAL)(?:\..*)?\z/i
7
7
 
8
- attr_reader :path
9
- attr_reader :search_root
10
8
  attr_reader :name
9
+ attr_reader :version
10
+ attr_reader :errors
11
+
12
+ # Create a new project dependency
13
+ #
14
+ # name - unique dependency name
15
+ # version - dependency version
16
+ # path - absolute file path to the dependency, to find license contents
17
+ # search_root - (optional) the root location to search for dependency license contents
18
+ # metadata - (optional) additional dependency data to cache
19
+ # errors - (optional) errors encountered when evaluating dependency
20
+ #
21
+ # Returns a new dependency object. Dependency metadata and license contents
22
+ # are available if no errors are set on the dependency.
23
+ def initialize(name:, version:, path:, search_root: nil, metadata: {}, errors: [])
24
+ # check the path for default errors if no other errors
25
+ # were found when loading the dependency
26
+ if errors.empty?
27
+ path_error = path_error(path, search_root)
28
+ errors.push(path_error) if path_error
29
+ end
11
30
 
12
- def initialize(path, metadata = {})
13
- @search_root = metadata.delete("search_root")
14
- @name = metadata.delete("path") || metadata["name"]
15
- super metadata
16
-
17
- self.path = path
18
- end
31
+ @name = name
32
+ @version = version
33
+ @metadata = metadata
34
+ @errors = errors
19
35
 
20
- # Returns a Licensee::Projects::FSProject for the dependency path
21
- def project
22
- @project ||= Licensee::Projects::FSProject.new(path, search_root: search_root, detect_packages: true, detect_readme: true)
23
- end
36
+ # if there are any errors, don't evaluate any dependency contents
37
+ return if errors.any?
24
38
 
25
- # Sets the path to source dependency license information
26
- def path=(path)
27
39
  # enforcing absolute paths makes life much easier when determining
28
40
  # an absolute file path in #notices
29
- unless Pathname.new(path).absolute?
30
- raise "Dependency path #{path} must be absolute"
41
+ if !Pathname.new(path).absolute?
42
+ # this is an internal error related to source implementation and
43
+ # should be raised, not stored to be handled by reporters
44
+ raise ArgumentError, "dependency path #{path} must be absolute"
31
45
  end
32
46
 
33
- @path = path
34
- reset_license!
47
+ super(path, search_root: search_root, detect_readme: true, detect_packages: true)
35
48
  end
36
49
 
37
- # Detects license information and sets it on this dependency object.
38
- # After calling `detect_license!``, the license is set at
39
- # `dependency["license"]` and legal text is set to `dependency.text`
40
- def detect_license!
41
- self["license"] = license_key
42
- self.text = [license_text, *notices].join("\n" + TEXT_SEPARATOR + "\n").rstrip
50
+ # Returns true if the dependency has any errors, false otherwise
51
+ def errors?
52
+ errors.any?
43
53
  end
44
54
 
45
- # Extract legal notices from the dependency source
46
- def notices
47
- local_files.uniq # unique local file paths
48
- .sort # sorted by the path
49
- .map { |f| File.read(f) } # read the file contents
50
- .map(&:rstrip) # strip whitespace
51
- .select { |t| t.length > 0 } # files with content only
55
+ # Returns a record for this dependency including metadata and legal contents
56
+ def record
57
+ return nil if errors?
58
+ @record ||= DependencyRecord.new(
59
+ metadata: license_metadata,
60
+ licenses: license_contents,
61
+ notices: notice_contents
62
+ )
52
63
  end
53
64
 
54
- # Returns an array of file paths used to locate legal notices
55
- def local_files
56
- return [] unless Dir.exist?(path)
65
+ # Returns a string representing the dependencys license
66
+ def license_key
67
+ return "none" if errors? || !license
68
+ license.key
69
+ end
57
70
 
58
- Dir.foreach(path).map do |file|
59
- next unless file.match(LEGAL_FILES)
71
+ # Returns the license text content from all matched sources
72
+ # except the package file, which doesn't contain license text.
73
+ def license_contents
74
+ return [] if errors?
75
+ matched_files.reject { |f| f == package_file }
76
+ .group_by(&:content)
77
+ .map { |content, files| { "sources" => content_sources(files), "text" => content } }
78
+ end
60
79
 
61
- file_path = File.join(path, file)
62
- next unless File.file?(file_path)
80
+ # Returns legal notices found at the dependency path
81
+ def notice_contents
82
+ return [] if errors?
83
+ notice_files.sort # sorted by the path
84
+ .map { |file| { "sources" => content_sources(file), "text" => File.read(file).rstrip } }
85
+ .select { |text| text.length > 0 } # files with content only
86
+ end
87
+
88
+ # Returns an array of file paths used to locate legal notices
89
+ def notice_files
90
+ return [] if errors?
63
91
 
64
- file_path
65
- end.compact
92
+ Dir.glob(dir_path.join("*"))
93
+ .grep(LEGAL_FILES_PATTERN)
94
+ .select { |path| File.file?(path) }
66
95
  end
67
96
 
68
97
  private
69
98
 
70
- # Resets all local project and license information
71
- def reset_license!
72
- @project = nil
73
- self.delete("license")
74
- self.text = nil
99
+ def path_error(path, search_root)
100
+ return "dependency path not found" if path.to_s.empty?
101
+ return if File.exist?(path)
102
+ return if search_root && File.exist?(search_root)
103
+
104
+ # if the given path doesn't exist
105
+ # AND a search root isn't given, or the search root doesn't exist
106
+ # then set an error that the expected dependency path doesn't exist
107
+ "expected dependency path #{path} does not exist"
75
108
  end
76
109
 
77
- # Regardless of the license detected, try to pull the license content
78
- # from the local LICENSE-type files, remote LICENSE, or the README, in that order
79
- def license_text
80
- content_files = Array(project.license_files)
81
- content_files << project.readme_file if content_files.empty? && project.readme_file
82
- content_files.map(&:content).join("\n#{LICENSE_SEPARATOR}\n")
110
+ # Returns the sources for a group of license or notice file contents
111
+ #
112
+ # Sources are returned as a single string with sources separated by ", "
113
+ def content_sources(files)
114
+ paths = Array(files).map do |file|
115
+ path = if file.is_a?(Licensee::ProjectFiles::ProjectFile)
116
+ dir_path.join(file[:dir], file[:name])
117
+ else
118
+ Pathname.new(file).expand_path(dir_path)
119
+ end
120
+
121
+ if path.fnmatch?(dir_path.join("**").to_path)
122
+ # files under the dependency path return the relative path to the file
123
+ path.relative_path_from(dir_path).to_path
124
+ else
125
+ # otherwise return the source_path as the immediate parent folder name
126
+ # joined with the file name
127
+ path.dirname.basename.join(path.basename).to_path
128
+ end
129
+ end
130
+
131
+ paths.join(", ")
83
132
  end
84
133
 
85
- # Returns a string representing the project's license
86
- def license_key
87
- return "none" unless project.license
88
- project.license.key
134
+ # Returns the metadata that represents this dependency. This metadata
135
+ # is written to YAML in the dependencys cached text file
136
+ def license_metadata
137
+ {
138
+ # can be overriden by values in @metadata
139
+ "name" => name,
140
+ "version" => version
141
+ }.merge(
142
+ @metadata
143
+ ).merge({
144
+ # overrides all other values
145
+ "license" => license_key
146
+ })
89
147
  end
90
148
  end
91
149
  end