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
@@ -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