license_finder 0.6.0 → 0.7.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. data/.gitignore +4 -0
  2. data/Gemfile +0 -1
  3. data/bin/license_finder +21 -1
  4. data/features/approve_dependencies.feature +10 -0
  5. data/features/license_finder.feature +13 -3
  6. data/features/license_finder_rake_task.feature +3 -3
  7. data/features/set_license.feature +14 -0
  8. data/features/step_definitions/steps.rb +5 -0
  9. data/lib/license_finder.rb +13 -2
  10. data/lib/license_finder/bundle.rb +25 -3
  11. data/lib/license_finder/bundle_syncer.rb +12 -0
  12. data/lib/license_finder/bundled_gem.rb +12 -1
  13. data/lib/license_finder/cli.rb +42 -3
  14. data/lib/license_finder/configuration.rb +1 -31
  15. data/lib/license_finder/dependency.rb +30 -94
  16. data/lib/license_finder/dependency_report.rb +30 -0
  17. data/lib/license_finder/html_report.rb +14 -0
  18. data/lib/license_finder/persistence.rb +1 -0
  19. data/lib/license_finder/persistence/yaml.rb +7 -0
  20. data/lib/license_finder/persistence/yaml/configuration.rb +34 -0
  21. data/lib/license_finder/persistence/yaml/dependency.rb +127 -0
  22. data/lib/license_finder/reporter.rb +7 -38
  23. data/lib/license_finder/source_syncer.rb +40 -0
  24. data/lib/license_finder/text_report.rb +9 -0
  25. data/lib/templates/dependency.html.erb +2 -2
  26. data/lib/templates/html_report.erb +93 -0
  27. data/lib/templates/text_report.erb +2 -0
  28. data/license_finder.gemspec +2 -2
  29. data/{README.markdown → readme.md} +22 -11
  30. data/spec/lib/license_finder/bundle_spec.rb +58 -0
  31. data/spec/lib/license_finder/bundle_syncer_spec.rb +22 -0
  32. data/spec/lib/license_finder/bundled_gem_spec.rb +17 -14
  33. data/spec/lib/license_finder/cli_spec.rb +38 -0
  34. data/spec/lib/license_finder/dependency_spec.rb +130 -223
  35. data/spec/lib/license_finder/html_report_spec.rb +67 -0
  36. data/spec/lib/license_finder/persistence/yaml/configuration_spec.rb +5 -0
  37. data/spec/lib/license_finder/persistence/yaml/dependency_spec.rb +5 -0
  38. data/spec/lib/license_finder/possible_license_file_spec.rb +4 -9
  39. data/spec/lib/license_finder/reporter_spec.rb +0 -1
  40. data/spec/lib/license_finder/source_syncer_spec.rb +37 -0
  41. data/spec/lib/license_finder/text_report_spec.rb +29 -0
  42. data/spec/lib/license_finder_spec.rb +9 -11
  43. data/spec/spec_helper.rb +1 -1
  44. data/spec/support/license_examples.rb +1 -1
  45. data/spec/support/shared_examples/persistence/configuration.rb +34 -0
  46. data/spec/support/shared_examples/persistence/dependency.rb +139 -0
  47. metadata +38 -26
  48. data/lib/license_finder/dependency_list.rb +0 -80
  49. data/lib/license_finder/viewable.rb +0 -31
  50. data/lib/templates/dependency_list.html.erb +0 -38
  51. data/spec/lib/license_finder/dependency_list_spec.rb +0 -243
data/.gitignore CHANGED
@@ -5,3 +5,7 @@ Gemfile.lock
5
5
  .rvmrc
6
6
  .idea/*
7
7
  tmp/
8
+ dependencies.yml
9
+ dependencies.txt
10
+ dependencies.html
11
+ config/
data/Gemfile CHANGED
@@ -1,4 +1,3 @@
1
1
  source "http://rubygems.org"
2
2
 
3
- # Specify your gem's dependencies in license_finder.gemspec
4
3
  gemspec
@@ -1,4 +1,24 @@
1
1
  #!/usr/bin/env ruby
2
2
 
3
3
  require 'license_finder'
4
- LicenseFinder::CLI.check_for_action_items
4
+ require 'optparse'
5
+
6
+ options = {}
7
+
8
+ OptionParser.new do |opts|
9
+ opts.banner = "Usage: license_finder [options] [dependency]"
10
+
11
+ opts.on("-a", "--approve", "Approve a gem by name.") do |gem_name|
12
+ options[:approve] = true
13
+ end
14
+
15
+ opts.on("-l", "--license [LICENSE]", "Update a gem's license.") do |license|
16
+ options[:license] = license
17
+ end
18
+ end.parse!
19
+
20
+ unless options.empty?
21
+ options[:dependency] = ARGV.last
22
+ end
23
+
24
+ LicenseFinder::CLI.execute! options
@@ -3,6 +3,16 @@ Feature: Approving non-whitelisted Dependencies
3
3
  As an application developer using license finder
4
4
  I want to be able to manually approve dependencies that have licenses which fall outside of my whitelist
5
5
 
6
+ Scenario: Approving a non-whitelisted dependency via the `license_finder` command
7
+ Given I have an app with license finder
8
+ And my app depends on a gem "gpl_gem" licensed with "GPL"
9
+ When I run "license_finder"
10
+ Then I should see "gpl_gem" in its output
11
+ When I run "license_finder -a gpl_gem"
12
+ When I run "license_finder"
13
+ Then I should not see "gpl_gem" in its output
14
+ Then I should see the "gpl_gem" in the html flagged as "approved"
15
+
6
16
  Scenario: Manually approving a non-whitelisted dependency
7
17
  Given I have an app with license finder
8
18
  And my app depends on a gem "gpl_gem" licensed with "GPL"
@@ -20,6 +20,14 @@ Feature: License Finder command line executable
20
20
  dependencies_file_dir: './'
21
21
  """
22
22
 
23
+ Scenario: Running with an empty dependencies.yml
24
+ Given I have an app with license finder
25
+ And my app depends on a gem "mit_licensed_gem" licensed with "MIT"
26
+ And I have a truncated dependencies.yml file
27
+ When I run "license_finder"
28
+ Then it should exit with status code 1
29
+ And I should see "mit_licensed_gem" in its output
30
+
23
31
  Scenario: Auditing an application with non-whitelisted licenses
24
32
  Given I have an app with license finder
25
33
  And my app depends on a gem "mit_licensed_gem" licensed with "MIT"
@@ -30,7 +38,9 @@ Feature: License Finder command line executable
30
38
  Scenario: Auditing an application with whitelisted licenses
31
39
  Given I have an app with license finder
32
40
  And my app depends on a gem "mit_licensed_gem" licensed with "MIT"
33
- And I whitelist the following licenses: "MIT, other"
34
41
  When I run "license_finder"
35
- Then it should exit with status code 0
36
- And I should see "All gems are approved for use" in its output
42
+ Then I should see "mit_licensed_gem" in its output
43
+ When I whitelist the following licenses: "MIT, other"
44
+ And I run "license_finder"
45
+ Then I should see "All gems are approved for use" in its output
46
+ And it should exit with status code 0
@@ -1,7 +1,7 @@
1
- Feature: License Finder command line executable
2
- So that I can report and manage my application's dependencies and licenses to my business
1
+ Feature: License Finder rake task
2
+ So that I can break my build suite if someone adds a dependency to the application with a non-whitelisted license
3
3
  As an application developer
4
- I want a command-line interface
4
+ I want a rake task that exit's with a non-zero exit status if there are any action items
5
5
 
6
6
  Scenario: Running without a configuration file
7
7
  Given I have an app with rake and license finder
@@ -0,0 +1,14 @@
1
+ Feature: Set a dependency's license through a command line interface
2
+ So that my dependencies all have the correct licenses
3
+ As an application developer
4
+ I want a command line interface to set licenses for specific dependencies
5
+
6
+ Scenario: Setting a license for a dependency
7
+ Given I have an app with license finder
8
+ And my app depends on a gem "other_license_gem" licensed with "other"
9
+ When I run "license_finder"
10
+ When I run "license_finder -l MIT other_license_gem"
11
+ Then I should see the following settings for "other_license_gem":
12
+ """
13
+ license: "MIT"
14
+ """
@@ -296,3 +296,8 @@ When /^the text "([^"]*)" should link to "([^"]*)"$/ do |text, link|
296
296
  html = Capybara.string File.read(@user.dependencies_html_path)
297
297
  html.find(:xpath, "//a[@href='#{link}']").text.should == text
298
298
  end
299
+ When /^I have a truncated dependencies.yml file$/ do
300
+ File.open(@user.dependencies_file_path, 'w+') do |f|
301
+ f.puts ""
302
+ end
303
+ end
@@ -1,20 +1,31 @@
1
1
  require 'pathname'
2
2
  require 'yaml'
3
+ require 'erb'
3
4
 
4
5
  module LicenseFinder
5
6
  ROOT_PATH = Pathname.new(__FILE__).dirname
6
7
 
8
+ DEPENDENCY_ATTRIBUTES = [
9
+ "name", "source", "version", "license", "license_url", "approved", "notes",
10
+ "license_files", "readme_files", "bundler_groups", "summary",
11
+ "description", "homepage", "children", "parents"
12
+ ]
13
+
7
14
  autoload :Bundle, 'license_finder/bundle'
8
15
  autoload :BundledGem, 'license_finder/bundled_gem'
9
16
  autoload :CLI, 'license_finder/cli'
10
17
  autoload :Configuration, 'license_finder/configuration'
18
+ autoload :Persistence, 'license_finder/persistence/yaml'
11
19
  autoload :Dependency, 'license_finder/dependency'
12
- autoload :DependencyList, 'license_finder/dependency_list'
13
20
  autoload :License, 'license_finder/license'
14
21
  autoload :LicenseUrl, 'license_finder/license_url'
15
22
  autoload :PossibleLicenseFile, 'license_finder/possible_license_file'
23
+ autoload :DependencyReport, 'license_finder/dependency_report'
24
+ autoload :HtmlReport, 'license_finder/html_report'
25
+ autoload :TextReport, 'license_finder/text_report'
16
26
  autoload :Reporter, 'license_finder/reporter'
17
- autoload :Viewable, 'license_finder/viewable'
27
+ autoload :BundleSyncer, 'license_finder/bundle_syncer'
28
+ autoload :SourceSyncer, 'license_finder/source_syncer'
18
29
 
19
30
  def self.config
20
31
  @config ||= Configuration.new
@@ -1,17 +1,39 @@
1
1
  module LicenseFinder
2
2
  class Bundle
3
+ def initialize(bundler_definition=nil)
4
+ @definition = bundler_definition || Bundler::Definition.build(gemfile_path, lockfile_path, nil)
5
+ end
6
+
3
7
  def gems
4
- definition.specs_for(included_groups).map do |spec|
8
+ return @gems if @gems
9
+
10
+ @gems ||= definition.specs_for(included_groups).map do |spec|
5
11
  dependency = dependencies.detect { |dep| dep.name == spec.name }
6
12
 
7
13
  BundledGem.new(spec, dependency)
8
14
  end
15
+
16
+ setup_parent_child_relationships
17
+
18
+ @gems
9
19
  end
10
20
 
11
21
  private
22
+ attr_reader :definition
12
23
 
13
- def definition
14
- @definition ||= Bundler::Definition.build(gemfile_path, lockfile_path, nil)
24
+ def setup_parent_child_relationships
25
+ dependency_index = {}
26
+
27
+ gems.each do |dep|
28
+ dependency_index[dep.dependency_name] = dep
29
+ end
30
+
31
+ gems.each do |dep|
32
+ dep.children.each do |child_dep|
33
+ license_finder_dependency = dependency_index[child_dep]
34
+ license_finder_dependency.parents << dep.dependency_name if license_finder_dependency
35
+ end
36
+ end
15
37
  end
16
38
 
17
39
  def dependencies
@@ -0,0 +1,12 @@
1
+ module LicenseFinder
2
+ module BundleSyncer
3
+ extend self
4
+
5
+ def sync!
6
+ source_dependencies = Bundle.new.gems.map(&:to_dependency)
7
+ target_dependencies = Dependency.all.select {|d| d.source == "bundle" }
8
+ SourceSyncer.new(source_dependencies, target_dependencies).sync!
9
+ end
10
+ end
11
+ end
12
+
@@ -3,6 +3,8 @@ module LicenseFinder
3
3
  LICENSE_FILE_NAMES = %w(LICENSE License Licence COPYING README Readme ReadMe)
4
4
  README_FILE_NAMES = %w(README Readme ReadMe)
5
5
 
6
+ attr_reader :parents
7
+
6
8
  def initialize(spec, bundler_dependency = nil)
7
9
  @spec = spec
8
10
  @bundler_dependency = bundler_dependency
@@ -12,6 +14,10 @@ module LicenseFinder
12
14
  "#{dependency_name} #{dependency_version}"
13
15
  end
14
16
 
17
+ def parents
18
+ @parents ||= []
19
+ end
20
+
15
21
  def dependency_name
16
22
  @spec.name
17
23
  end
@@ -20,6 +26,10 @@ module LicenseFinder
20
26
  @spec.version.to_s
21
27
  end
22
28
 
29
+ def children
30
+ @children ||= @spec.dependencies.collect(&:name)
31
+ end
32
+
23
33
  def to_dependency
24
34
  @dependency ||= LicenseFinder::Dependency.new(
25
35
  'name' => @spec.name,
@@ -32,7 +42,8 @@ module LicenseFinder
32
42
  'summary' => @spec.summary,
33
43
  'description' => @spec.description,
34
44
  'homepage' => @spec.homepage,
35
- 'children' => @spec.dependencies
45
+ 'children' => children,
46
+ 'parents' => parents
36
47
  )
37
48
  end
38
49
 
@@ -3,15 +3,54 @@ module LicenseFinder
3
3
  extend self
4
4
 
5
5
  def check_for_action_items
6
- found = LicenseFinder::Reporter.new.action_items
6
+ create_default_configuration
7
+ BundleSyncer.sync!
8
+ generate_reports
7
9
 
8
- if found.size == 0
10
+ unapproved = Dependency.unapproved
11
+
12
+ if unapproved.count == 0
9
13
  puts "All gems are approved for use"
10
14
  else
11
15
  puts "Dependencies that need approval:"
12
- puts found
16
+ puts TextReport.new(unapproved)
13
17
  exit 1
14
18
  end
15
19
  end
20
+
21
+ def execute!(options={})
22
+ create_default_configuration
23
+
24
+ if options.empty?
25
+ check_for_action_items
26
+ else
27
+ dependency = Dependency.find_by_name(options[:dependency])
28
+
29
+ if options[:approve]
30
+ dependency.approve!
31
+ puts "The #{dependency.name} has been approved!\n\n"
32
+ elsif options[:license]
33
+ dependency.update_attributes :license => options[:license]
34
+ puts "The #{dependency.name} has been marked as using #{options[:license]} license!\n\n"
35
+ end
36
+
37
+ generate_reports
38
+ end
39
+ end
40
+
41
+ private
42
+ def generate_reports
43
+ LicenseFinder::Reporter.write_reports
44
+ end
45
+
46
+ def create_default_configuration
47
+ unless File.exists?(LicenseFinder.config.config_file_path)
48
+ FileUtils.mkdir_p(File.join('.', 'config'))
49
+ FileUtils.cp(
50
+ File.join(File.dirname(__FILE__), '..', '..', 'files', 'license_finder.yml'),
51
+ LicenseFinder.config.config_file_path
52
+ )
53
+ end
54
+ end
16
55
  end
17
56
  end
@@ -1,34 +1,4 @@
1
1
  module LicenseFinder
2
- class Configuration
3
- attr_reader :whitelist, :ignore_groups, :dependencies_dir
4
-
5
- def initialize
6
- config = {}
7
-
8
- if File.exists?(config_file_path)
9
- yaml = File.open(config_file_path).readlines.join
10
- config = YAML.load(yaml)
11
- end
12
-
13
- @whitelist = config['whitelist'] || []
14
- @ignore_groups = (config["ignore_groups"] || []).map(&:to_sym)
15
- @dependencies_dir = config['dependencies_file_dir'] || '.'
16
- end
17
-
18
- def config_file_path
19
- File.join('.', 'config', 'license_finder.yml')
20
- end
21
-
22
- def dependencies_yaml
23
- File.join(dependencies_dir, "dependencies.yml")
24
- end
25
-
26
- def dependencies_text
27
- File.join(dependencies_dir, "dependencies.txt")
28
- end
29
-
30
- def dependencies_html
31
- File.join(dependencies_dir, "dependencies.html")
32
- end
2
+ class Configuration < LicenseFinder::Persistence::Configuration
33
3
  end
34
4
  end
@@ -1,37 +1,33 @@
1
- # encoding: UTF-8
2
- require "erb"
3
-
4
1
  module LicenseFinder
5
- class Dependency
6
- include Viewable
2
+ class Dependency < LicenseFinder::Persistence::Dependency
3
+ def approved
4
+ return super if super
5
+ self.approved = config.whitelist.include?(license)
6
+ end
7
7
 
8
- attr_accessor :name, :version, :license, :approved, :license_url, :notes, :license_files,
9
- :readme_files, :source, :bundler_groups, :homepage, :children, :parents
8
+ def license_files
9
+ super || (self.license_files = [])
10
+ end
11
+
12
+ def readme_files
13
+ super || (self.readme_files = [])
14
+ end
10
15
 
11
- attr_reader :summary, :description
16
+ def bundler_groups
17
+ super || (self.bundler_groups = [])
18
+ end
12
19
 
13
- def self.from_hash(attrs)
14
- attrs['license_files'] = attrs['license_files'].map { |lf| lf['path'] } if attrs['license_files']
15
- attrs['readme_files'] = attrs['readme_files'].map { |rf| rf['path'] } if attrs['readme_files']
20
+ def children
21
+ super || (self.children = [])
22
+ end
16
23
 
17
- new(attrs)
24
+ def parents
25
+ super || (self.parents = [])
18
26
  end
19
27
 
20
- def initialize(attributes = {})
21
- @source = attributes['source']
22
- @name = attributes['name']
23
- @version = attributes['version']
24
- @license = attributes['license']
25
- @approved = attributes['approved'] || LicenseFinder.config.whitelist.include?(attributes['license'])
26
- @notes = attributes['notes'] || ''
27
- @license_files = attributes['license_files'] || []
28
- @readme_files = attributes['readme_files'] || []
29
- @bundler_groups = attributes['bundler_groups'] || []
30
- @summary = attributes['summary']
31
- @description = attributes['description']
32
- @homepage = attributes['homepage']
33
- @children = attributes.fetch('children', [])
34
- @parents = attributes.fetch('parents', [])
28
+ def approve!
29
+ self.approved = true
30
+ save
35
31
  end
36
32
 
37
33
  def license_url
@@ -41,78 +37,18 @@ module LicenseFinder
41
37
  def merge(other)
42
38
  raise "Cannot merge dependencies with different names. Expected #{name}, was #{other.name}." unless other.name == name
43
39
 
44
- merged = self.class.new(
45
- 'name' => name,
46
- 'version' => other.version,
47
- 'license_files' => other.license_files,
48
- 'readme_files' => other.readme_files,
49
- 'license_url' => other.license_url,
50
- 'notes' => notes,
51
- 'source' => other.source,
52
- 'summary' => other.summary,
53
- 'description' => other.description,
54
- 'bundler_groups' => other.bundler_groups,
55
- 'homepage' => other.homepage,
56
- 'children' => other.children,
57
- 'parents' => other.parents
58
- )
40
+ new_attributes = other.attributes.merge("notes" => notes)
59
41
 
60
- case other.license
61
- when license, 'other'
62
- merged.license = license
63
- merged.approved = approved
42
+ if other.license == license || other.license == 'other'
43
+ new_attributes["approved"] = approved
44
+ new_attributes["license"] = license
64
45
  else
65
- merged.license = other.license
66
- merged.approved = other.approved
67
- end
68
-
69
- merged
70
- end
71
-
72
- def as_yaml
73
- attrs = {
74
- 'name' => name,
75
- 'version' => version,
76
- 'license' => license,
77
- 'approved' => approved,
78
- 'source' => source,
79
- 'license_url' => license_url,
80
- 'homepage' => homepage,
81
- 'notes' => notes,
82
- 'license_files' => nil,
83
- 'readme_files' => nil
84
- }
85
-
86
- unless license_files.empty?
87
- attrs['license_files'] = license_files.map do |file|
88
- {'path' => file}
89
- end
46
+ new_attributes["approved"] = nil
90
47
  end
91
48
 
92
- unless readme_files.empty?
93
- attrs['readme_files'] = readme_files.map do |file|
94
- {'path' => file}
95
- end
96
- end
97
-
98
- attrs
99
- end
100
-
101
- def to_s
102
- [name, version, license].join ", "
103
- end
104
-
105
- private
49
+ update_attributes new_attributes
106
50
 
107
- def constantize(string)
108
- names = string.split('::')
109
- names.shift if names.empty? || names.first.empty?
110
-
111
- constant = Object
112
- names.each do |name|
113
- constant = constant.const_defined?(name, false) ? constant.const_get(name) : constant.const_missing(name)
114
- end
115
- constant
51
+ self
116
52
  end
117
53
  end
118
54
  end