license_finder 0.6.0 → 0.7.0

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