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
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: '0923f356c4362acaa8b166869c724e40b77334c6'
|
4
|
+
data.tar.gz: a21149dd924c42abfb7cdbc06550604bfdd9a54c
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 14c7dfea1fc4e9f44015b2c52034f6030239690a2976ed01410b02980ba3b2f202f5bce36bc8f2d8c549d5af67b7d33e8a6985609c82124d979154c21827cc8b
|
7
|
+
data.tar.gz: beb248f3754149ff4063afcf0e151d112a54369a0cc227c8854ef687c8c9e66e6682be043824e727064a55fb78f0c26578065a8932a8d331fcb36f1110c2f804
|
data/LICENSE.md
CHANGED
data/bin/licensee
CHANGED
@@ -4,47 +4,61 @@ require_relative '../lib/licensee'
|
|
4
4
|
|
5
5
|
path = ARGV[0] || Dir.pwd
|
6
6
|
|
7
|
-
|
8
|
-
|
7
|
+
# Given a string or object, prepares it for output and human consumption
|
8
|
+
def humanize(value, type = nil)
|
9
|
+
case type
|
10
|
+
when :license
|
11
|
+
value.name
|
12
|
+
when :matcher
|
13
|
+
value.class
|
14
|
+
when :confidence
|
15
|
+
Licensee::ContentHelper.format_percent(value)
|
16
|
+
when :method
|
17
|
+
value.to_s.tr('_', ' ').capitalize
|
18
|
+
else
|
19
|
+
value
|
20
|
+
end
|
9
21
|
end
|
10
22
|
|
11
|
-
|
12
|
-
|
13
|
-
|
23
|
+
# Methods to call when displaying information about ProjectFiles
|
24
|
+
MATCHED_FILE_METHODS = %i[
|
25
|
+
content_hash attribution confidence matcher license
|
26
|
+
].freeze
|
14
27
|
|
15
|
-
|
16
|
-
puts "License file: #{license_file.filename}"
|
17
|
-
puts "License hash: #{license_file.hash}"
|
18
|
-
puts "Attribution: #{license_file.attribution}" if license_file.attribution
|
19
|
-
end
|
28
|
+
project = Licensee.project(path, detect_packages: true, detect_readme: true)
|
20
29
|
|
21
|
-
|
30
|
+
if project.license
|
31
|
+
puts "License: #{project.license.name}"
|
32
|
+
elsif project.licenses
|
33
|
+
puts "Licenses: #{project.licenses.map(&:name)}"
|
34
|
+
else
|
22
35
|
puts 'License: Not detected'
|
23
|
-
exit 1
|
24
36
|
end
|
25
37
|
|
26
|
-
|
27
|
-
|
38
|
+
puts "Matched files: #{project.matched_files.map(&:filename)}"
|
39
|
+
|
40
|
+
project.matched_files.each do |matched_file|
|
41
|
+
puts "#{matched_file.filename}:"
|
28
42
|
|
29
|
-
|
30
|
-
|
43
|
+
MATCHED_FILE_METHODS.each do |method|
|
44
|
+
next unless matched_file.respond_to? method
|
45
|
+
value = matched_file.public_send method
|
46
|
+
next if value.nil?
|
47
|
+
puts " #{humanize(method, :method)}: #{humanize(value, method)}"
|
31
48
|
end
|
32
49
|
|
33
|
-
|
34
|
-
|
35
|
-
end
|
50
|
+
next unless matched_file.is_a? Licensee::ProjectFiles::LicenseFile
|
51
|
+
next unless matched_file.confidence != 100
|
36
52
|
|
37
|
-
if matched_file.is_a?(Licensee::Project::LicenseFile)
|
38
53
|
matcher = Licensee::Matchers::Dice.new(matched_file)
|
39
54
|
licenses = matcher.licenses_by_similiarity
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
end
|
55
|
+
next if licenses.empty?
|
56
|
+
puts ' Closest licenses:'
|
57
|
+
licenses[0...3].each do |license, similarity|
|
58
|
+
spdx_id = license.meta['spdx-id']
|
59
|
+
percent = Licensee::ContentHelper.format_percent(similarity)
|
60
|
+
puts " * #{spdx_id} similarity: #{percent}"
|
47
61
|
end
|
48
62
|
end
|
49
63
|
|
50
|
-
exit
|
64
|
+
exit !project.licenses.empty?
|
data/lib/licensee.rb
CHANGED
@@ -1,32 +1,18 @@
|
|
1
1
|
require_relative 'licensee/version'
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
# Projects
|
7
|
-
require_relative 'licensee/project'
|
8
|
-
require_relative 'licensee/projects/git_project'
|
9
|
-
require_relative 'licensee/projects/fs_project'
|
10
|
-
|
11
|
-
# Project files
|
12
|
-
require_relative 'licensee/project_file'
|
13
|
-
require_relative 'licensee/project_files/license_file'
|
14
|
-
require_relative 'licensee/project_files/package_info'
|
15
|
-
require_relative 'licensee/project_files/readme'
|
16
|
-
|
17
|
-
# Matchers
|
18
|
-
require_relative 'licensee/matchers/exact_matcher'
|
19
|
-
require_relative 'licensee/matchers/copyright_matcher'
|
20
|
-
require_relative 'licensee/matchers/dice_matcher'
|
21
|
-
require_relative 'licensee/matchers/package_matcher'
|
22
|
-
require_relative 'licensee/matchers/gemspec_matcher'
|
23
|
-
require_relative 'licensee/matchers/npm_bower_matcher'
|
24
|
-
require_relative 'licensee/matchers/cran_matcher'
|
25
|
-
require_relative 'licensee/matchers/dist_zilla_matcher'
|
2
|
+
require 'forwardable'
|
3
|
+
require 'pathname'
|
4
|
+
require 'rugged'
|
26
5
|
|
27
6
|
module Licensee
|
7
|
+
autoload :ContentHelper, 'licensee/content_helper'
|
8
|
+
autoload :License, 'licensee/license'
|
9
|
+
autoload :Rule, 'licensee/rule'
|
10
|
+
autoload :Matchers, 'licensee/matchers'
|
11
|
+
autoload :Projects, 'licensee/projects'
|
12
|
+
autoload :ProjectFiles, 'licensee/project_files'
|
13
|
+
|
28
14
|
# Over which percent is a match considered a match by default
|
29
|
-
CONFIDENCE_THRESHOLD =
|
15
|
+
CONFIDENCE_THRESHOLD = 98
|
30
16
|
|
31
17
|
# Base domain from which to build license URLs
|
32
18
|
DOMAIN = 'http://choosealicense.com'.freeze
|
@@ -45,9 +31,9 @@ module Licensee
|
|
45
31
|
end
|
46
32
|
|
47
33
|
def project(path, **args)
|
48
|
-
Licensee::GitProject.new(path, args)
|
49
|
-
rescue Licensee::GitProject::InvalidRepository
|
50
|
-
Licensee::FSProject.new(path, args)
|
34
|
+
Licensee::Projects::GitProject.new(path, args)
|
35
|
+
rescue Licensee::Projects::GitProject::InvalidRepository
|
36
|
+
Licensee::Projects::FSProject.new(path, args)
|
51
37
|
end
|
52
38
|
|
53
39
|
def confidence_threshold
|
@@ -5,13 +5,12 @@ module Licensee
|
|
5
5
|
module ContentHelper
|
6
6
|
DIGEST = Digest::SHA1
|
7
7
|
END_OF_TERMS_REGEX = /^\s*end of terms and conditions\s*$/i
|
8
|
-
HR_REGEX =
|
8
|
+
HR_REGEX = /[=\-\*][=\-\*\s]{3,}/
|
9
9
|
ALT_TITLE_REGEX = {
|
10
10
|
'bsd-2-clause' => /bsd 2-clause( \"simplified\")? license/i,
|
11
11
|
'bsd-3-clause' => /bsd 3-clause( \"new\" or \"revised\")? license/i,
|
12
12
|
'bsd-3-clause-clear' => /bsd 3-clause( clear)? license/i
|
13
13
|
}.freeze
|
14
|
-
MAX_SCALED_DELTA = 150
|
15
14
|
|
16
15
|
# A set of each word in the license, without duplicates
|
17
16
|
def wordset
|
@@ -29,7 +28,7 @@ module Licensee
|
|
29
28
|
# Number of characters that could be added/removed to still be
|
30
29
|
# considered a potential match
|
31
30
|
def max_delta
|
32
|
-
|
31
|
+
@max_delta ||= (length * Licensee.inverse_confidence_threshold).to_i
|
33
32
|
end
|
34
33
|
|
35
34
|
# Given another license or project file, calculates the difference in length
|
@@ -46,8 +45,8 @@ module Licensee
|
|
46
45
|
end
|
47
46
|
|
48
47
|
# SHA1 of the normalized content
|
49
|
-
def
|
50
|
-
@
|
48
|
+
def content_hash
|
49
|
+
@content_hash ||= DIGEST.hexdigest content_normalized
|
51
50
|
end
|
52
51
|
|
53
52
|
# Content with the title and version removed
|
@@ -65,7 +64,11 @@ module Licensee
|
|
65
64
|
end
|
66
65
|
|
67
66
|
# Content without title, version, copyright, whitespace, or insturctions
|
68
|
-
|
67
|
+
#
|
68
|
+
# wrap - Optional width to wrap the content
|
69
|
+
#
|
70
|
+
# Returns a string
|
71
|
+
def content_normalized(wrap: nil)
|
69
72
|
return unless content
|
70
73
|
@content_normalized ||= begin
|
71
74
|
string = content_without_title_and_version.downcase
|
@@ -75,6 +78,33 @@ module Licensee
|
|
75
78
|
string, _partition, _instructions = string.partition(END_OF_TERMS_REGEX)
|
76
79
|
strip_whitespace(string)
|
77
80
|
end
|
81
|
+
|
82
|
+
if wrap.nil?
|
83
|
+
@content_normalized
|
84
|
+
else
|
85
|
+
Licensee::ContentHelper.wrap(@content_normalized, wrap)
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
# Wrap text to the given line length
|
90
|
+
def self.wrap(text, line_width = 80)
|
91
|
+
return if text.nil?
|
92
|
+
text = text.clone
|
93
|
+
text.gsub!(/([^\n])\n([^\n])/, '\1 \2')
|
94
|
+
|
95
|
+
text = text.split("\n").collect do |line|
|
96
|
+
if line.length > line_width
|
97
|
+
line.gsub(/(.{1,#{line_width}})(\s+|$)/, "\\1\n").strip
|
98
|
+
else
|
99
|
+
line
|
100
|
+
end
|
101
|
+
end * "\n"
|
102
|
+
|
103
|
+
text.strip
|
104
|
+
end
|
105
|
+
|
106
|
+
def self.format_percent(float)
|
107
|
+
"#{format('%.2f', float)}%"
|
78
108
|
end
|
79
109
|
|
80
110
|
private
|
@@ -104,7 +134,7 @@ module Licensee
|
|
104
134
|
|
105
135
|
# Strip HRs from MPL
|
106
136
|
def strip_hrs(string)
|
107
|
-
string.gsub HR_REGEX, ''
|
137
|
+
string.gsub HR_REGEX, ' '
|
108
138
|
end
|
109
139
|
|
110
140
|
# Strip leading #s from the document
|
@@ -113,11 +143,7 @@ module Licensee
|
|
113
143
|
end
|
114
144
|
|
115
145
|
def strip_whitespace(string)
|
116
|
-
string.
|
117
|
-
end
|
118
|
-
|
119
|
-
def scaled_delta
|
120
|
-
@scaled_delta ||= (length * Licensee.inverse_confidence_threshold).to_i
|
146
|
+
string.gsub(/\s+/, ' ').squeeze(' ').strip
|
121
147
|
end
|
122
148
|
end
|
123
149
|
end
|
data/lib/licensee/license.rb
CHANGED
@@ -4,6 +4,9 @@ require 'yaml'
|
|
4
4
|
module Licensee
|
5
5
|
class InvalidLicense < ArgumentError; end
|
6
6
|
class License
|
7
|
+
@all = {}
|
8
|
+
@keys_licenses = {}
|
9
|
+
|
7
10
|
class << self
|
8
11
|
# All license objects defined via Licensee (via choosealicense.com)
|
9
12
|
#
|
@@ -13,29 +16,31 @@ module Licensee
|
|
13
16
|
#
|
14
17
|
# Returns an Array of License objects.
|
15
18
|
def all(options = {})
|
16
|
-
options
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
19
|
+
@all[options] ||= begin
|
20
|
+
options = { hidden: false, featured: nil }.merge(options)
|
21
|
+
output = licenses.dup
|
22
|
+
output.reject!(&:hidden?) unless options[:hidden]
|
23
|
+
return output if options[:featured].nil?
|
24
|
+
output.select { |l| l.featured? == options[:featured] }
|
25
|
+
end
|
21
26
|
end
|
22
27
|
|
23
28
|
def keys
|
24
29
|
@keys ||= license_files.map do |license_file|
|
25
|
-
File.basename(license_file, '.txt').downcase
|
30
|
+
::File.basename(license_file, '.txt').downcase
|
26
31
|
end + PSEUDO_LICENSES
|
27
32
|
end
|
28
33
|
|
29
34
|
def find(key, options = {})
|
30
35
|
options = { hidden: true }.merge(options)
|
31
|
-
|
36
|
+
keys_licenses(options)[key.downcase]
|
32
37
|
end
|
33
38
|
alias [] find
|
34
39
|
alias find_by_key find
|
35
40
|
|
36
41
|
def license_dir
|
37
|
-
dir = File.dirname(__FILE__)
|
38
|
-
File.expand_path '../../vendor/choosealicense.com/_licenses', dir
|
42
|
+
dir = ::File.dirname(__FILE__)
|
43
|
+
::File.expand_path '../../vendor/choosealicense.com/_licenses', dir
|
39
44
|
end
|
40
45
|
|
41
46
|
def license_files
|
@@ -47,6 +52,10 @@ module Licensee
|
|
47
52
|
def licenses
|
48
53
|
@licenses ||= keys.map { |key| new(key) }
|
49
54
|
end
|
55
|
+
|
56
|
+
def keys_licenses(options = {})
|
57
|
+
@keys_licenses[options] ||= all(options).map { |l| [l.key, l] }.to_h
|
58
|
+
end
|
50
59
|
end
|
51
60
|
|
52
61
|
attr_reader :key
|
@@ -111,6 +120,10 @@ module Licensee
|
|
111
120
|
key == 'gpl-2.0' || key == 'gpl-3.0'
|
112
121
|
end
|
113
122
|
|
123
|
+
def lgpl?
|
124
|
+
key == 'lgpl-2.1' || key == 'lgpl-3.0'
|
125
|
+
end
|
126
|
+
|
114
127
|
# Is this license a Creative Commons license?
|
115
128
|
def creative_commons?
|
116
129
|
key.start_with?('cc-')
|
@@ -0,0 +1,14 @@
|
|
1
|
+
module Licensee
|
2
|
+
module Matchers
|
3
|
+
autoload :Matcher, 'licensee/matchers/matcher'
|
4
|
+
autoload :Cabal, 'licensee/matchers/cabal'
|
5
|
+
autoload :Copyright, 'licensee/matchers/copyright'
|
6
|
+
autoload :Cran, 'licensee/matchers/cran'
|
7
|
+
autoload :Dice, 'licensee/matchers/dice'
|
8
|
+
autoload :DistZilla, 'licensee/matchers/dist_zilla'
|
9
|
+
autoload :Exact, 'licensee/matchers/exact'
|
10
|
+
autoload :Gemspec, 'licensee/matchers/gemspec'
|
11
|
+
autoload :NpmBower, 'licensee/matchers/npm_bower'
|
12
|
+
autoload :Package, 'licensee/matchers/package'
|
13
|
+
end
|
14
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
module Licensee
|
2
|
+
module Matchers
|
3
|
+
class Cabal < Licensee::Matchers::Package
|
4
|
+
# While we could parse the cabal file, prefer
|
5
|
+
# a lenient regex for speed and security. Moar parsing moar problems.
|
6
|
+
LICENSE_REGEX = /^\s*license\s*\:\s*([a-z\-0-9\.]+)\s*$/ix
|
7
|
+
|
8
|
+
private
|
9
|
+
|
10
|
+
def license_property
|
11
|
+
match = @file.content.match LICENSE_REGEX
|
12
|
+
match[1].downcase if match && match[1]
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
@@ -2,7 +2,7 @@
|
|
2
2
|
|
3
3
|
module Licensee
|
4
4
|
module Matchers
|
5
|
-
class Copyright
|
5
|
+
class Copyright < Licensee::Matchers::Matcher
|
6
6
|
attr_reader :file
|
7
7
|
|
8
8
|
# rubocop:disable Metrics/LineLength
|
@@ -10,10 +10,6 @@ module Licensee
|
|
10
10
|
REGEX = /\A\s*#{COPYRIGHT_SYMBOLS}.*$/i
|
11
11
|
# rubocop:enable Metrics/LineLength
|
12
12
|
|
13
|
-
def initialize(file)
|
14
|
-
@file = file
|
15
|
-
end
|
16
|
-
|
17
13
|
def match
|
18
14
|
# Note: must use content, and not content_normalized here
|
19
15
|
if @file.content.strip =~ /\A#{REGEX}\z/i
|
@@ -1,12 +1,6 @@
|
|
1
1
|
module Licensee
|
2
2
|
module Matchers
|
3
|
-
class Dice
|
4
|
-
attr_reader :file
|
5
|
-
|
6
|
-
def initialize(file)
|
7
|
-
@file = file
|
8
|
-
end
|
9
|
-
|
3
|
+
class Dice < Licensee::Matchers::Matcher
|
10
4
|
# Return the first potential license that is more similar
|
11
5
|
# than the confidence threshold
|
12
6
|
def match
|
@@ -8,7 +8,8 @@ module Licensee
|
|
8
8
|
end
|
9
9
|
|
10
10
|
def match
|
11
|
-
|
11
|
+
return @match if defined? @match
|
12
|
+
@match = Licensee.licenses(hidden: true).find do |license|
|
12
13
|
license.length == @file.length && license.wordset == @file.wordset
|
13
14
|
end
|
14
15
|
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
module Licensee
|
2
|
+
module Matchers
|
3
|
+
class Gemspec < Licensee::Matchers::Package
|
4
|
+
DECLARATION_REGEX = /
|
5
|
+
^\s*[a-z0-9_]+\.([a-z0-9_]+)\s*\=\s*[\'\"]([a-z\-0-9\.]+)[\'\"]\s*$
|
6
|
+
/ix
|
7
|
+
|
8
|
+
LICENSE_REGEX = /
|
9
|
+
^\s*[a-z0-9_]+\.license\s*\=\s*[\'\"]([a-z\-0-9\.]+)[\'\"]\s*$
|
10
|
+
/ix
|
11
|
+
|
12
|
+
private
|
13
|
+
|
14
|
+
def license_property
|
15
|
+
match = @file.content.match LICENSE_REGEX
|
16
|
+
match[1].downcase if match && match[1]
|
17
|
+
end
|
18
|
+
|
19
|
+
def declarations
|
20
|
+
@declarations ||= @file.content.match DECLARATION_REGEX
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|