mirror of https://github.com/licensee/licensee.git
Merge pull request #209 from jonabc/ancestor-search
enable file system license searching in parent directories
This commit is contained in:
commit
63b12fd035
|
@ -51,7 +51,7 @@ module Licensee
|
|||
return [] if files.empty? || files.nil?
|
||||
files = find_files { |n| LicenseFile.name_score(n) }
|
||||
files = files.map do |file|
|
||||
LicenseFile.new(load_file(file), file[:name])
|
||||
LicenseFile.new(load_file(file), file)
|
||||
end
|
||||
|
||||
prioritize_lgpl(files)
|
||||
|
@ -90,7 +90,7 @@ module Licensee
|
|||
# sorted by file score descending
|
||||
def find_files
|
||||
return [] if files.empty? || files.nil?
|
||||
found = files.each { |file| file[:score] = yield(file[:name]) }
|
||||
found = files.map { |file| file.merge(score: yield(file[:name])) }
|
||||
found.select! { |file| file[:score] > 0 }
|
||||
found.sort { |a, b| b[:score] <=> a[:score] }
|
||||
end
|
||||
|
|
|
@ -1,7 +1,11 @@
|
|||
require 'forwardable'
|
||||
|
||||
module Licensee
|
||||
class Project
|
||||
class File
|
||||
attr_reader :content, :filename
|
||||
extend Forwardable
|
||||
|
||||
attr_reader :content
|
||||
|
||||
ENCODING = Encoding::UTF_8
|
||||
ENCODING_OPTIONS = {
|
||||
|
@ -10,13 +14,28 @@ module Licensee
|
|||
replace: ''
|
||||
}.freeze
|
||||
|
||||
def initialize(content, filename = nil)
|
||||
# Create a new Licensee::Project::File with content and metadata
|
||||
#
|
||||
# content - file content
|
||||
# metadata - can be either the string filename, or a hash containing
|
||||
# metadata about the file content. If a hash is given, the
|
||||
# filename should be given using the :name key. See individual
|
||||
# project types for additional available metadata
|
||||
#
|
||||
# Returns a new Licensee::Project::File
|
||||
def initialize(content, metadata = {})
|
||||
@content = content
|
||||
@content.force_encoding(ENCODING)
|
||||
unless @content.valid_encoding?
|
||||
@content.encode!(ENCODING, ENCODING_OPTIONS)
|
||||
end
|
||||
@filename = filename
|
||||
|
||||
metadata = { name: metadata } if metadata.is_a? String
|
||||
@data = metadata || {}
|
||||
end
|
||||
|
||||
def filename
|
||||
@data[:name]
|
||||
end
|
||||
|
||||
def matcher
|
||||
|
@ -34,6 +53,7 @@ module Licensee
|
|||
|
||||
alias match license
|
||||
alias path filename
|
||||
def_delegator :@data, :[]
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -1,6 +1,12 @@
|
|||
require 'pathname'
|
||||
|
||||
# Filesystem-based project
|
||||
#
|
||||
# Analyze a folder on the filesystem for license information
|
||||
#
|
||||
# Project files for this project type will contain the following keys:
|
||||
# :name - the relative file name
|
||||
# :dir - the directory path containing the file
|
||||
module Licensee
|
||||
class FSProject < Project
|
||||
def initialize(path, **args)
|
||||
|
@ -11,22 +17,28 @@ module Licensee
|
|||
@pattern = '*'
|
||||
@dir = path
|
||||
end
|
||||
|
||||
@root = args.delete(:search_root) || @dir
|
||||
unless valid_search_root?
|
||||
raise 'Search root must be the FSProject path directory or its ancestor'
|
||||
end
|
||||
|
||||
super(**args)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
# Returns an array of hashes representing the project's files.
|
||||
# Hashes will have the :name key, with the relative path to the file
|
||||
# Hashes will have the the following keys:
|
||||
# :name - the relative file name
|
||||
# :dir - the directory path containing the file
|
||||
def files
|
||||
files = []
|
||||
|
||||
Dir.glob(::File.join(@dir, @pattern).tr('\\', '/')) do |file|
|
||||
next unless ::File.file?(file)
|
||||
files.push(name: ::File.basename(file))
|
||||
@files ||= search_directories.flat_map do |dir|
|
||||
Dir.glob(::File.join(dir, @pattern).tr('\\', '/')).map do |file|
|
||||
next unless ::File.file?(file)
|
||||
{ name: ::File.basename(file), dir: dir }
|
||||
end.compact
|
||||
end
|
||||
|
||||
files
|
||||
end
|
||||
|
||||
# Retrieve a file's content from disk
|
||||
|
@ -35,7 +47,32 @@ module Licensee
|
|||
#
|
||||
# Returns the file contents as a string
|
||||
def load_file(file)
|
||||
::File.read(::File.join(@dir, file[:name]))
|
||||
::File.read(::File.join(file[:dir], file[:name]))
|
||||
end
|
||||
|
||||
# Returns true if @dir is @root or it's descendant
|
||||
def valid_search_root?
|
||||
dir = Pathname.new(@dir)
|
||||
dir.fnmatch?(@root) || dir.fnmatch?(::File.join(@root, '**'))
|
||||
end
|
||||
|
||||
# Returns the set of unique paths to search for project files
|
||||
# in order from @dir -> @root
|
||||
def search_directories
|
||||
search_enumerator.map(&:to_path)
|
||||
.push(@root) # ensure root is included in the search
|
||||
.uniq # don't include the root twice if @dir == @root
|
||||
end
|
||||
|
||||
# Enumerates all directories to search, from @dir to @root
|
||||
def search_enumerator
|
||||
root = Pathname.new(@root)
|
||||
dir = Pathname.new(@dir)
|
||||
Enumerator.new do |yielder|
|
||||
dir.relative_path_from(root).ascend do |relative|
|
||||
yielder.yield root.join(relative)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -1,6 +1,10 @@
|
|||
# Git-based project
|
||||
#
|
||||
# Analyze a given (bare) Git repository for license information
|
||||
#
|
||||
# Project files for this project type will contain the following keys:
|
||||
# :name - the file's path relative to the repo root
|
||||
# :oid - the file's OID
|
||||
module Licensee
|
||||
class GitProject < Project
|
||||
attr_reader :repository, :revision
|
||||
|
@ -51,7 +55,7 @@ module Licensee
|
|||
# :name - the file's path relative to the repo root
|
||||
# :oid - the file's OID
|
||||
def files
|
||||
commit.tree.map do |entry|
|
||||
@files ||= commit.tree.map do |entry|
|
||||
next unless entry[:type] == :blob
|
||||
{ name: entry[:name], oid: entry[:oid] }
|
||||
end.compact
|
||||
|
|
|
@ -0,0 +1,21 @@
|
|||
MIT License
|
||||
|
||||
Copyright (c) 2016 Ben Balter
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
|
@ -0,0 +1,21 @@
|
|||
MIT License
|
||||
|
||||
Copyright (c) 2016 Ben Balter
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
|
@ -82,6 +82,31 @@
|
|||
content = subject.send(:load_file, files.first)
|
||||
expect(content).to match('Permission is hereby granted')
|
||||
end
|
||||
|
||||
if described_class == Licensee::FSProject
|
||||
context 'with search root argument' do
|
||||
let(:fixture) { 'license-in-parent-folder/license-folder/package' }
|
||||
let(:path) { fixture_path(fixture) }
|
||||
let(:license_folder) { 'license-in-parent-folder/license-folder' }
|
||||
let(:search_root) { fixture_path(license_folder) }
|
||||
let(:subject) { described_class.new(path, search_root: search_root) }
|
||||
let(:files) { subject.send(:files) }
|
||||
|
||||
it 'looks for licenses in parent directories up to the search root' do
|
||||
# should not include the license in 'license-in-parent-folder' dir
|
||||
expect(files.count).to eql(1)
|
||||
expect(files.first[:name]).to eql('LICENSE.txt')
|
||||
end
|
||||
end
|
||||
|
||||
context 'without search root argument' do
|
||||
let(:fixture) { 'license-in-parent-folder/license-folder/package' }
|
||||
|
||||
it 'looks for licenses in current directory only' do
|
||||
expect(files.count).to eql(0)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'encoding correctness' do
|
||||
|
|
|
@ -30,4 +30,17 @@ RSpec.describe Licensee::Project::File do
|
|||
it 'returns the license' do
|
||||
expect(subject.license).to eql(mit)
|
||||
end
|
||||
|
||||
context 'with additional metadata' do
|
||||
subject { described_class.new(content, name: filename, dir: Dir.pwd) }
|
||||
|
||||
it 'stores the filename' do
|
||||
expect(subject.filename).to eql(filename)
|
||||
expect(subject[:name]).to eql(filename)
|
||||
end
|
||||
|
||||
it 'stores additional metadata' do
|
||||
expect(subject[:dir]).to eql(Dir.pwd)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
Loading…
Reference in New Issue