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.
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