mirror of https://github.com/licensee/licensee.git
Add support for GitHub projects
Rather than querying the local filesystem, we can use the Github API to resolve folder contents and the individual files within. This isn't going to be performant - requests are not made in parallel - and there is absolutely no error handling.
This commit is contained in:
parent
69a67f055b
commit
1b9d1f2b00
|
@ -2,10 +2,16 @@
|
|||
|
||||
## Command line usage
|
||||
|
||||
1. `cd` into a project directory
|
||||
2. Execute the `licensee` command
|
||||
This gem includes an executable which can be run using the `licensee [PATH]` command,
|
||||
where `[PATH]` is:
|
||||
|
||||
You'll get an output that looks like:
|
||||
* A directory, for example: `licensee vendor/gems/activesupport`
|
||||
* A file, for example: `licensee LICENSE.txt`
|
||||
* A GitHub repository, for example: `licensee https://github.com/facebook/react`
|
||||
|
||||
If you don't specify any arguments, `licensee` will just scan the current directory.
|
||||
|
||||
In all cases, you'll get an output that looks like:
|
||||
|
||||
```
|
||||
License: MIT
|
||||
|
@ -13,8 +19,6 @@ Confidence: 98.42%
|
|||
Matcher: Licensee::GitMatcher
|
||||
```
|
||||
|
||||
Alternately, `licensee <directory>` will treat the argument as the project directory, and `licensee <file>` will attempt to match the individual file specified, both with output that looks like the above.
|
||||
|
||||
## License Ruby API
|
||||
|
||||
```ruby
|
||||
|
@ -37,6 +41,24 @@ license.meta["permissions"]
|
|||
=> ["commercial-use","modifications","distribution","private-use"]
|
||||
```
|
||||
|
||||
If you wish to scan private GitHub repositories, or are hitting API rate limits, you can configure the embedded [Octokit](https://github.com/octokit/octokit.rb)
|
||||
client using environment variables, for example:
|
||||
|
||||
```sh
|
||||
OCTOKIT_ACCESS_TOKEN=abc123 licensee https://github.com/benbalter/licensee
|
||||
```
|
||||
|
||||
Octokit can also be configured using standard module-level configuration:
|
||||
|
||||
```ruby
|
||||
# see https://github.com/octokit/octokit.rb#configuring-module-defaults
|
||||
Octokit.configure do |c|
|
||||
c.access_token = "<your 40 char token>"
|
||||
end
|
||||
|
||||
license = Licensee.license "https://github.com/benbalter/licensee"
|
||||
```
|
||||
|
||||
## Advanced API usage
|
||||
|
||||
You can gather more information by working with the project object, and the top level Licensee class.
|
||||
|
|
|
@ -35,7 +35,11 @@ module Licensee
|
|||
end
|
||||
|
||||
def project(path, **args)
|
||||
Licensee::Projects::GitProject.new(path, args)
|
||||
if path =~ %r{\Ahttps://github.com}
|
||||
Licensee::Projects::GitHubProject.new(path, args)
|
||||
else
|
||||
Licensee::Projects::GitProject.new(path, args)
|
||||
end
|
||||
rescue Licensee::Projects::GitProject::InvalidRepository
|
||||
Licensee::Projects::FSProject.new(path, args)
|
||||
end
|
||||
|
|
|
@ -3,5 +3,6 @@ module Licensee
|
|||
autoload :Project, 'licensee/projects/project'
|
||||
autoload :FSProject, 'licensee/projects/fs_project'
|
||||
autoload :GitProject, 'licensee/projects/git_project'
|
||||
autoload :GitHubProject, 'licensee/projects/github_project'
|
||||
end
|
||||
end
|
||||
|
|
|
@ -0,0 +1,46 @@
|
|||
# GitHub project
|
||||
#
|
||||
# Analyses a remote GitHub repository for license information
|
||||
#
|
||||
# Only the root directory of a repository will be scanned because every
|
||||
# `#load_file(..)` call incurs a separate API request.
|
||||
|
||||
require 'octokit'
|
||||
|
||||
module Licensee
|
||||
module Projects
|
||||
class GitHubProject < Licensee::Projects::Project
|
||||
# If there's any trailing data (e.g. `.git`) this pattern will ignore it:
|
||||
# we're going to use the API rather than clone the repo.
|
||||
GITHUB_REPO_PATTERN = %r{https://github.com/([^\/]+\/[^\/\.]+).*}
|
||||
|
||||
class RepoNotFound < StandardError; end
|
||||
|
||||
def initialize(github_url, **args)
|
||||
@repo = github_url[GITHUB_REPO_PATTERN, 1]
|
||||
raise ArgumentError, "Not a github URL: #{github_url}" unless @repo
|
||||
super(**args)
|
||||
end
|
||||
|
||||
attr_reader :repo
|
||||
|
||||
private
|
||||
|
||||
def files
|
||||
@files ||= contents.map { |data| { name: data[:name], dir: '/' } }
|
||||
rescue Octokit::NotFound
|
||||
raise RepoNotFound,
|
||||
"Could not load GitHub repo #{repo}, it may be private or deleted"
|
||||
end
|
||||
|
||||
def load_file(file)
|
||||
Octokit.contents(@repo, path: file[:name],
|
||||
accept: 'application/vnd.github.v3.raw')
|
||||
end
|
||||
|
||||
def contents
|
||||
Octokit.contents(@repo).select { |data| data[:type] == 'file' }
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -18,7 +18,9 @@ Gem::Specification.new do |gem|
|
|||
gem.bindir = 'bin'
|
||||
gem.executables << 'licensee'
|
||||
|
||||
gem.add_dependency('octokit', '~> 4.8.0')
|
||||
gem.add_dependency('rugged', '~> 0.24')
|
||||
|
||||
gem.add_development_dependency('coveralls', '~> 0.8')
|
||||
gem.add_development_dependency('mustache', '>= 0.9', '< 2.0')
|
||||
gem.add_development_dependency('pry', '~> 0.9')
|
||||
|
|
|
@ -0,0 +1,140 @@
|
|||
RSpec.describe Licensee::Projects::GitHubProject do
|
||||
describe '#initialize' do
|
||||
subject(:instance) { described_class.new(github_url) }
|
||||
|
||||
context 'with a GitHub URI' do
|
||||
let(:github_url) { 'https://github.com/benbalter/licensee' }
|
||||
|
||||
it 'should set @repo' do
|
||||
expect(instance.repo).to eq('benbalter/licensee')
|
||||
end
|
||||
end
|
||||
|
||||
context 'with a GitHub git URI' do
|
||||
let(:github_url) { 'https://github.com/benbalter/licensee.git' }
|
||||
|
||||
it 'should set @repo, stripping the trailing extension' do
|
||||
expect(instance.repo).to eq('benbalter/licensee')
|
||||
end
|
||||
end
|
||||
|
||||
context 'with a non-GitHub URI' do
|
||||
let(:github_url) { 'https://gitlab.com/benbalter/licensee' }
|
||||
|
||||
it 'should raise an ArgumentError' do
|
||||
expect { instance }.to raise_error(ArgumentError)
|
||||
end
|
||||
end
|
||||
|
||||
context 'with a local folder' do
|
||||
let(:github_url) { fixture_path('mit') }
|
||||
|
||||
it 'should raise an ArgumentError' do
|
||||
expect { instance }.to raise_error(ArgumentError)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
subject { described_class.new(github_url) }
|
||||
|
||||
let(:repo) { 'benbalter/licensee' }
|
||||
let(:github_url) { 'https://github.com/benbalter/licensee' }
|
||||
let(:mit) { Licensee::License.find('mit') }
|
||||
let(:readme_file) do
|
||||
File.read(fixture_path('mit/README.md'))
|
||||
end
|
||||
let(:license_file) { File.read(fixture_path('mit/LICENSE.txt')) }
|
||||
|
||||
context 'when the repo exists' do
|
||||
before do
|
||||
allow(Octokit)
|
||||
.to receive(:contents)
|
||||
.with('benbalter/licensee')
|
||||
.and_return([
|
||||
{
|
||||
name: 'LICENSE.txt',
|
||||
path: 'LICENSE.txt',
|
||||
sha: 'sha1',
|
||||
size: 1072,
|
||||
url: 'https://api.github.com/repos/benbalter/licensee/contents/LICENSE.txt?ref=master',
|
||||
html_url: 'https://github.com/benbalter/licensee/blob/master/LICENSE.txt',
|
||||
git_url: 'https://api.github.com/repos/benbalter/licensee/git/blobs/sha1',
|
||||
download_url: 'https://raw.githubusercontent.com/benbalter/licensee/master/LICENSE.txt',
|
||||
type: 'file',
|
||||
_links: {}
|
||||
},
|
||||
{ name: 'README.md',
|
||||
path: 'README.md',
|
||||
sha: 'sha2',
|
||||
size: 13_420,
|
||||
url: 'https://api.github.com/repos/benbalter/licensee/contents/README.md?ref=master',
|
||||
html_url: 'https://github.com/benbalter/licensee/blob/master/README.md',
|
||||
git_url: 'https://api.github.com/repos/benbalter/licensee/git/blobs/sha2',
|
||||
download_url: 'https://raw.githubusercontent.com/benbalter/licensee/master/README.md',
|
||||
type: 'file',
|
||||
_links: {} }
|
||||
])
|
||||
|
||||
allow(Octokit)
|
||||
.to receive(:contents)
|
||||
.with(repo, path: 'LICENSE.txt',
|
||||
accept: 'application/vnd.github.v3.raw')
|
||||
.and_return(license_file)
|
||||
|
||||
allow(Octokit)
|
||||
.to receive(:contents)
|
||||
.with(repo, path: 'README.md', accept: 'application/vnd.github.v3.raw')
|
||||
.and_return(readme_file)
|
||||
end
|
||||
|
||||
it 'returns the license' do
|
||||
expect(subject.license).to be_a(Licensee::License)
|
||||
expect(subject.license).to eql(mit)
|
||||
end
|
||||
|
||||
it 'returns the matched file' do
|
||||
expect(subject.matched_file).to be_a(Licensee::ProjectFiles::LicenseFile)
|
||||
expect(subject.matched_file.filename).to eql('LICENSE.txt')
|
||||
end
|
||||
|
||||
it 'returns the license file' do
|
||||
expect(subject.license_file).to be_a(Licensee::ProjectFiles::LicenseFile)
|
||||
expect(subject.license_file.filename).to eql('LICENSE.txt')
|
||||
end
|
||||
|
||||
it "doesn't return the readme" do
|
||||
expect(subject.readme_file).to be_nil
|
||||
end
|
||||
|
||||
it "doesn't return the package file" do
|
||||
expect(subject.package_file).to be_nil
|
||||
end
|
||||
|
||||
context 'readme detection' do
|
||||
subject { described_class.new(github_url, detect_readme: true) }
|
||||
|
||||
it 'returns the readme' do
|
||||
expect(subject.readme_file).to be_a(Licensee::ProjectFiles::ReadmeFile)
|
||||
expect(subject.readme_file.filename).to eql('README.md')
|
||||
end
|
||||
|
||||
it 'returns the license' do
|
||||
expect(subject.license).to be_a(Licensee::License)
|
||||
expect(subject.license).to eql(mit)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'when the repo cannot be found' do
|
||||
let(:github_url) { 'https://github.com/benbalter/not-foundsss' }
|
||||
|
||||
before do
|
||||
allow(Octokit)
|
||||
.to receive(:contents).with(anything).and_raise(Octokit::NotFound)
|
||||
end
|
||||
|
||||
it 'raises a RepoNotFound error' do
|
||||
expect { subject.license }.to raise_error(described_class::RepoNotFound)
|
||||
end
|
||||
end
|
||||
end
|
|
@ -19,8 +19,20 @@ RSpec.describe Licensee do
|
|||
expect(Licensee.license(license_path)).to eql(mit_license)
|
||||
end
|
||||
|
||||
it 'inits a project' do
|
||||
expect(Licensee.project(project_path)).to be_a(Licensee::Projects::Project)
|
||||
describe '.project' do
|
||||
subject { Licensee.project(project_path) }
|
||||
|
||||
it 'inits a project' do
|
||||
expect(subject).to be_a(Licensee::Projects::Project)
|
||||
end
|
||||
|
||||
context 'given a GitHub repository' do
|
||||
let(:project_path) { 'https://github.com/benbalter/licensee' }
|
||||
|
||||
it 'creates a GitHubProject' do
|
||||
expect(subject).to be_a(Licensee::Projects::GitHubProject)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'confidence threshold' do
|
||||
|
|
Loading…
Reference in New Issue