licensee 8.9.2 → 9.0.0.beta.1
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.
- 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
|