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.
Files changed (60) hide show
  1. checksums.yaml +4 -4
  2. data/LICENSE.md +1 -1
  3. data/bin/licensee +42 -28
  4. data/lib/licensee.rb +14 -28
  5. data/lib/licensee/content_helper.rb +38 -12
  6. data/lib/licensee/license.rb +22 -9
  7. data/lib/licensee/matchers.rb +14 -0
  8. data/lib/licensee/matchers/cabal.rb +16 -0
  9. data/lib/licensee/matchers/{copyright_matcher.rb → copyright.rb} +1 -5
  10. data/lib/licensee/matchers/{cran_matcher.rb → cran.rb} +1 -1
  11. data/lib/licensee/matchers/{dice_matcher.rb → dice.rb} +1 -7
  12. data/lib/licensee/matchers/{dist_zilla_matcher.rb → dist_zilla.rb} +1 -1
  13. data/lib/licensee/matchers/{exact_matcher.rb → exact.rb} +2 -1
  14. data/lib/licensee/matchers/gemspec.rb +24 -0
  15. data/lib/licensee/matchers/{package_matcher.rb → matcher.rb} +5 -3
  16. data/lib/licensee/matchers/{npm_bower_matcher.rb → npm_bower.rb} +2 -2
  17. data/lib/licensee/matchers/package.rb +22 -0
  18. data/lib/licensee/project_files.rb +8 -0
  19. data/lib/licensee/project_files/license_file.rb +22 -4
  20. data/lib/licensee/project_files/package_manager_file.rb +37 -0
  21. data/lib/licensee/project_files/project_file.rb +65 -0
  22. data/lib/licensee/project_files/{readme.rb → readme_file.rb} +2 -2
  23. data/lib/licensee/projects.rb +7 -0
  24. data/lib/licensee/projects/fs_project.rb +64 -27
  25. data/lib/licensee/projects/git_project.rb +49 -43
  26. data/lib/licensee/projects/project.rb +149 -0
  27. data/lib/licensee/version.rb +1 -1
  28. data/spec/bin_spec.rb +9 -2
  29. data/spec/fixtures/crlf-license/LICENSE +674 -0
  30. data/spec/fixtures/fcpl-modified-mpl/LICENSE +193 -193
  31. data/spec/fixtures/ipsum.txt +19 -0
  32. data/spec/fixtures/license-in-parent-folder/LICENSE.txt +21 -0
  33. data/spec/fixtures/license-in-parent-folder/license-folder/LICENSE.txt +21 -0
  34. data/spec/fixtures/multiple-license-files/LICENSE +362 -0
  35. data/spec/fixtures/multiple-license-files/LICENSE.txt +21 -0
  36. data/spec/integration_spec.rb +57 -22
  37. data/spec/licensee/content_helper_spec.rb +33 -9
  38. data/spec/licensee/license_spec.rb +8 -1
  39. data/spec/licensee/matchers/cabal_matcher_spec.rb +45 -0
  40. data/spec/licensee/matchers/copyright_matcher_spec.rb +1 -1
  41. data/spec/licensee/matchers/cran_matcher_spec.rb +11 -2
  42. data/spec/licensee/matchers/dice_matcher_spec.rb +1 -2
  43. data/spec/licensee/matchers/dist_zilla_matcher_spec.rb +11 -2
  44. data/spec/licensee/matchers/exact_matcher_spec.rb +1 -1
  45. data/spec/licensee/matchers/gemspec_matcher_spec.rb +19 -1
  46. data/spec/licensee/matchers/npm_bower_matcher_spec.rb +23 -6
  47. data/spec/licensee/matchers/package_matcher_spec.rb +31 -2
  48. data/spec/licensee/project_files/license_file_spec.rb +62 -6
  49. data/spec/licensee/project_files/package_info_spec.rb +10 -1
  50. data/spec/{project_file_spec.rb → licensee/project_files/project_file_spec.rb} +14 -1
  51. data/spec/licensee/project_files/readme_spec.rb +1 -1
  52. data/spec/licensee/project_spec.rb +134 -9
  53. data/spec/licensee_spec.rb +4 -3
  54. data/spec/spec_helper.rb +12 -23
  55. data/spec/vendored_license_spec.rb +16 -13
  56. metadata +29 -17
  57. data/lib/licensee/matchers/gemspec_matcher.rb +0 -19
  58. data/lib/licensee/project.rb +0 -87
  59. data/lib/licensee/project_file.rb +0 -39
  60. 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: 7bd286955df8a0e4dff2cbeb964fcdfc28595250
4
- data.tar.gz: fd1cac842289dd0399faf4fca39344955999d07b
3
+ metadata.gz: '0923f356c4362acaa8b166869c724e40b77334c6'
4
+ data.tar.gz: a21149dd924c42abfb7cdbc06550604bfdd9a54c
5
5
  SHA512:
6
- metadata.gz: e5e506fa77c9718cad668dd3d4f5aaac281073ebe8822a33d7e30db258c1a2ad0ce4052355cdb48063729b0cfabd55097b9052ac7228d189a673b2812e184bba
7
- data.tar.gz: 192310645fd0a2fe87ee0c530d795e568490e19bd7ac973a9f3d1d1553413e8e3980c14dd12e38725456e18a04f2f45600a4dd9e2b358bc4910e4a8243d3a32e
6
+ metadata.gz: 14c7dfea1fc4e9f44015b2c52034f6030239690a2976ed01410b02980ba3b2f202f5bce36bc8f2d8c549d5af67b7d33e8a6985609c82124d979154c21827cc8b
7
+ data.tar.gz: beb248f3754149ff4063afcf0e151d112a54369a0cc227c8854ef687c8c9e66e6682be043824e727064a55fb78f0c26578065a8932a8d331fcb36f1110c2f804
data/LICENSE.md CHANGED
@@ -1,6 +1,6 @@
1
1
  MIT License
2
2
 
3
- Copyright (c) 2014-2016 Ben Balter
3
+ Copyright (c) 2014-2017 Ben Balter
4
4
 
5
5
  Permission is hereby granted, free of charge, to any person obtaining a copy
6
6
  of this software and associated documentation files (the "Software"), to deal
data/bin/licensee CHANGED
@@ -4,47 +4,61 @@ require_relative '../lib/licensee'
4
4
 
5
5
  path = ARGV[0] || Dir.pwd
6
6
 
7
- def format_percent(float)
8
- "#{format('%.2f', float)}%"
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
- project = Licensee.project(path, detect_packages: true, detect_readme: true)
12
- license_file = project.license_file
13
- matched_file = project.matched_file
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
- if license_file
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
- unless matched_file
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
- if matched_file.license
27
- puts "License: #{matched_file.license.meta['title']}"
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
- if matched_file.confidence
30
- puts "Confidence: #{format_percent(matched_file.confidence)}"
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
- puts "Method: #{matched_file.matcher.class}" if matched_file.matcher
34
- exit 0
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
- unless licenses.empty?
41
- puts
42
- puts "Here's the closest licenses:"
43
- licenses[0...3].each do |license, similarity|
44
- spdx_id = license.meta['spdx-id']
45
- puts "* #{spdx_id} similarity: #{format_percent(similarity)}"
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 1
64
+ exit !project.licenses.empty?
data/lib/licensee.rb CHANGED
@@ -1,32 +1,18 @@
1
1
  require_relative 'licensee/version'
2
- require_relative 'licensee/content_helper'
3
- require_relative 'licensee/license'
4
- require_relative 'licensee/rule'
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 = 95
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 = /^\s*[=-]{4,}/
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
- scaled_delta < MAX_SCALED_DELTA ? scaled_delta : MAX_SCALED_DELTA
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 hash
50
- @hash ||= DIGEST.hexdigest content_normalized
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
- def content_normalized
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.tr("\n", ' ').squeeze(' ').strip
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
@@ -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 = { hidden: false, featured: nil }.merge(options)
17
- output = licenses.dup
18
- output.reject!(&:hidden?) unless options[:hidden]
19
- return output if options[:featured].nil?
20
- output.select { |l| l.featured? == options[:featured] }
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
- all(options).find { |license| key.casecmp(license.key).zero? }
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,6 +1,6 @@
1
1
  module Licensee
2
2
  module Matchers
3
- class Cran < Package
3
+ class Cran < Licensee::Matchers::Package
4
4
  attr_reader :file
5
5
 
6
6
  # While we could parse the DESCRIPTION file, prefer
@@ -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
@@ -1,6 +1,6 @@
1
1
  module Licensee
2
2
  module Matchers
3
- class DistZilla < Package
3
+ class DistZilla < Licensee::Matchers::Package
4
4
  attr_reader :file
5
5
 
6
6
  LICENSE_REGEX = /^license\s*=\s*([a-z\-0-9\._]+)/i
@@ -8,7 +8,8 @@ module Licensee
8
8
  end
9
9
 
10
10
  def match
11
- Licensee.licenses(hidden: true).find do |license|
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