licensed 1.5.2 → 2.0.0

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