licensee 8.9.2 → 9.0.0.beta.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/LICENSE.md +1 -1
- data/bin/licensee +42 -28
- data/lib/licensee.rb +14 -28
- data/lib/licensee/content_helper.rb +38 -12
- data/lib/licensee/license.rb +22 -9
- data/lib/licensee/matchers.rb +14 -0
- data/lib/licensee/matchers/cabal.rb +16 -0
- data/lib/licensee/matchers/{copyright_matcher.rb → copyright.rb} +1 -5
- data/lib/licensee/matchers/{cran_matcher.rb → cran.rb} +1 -1
- data/lib/licensee/matchers/{dice_matcher.rb → dice.rb} +1 -7
- data/lib/licensee/matchers/{dist_zilla_matcher.rb → dist_zilla.rb} +1 -1
- data/lib/licensee/matchers/{exact_matcher.rb → exact.rb} +2 -1
- data/lib/licensee/matchers/gemspec.rb +24 -0
- data/lib/licensee/matchers/{package_matcher.rb → matcher.rb} +5 -3
- data/lib/licensee/matchers/{npm_bower_matcher.rb → npm_bower.rb} +2 -2
- data/lib/licensee/matchers/package.rb +22 -0
- data/lib/licensee/project_files.rb +8 -0
- data/lib/licensee/project_files/license_file.rb +22 -4
- data/lib/licensee/project_files/package_manager_file.rb +37 -0
- data/lib/licensee/project_files/project_file.rb +65 -0
- data/lib/licensee/project_files/{readme.rb → readme_file.rb} +2 -2
- data/lib/licensee/projects.rb +7 -0
- data/lib/licensee/projects/fs_project.rb +64 -27
- data/lib/licensee/projects/git_project.rb +49 -43
- data/lib/licensee/projects/project.rb +149 -0
- data/lib/licensee/version.rb +1 -1
- data/spec/bin_spec.rb +9 -2
- data/spec/fixtures/crlf-license/LICENSE +674 -0
- data/spec/fixtures/fcpl-modified-mpl/LICENSE +193 -193
- data/spec/fixtures/ipsum.txt +19 -0
- data/spec/fixtures/license-in-parent-folder/LICENSE.txt +21 -0
- data/spec/fixtures/license-in-parent-folder/license-folder/LICENSE.txt +21 -0
- data/spec/fixtures/multiple-license-files/LICENSE +362 -0
- data/spec/fixtures/multiple-license-files/LICENSE.txt +21 -0
- data/spec/integration_spec.rb +57 -22
- data/spec/licensee/content_helper_spec.rb +33 -9
- data/spec/licensee/license_spec.rb +8 -1
- data/spec/licensee/matchers/cabal_matcher_spec.rb +45 -0
- data/spec/licensee/matchers/copyright_matcher_spec.rb +1 -1
- data/spec/licensee/matchers/cran_matcher_spec.rb +11 -2
- data/spec/licensee/matchers/dice_matcher_spec.rb +1 -2
- data/spec/licensee/matchers/dist_zilla_matcher_spec.rb +11 -2
- data/spec/licensee/matchers/exact_matcher_spec.rb +1 -1
- data/spec/licensee/matchers/gemspec_matcher_spec.rb +19 -1
- data/spec/licensee/matchers/npm_bower_matcher_spec.rb +23 -6
- data/spec/licensee/matchers/package_matcher_spec.rb +31 -2
- data/spec/licensee/project_files/license_file_spec.rb +62 -6
- data/spec/licensee/project_files/package_info_spec.rb +10 -1
- data/spec/{project_file_spec.rb → licensee/project_files/project_file_spec.rb} +14 -1
- data/spec/licensee/project_files/readme_spec.rb +1 -1
- data/spec/licensee/project_spec.rb +134 -9
- data/spec/licensee_spec.rb +4 -3
- data/spec/spec_helper.rb +12 -23
- data/spec/vendored_license_spec.rb +16 -13
- metadata +29 -17
- data/lib/licensee/matchers/gemspec_matcher.rb +0 -19
- data/lib/licensee/project.rb +0 -87
- data/lib/licensee/project_file.rb +0 -39
- data/lib/licensee/project_files/package_info.rb +0 -31
@@ -1,16 +1,18 @@
|
|
1
1
|
module Licensee
|
2
2
|
module Matchers
|
3
|
-
class
|
3
|
+
class Matcher
|
4
|
+
attr_reader :file
|
5
|
+
|
4
6
|
def initialize(file)
|
5
7
|
@file = file
|
6
8
|
end
|
7
9
|
|
8
10
|
def match
|
9
|
-
|
11
|
+
raise 'Not implemented'
|
10
12
|
end
|
11
13
|
|
12
14
|
def confidence
|
13
|
-
|
15
|
+
raise 'Not implemented'
|
14
16
|
end
|
15
17
|
end
|
16
18
|
end
|
@@ -1,10 +1,10 @@
|
|
1
1
|
module Licensee
|
2
2
|
module Matchers
|
3
|
-
class NpmBower < Package
|
3
|
+
class NpmBower < Licensee::Matchers::Package
|
4
4
|
# While we could parse the package.json or bower.json file, prefer
|
5
5
|
# a lenient regex for speed and security. Moar parsing moar problems.
|
6
6
|
LICENSE_REGEX = /
|
7
|
-
s*[\"\']license[\"\']\s*\:\s*[\'\"]([a-z\-0-9\.]+)[\'\"],?\s*
|
7
|
+
\s*[\"\']license[\"\']\s*\:\s*[\'\"]([a-z\-0-9\.]+)[\'\"],?\s*
|
8
8
|
/ix
|
9
9
|
|
10
10
|
private
|
@@ -0,0 +1,22 @@
|
|
1
|
+
module Licensee
|
2
|
+
module Matchers
|
3
|
+
class Package < Licensee::Matchers::Matcher
|
4
|
+
def match
|
5
|
+
return @match if defined? @match
|
6
|
+
return if license_property.nil? || license_property.to_s.empty?
|
7
|
+
@match = Licensee.licenses(hidden: true).find do |license|
|
8
|
+
license.key == license_property
|
9
|
+
end
|
10
|
+
@match ||= License.find('other')
|
11
|
+
end
|
12
|
+
|
13
|
+
def confidence
|
14
|
+
90
|
15
|
+
end
|
16
|
+
|
17
|
+
def license_property
|
18
|
+
raise 'Not implemented'
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,8 @@
|
|
1
|
+
module Licensee
|
2
|
+
module ProjectFiles
|
3
|
+
autoload :ProjectFile, 'licensee/project_files/project_file'
|
4
|
+
autoload :LicenseFile, 'licensee/project_files/license_file'
|
5
|
+
autoload :PackageManagerFile, 'licensee/project_files/package_manager_file'
|
6
|
+
autoload :ReadmeFile, 'licensee/project_files/readme_file'
|
7
|
+
end
|
8
|
+
end
|
@@ -1,6 +1,6 @@
|
|
1
1
|
module Licensee
|
2
|
-
|
3
|
-
class LicenseFile < Licensee::
|
2
|
+
module ProjectFiles
|
3
|
+
class LicenseFile < Licensee::ProjectFiles::ProjectFile
|
4
4
|
include Licensee::ContentHelper
|
5
5
|
|
6
6
|
# List of extensions to give preference to
|
@@ -27,8 +27,10 @@ module Licensee
|
|
27
27
|
/\A#{COPYING_REGEX}#{PREFERRED_EXT_REGEX}\z/ => 0.7, # COPYING.md
|
28
28
|
/\A#{LICENSE_REGEX}#{ANY_EXT_REGEX}\z/ => 0.6, # LICENSE.textile
|
29
29
|
/\A#{COPYING_REGEX}#{ANY_EXT_REGEX}\z/ => 0.5, # COPYING.textile
|
30
|
-
/#{LICENSE_REGEX}
|
31
|
-
/#{COPYING_REGEX}
|
30
|
+
/#{LICENSE_REGEX}-/ => 0.4, # LICENSE-MIT
|
31
|
+
/#{COPYING_REGEX}-/ => 0.35, # COPYING-MIT
|
32
|
+
/-#{LICENSE_REGEX}/ => 0.3, # MIT-LICENSE-MIT
|
33
|
+
/-#{COPYING_REGEX}/ => 0.25, # MIT-COPYING
|
32
34
|
/\A#{OFL_REGEX}#{PREFERRED_EXT_REGEX}/ => 0.2, # OFL.md
|
33
35
|
/\A#{OFL_REGEX}#{ANY_EXT_REGEX}/ => 0.1, # OFL.textile
|
34
36
|
/\A#{OFL_REGEX}\z/ => 0.05, # OFL
|
@@ -58,6 +60,22 @@ module Licensee
|
|
58
60
|
content.strip =~ CC_FALSE_POSITIVE_REGEX
|
59
61
|
end
|
60
62
|
|
63
|
+
def lgpl?
|
64
|
+
LicenseFile.lesser_gpl_score(filename) == 1 && license && license.lgpl?
|
65
|
+
end
|
66
|
+
|
67
|
+
def gpl?
|
68
|
+
license && license.gpl?
|
69
|
+
end
|
70
|
+
|
71
|
+
def license
|
72
|
+
if matcher && matcher.match
|
73
|
+
matcher.match
|
74
|
+
else
|
75
|
+
License.find('other')
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
61
79
|
def self.name_score(filename)
|
62
80
|
FILENAME_REGEXES.find { |regex, _| filename =~ regex }[1]
|
63
81
|
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
module Licensee
|
2
|
+
module ProjectFiles
|
3
|
+
class PackageManagerFile < Licensee::ProjectFiles::ProjectFile
|
4
|
+
# Hash of Extension => [possible matchers]
|
5
|
+
MATCHERS_EXTENSIONS = {
|
6
|
+
'.gemspec' => [Matchers::Gemspec],
|
7
|
+
'.json' => [Matchers::NpmBower],
|
8
|
+
'.cabal' => [Matchers::Cabal]
|
9
|
+
}.freeze
|
10
|
+
|
11
|
+
# Hash of Filename => [possible matchers]
|
12
|
+
FILENAMES_EXTENSIONS = {
|
13
|
+
'DESCRIPTION' => [Matchers::Cran],
|
14
|
+
'dist.ini' => [Matchers::DistZilla]
|
15
|
+
}.freeze
|
16
|
+
|
17
|
+
def possible_matchers
|
18
|
+
MATCHERS_EXTENSIONS[extension] || FILENAMES_EXTENSIONS[filename] || []
|
19
|
+
end
|
20
|
+
|
21
|
+
def self.name_score(filename)
|
22
|
+
return 1.0 if ['.gemspec', '.cabal'].include?(File.extname(filename))
|
23
|
+
return 1.0 if filename == 'package.json'
|
24
|
+
return 0.8 if filename == 'dist.ini'
|
25
|
+
return 0.9 if filename == 'DESCRIPTION'
|
26
|
+
return 0.75 if filename == 'bower.json'
|
27
|
+
0.0
|
28
|
+
end
|
29
|
+
|
30
|
+
private
|
31
|
+
|
32
|
+
def extension
|
33
|
+
@extension ||= File.extname(filename)
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
@@ -0,0 +1,65 @@
|
|
1
|
+
# A project file is a file within a project that contains license information
|
2
|
+
# Currently extended by LicenseFile, PackageManagerFile, and ReadmeFile
|
3
|
+
#
|
4
|
+
# Sublcasses should implement the possible_matchers method
|
5
|
+
module Licensee
|
6
|
+
module ProjectFiles
|
7
|
+
class ProjectFile
|
8
|
+
extend Forwardable
|
9
|
+
def_delegator :@data, :[]
|
10
|
+
|
11
|
+
attr_reader :content
|
12
|
+
|
13
|
+
ENCODING = Encoding::UTF_8
|
14
|
+
ENCODING_OPTIONS = {
|
15
|
+
invalid: :replace,
|
16
|
+
undef: :replace,
|
17
|
+
replace: ''
|
18
|
+
}.freeze
|
19
|
+
|
20
|
+
# Create a new Licensee::ProjectFile with content and metadata
|
21
|
+
#
|
22
|
+
# content - file content
|
23
|
+
# metadata - can be either the string filename, or a hash containing
|
24
|
+
# metadata about the file content. If a hash is given, the
|
25
|
+
# filename should be given using the :name key. See individual
|
26
|
+
# project types for additional available metadata
|
27
|
+
#
|
28
|
+
# Returns a new Licensee::ProjectFile
|
29
|
+
def initialize(content, metadata = {})
|
30
|
+
@content = content
|
31
|
+
@content.force_encoding(ENCODING)
|
32
|
+
unless @content.valid_encoding?
|
33
|
+
@content.encode!(ENCODING, ENCODING_OPTIONS)
|
34
|
+
end
|
35
|
+
|
36
|
+
metadata = { name: metadata } if metadata.is_a? String
|
37
|
+
@data = metadata || {}
|
38
|
+
end
|
39
|
+
|
40
|
+
def filename
|
41
|
+
@data[:name]
|
42
|
+
end
|
43
|
+
|
44
|
+
def possible_matchers
|
45
|
+
raise 'Not implemented'
|
46
|
+
end
|
47
|
+
|
48
|
+
def matcher
|
49
|
+
@matcher ||= possible_matchers.map { |m| m.new(self) }.find(&:match)
|
50
|
+
end
|
51
|
+
|
52
|
+
# Returns the percent confident with the match
|
53
|
+
def confidence
|
54
|
+
matcher && matcher.confidence
|
55
|
+
end
|
56
|
+
|
57
|
+
def license
|
58
|
+
matcher && matcher.match
|
59
|
+
end
|
60
|
+
|
61
|
+
alias match license
|
62
|
+
alias path filename
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
@@ -1,41 +1,78 @@
|
|
1
1
|
# Filesystem-based project
|
2
2
|
#
|
3
3
|
# Analyze a folder on the filesystem for license information
|
4
|
+
#
|
5
|
+
# Project files for this project type will contain the following keys:
|
6
|
+
# :name - the relative file name
|
7
|
+
# :dir - the directory path containing the file
|
4
8
|
module Licensee
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
9
|
+
module Projects
|
10
|
+
class FSProject < Licensee::Projects::Project
|
11
|
+
def initialize(path, **args)
|
12
|
+
if ::File.file?(path)
|
13
|
+
@pattern = ::File.basename(path)
|
14
|
+
@dir = ::File.dirname(path)
|
15
|
+
else
|
16
|
+
@pattern = '*'
|
17
|
+
@dir = path
|
18
|
+
end
|
19
|
+
|
20
|
+
@root = args.delete(:search_root) || @dir
|
21
|
+
unless valid_search_root?
|
22
|
+
raise 'Search root must be the project path directory or its ancestor'
|
23
|
+
end
|
24
|
+
|
25
|
+
super(**args)
|
13
26
|
end
|
14
|
-
super(**args)
|
15
|
-
end
|
16
27
|
|
17
|
-
|
28
|
+
private
|
29
|
+
|
30
|
+
# Returns an array of hashes representing the project's files.
|
31
|
+
# Hashes will have the the following keys:
|
32
|
+
# :name - the relative file name
|
33
|
+
# :dir - the directory path containing the file
|
34
|
+
def files
|
35
|
+
@files ||= search_directories.flat_map do |dir|
|
36
|
+
Dir.glob(::File.join(dir, @pattern).tr('\\', '/')).map do |file|
|
37
|
+
next unless ::File.file?(file)
|
38
|
+
{ name: ::File.basename(file), dir: dir }
|
39
|
+
end.compact
|
40
|
+
end
|
41
|
+
end
|
18
42
|
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
43
|
+
# Retrieve a file's content from disk
|
44
|
+
#
|
45
|
+
# file - the file hash, with the :name key as the file's relative path
|
46
|
+
#
|
47
|
+
# Returns the file contents as a string
|
48
|
+
def load_file(file)
|
49
|
+
::File.read(::File.join(file[:dir], file[:name]))
|
50
|
+
end
|
23
51
|
|
24
|
-
|
25
|
-
|
26
|
-
|
52
|
+
# Returns true if @dir is @root or it's descendant
|
53
|
+
def valid_search_root?
|
54
|
+
dir = Pathname.new(@dir)
|
55
|
+
dir.fnmatch?(@root) || dir.fnmatch?(::File.join(@root, '**'))
|
27
56
|
end
|
28
57
|
|
29
|
-
files
|
30
|
-
|
58
|
+
# Returns the set of unique paths to search for project files
|
59
|
+
# in order from @dir -> @root
|
60
|
+
def search_directories
|
61
|
+
search_enumerator.map(&:to_path)
|
62
|
+
.push(@root) # ensure root is included in the search
|
63
|
+
.uniq # don't include the root twice if @dir == @root
|
64
|
+
end
|
31
65
|
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
66
|
+
# Enumerates all directories to search, from @dir to @root
|
67
|
+
def search_enumerator
|
68
|
+
root = Pathname.new(@root)
|
69
|
+
dir = Pathname.new(@dir)
|
70
|
+
Enumerator.new do |yielder|
|
71
|
+
dir.relative_path_from(root).ascend do |relative|
|
72
|
+
yielder.yield root.join(relative)
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
39
76
|
end
|
40
77
|
end
|
41
78
|
end
|
@@ -1,60 +1,66 @@
|
|
1
1
|
# Git-based project
|
2
2
|
#
|
3
3
|
# Analyze a given (bare) Git repository for license information
|
4
|
+
#
|
5
|
+
# Project files for this project type will contain the following keys:
|
6
|
+
# :name - the file's path relative to the repo root
|
7
|
+
# :oid - the file's OID
|
4
8
|
module Licensee
|
5
|
-
|
6
|
-
|
9
|
+
module Projects
|
10
|
+
class GitProject < Licensee::Projects::Project
|
11
|
+
attr_reader :repository, :revision
|
7
12
|
|
8
|
-
|
13
|
+
class InvalidRepository < ArgumentError; end
|
9
14
|
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
15
|
+
def initialize(repo, revision: nil, **args)
|
16
|
+
@repository = if repo.is_a? Rugged::Repository
|
17
|
+
repo
|
18
|
+
else
|
19
|
+
Rugged::Repository.new(repo)
|
20
|
+
end
|
16
21
|
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
+
@revision = revision
|
23
|
+
super(**args)
|
24
|
+
rescue Rugged::OSError, Rugged::RepositoryError
|
25
|
+
raise InvalidRepository
|
26
|
+
end
|
22
27
|
|
23
|
-
|
24
|
-
|
25
|
-
|
28
|
+
def close
|
29
|
+
@repository.close
|
30
|
+
end
|
26
31
|
|
27
|
-
|
32
|
+
private
|
28
33
|
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
+
def commit
|
35
|
+
@commit ||= if revision
|
36
|
+
repository.lookup(revision)
|
37
|
+
else
|
38
|
+
repository.last_commit
|
39
|
+
end
|
34
40
|
end
|
35
|
-
end
|
36
41
|
|
37
|
-
|
42
|
+
MAX_LICENSE_SIZE = 64 * 1024
|
38
43
|
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
44
|
+
# Retrieve a file's content from the Git database
|
45
|
+
#
|
46
|
+
# file - the file hash, including the file's OID
|
47
|
+
#
|
48
|
+
# Returns a string representing the file's contents
|
49
|
+
def load_file(file)
|
50
|
+
data, = Rugged::Blob.to_buffer(repository, file[:oid], MAX_LICENSE_SIZE)
|
51
|
+
data
|
52
|
+
end
|
48
53
|
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
54
|
+
# Returns an array of hashes representing the project's files.
|
55
|
+
# Hashes will have the the following keys:
|
56
|
+
# :name - the file's path relative to the repo root
|
57
|
+
# :oid - the file's OID
|
58
|
+
def files
|
59
|
+
@files ||= commit.tree.map do |entry|
|
60
|
+
next unless entry[:type] == :blob
|
61
|
+
{ name: entry[:name], oid: entry[:oid] }
|
62
|
+
end.compact
|
63
|
+
end
|
58
64
|
end
|
59
65
|
end
|
60
66
|
end
|