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
@@ -0,0 +1,69 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Licensed
4
+ module Sources
5
+ class Source
6
+ class DependencyEnumerationNotImplementedError < StandardError
7
+ def initialize(message = "Source classes must implemented `enumerate_dependencies`")
8
+ super
9
+ end
10
+ end
11
+
12
+ class Error < StandardError; end
13
+
14
+ class << self
15
+ attr_reader :sources
16
+ def inherited(klass)
17
+ # add child source classes are defined,
18
+ # add them to the known sources list
19
+ (@sources ||= []) << klass
20
+ end
21
+
22
+ # Returns the source name as the snake cased class name
23
+ def type
24
+ self.name.split(/::/)
25
+ .last
26
+ .gsub(/([A-Z\d]+)([A-Z][a-z])/, "\\1_\\2".freeze)
27
+ .gsub(/([a-z\d])([A-Z])/, "\\1_\\2".freeze)
28
+ .downcase
29
+ end
30
+ end
31
+
32
+ # all sources have a configuration
33
+ attr_accessor :config
34
+
35
+ def initialize(configuration)
36
+ @config = configuration
37
+ end
38
+
39
+ # Returns whether a source is enabled based on the environment in which licensed is run
40
+ # Defaults to false.
41
+ def enabled?
42
+ false
43
+ end
44
+
45
+ # Returns all dependencies that should be evaluated.
46
+ # Excludes ignored dependencies.
47
+ def dependencies
48
+ cached_dependencies.reject { |d| ignored?(d) }
49
+ end
50
+
51
+ # Enumerate all source dependencies. Must be implemented by each source class.
52
+ def enumerate_dependencies
53
+ raise DependencyEnumerationNotImplementedError
54
+ end
55
+
56
+ # Returns whether a dependency is ignored in the configuration.
57
+ def ignored?(dependency)
58
+ config.ignored?("type" => self.class.type, "name" => dependency.name)
59
+ end
60
+
61
+ private
62
+
63
+ # Returns a cached list of dependencies
64
+ def cached_dependencies
65
+ @dependencies ||= enumerate_dependencies.compact
66
+ end
67
+ end
68
+ end
69
+ end
@@ -31,6 +31,10 @@ module Licensed
31
31
  @shell.say msg, :red, newline if level?("error")
32
32
  end
33
33
 
34
+ def newline
35
+ info ""
36
+ end
37
+
34
38
  def level=(level)
35
39
  raise ArgumentError unless LEVELS.include?(level.to_s)
36
40
  @level = level
@@ -1,4 +1,9 @@
1
1
  # frozen_string_literal: true
2
2
  module Licensed
3
- VERSION = "1.5.2".freeze
3
+ VERSION = "2.0.0".freeze
4
+
5
+ def self.previous_major_versions
6
+ major_version = Gem::Version.new(Licensed::VERSION).segments.first
7
+ (1...major_version).to_a
8
+ end
4
9
  end
@@ -24,10 +24,10 @@ Gem::Specification.new do |spec|
24
24
  spec.required_ruby_version = ">= 2.3.0"
25
25
 
26
26
  spec.add_dependency "licensee", "~> 9.0"
27
- spec.add_dependency "thor", "~>0.19"
28
- spec.add_dependency "pathname-common_prefix", "~>0.0.1"
29
- spec.add_dependency "tomlrb", "~>1.2"
30
- spec.add_dependency "bundler", "~> 1.10"
27
+ spec.add_dependency "thor", "~> 0.19"
28
+ spec.add_dependency "pathname-common_prefix", "~> 0.0.1"
29
+ spec.add_dependency "tomlrb", "~> 1.2"
30
+ spec.add_dependency "bundler", ">= 1.10"
31
31
 
32
32
  spec.add_development_dependency "rake", "~> 10.0"
33
33
  spec.add_development_dependency "minitest", "~> 5.8"
@@ -14,7 +14,7 @@ cd $BASE_PATH/test/fixtures/bundler
14
14
  unset BUNDLE_GEMFILE
15
15
 
16
16
  if [ "$1" == "-f" ]; then
17
- find . -not -regex "\.*" -and -not -name "Gemfile" -print0 | xargs -0 rm -rf
17
+ find . -not -regex "\.*" -and -not -name "Gemfile" -and -not \( -path ./pathed-gem-fixture -prune \) -print0 | xargs -0 rm -rf
18
18
  fi
19
19
 
20
20
  bundle install --path vendor/gems --without ignore
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: licensed
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.5.2
4
+ version: 2.0.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - GitHub
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2018-12-27 00:00:00.000000000 Z
11
+ date: 2019-02-11 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: licensee
@@ -70,14 +70,14 @@ dependencies:
70
70
  name: bundler
71
71
  requirement: !ruby/object:Gem::Requirement
72
72
  requirements:
73
- - - "~>"
73
+ - - ">="
74
74
  - !ruby/object:Gem::Version
75
75
  version: '1.10'
76
76
  type: :runtime
77
77
  prerelease: false
78
78
  version_requirements: !ruby/object:Gem::Requirement
79
79
  requirements:
80
- - - "~>"
80
+ - - ">="
81
81
  - !ruby/object:Gem::Version
82
82
  version: '1.10'
83
83
  - !ruby/object:Gem::Dependency
@@ -170,8 +170,12 @@ files:
170
170
  - README.md
171
171
  - Rakefile
172
172
  - docker/Dockerfile.build-linux
173
+ - docs/adding_a_new_source.md
174
+ - docs/commands.md
173
175
  - docs/configuration.md
176
+ - docs/migrating_to_newer_versions.md
174
177
  - docs/packaging.md
178
+ - docs/reporters.md
175
179
  - docs/sources/bower.md
176
180
  - docs/sources/bundler.md
177
181
  - docs/sources/cabal.md
@@ -185,24 +189,34 @@ files:
185
189
  - exe/licensed
186
190
  - lib/licensed.rb
187
191
  - lib/licensed/cli.rb
188
- - lib/licensed/command/cache.rb
189
- - lib/licensed/command/list.rb
190
- - lib/licensed/command/status.rb
191
- - lib/licensed/command/version.rb
192
+ - lib/licensed/commands.rb
193
+ - lib/licensed/commands/cache.rb
194
+ - lib/licensed/commands/command.rb
195
+ - lib/licensed/commands/list.rb
196
+ - lib/licensed/commands/status.rb
192
197
  - lib/licensed/configuration.rb
193
198
  - lib/licensed/dependency.rb
199
+ - lib/licensed/dependency_record.rb
194
200
  - lib/licensed/git.rb
195
- - lib/licensed/license.rb
201
+ - lib/licensed/migrations.rb
202
+ - lib/licensed/migrations/v2.rb
203
+ - lib/licensed/reporters.rb
204
+ - lib/licensed/reporters/cache_reporter.rb
205
+ - lib/licensed/reporters/list_reporter.rb
206
+ - lib/licensed/reporters/reporter.rb
207
+ - lib/licensed/reporters/status_reporter.rb
196
208
  - lib/licensed/shell.rb
197
- - lib/licensed/source/bower.rb
198
- - lib/licensed/source/bundler.rb
199
- - lib/licensed/source/cabal.rb
200
- - lib/licensed/source/dep.rb
201
- - lib/licensed/source/git_submodule.rb
202
- - lib/licensed/source/go.rb
203
- - lib/licensed/source/manifest.rb
204
- - lib/licensed/source/npm.rb
205
- - lib/licensed/source/pip.rb
209
+ - lib/licensed/sources.rb
210
+ - lib/licensed/sources/bower.rb
211
+ - lib/licensed/sources/bundler.rb
212
+ - lib/licensed/sources/cabal.rb
213
+ - lib/licensed/sources/dep.rb
214
+ - lib/licensed/sources/git_submodule.rb
215
+ - lib/licensed/sources/go.rb
216
+ - lib/licensed/sources/manifest.rb
217
+ - lib/licensed/sources/npm.rb
218
+ - lib/licensed/sources/pip.rb
219
+ - lib/licensed/sources/source.rb
206
220
  - lib/licensed/ui/shell.rb
207
221
  - lib/licensed/version.rb
208
222
  - licensed.gemspec
@@ -1,82 +0,0 @@
1
- # frozen_string_literal: true
2
- module Licensed
3
- module Command
4
- class Cache
5
- attr_reader :config
6
-
7
- def initialize(config)
8
- @config = config
9
- end
10
-
11
- def run(force: false)
12
- summary = @config.apps.flat_map do |app|
13
- app_name = app["name"]
14
- @config.ui.info "Caching licenses for #{app_name}:"
15
-
16
- # load the app environment
17
- Dir.chdir app.source_path do
18
-
19
- # map each available app source to it's dependencies
20
- app.sources.map do |source|
21
- type = source.class.type
22
-
23
- @config.ui.info " #{type} dependencies:"
24
-
25
- names = []
26
- cache_path = app.cache_path.join(type)
27
-
28
- # exclude ignored dependencies
29
- dependencies = source.dependencies.select { |d| !app.ignored?(d) }
30
-
31
- # ensure each dependency is cached
32
- dependencies.each do |dependency|
33
- name = dependency.name
34
- version = dependency["version"]
35
-
36
- names << name
37
- filename = cache_path.join("#{name}.txt")
38
-
39
- # try to load existing license from disk
40
- # or default to a blank license
41
- license = Licensed::License.read(filename) || Licensed::License.new
42
-
43
- # cached version string exists and did not change, no need to re-cache
44
- has_version = !license["version"].nil? && !license["version"].empty?
45
- if !force && has_version && version == license["version"]
46
- @config.ui.info " Using #{name} (#{version})"
47
- next
48
- end
49
-
50
- @config.ui.info " Caching #{name} (#{version})"
51
-
52
- dependency.detect_license!
53
- # use the cached license value if the license text wasn't updated
54
- dependency["license"] = license["license"] if dependency.license_text_match?(license)
55
-
56
- dependency.save(filename)
57
- end
58
-
59
- # Clean up cached files that dont match current dependencies
60
- Dir.glob(cache_path.join("**/*.txt")).each do |file|
61
- file_path = Pathname.new(file)
62
- relative_path = file_path.relative_path_from(cache_path).to_s
63
- FileUtils.rm(file) unless names.include?(relative_path.chomp(".txt"))
64
- end
65
-
66
- "* #{app_name} #{type} dependencies: #{dependencies.size}"
67
- end
68
- end
69
- end
70
-
71
- @config.ui.confirm "License caching complete!"
72
- summary.each do |message|
73
- @config.ui.confirm message
74
- end
75
- end
76
-
77
- def success?
78
- true
79
- end
80
- end
81
- end
82
- end
@@ -1,43 +0,0 @@
1
- # frozen_string_literal: true
2
- module Licensed
3
- module Command
4
- class List
5
- attr_reader :config
6
-
7
- def initialize(config)
8
- @config = config
9
- end
10
-
11
- def run
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
- type = source.class.type
17
-
18
- @config.ui.info " #{type} dependencies:"
19
-
20
- source_dependencies = dependencies(app, source)
21
- source_dependencies.each do |dependency|
22
- @config.ui.info " Found #{dependency.name} (#{dependency["version"]})"
23
- end
24
-
25
- @config.ui.confirm " * #{type} dependencies: #{source_dependencies.size}"
26
- end
27
- end
28
- end
29
- end
30
-
31
- # Returns an apps non-ignored dependencies, sorted by name
32
- def dependencies(app, source)
33
- source.dependencies
34
- .select { |d| !app.ignored?(d) }
35
- .sort_by { |d| d.name }
36
- end
37
-
38
- def success?
39
- true
40
- end
41
- end
42
- end
43
- end
@@ -1,79 +0,0 @@
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
- name = dependency.name
29
- filename = app.cache_path.join(dependency["type"], "#{name}.txt")
30
-
31
- warnings = []
32
-
33
- # verify cached license data for dependency
34
- if File.exist?(filename)
35
- license = License.read(filename)
36
-
37
- if license["version"] != dependency["version"]
38
- warnings << "cached license data out of date"
39
- end
40
- warnings << "missing license text" if license.license_text.empty?
41
- unless allowed_or_reviewed?(app, license)
42
- warnings << "license needs reviewed: #{license["license"]}."
43
- end
44
- else
45
- warnings << "cached license data missing"
46
- end
47
-
48
- if warnings.size > 0
49
- @config.ui.error("F", false)
50
- [filename, warnings]
51
- else
52
- @config.ui.confirm(".", false)
53
- nil
54
- end
55
- end.compact
56
-
57
- unless results.empty?
58
- @config.ui.warn "\n\nWarnings:"
59
-
60
- results.each do |filename, warnings|
61
- @config.ui.info "\n#{filename}:"
62
- warnings.each do |warning|
63
- @config.ui.error " - #{warning}"
64
- end
65
- end
66
- end
67
-
68
- puts "\n#{dependencies.size} dependencies checked, #{results.size} warnings found."
69
- results
70
- end
71
- end
72
- end
73
-
74
- def success?
75
- @results.empty?
76
- end
77
- end
78
- end
79
- end
@@ -1,18 +0,0 @@
1
- # frozen_string_literal: true
2
- module Licensed
3
- module Command
4
- class Version
5
- def initialize
6
- @ui = Licensed::UI::Shell.new
7
- end
8
-
9
- def run
10
- @ui.info(Licensed::VERSION)
11
- end
12
-
13
- def success?
14
- true
15
- end
16
- end
17
- end
18
- end
@@ -1,68 +0,0 @@
1
- # frozen_string_literal: true
2
- require "yaml"
3
- require "fileutils"
4
- require "forwardable"
5
- require "licensee"
6
-
7
- module Licensed
8
- class License
9
- include Licensee::ContentHelper
10
- extend Forwardable
11
-
12
- YAML_FRONTMATTER_PATTERN = /\A---\s*\n(.*?\n?)^---\s*$\n?(.*)\z/m
13
- TEXT_SEPARATOR = ("-" * 80).freeze
14
- LICENSE_SEPARATOR = ("*" * 80).freeze
15
-
16
- # Read an existing license file
17
- #
18
- # filename - A String path to the file
19
- #
20
- # Returns a Licensed::License
21
- def self.read(filename)
22
- return unless File.exist?(filename)
23
- match = File.read(filename).scrub.match(YAML_FRONTMATTER_PATTERN)
24
- new(YAML.load(match[1]), match[2])
25
- end
26
-
27
- def_delegators :@metadata, :[], :[]=, :delete
28
-
29
- # The license text and other legal notices
30
- attr_accessor :text
31
-
32
- # Construct a new license
33
- #
34
- # filename - the String path of the file
35
- # metadata - a Hash of the metadata for the package
36
- # text - a String of the license text and other legal notices
37
- def initialize(metadata = {}, text = nil)
38
- @metadata = metadata
39
- @text = text
40
- end
41
-
42
- # Save the metadata and license to a file
43
- def save(filename)
44
- FileUtils.mkdir_p(File.dirname(filename))
45
- File.write(filename, YAML.dump(@metadata) + "---\n#{text}")
46
- end
47
-
48
- # Returns the license text without any notices
49
- def license_text
50
- return unless text
51
-
52
- # if the text contains the separator, the first string in the array
53
- # should always be the license text whether empty or not.
54
- # if the text didn't contain the separator, the text itself is the entirety
55
- # of the license text
56
- split = text.split(TEXT_SEPARATOR)
57
- split.length > 1 ? split.first.rstrip : text.rstrip
58
- end
59
- alias_method :content, :license_text # use license_text for content matching
60
-
61
- # Returns whether the current license should be updated to `other`
62
- # based on whether the normalized license content matches
63
- def license_text_match?(other)
64
- return false unless other.is_a?(License)
65
- self.content_normalized == other.content_normalized
66
- end
67
- end
68
- end