license_finder 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (57) hide show
  1. data/.gitignore +6 -0
  2. data/Gemfile +4 -0
  3. data/MIT-LICENSE +22 -0
  4. data/README.markdown +84 -0
  5. data/Rakefile +12 -0
  6. data/bin/license_finder +7 -0
  7. data/files/license_finder.yml +7 -0
  8. data/lib/license_finder.rb +15 -0
  9. data/lib/license_finder/dependency.rb +63 -0
  10. data/lib/license_finder/dependency_list.rb +55 -0
  11. data/lib/license_finder/file_parser.rb +32 -0
  12. data/lib/license_finder/finder.rb +48 -0
  13. data/lib/license_finder/gem_spec_details.rb +101 -0
  14. data/lib/license_finder/license_file.rb +77 -0
  15. data/lib/license_finder/railtie.rb +9 -0
  16. data/lib/license_finder/version.rb +3 -0
  17. data/lib/tasks/license_finder.rake +17 -0
  18. data/lib/templates/Apache-2.0-body +172 -0
  19. data/lib/templates/GPL-2.0-body +339 -0
  20. data/lib/templates/MIT-body +9 -0
  21. data/license_finder.gemspec +23 -0
  22. data/spec/dependency_list_spec.rb +202 -0
  23. data/spec/dependency_spec.rb +57 -0
  24. data/spec/file_parser_spec.rb +16 -0
  25. data/spec/finder_spec.rb +44 -0
  26. data/spec/fixtures/APACHE-2-LICENSE +202 -0
  27. data/spec/fixtures/GPLv2 +339 -0
  28. data/spec/fixtures/MIT-LICENSE +22 -0
  29. data/spec/fixtures/MIT-LICENSE-with-varied-disclaimer +22 -0
  30. data/spec/fixtures/README-with-MIT-LICENSE +222 -0
  31. data/spec/fixtures/apache_licensed_gem/LICENSE +191 -0
  32. data/spec/fixtures/gplv2_licensed_gem/LICENSE +339 -0
  33. data/spec/fixtures/license_directory/COPYING +0 -0
  34. data/spec/fixtures/license_directory/LICENSE/BSD-2-Clause.txt +25 -0
  35. data/spec/fixtures/license_directory/LICENSE/GPL-2.0.txt +339 -0
  36. data/spec/fixtures/license_directory/LICENSE/LICENSE +191 -0
  37. data/spec/fixtures/license_directory/LICENSE/MIT.txt +21 -0
  38. data/spec/fixtures/license_directory/LICENSE/RUBY.txt +60 -0
  39. data/spec/fixtures/license_names/COPYING.txt +0 -0
  40. data/spec/fixtures/license_names/LICENSE +0 -0
  41. data/spec/fixtures/license_names/Mit-License +0 -0
  42. data/spec/fixtures/license_names/README.rdoc +0 -0
  43. data/spec/fixtures/mit_licensed_gem/LICENSE +22 -0
  44. data/spec/fixtures/mit_licensed_gem_in_README/README.rdoc +222 -0
  45. data/spec/fixtures/mit_licensed_gem_via_url/README +210 -0
  46. data/spec/fixtures/nested_gem/vendor/LICENSE +0 -0
  47. data/spec/fixtures/nested_readme/vendor/README +0 -0
  48. data/spec/fixtures/no_license/.gitkeep +0 -0
  49. data/spec/fixtures/other_licensed_gem/LICENSE +3 -0
  50. data/spec/fixtures/readme/Project ReadMe b/data/spec/fixtures/readme/Project → ReadMe +0 -0
  51. data/spec/fixtures/readme/README +0 -0
  52. data/spec/fixtures/readme/Readme.markdown +0 -0
  53. data/spec/fixtures/utf8_gem/README +210 -0
  54. data/spec/gem_spec_details_spec.rb +167 -0
  55. data/spec/license_file_spec.rb +129 -0
  56. data/spec/spec_helper.rb +10 -0
  57. metadata +159 -0
data/.gitignore ADDED
@@ -0,0 +1,6 @@
1
+ pkg/*
2
+ *.gem
3
+ .bundle
4
+ Gemfile.lock
5
+ .rvmrc
6
+ .idea/*
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source "http://rubygems.org"
2
+
3
+ # Specify your gem's dependencies in license_finder.gemspec
4
+ gemspec
data/MIT-LICENSE ADDED
@@ -0,0 +1,22 @@
1
+ The MIT License
2
+
3
+ Copyright (c) 2010 Jacob Maine
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
22
+
data/README.markdown ADDED
@@ -0,0 +1,84 @@
1
+ Goal
2
+ ====
3
+
4
+ With bundler it's easy for your project to depend on many gems. This decomposition is nice, but managing licenses becomes difficult. This tool gathers info about the licenses of the gems in your project.
5
+
6
+ Usage
7
+ =====
8
+
9
+ For a Rails project add license_finder to your Gemfile:
10
+ gem 'license_finder', :git => "https://github.com/pivotal/LicenseFinder.git"
11
+
12
+ Run 'rake license:init'
13
+ This will create a config/license_finder.yml file that lets you configure license finder.
14
+
15
+ Run 'rake license:generate_dependencies'
16
+ This will write out a dependencies.yml and dependencies.txt file in the root of your project.
17
+ It will also merge in an existing dependencies.yml file, if one exists.
18
+
19
+ Run 'rake license:action_items'
20
+ This will output a list of unapproved dependencies to the console
21
+
22
+ As a standalone script:
23
+ cd ~
24
+ git clone http://github.com/pivotal/LicenseFinder.git license_finder
25
+ cd your/project
26
+ ~/license_finder/bin/license_finder
27
+
28
+ Optionally add `--with-licenses` to include the full text of the licenses in the output.
29
+
30
+ Sample Output
31
+ =============
32
+
33
+ dependencies.yml:
34
+
35
+ ---
36
+ - name: "json_pure"
37
+ version: "1.5.1"
38
+ license: "other"
39
+ approved: false
40
+
41
+ - name: "rake"
42
+ version: "0.8.7"
43
+ license: "MIT"
44
+ approved: true
45
+
46
+ dependencies.txt:
47
+
48
+ json_pure 1.5.1, other
49
+ rake 0.8.7, MIT
50
+
51
+ bin/license_finder:
52
+
53
+ ---
54
+ json_pure 1.5.1:
55
+ dependency_name: json_pure
56
+ dependency_version: 1.5.1
57
+ install_path: /some/path/.rvm/gems/ruby-1.9.2-p180/gems/json_pure-1.5.1
58
+ license_files:
59
+ - file_name: COPYING
60
+ header_type: other
61
+ body_type: other
62
+ disclaimer_of_liability: other
63
+ - file_name: COPYING-json-jruby
64
+ header_type: other
65
+ body_type: other
66
+ disclaimer_of_liability: other
67
+ readme_files:
68
+ - file_name: README
69
+ mentions_license: true
70
+ - file_name: README-json-jruby.markdown
71
+ mentions_license: false
72
+ ---
73
+ rake 0.8.7:
74
+ dependency_name: rake
75
+ dependency_version: 0.8.7
76
+ install_path: /some/path/.rvm/gems/ruby-1.9.2-p180/gems/rake-0.8.7
77
+ license_files:
78
+ - file_name: MIT-LICENSE
79
+ header_type: other
80
+ body_type: mit
81
+ disclaimer_of_liability: "mit: THE AUTHORS OR COPYRIGHT HOLDERS"
82
+ readme_files:
83
+ - file_name: README
84
+ mentions_license: true
data/Rakefile ADDED
@@ -0,0 +1,12 @@
1
+ require 'bundler'
2
+ Bundler::GemHelper.install_tasks
3
+
4
+ require 'rspec/core/rake_task'
5
+
6
+ desc "Run all specs in spec/"
7
+ RSpec::Core::RakeTask.new(:spec) do |t|
8
+ t.fail_on_error = true
9
+ t.pattern = "./spec/**/*_spec.rb"
10
+ t.rspec_opts = %w[--color]
11
+ end
12
+
@@ -0,0 +1,7 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'pathname'
4
+ $LOAD_PATH.unshift Pathname.new(__FILE__).dirname + ".." + "lib"
5
+ require "license_finder"
6
+
7
+ LicenseFinder::Finder.new.from_bundler.each { |lf| puts lf.to_s(ARGV.first == "--with-licenses") }
@@ -0,0 +1,7 @@
1
+ ---
2
+ whitelist:
3
+ #- MIT
4
+ #- Apache 2.0
5
+ ignore_groups:
6
+ #- test
7
+ #- development
@@ -0,0 +1,15 @@
1
+ require 'pathname'
2
+ require 'yaml'
3
+
4
+ module LicenseFinder
5
+ ROOT_PATH = Pathname.new(__FILE__).dirname
6
+ end
7
+
8
+ require 'license_finder/railtie' if defined?(Rails)
9
+ require 'license_finder/finder'
10
+ require 'license_finder/gem_spec_details'
11
+ require 'license_finder/file_parser'
12
+ require 'license_finder/license_file'
13
+
14
+ require 'license_finder/dependency'
15
+ require 'license_finder/dependency_list'
@@ -0,0 +1,63 @@
1
+ module LicenseFinder
2
+ class Dependency
3
+
4
+ attr_reader :name, :version, :license, :approved, :license_url, :notes, :license_files, :readme_files
5
+
6
+ def self.from_hash(attrs)
7
+ lfs = attrs['license_files'] ? attrs['license_files'].map { |lf| lf['path'] } : []
8
+ rfs = attrs['readme_files'] ? attrs['readme_files'].map { |rf| rf['path'] } : []
9
+ new(attrs['name'], attrs['version'], attrs['license'], attrs['approved'], attrs['license_url'], attrs['notes'], lfs, rfs)
10
+ end
11
+
12
+ def initialize(name, version, license, approved, license_url = '', notes = '', license_files = [], readme_files = [])
13
+ @name = name
14
+ @version = version
15
+ @license = license
16
+ @approved = approved
17
+ @license_url = license_url
18
+ @notes = notes
19
+ @license_files = license_files
20
+ @readme_files = readme_files
21
+ end
22
+
23
+ def to_yaml_entry
24
+ attrs = "- name: \"#{name}\"\n version: \"#{version}\"\n license: \"#{license}\"\n approved: #{approved}\n license_url: \"#{license_url}\"\n notes: \"#{notes}\"\n"
25
+ attrs << " license_files:\n"
26
+ if !self.license_files.empty?
27
+ self.license_files.each do |lf|
28
+ attrs << " - path: \"#{lf}\"\n"
29
+ end
30
+ end
31
+ attrs << " readme_files:\n"
32
+ if !self.readme_files.empty?
33
+ self.readme_files.each do |rf|
34
+ attrs << " - path: \"#{rf}\"\n"
35
+ end
36
+ end
37
+ attrs
38
+ end
39
+
40
+ def to_s
41
+ url = ", #{license_url}" if license_url != ''
42
+ str = "#{name} #{version}, #{license}#{url}"
43
+ if license == 'other'
44
+ str << "\n license files:"
45
+ unless self.license_files.empty?
46
+ self.license_files.each do |lf|
47
+ str << "\n #{lf}"
48
+ end
49
+ end
50
+ str << "\n readme files:"
51
+ unless self.readme_files.empty?
52
+ self.readme_files.each do |lf|
53
+ str << "\n #{lf}"
54
+ end
55
+ end
56
+ end
57
+
58
+ str
59
+ end
60
+
61
+ end
62
+ end
63
+
@@ -0,0 +1,55 @@
1
+ module LicenseFinder
2
+ class DependencyList
3
+
4
+ attr_reader :dependencies
5
+
6
+ def self.from_bundler(whitelist = [], ignore_groups = [])
7
+ gemfile = Pathname.new("Gemfile").expand_path
8
+ root = gemfile.dirname
9
+ lockfile = root.join('Gemfile.lock')
10
+ definition = Bundler::Definition.build(gemfile, lockfile, nil)
11
+
12
+ groups = definition.groups - ignore_groups
13
+
14
+ new(definition.specs_for(groups).map { |spec| GemSpecDetails.new(spec, whitelist).dependency })
15
+ end
16
+
17
+ def initialize(dependencies)
18
+ @dependencies = dependencies
19
+ end
20
+
21
+ def self.from_yaml(yml)
22
+ deps = YAML.load(yml)
23
+ new(deps.map { |dhash| Dependency.from_hash(dhash) })
24
+ end
25
+
26
+ def merge(new_list)
27
+ deps = new_list.dependencies.map do |new_dep|
28
+ old_dep = self.dependencies.detect { |d| d.name == new_dep.name }
29
+ if old_dep && old_dep.license == new_dep.license
30
+ Dependency.new(new_dep.name, new_dep.version, new_dep.license, old_dep.approved || new_dep.approved, old_dep.license_url, old_dep.notes, new_dep.license_files, new_dep.readme_files)
31
+ elsif old_dep && new_dep.license == 'other'
32
+ Dependency.new(new_dep.name, new_dep.version, old_dep.license, old_dep.approved, old_dep.license_url, old_dep.notes, new_dep.license_files, new_dep.readme_files)
33
+ else
34
+ new_dep
35
+ end
36
+ end
37
+
38
+ self.class.new(deps)
39
+ end
40
+
41
+ def to_yaml
42
+ result = "--- \n"
43
+ dependencies.sort_by(&:name).inject(result) { |r, d| r << d.to_yaml_entry; r }
44
+ end
45
+
46
+ def to_s
47
+ dependencies.sort_by(&:name).map(&:to_s).join("\n")
48
+ end
49
+
50
+ def action_items
51
+ dependencies.sort_by(&:name).reject(&:approved).map(&:to_s).join("\n")
52
+ end
53
+ end
54
+ end
55
+
@@ -0,0 +1,32 @@
1
+ module LicenseFinder
2
+ class FileParser
3
+ def initialize(install_path, file_path)
4
+ @install_path = Pathname.new(install_path)
5
+ @file_path = Pathname.new(file_path)
6
+ end
7
+
8
+ def file_path
9
+ @file_path.relative_path_from(@install_path).to_s
10
+ end
11
+
12
+ def full_file_path
13
+ @file_path.realpath.to_s
14
+ end
15
+
16
+ def file_name
17
+ @file_path.basename.to_s
18
+ end
19
+
20
+ def text
21
+ @text ||= @file_path.send((@file_path.respond_to? :binread) ? :binread : :read)
22
+ end
23
+
24
+ private
25
+
26
+ def on_single_line(text)
27
+ text.gsub(/\s+/, ' ').gsub("'", "\"")
28
+ rescue
29
+ ''
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,48 @@
1
+ module LicenseFinder
2
+ class Finder
3
+
4
+ attr_reader :whitelist, :ignore_groups
5
+ def initialize
6
+ if File.exists?('./config/license_finder.yml')
7
+ config = YAML.load(File.open('./config/license_finder.yml').readlines.join)
8
+ @whitelist = config['whitelist'] || []
9
+ @ignore_groups = config['ignore_groups'] ? config['ignore_groups'].map{|g| g.to_sym} : []
10
+ end
11
+ end
12
+
13
+ def from_bundler
14
+ require 'bundler'
15
+ Bundler.load.specs.map { |spec| GemSpecDetails.new(spec) }.sort_by &:sort_order
16
+ end
17
+
18
+ def write_files
19
+ new_list = generate_list
20
+
21
+ File.open('./dependencies.yml', 'w+') do |f|
22
+ f.puts new_list.to_yaml
23
+ end
24
+ File.open('./dependencies.txt', 'w+') do |f|
25
+ f.puts new_list.to_s
26
+ end
27
+
28
+ end
29
+
30
+ def action_items
31
+ new_list = generate_list
32
+ new_list.action_items
33
+ end
34
+
35
+ private
36
+ def generate_list
37
+ bundler_list = DependencyList.from_bundler(whitelist, ignore_groups)
38
+
39
+ if (File.exists?('./dependencies.yml'))
40
+ yml = File.open('./dependencies.yml').readlines.join
41
+ existing_list = DependencyList.from_yaml(yml)
42
+ existing_list.merge(bundler_list)
43
+ else
44
+ bundler_list
45
+ end
46
+ end
47
+ end
48
+ end
@@ -0,0 +1,101 @@
1
+ module LicenseFinder
2
+ class GemSpecDetails
3
+ LICENSE_FILE_NAMES = '*{LICENSE,License,COPYING,README,Readme,ReadMe}*' # follows Dir.glob format
4
+ README_FILE_NAMES = '*{README,Readme,ReadMe}*' # follows Dir.glob format
5
+
6
+ def initialize(spec, whitelist = [])
7
+ @spec = spec
8
+ @whitelist = whitelist
9
+ end
10
+
11
+ attr_reader :spec
12
+
13
+ def name
14
+ "#{dependency_name} #{dependency_version}"
15
+ end
16
+
17
+ def dependency_name
18
+ spec.name
19
+ end
20
+
21
+ def dependency_version
22
+ spec.version.to_s
23
+ end
24
+
25
+ def dependency
26
+ license = determine_license
27
+ @dependency ||= Dependency.new(@spec.name, @spec.version, license, @whitelist.include?(license), '', '', license_files.map(&:full_file_path), readme_files.map(&:full_file_path))
28
+ end
29
+
30
+ def determine_license
31
+ return 'MIT' if license_files.any?{|f| f.mit_license_body? || f.mit_license_header?}
32
+ return 'Apache 2.0' if license_files.any?(&:apache_license_body?)
33
+ return 'GPLv2' if license_files.any?(&:gplv2_license_body?)
34
+ 'other'
35
+ end
36
+
37
+ def license_files
38
+ paths_with_license_names = Dir.glob(File.join(install_path, '**', LICENSE_FILE_NAMES))
39
+ paths_for_license_files = paths_with_license_names.map do |path|
40
+ File.directory?(path) ? paths_for_files_in_license_directory(path) : path
41
+ end.flatten.uniq
42
+ get_files_for_paths(paths_for_license_files)
43
+ end
44
+
45
+ def readme_files
46
+ Dir.glob(File.join(install_path, '**', README_FILE_NAMES)).map do |path|
47
+ file = LicenseFile.new(install_path, path)
48
+ file.include_license_text = include_license_text?
49
+ file
50
+ end
51
+ end
52
+
53
+ def install_path
54
+ spec.full_gem_path
55
+ end
56
+
57
+ def to_s(include_license_text = true)
58
+ self.include_license_text = include_license_text
59
+
60
+ { name => to_hash }.to_yaml
61
+ end
62
+
63
+ def to_hash
64
+ {
65
+ 'dependency_name' => dependency_name,
66
+ 'dependency_version' => dependency_version,
67
+ 'install_path' => install_path,
68
+ 'license_files' => license_files.map { |file| file.to_hash }
69
+ }
70
+ end
71
+
72
+ def sort_order
73
+ dependency_name.downcase
74
+ end
75
+
76
+ private
77
+
78
+ attr_writer :include_license_text
79
+
80
+ def include_license_text?
81
+ @include_license_text
82
+ end
83
+
84
+ def get_file_for_path(path)
85
+ file = LicenseFile.new(install_path, path)
86
+ file.include_license_text = include_license_text?
87
+ file
88
+ end
89
+
90
+ def paths_for_files_in_license_directory(path)
91
+ entries_in_directory = Dir::entries(path).reject { |p| p.match(/^(\.){1,2}$/) }
92
+ entries_in_directory.map { |entry_name| File.join(path, entry_name) }
93
+ end
94
+
95
+ def get_files_for_paths(paths_for_license_files)
96
+ paths_for_license_files.map do |path|
97
+ get_file_for_path(path)
98
+ end
99
+ end
100
+ end
101
+ end