зеркало из https://github.com/github/licensed.git
v1.0.0
This commit is contained in:
Коммит
43ae415fea
|
@ -0,0 +1,26 @@
|
|||
/.bundle/
|
||||
/.yardoc
|
||||
/Gemfile.lock
|
||||
/_yardoc/
|
||||
/coverage/
|
||||
/doc/
|
||||
/pkg/
|
||||
/spec/reports/
|
||||
/tmp/
|
||||
|
||||
# test fixtures
|
||||
test/fixtures/bundler/.bundle/
|
||||
test/fixtures/bundler/vendor/
|
||||
test/fixtures/bundler/Gemfile.lock
|
||||
test/fixtures/bower/bower_components
|
||||
test/fixtures/npm/node_modules
|
||||
test/fixtures/npm/package-lock.json
|
||||
test/fixtures/go/src/*
|
||||
test/fixtures/go/pkg
|
||||
!test/fixtures/go/src/test
|
||||
test/fixtures/haskell/dist*
|
||||
|
||||
vendor/licenses
|
||||
.licenses
|
||||
*.gem
|
||||
vendor/gems
|
|
@ -0,0 +1,3 @@
|
|||
inherit_gem:
|
||||
rubocop-github:
|
||||
- config/default.yml
|
|
@ -0,0 +1 @@
|
|||
2.4.0
|
|
@ -0,0 +1,4 @@
|
|||
language: ruby
|
||||
rvm:
|
||||
- 2.2.3
|
||||
before_install: gem install bundler -v 1.10.6
|
|
@ -0,0 +1,13 @@
|
|||
# Changelog
|
||||
All notable changes to this project will be documented in this file.
|
||||
|
||||
The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/)
|
||||
and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html).
|
||||
|
||||
## [Unreleased]
|
||||
|
||||
## 1.0.0 - 2018-??-??
|
||||
|
||||
Initial release :tada:
|
||||
|
||||
[Unreleased]: https://github.com/github/licensed/compare/v1.0.0...HEAD
|
|
@ -0,0 +1,73 @@
|
|||
# Contributor Covenant Code of Conduct
|
||||
|
||||
## Our Pledge
|
||||
|
||||
In the interest of fostering an open and welcoming environment, we as
|
||||
contributors and maintainers pledge to making participation in our project and
|
||||
our community a harassment-free experience for everyone, regardless of age, body
|
||||
size, disability, ethnicity, gender identity and expression, level of experience,
|
||||
education, socio-economic status, nationality, personal appearance, race,
|
||||
religion, or sexual identity and orientation.
|
||||
|
||||
## Our Standards
|
||||
|
||||
Examples of behavior that contributes to creating a positive environment
|
||||
include:
|
||||
|
||||
* Using welcoming and inclusive language
|
||||
* Being respectful of differing viewpoints and experiences
|
||||
* Gracefully accepting constructive criticism
|
||||
* Focusing on what is best for the community
|
||||
* Showing empathy towards other community members
|
||||
|
||||
Examples of unacceptable behavior by participants include:
|
||||
|
||||
* The use of sexualized language or imagery and unwelcome sexual attention or
|
||||
advances
|
||||
* Trolling, insulting/derogatory comments, and personal or political attacks
|
||||
* Public or private harassment
|
||||
* Publishing others' private information, such as a physical or electronic
|
||||
address, without explicit permission
|
||||
* Other conduct which could reasonably be considered inappropriate in a
|
||||
professional setting
|
||||
|
||||
## Our Responsibilities
|
||||
|
||||
Project maintainers are responsible for clarifying the standards of acceptable
|
||||
behavior and are expected to take appropriate and fair corrective action in
|
||||
response to any instances of unacceptable behavior.
|
||||
|
||||
Project maintainers have the right and responsibility to remove, edit, or
|
||||
reject comments, commits, code, wiki edits, issues, and other contributions
|
||||
that are not aligned to this Code of Conduct, or to ban temporarily or
|
||||
permanently any contributor for other behaviors that they deem inappropriate,
|
||||
threatening, offensive, or harmful.
|
||||
|
||||
## Scope
|
||||
|
||||
This Code of Conduct applies both within project spaces and in public spaces
|
||||
when an individual is representing the project or its community. Examples of
|
||||
representing a project or community include using an official project e-mail
|
||||
address, posting via an official social media account, or acting as an appointed
|
||||
representative at an online or offline event. Representation of a project may be
|
||||
further defined and clarified by project maintainers.
|
||||
|
||||
## Enforcement
|
||||
|
||||
Instances of abusive, harassing, or otherwise unacceptable behavior may be
|
||||
reported by contacting the project team at opensource+licensed@github.com. All
|
||||
complaints will be reviewed and investigated and will result in a response that
|
||||
is deemed necessary and appropriate to the circumstances. The project team is
|
||||
obligated to maintain confidentiality with regard to the reporter of an incident.
|
||||
Further details of specific enforcement policies may be posted separately.
|
||||
|
||||
Project maintainers who do not follow or enforce the Code of Conduct in good
|
||||
faith may face temporary or permanent repercussions as determined by other
|
||||
members of the project's leadership.
|
||||
|
||||
## Attribution
|
||||
|
||||
This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4,
|
||||
available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html
|
||||
|
||||
[homepage]: https://www.contributor-covenant.org
|
|
@ -0,0 +1,51 @@
|
|||
## Contributing
|
||||
|
||||
[fork]: https://github.com/github/licensed/fork
|
||||
[pr]: https://github.com/github/licensed/compare
|
||||
[style]: https://github.com/styleguide/ruby
|
||||
[code-of-conduct]: CODE_OF_CONDUCT.md
|
||||
|
||||
Hi there! We're thrilled that you'd like to contribute to this project. Your help is essential for keeping it great.
|
||||
|
||||
Please note that this project is released with a [Contributor Code of Conduct][code-of-conduct]. By participating in this project you agree to abide by its terms.
|
||||
|
||||
## Submitting a pull request
|
||||
|
||||
0. [Fork][fork] and clone the repository
|
||||
0. Configure and install the dependencies: `script/bootstrap`
|
||||
0. Make sure the tests pass on your machine: `rake`
|
||||
0. Create a new branch: `git checkout -b my-branch-name`
|
||||
0. Make your change, add tests, and make sure the tests still pass
|
||||
0. Push to your fork and [submit a pull request][pr]
|
||||
0. Pat your self on the back and wait for your pull request to be reviewed and merged.
|
||||
|
||||
Here are a few things you can do that will increase the likelihood of your pull request being accepted:
|
||||
|
||||
- Follow the [style guide][style].
|
||||
- Write tests.
|
||||
- Keep your change as focused as possible. If there are multiple changes you would like to make that are not dependent upon each other, consider submitting them as separate pull requests.
|
||||
- Write a [good commit message](http://tbaggery.com/2008/04/19/a-note-about-git-commit-messages.html).
|
||||
|
||||
## Releasing
|
||||
If you are the current maintainer of this gem:
|
||||
|
||||
1. Create a branch for the release: git checkout -b cut-release-vxx.xx.xx
|
||||
2. Make sure your local dependencies are up to date: script/bootstrap
|
||||
3. Ensure that tests are green: bundle exec rake test
|
||||
4. Bump gem version in lib/licensed/version.rb.
|
||||
5. Update [`CHANGELOG.md`](CHANGELOG.md)
|
||||
6. Make a PR to github/licensed.
|
||||
7. Build a local gem: bundle exec rake build
|
||||
8. Test the gem:
|
||||
1. Bump the Gemfile and Gemfile.lock versions for an app which relies on this gem
|
||||
2. Install the new gem locally
|
||||
3. Test behavior locally, branch deploy, whatever needs to happen
|
||||
9. Merge github/licensed PR
|
||||
10. Tag and push: git tag vx.xx.xx; git push --tags
|
||||
11. Push to rubygems.org -- gem push licensed-x.xx.xx.gem
|
||||
|
||||
## Resources
|
||||
|
||||
- [How to Contribute to Open Source](https://opensource.guide/how-to-contribute/)
|
||||
- [Using Pull Requests](https://help.github.com/articles/about-pull-requests/)
|
||||
- [GitHub Help](https://help.github.com)
|
|
@ -0,0 +1,5 @@
|
|||
# frozen_string_literal: true
|
||||
source "https://rubygems.org"
|
||||
|
||||
# Specify your gem's dependencies in licensed.gemspec
|
||||
gemspec
|
|
@ -0,0 +1,21 @@
|
|||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2015-2018 GitHub, Inc. and contributors
|
||||
|
||||
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,123 @@
|
|||
# Licensed
|
||||
|
||||
Licensed is a Ruby gem to cache the licenses of dependencies and check their status.
|
||||
|
||||
Licensed is **not** a complete open source license compliance solution. Please understand the important [disclaimer](#disclaimer) below to make appropriate use of Licensed.
|
||||
|
||||
## Current Status
|
||||
|
||||
Licensed is in active development and currently used at GitHub. See the [open issues](https://github.com/github/licensed/issues) for a list of potential work.
|
||||
|
||||
## Installation
|
||||
|
||||
Add this line to your application's Gemfile:
|
||||
|
||||
```ruby
|
||||
gem 'licensed', :group => 'development'
|
||||
```
|
||||
|
||||
And then execute:
|
||||
|
||||
```bash
|
||||
$ bundle
|
||||
```
|
||||
|
||||
#### Dependencies
|
||||
|
||||
Licensed uses the the `libgit2` bindings for Ruby provided by `rugged`. `rugged` has its own dependencies - `cmake` and `pkg-config` - which you may need to install before you can install Licensed.
|
||||
|
||||
For example, on macOS with Homebrew: `brew install cmake pkg-config` and on Ubuntu: `apt-get install cmake pkg-config`.
|
||||
|
||||
## Usage
|
||||
|
||||
- `licensed list`: Output enumerated dependencies only.
|
||||
|
||||
- `licensed cache`: Cache licenses and metadata.
|
||||
|
||||
- `licensed status`: Check status of dependencies' cached licenses. For example:
|
||||
|
||||
```
|
||||
$ bundle exec licensed status
|
||||
Checking licenses for 3 dependencies
|
||||
|
||||
Warnings:
|
||||
|
||||
.licenses/rubygem/bundler.txt:
|
||||
- license needs reviewed: mit.
|
||||
|
||||
.licenses/rubygem/licensee.txt:
|
||||
- cached license data missing
|
||||
|
||||
.licenses/bower/jquery.txt:
|
||||
- license needs reviewed: mit.
|
||||
- cached license data out of date
|
||||
|
||||
3 dependencies checked, 3 warnings found.
|
||||
```
|
||||
|
||||
### Configuration
|
||||
|
||||
All commands accept a `-c|--config` option to specify a path to a configuration file or directory.
|
||||
|
||||
If a directory is specified, `licensed` will look in that directory for a file named (in order of preference):
|
||||
1. `.licensed.yml`
|
||||
2. `.licensed.yaml`
|
||||
3. `.licensed.json`
|
||||
|
||||
If the option is not specified, the value will be set to the current directory.
|
||||
|
||||
See the [configuration file documentation](./docs/configuration.md) for more details on the configuration format.
|
||||
|
||||
### Sources
|
||||
|
||||
Dependencies will be automatically detected for
|
||||
1. [Bower](./docs/sources/bower.md)
|
||||
2. [Bundler (rubygem)](./docs/sources/bundler.md)
|
||||
3. [Cabal](./docs/sources/cabal.md)
|
||||
4. [Go](./docs/sources/go.md)
|
||||
5. [Manifest lists](./docs/sources/manifests.md)
|
||||
6. [NPM](./docs/sources/npm.md)
|
||||
|
||||
You can disable any of them in the configuration file:
|
||||
|
||||
```yml
|
||||
sources:
|
||||
rubygem: false
|
||||
npm: false
|
||||
bower: false
|
||||
cabal: false
|
||||
```
|
||||
|
||||
## Development
|
||||
|
||||
After checking out the repo, run `script/bootstrap` to install dependencies. Then, run `script/cibuild` to run the tests. You can also run `script/console` for an interactive prompt that will allow you to experiment.
|
||||
|
||||
To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).
|
||||
|
||||
#### Adding sources
|
||||
|
||||
When adding new dependency sources, ensure that `script/bootstrap` scripting and tests are only run if the required tooling is available on the development machine.
|
||||
|
||||
* See `script/bootstrap` for examples of gating scripting based on whether tooling executables are found.
|
||||
* Use `Licensed::Shell.tool_available?` when writing test files to gate running a test suite when tooling executables aren't available.
|
||||
```ruby
|
||||
if Licensed::Shell.tool_available?('bundle')
|
||||
describe Licensed::Source::Bundler do
|
||||
...
|
||||
end
|
||||
end
|
||||
```
|
||||
|
||||
## Contributing
|
||||
|
||||
Bug reports and pull requests are welcome on GitHub at https://github.com/github/licensed. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [Contributor Covenant](http://contributor-covenant.org/) code of conduct. See [CONTRIBUTING](CONTRIBUTING.md) for more details.
|
||||
|
||||
## Disclaimer
|
||||
|
||||
Licensed is **not** a complete open source license compliance solution. Like any bug, licensing issues are far cheaper to fix if found early. Licensed is intended to provide automation around documenting the licenses of dependencies and whether they are configured to be allowed by a user of licensed, in other words, to surface the most obvious licensing issues early.
|
||||
|
||||
Licensed is not a substitute for human review of each dependency for licensing or any other issues. It is not the goal of Licensed or GitHub, Inc. to provide legal advice about licensing or any other issues. If you have any questions regarding licensing compliance for your code or any other legal issues relating to it, it’s up to you to do further research or consult with a professional.
|
||||
|
||||
## License
|
||||
|
||||
The gem is available as open source under the terms of the [MIT License](http://opensource.org/licenses/MIT).
|
|
@ -0,0 +1,11 @@
|
|||
# frozen_string_literal: true
|
||||
require "bundler/gem_tasks"
|
||||
require "rake/testtask"
|
||||
|
||||
Rake::TestTask.new(:test) do |t|
|
||||
t.libs << "test"
|
||||
t.libs << "lib"
|
||||
t.test_files = FileList["test/**/*_test.rb"]
|
||||
end
|
||||
|
||||
task default: :test
|
|
@ -0,0 +1,131 @@
|
|||
# Configuration file
|
||||
|
||||
A configuration file specifies the details of enumerating and operating on license metadata for apps.
|
||||
|
||||
Configuration can be specified in either YML or JSON formats. Examples below are given in YML.
|
||||
|
||||
## Applications
|
||||
|
||||
What is an "app"? In the context of `licensed`, an app is a combination of a source path and a cache path.
|
||||
|
||||
Configuration can be set up for single or multiple applications in the same repo. There are a number of settings available for each app:
|
||||
```yml
|
||||
# If not set, defaults to the directory name of `source_path`
|
||||
name: 'My application'
|
||||
|
||||
# Path is relative to git repository root
|
||||
# If not set, defaults to '.licenses'
|
||||
cache_path: 'relative/path/to/cache'
|
||||
|
||||
# Path is relative to git repository root and specifies the working directory when enumerating dependencies
|
||||
# Optional for single app configuration, required when specifying multiple apps
|
||||
# Defaults to current directory when running `licensed`
|
||||
source_path: 'relative/path/to/source'
|
||||
|
||||
# Sources of metadata
|
||||
# All sources will attempt to run unless explicitly disabled
|
||||
sources:
|
||||
- bower: true
|
||||
- rubygem: false
|
||||
|
||||
# Dependencies with these licenses are allowed by default.
|
||||
allowed:
|
||||
- mit
|
||||
- apache-2.0
|
||||
- bsd-2-clause
|
||||
- bsd-3-clause
|
||||
- cc0-1.0
|
||||
|
||||
# These dependencies are explicitly ignored.
|
||||
ignored:
|
||||
rubygem:
|
||||
- some-internal-gem
|
||||
|
||||
bower:
|
||||
- some-internal-package
|
||||
|
||||
# These dependencies have been reviewed.
|
||||
reviewed:
|
||||
rubygem:
|
||||
- bcrypt-ruby
|
||||
|
||||
bower:
|
||||
- classlist # public domain
|
||||
- octicons
|
||||
```
|
||||
|
||||
### Specifying a single app
|
||||
To specify a single app, either include a single app with `source_path` in the `apps` configuration, or remove the `apps` setting entirely.
|
||||
|
||||
If the configuration does not contain an `apps` value, the root configuration will be used as an app definition. In this scenario, the `source_path` is not a required value and will default to the directory that `licensed` was executed from.
|
||||
|
||||
If the configuration contains an `apps` value with a single app configuration, `source_path` must be specified. Additionally, the applications inherited `cache_path` value will contain the application name. See [Inherited cache_path values](#inherited_cache_path_values)
|
||||
|
||||
### Specifying multiple apps
|
||||
The configuration file can specify multiple source paths to enumerate metadata, each with their own configuration.
|
||||
|
||||
Nearly all configuration settings can be inherited from root configuration to app configuration. Only `source_path` is required to define an app.
|
||||
|
||||
Here are some examples:
|
||||
|
||||
#### Inheriting configuration
|
||||
```yml
|
||||
sources:
|
||||
- go: true
|
||||
- rubygem: false
|
||||
|
||||
ignored:
|
||||
rubygem:
|
||||
- some-internal-gem
|
||||
|
||||
reviewed:
|
||||
rubygem:
|
||||
- bcrypt-ruby
|
||||
|
||||
cache_path: 'path/to/cache'
|
||||
apps:
|
||||
- source_path: 'path/to/app1'
|
||||
- source_path: 'path/to/app2'
|
||||
sources:
|
||||
- rubygem: true
|
||||
- go: false
|
||||
```
|
||||
|
||||
In this example, two apps have been declared. The first app, with `source_path` `path/to/app1`, inherits all configuration settings from the root configuration. The second app, with `source_path` `path/to/app2`, overrides the `sources` configuration and inherits all other settings.
|
||||
|
||||
#### Default app names
|
||||
An app will not inherit a name set from the root configuration. If not provided, the `name` value will default to the directory name from `source_path`.
|
||||
```yml
|
||||
apps:
|
||||
- source_path: 'path/to/app1'
|
||||
- source_path: 'path/to/app2'
|
||||
```
|
||||
|
||||
In this example, the apps have names of `app1` and `app2`, respectively.
|
||||
|
||||
#### Inherited cache_path values
|
||||
When an app inherits a `cache_path` from the root configuration, it will automatically append it's name to the end of the path to separate it's metadata from other apps. To force multiple apps to use the same path to cached metadata, explicitly set the `cache_path` value for each app.
|
||||
```yml
|
||||
cache_path: 'path/to/cache'
|
||||
apps:
|
||||
- source_path: 'path/to/app1'
|
||||
name: 'app1'
|
||||
- source_path: 'path/to/app2'
|
||||
name: 'app2'
|
||||
- source_path: 'path/to/app3'
|
||||
name: 'app3'
|
||||
cache_path: 'path/to/app3/cache'
|
||||
```
|
||||
|
||||
In this example `app1` and `app2` have `cache_path` values of `path/to/cache/app1` and `path/to/cache/app2`, respectively. `app3` has an explicit path set to `path/to/app3/cache`
|
||||
|
||||
```yml
|
||||
apps:
|
||||
- source_path: 'path/to/app1'
|
||||
```
|
||||
|
||||
In this example, the root configuration will contain a default cache path of `.licenses`. `app1` will inherit this value and append it's name, resulting in a cache path of `.licenses/app1`.
|
||||
|
||||
## Source specific configuration
|
||||
|
||||
See the [source documentation](./sources) for details on any source specific configuration.
|
|
@ -0,0 +1,5 @@
|
|||
# Bower
|
||||
|
||||
The bower source will detect dependencies when the source is enabled and either `.bowerrc` or `bower.json` files are found at an apps `source_path`.
|
||||
|
||||
It enumerates dependencies and metadata from parsing `.bower.json` files for bower components.
|
|
@ -0,0 +1,7 @@
|
|||
# Bundler (rubygem)
|
||||
|
||||
The bundler source will detect dependencies when the source is enabled, `Gemfile` and `Gemfile.lock` files are found at an apps `source_path`. The source uses the `Bundler` API to enumerate dependencies from `Gemfile` and `Gemfile.lock`.
|
||||
|
||||
The bundler source will exclude gems in the `:development` and `:test` groups. Be aware that if you have a local
|
||||
bundler configuration (e.g. `.bundle`), that configuration will be respected as well. For example, if you have a local
|
||||
configuration set for `without: [':server']`, the bundler source will exclude all gems in the `:server` group.
|
|
@ -0,0 +1,39 @@
|
|||
# Cabal
|
||||
|
||||
The cabal source uses the `ghc-pkg` command to enumerate dependencies and provide metadata. It is un-opinionated on GHC packagedb locations and requires some configuration to ensure that all packages are properly found.
|
||||
|
||||
The rubygem source will detect dependencies when the source is enabled and a `.cabal` file is found at an apps `source_path`.
|
||||
|
||||
### Specifying GHC packagedb locations through environment
|
||||
You can configure the `cabal` source to use specific packagedb locations by setting the `GHC_PACKAGE_PATH` environment variable before running `licensed`.
|
||||
|
||||
For example, the following is a useful configuration for use with `cabal new-build`.
|
||||
```bash
|
||||
ghc_version="$(ghc --numeric-version)"
|
||||
CABAL_STORE_PACKAGE_DB="$(cd ~/.cabal/store/ghc-$ghc_version/package.db && pwd)"
|
||||
LOCAL_PACKAGE_DB="$ROOT/dist-newstyle/packagedb/ghc-$ghc_version"
|
||||
GLOBAL_PACKAGE_DB="$(ghc-pkg list --global | head -n 1)"
|
||||
export GHC_PACKAGE_PATH="$LOCAL_PACKAGE_DB:$CABAL_STORE_PACKAGE_DB:$GLOBAL_PACKAGE_DB:$GHC_PACKAGE_PATH"
|
||||
|
||||
bundle exec licensed <args>
|
||||
```
|
||||
|
||||
### Specifying GHC packagedb locations through configuration
|
||||
Alternatively, the `cabal` source can use packagedb locations set in the app configuration. The following is an example configuration identical to the above environment configuration.
|
||||
|
||||
```yml
|
||||
cabal:
|
||||
ghc_package_db:
|
||||
- ~/.cabal/store/ghc-<ghc_version>/package.db
|
||||
- dist-newstyle/packagedb/ghc-<ghc_version>
|
||||
- global
|
||||
```
|
||||
|
||||
Ordering is preserved when evaluating the configuration values. Paths are expanded from the root of the `git` repository and used as values for CLI `--package-db="<path>"` arguments. Additionally, in all specified paths, `<ghc_version>` will be replaced with the result of `ghc --numeric-version`.
|
||||
|
||||
The `global` and `user` keywords are supported and will expand to the `--global` and `--user` CLI arguments, respectively.
|
||||
|
||||
Like most other settings, the `cabal` configuration can be specified for the root configuration, or per-app. If specified at root, it will be inherited by all apps unless explicitly overridden.
|
||||
|
||||
### Stack sandboxes
|
||||
The current recommended way to use `licensed` with stack is to run the `licensed` command with `stack exec`: `stack exec -- bundle exec licensed <args>`. This will allow stack to control the GHC packagedb locations.
|
|
@ -0,0 +1,12 @@
|
|||
# Go
|
||||
|
||||
The go source uses `go` CLI commands to enumerate dependencies and properties. It is expected that `go` projects have been built, and that `GO_PATH` and `GO_ROOT` are properly set before running `licensed`.
|
||||
|
||||
Source paths for go projects should point to a location that contains an entrypoint to the executable or library.
|
||||
|
||||
An example usage might see a configuration like:
|
||||
```YAML
|
||||
source_path: go/path/src/github.com/BurntSushi/toml/cmd/tomlv
|
||||
```
|
||||
|
||||
Note that this configuration points directly to the tomlv command source, which contains `func main`.
|
|
@ -0,0 +1,26 @@
|
|||
# Manifests
|
||||
|
||||
The manifest source can be used when no package managers are available.
|
||||
|
||||
Manifest files are used to match source files with their corresponding packages to find package dependencies. Manifest file paths can be specified in the app configuration with the following setting:
|
||||
```yml
|
||||
manifest:
|
||||
path: 'path/to/manifest.json'
|
||||
```
|
||||
|
||||
If a manifest path is not specified for an app, the file will be looked for at the apps `<cache_path>/manifest.json`.
|
||||
|
||||
The manifest can be a JSON or YAML file with a single root object and properties mapping file paths to package names.
|
||||
```JSON
|
||||
{
|
||||
"file1": "package1",
|
||||
"path/to/file2": "package1",
|
||||
"other/file3": "package2"
|
||||
}
|
||||
```
|
||||
|
||||
File paths are relative to the git repository root. Package names will be used for the metadata file names at `path/to/cache/manifest/<package>.txt`
|
||||
|
||||
If multiple source files map to a single package and they share a common path under the git repository root, that directory will be used to find license information, if available.
|
||||
|
||||
It is the responsibility of the repository owner to maintain the manifest file.
|
|
@ -0,0 +1,3 @@
|
|||
# NPM
|
||||
|
||||
The npm source will detect dependencies when the source is enabled and `package.json` is found at an apps `source_path`. It uses `npm list` to enumerate dependencies and metadata.
|
|
@ -0,0 +1,3 @@
|
|||
# HaskellStack
|
||||
|
||||
It is not recommended to use this source. Please see [cabal documentation](./cabal.md) for using the cabal source with stack.
|
|
@ -0,0 +1,5 @@
|
|||
#!/usr/bin/env ruby
|
||||
# frozen_string_literal: true
|
||||
|
||||
require "licensed/cli"
|
||||
Licensed::CLI.start
|
|
@ -0,0 +1,46 @@
|
|||
# frozen_string_literal: true
|
||||
require "licensed/version"
|
||||
require "licensed/shell"
|
||||
require "licensed/configuration"
|
||||
require "licensed/license"
|
||||
require "licensed/dependency"
|
||||
require "licensed/git"
|
||||
require "licensed/source/bundler"
|
||||
require "licensed/source/bower"
|
||||
require "licensed/source/manifest"
|
||||
require "licensed/source/npm"
|
||||
require "licensed/source/go"
|
||||
require "licensed/source/cabal"
|
||||
require "licensed/command/cache"
|
||||
require "licensed/command/status"
|
||||
require "licensed/command/list"
|
||||
require "licensed/ui/shell"
|
||||
require "octokit"
|
||||
|
||||
module Licensed
|
||||
class << self
|
||||
attr_accessor :use_github
|
||||
end
|
||||
|
||||
self.use_github = true
|
||||
|
||||
GITHUB_URL = %r{\Ahttps://github.com/([a-z0-9]+(-[a-z0-9]+)*/(\w|\.|\-)+)}
|
||||
LICENSE_CONTENT_TYPE = "application/vnd.github.drax-preview+json"
|
||||
|
||||
# Load license content from a GitHub url. Returns nil if the url does not point
|
||||
# to a GitHub repository, or if the license content is not found
|
||||
def self.from_github(url)
|
||||
return unless use_github && match = GITHUB_URL.match(url)
|
||||
|
||||
license_url = Octokit::Repository.path(match[1]) + "/license"
|
||||
response = octokit.get license_url, accept: LICENSE_CONTENT_TYPE
|
||||
content = Base64.decode64(response["content"]).force_encoding("UTF-8")
|
||||
Licensee::ProjectFiles::LicenseFile.new(content, response["name"])
|
||||
rescue Octokit::NotFound
|
||||
nil
|
||||
end
|
||||
|
||||
def self.octokit
|
||||
@octokit ||= Octokit::Client.new(access_token: ENV["GITHUB_TOKEN"])
|
||||
end
|
||||
end
|
|
@ -0,0 +1,52 @@
|
|||
# frozen_string_literal: true
|
||||
require "licensed"
|
||||
require "thor"
|
||||
|
||||
module Licensed
|
||||
class CLI < Thor
|
||||
|
||||
desc "cache", "Cache the licenses of dependencies"
|
||||
method_option :force, type: :boolean,
|
||||
desc: "Overwrite licenses even if version has not changed."
|
||||
method_option :offline, type: :boolean,
|
||||
desc: "Do not make network calls."
|
||||
method_option :config, aliases: "-c", type: :string,
|
||||
desc: "Path to licensed configuration file"
|
||||
def cache
|
||||
Licensed.use_github = false if options[:offline]
|
||||
run Licensed::Command::Cache.new(config), force: options[:force]
|
||||
end
|
||||
|
||||
desc "status", "Check status of dependencies' cached licenses"
|
||||
method_option :config, aliases: "-c", type: :string,
|
||||
desc: "Path to licensed configuration file"
|
||||
def status
|
||||
run Licensed::Command::Status.new(config)
|
||||
end
|
||||
|
||||
desc "list", "List dependencies"
|
||||
method_option :config, aliases: "-c", type: :string,
|
||||
desc: "Path to licensed configuration file"
|
||||
def list
|
||||
run Licensed::Command::List.new(config)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
# Returns a configuration object for the CLI command
|
||||
def config
|
||||
@config ||= Configuration.load_from(config_path)
|
||||
end
|
||||
|
||||
# Returns a config path from the CLI if set.
|
||||
# Defaults to the current directory if CLI option is not set
|
||||
def config_path
|
||||
options["config"] || Dir.pwd
|
||||
end
|
||||
|
||||
def run(command, *args)
|
||||
command.run(*args)
|
||||
exit command.success?
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,76 @@
|
|||
# frozen_string_literal: true
|
||||
module Licensed
|
||||
module Command
|
||||
class Cache
|
||||
attr_reader :config
|
||||
|
||||
def initialize(config)
|
||||
@config = config
|
||||
end
|
||||
|
||||
def run(force: false)
|
||||
summary = @config.apps.flat_map do |app|
|
||||
app_name = app["name"]
|
||||
@config.ui.info "Caching licenes for #{app_name}:"
|
||||
|
||||
# load the app environment
|
||||
Dir.chdir app.source_path do
|
||||
|
||||
# map each available app source to it's dependencies
|
||||
app.sources.map do |source|
|
||||
@config.ui.info " #{source.type} dependencies:"
|
||||
|
||||
names = []
|
||||
cache_path = app.cache_path.join(source.type)
|
||||
|
||||
# exclude ignored dependencies
|
||||
dependencies = source.dependencies.select { |d| !app.ignored?(d) }
|
||||
|
||||
# ensure each dependency is cached
|
||||
dependencies.each do |dependency|
|
||||
name = dependency["name"]
|
||||
version = dependency["version"]
|
||||
|
||||
names << name
|
||||
filename = cache_path.join("#{name}.txt")
|
||||
|
||||
if File.exist?(filename)
|
||||
license = Licensed::License.read(filename)
|
||||
|
||||
# Version did not change, no need to re-cache
|
||||
if !force && version == license["version"]
|
||||
@config.ui.info " Using #{name} (#{version})"
|
||||
next
|
||||
end
|
||||
end
|
||||
|
||||
@config.ui.info " Caching #{name} (#{version})"
|
||||
|
||||
dependency.detect_license!
|
||||
dependency.save(filename)
|
||||
end
|
||||
|
||||
# Clean up cached files that dont match current dependencies
|
||||
Dir.glob(cache_path.join("**/*.txt")).each do |file|
|
||||
file_path = Pathname.new(file)
|
||||
relative_path = file_path.relative_path_from(cache_path).to_s
|
||||
FileUtils.rm(file) unless names.include?(relative_path.chomp(".txt"))
|
||||
end
|
||||
|
||||
"* #{app_name} #{source.type} dependencies: #{dependencies.size}"
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@config.ui.confirm "License caching complete!"
|
||||
summary.each do |message|
|
||||
@config.ui.confirm message
|
||||
end
|
||||
end
|
||||
|
||||
def success?
|
||||
true
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,41 @@
|
|||
# frozen_string_literal: true
|
||||
module Licensed
|
||||
module Command
|
||||
class List
|
||||
attr_reader :config
|
||||
|
||||
def initialize(config)
|
||||
@config = config
|
||||
end
|
||||
|
||||
def run
|
||||
@config.apps.each do |app|
|
||||
@config.ui.info "Displaying dependencies for #{app['name']}"
|
||||
Dir.chdir app.source_path do
|
||||
app.sources.each do |source|
|
||||
@config.ui.info " #{source.type} dependencies:"
|
||||
|
||||
source_dependencies = dependencies(app, source)
|
||||
source_dependencies.each do |dependency|
|
||||
@config.ui.info " Found #{dependency['name']} (#{dependency['version']})"
|
||||
end
|
||||
|
||||
@config.ui.confirm " * #{source.type} dependencies: #{source_dependencies.size}"
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# Returns an apps non-ignored dependencies, sorted by name
|
||||
def dependencies(app, source)
|
||||
source.dependencies
|
||||
.select { |d| !app.ignored?(d) }
|
||||
.sort_by { |d| d["name"] }
|
||||
end
|
||||
|
||||
def success?
|
||||
true
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,78 @@
|
|||
# frozen_string_literal: true
|
||||
require "yaml"
|
||||
|
||||
module Licensed
|
||||
module Command
|
||||
class Status
|
||||
attr_reader :config
|
||||
|
||||
def initialize(config)
|
||||
@config = config
|
||||
end
|
||||
|
||||
def allowed_or_reviewed?(app, dependency)
|
||||
app.allowed?(dependency) || app.reviewed?(dependency)
|
||||
end
|
||||
|
||||
def app_dependencies(app)
|
||||
app.sources.flat_map(&:dependencies).select { |d| !app.ignored?(d) }
|
||||
end
|
||||
|
||||
def run
|
||||
@results = @config.apps.flat_map do |app|
|
||||
Dir.chdir app.source_path do
|
||||
dependencies = app_dependencies(app)
|
||||
@config.ui.info "Checking licenses for #{app['name']}: #{dependencies.size} dependencies"
|
||||
|
||||
results = dependencies.map do |dependency|
|
||||
filename = app.cache_path.join(dependency["type"], "#{dependency["name"]}.txt")
|
||||
|
||||
warnings = []
|
||||
|
||||
# verify cached license data for dependency
|
||||
if File.exist?(filename)
|
||||
license = License.read(filename)
|
||||
|
||||
if license["version"] != dependency["version"]
|
||||
warnings << "cached license data out of date"
|
||||
end
|
||||
warnings << "missing license text" if license.text.strip.empty?
|
||||
unless allowed_or_reviewed?(app, license)
|
||||
warnings << "license needs reviewed: #{license["license"]}."
|
||||
end
|
||||
else
|
||||
warnings << "cached license data missing"
|
||||
end
|
||||
|
||||
if warnings.size > 0
|
||||
@config.ui.error("F", false)
|
||||
[filename, warnings]
|
||||
else
|
||||
@config.ui.confirm(".", false)
|
||||
nil
|
||||
end
|
||||
end.compact
|
||||
|
||||
unless results.empty?
|
||||
@config.ui.warn "\n\nWarnings:"
|
||||
|
||||
results.each do |filename, warnings|
|
||||
@config.ui.info "\n#{filename}:"
|
||||
warnings.each do |warning|
|
||||
@config.ui.error " - #{warning}"
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
puts "\n#{dependencies.size} dependencies checked, #{results.size} warnings found."
|
||||
results
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def success?
|
||||
@results.empty?
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,181 @@
|
|||
# frozen_string_literal: true
|
||||
require "pathname"
|
||||
|
||||
module Licensed
|
||||
class AppConfiguration < Hash
|
||||
DEFAULT_CACHE_PATH = ".licenses".freeze
|
||||
DEFAULT_CONFIG_FILES = [
|
||||
".licensed.yml".freeze,
|
||||
".licensed.yaml".freeze,
|
||||
".licensed.json".freeze
|
||||
].freeze
|
||||
|
||||
def initialize(options = {}, inherited_options = {})
|
||||
super()
|
||||
|
||||
# update order:
|
||||
# 1. anything inherited from root config
|
||||
# 2. app defaults
|
||||
# 3. explicitly configured app settings
|
||||
update(inherited_options)
|
||||
update(defaults_for(options, inherited_options))
|
||||
update(options)
|
||||
|
||||
self["sources"] ||= {}
|
||||
self["reviewed"] ||= {}
|
||||
self["ignored"] ||= {}
|
||||
self["allowed"] ||= []
|
||||
|
||||
verify_arg "source_path"
|
||||
verify_arg "cache_path"
|
||||
end
|
||||
|
||||
# Returns the path to the app cache directory as a Pathname
|
||||
def cache_path
|
||||
Licensed::Git.repository_root.join(self["cache_path"])
|
||||
end
|
||||
|
||||
# Returns the path to the app source directory as a Pathname
|
||||
def source_path
|
||||
Licensed::Git.repository_root.join(self["source_path"])
|
||||
end
|
||||
|
||||
def pwd
|
||||
Pathname.pwd
|
||||
end
|
||||
|
||||
# Returns an array of enabled app sources
|
||||
def sources
|
||||
@sources ||= [
|
||||
Source::Bundler.new(self),
|
||||
Source::Bower.new(self),
|
||||
Source::Cabal.new(self),
|
||||
Source::Go.new(self),
|
||||
Source::Manifest.new(self),
|
||||
Source::NPM.new(self)
|
||||
].select(&:enabled?)
|
||||
end
|
||||
|
||||
# Returns whether a source type is enabled
|
||||
def enabled?(source_type)
|
||||
self["sources"].fetch(source_type, true)
|
||||
end
|
||||
|
||||
# Is the given dependency reviewed?
|
||||
def reviewed?(dependency)
|
||||
Array(self["reviewed"][dependency["type"]]).include?(dependency["name"])
|
||||
end
|
||||
|
||||
# Is the given dependency ignored?
|
||||
def ignored?(dependency)
|
||||
Array(self["ignored"][dependency["type"]]).include?(dependency["name"])
|
||||
end
|
||||
|
||||
# Is the license of the dependency allowed?
|
||||
def allowed?(dependency)
|
||||
Array(self["allowed"]).include?(dependency["license"])
|
||||
end
|
||||
|
||||
# Ignore a dependency
|
||||
def ignore(dependency)
|
||||
(self["ignored"][dependency["type"]] ||= []) << dependency["name"]
|
||||
end
|
||||
|
||||
# Set a dependency as reviewed
|
||||
def review(dependency)
|
||||
(self["reviewed"][dependency["type"]] ||= []) << dependency["name"]
|
||||
end
|
||||
|
||||
# Set a license as explicitly allowed
|
||||
def allow(license)
|
||||
self["allowed"] << license
|
||||
end
|
||||
|
||||
def defaults_for(options, inherited_options)
|
||||
name = options["name"] || File.basename(options["source_path"])
|
||||
cache_path = inherited_options["cache_path"] || DEFAULT_CACHE_PATH
|
||||
{
|
||||
"name" => name,
|
||||
"cache_path" => File.join(cache_path, name)
|
||||
}
|
||||
end
|
||||
|
||||
def verify_arg(property)
|
||||
return if self[property]
|
||||
raise Licensed::Configuration::LoadError,
|
||||
"App #{self["name"]} is missing required property #{property}"
|
||||
end
|
||||
end
|
||||
|
||||
class Configuration < AppConfiguration
|
||||
class LoadError < StandardError; end
|
||||
|
||||
attr_accessor :ui
|
||||
|
||||
# Loads and returns a Licensed::Configuration object from the given path.
|
||||
# The path can be relative or absolute, and can point at a file or directory.
|
||||
# If the path given is a directory, the directory will be searched for a
|
||||
# `config.yml` file.
|
||||
def self.load_from(path)
|
||||
config_path = Pathname.pwd.join(path)
|
||||
config_path = find_config(config_path) if config_path.directory?
|
||||
Configuration.new(parse_config(config_path))
|
||||
end
|
||||
|
||||
def initialize(options = {})
|
||||
@ui = Licensed::UI::Shell.new
|
||||
|
||||
apps = options.delete("apps") || []
|
||||
super(default_options.merge(options))
|
||||
|
||||
self["apps"] = apps.map { |app| AppConfiguration.new(app, options) }
|
||||
end
|
||||
|
||||
# Returns an array of the applications for this licensed configuration.
|
||||
# If the configuration did not explicitly configure any applications,
|
||||
# return self as an application configuration.
|
||||
def apps
|
||||
return [self] if self["apps"].empty?
|
||||
self["apps"]
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
# Find a default configuration file in the given directory.
|
||||
# File preference is given by the order of elements in DEFAULT_CONFIG_FILES
|
||||
#
|
||||
# Raises Licensed::Configuration::LoadError if a file isn't found
|
||||
def self.find_config(directory)
|
||||
config_file = DEFAULT_CONFIG_FILES.map { |file| directory.join(file) }
|
||||
.find { |file| file.exist? }
|
||||
|
||||
config_file || raise(LoadError, "Licensed configuration not found in #{directory}")
|
||||
end
|
||||
|
||||
# Parses the configuration given at `config_path` and returns the values
|
||||
# as a Hash
|
||||
#
|
||||
# Raises Licensed::Configuration::LoadError if the file type isn't known
|
||||
def self.parse_config(config_path)
|
||||
return {} unless config_path.file?
|
||||
|
||||
extension = config_path.extname.downcase.delete "."
|
||||
case extension
|
||||
when "json"
|
||||
JSON.parse(File.read(config_path))
|
||||
when "yml", "yaml"
|
||||
YAML.load_file(config_path)
|
||||
else
|
||||
raise LoadError, "Unknown file type #{extension} for #{config_path}"
|
||||
end
|
||||
end
|
||||
|
||||
def default_options
|
||||
# manually set a cache path without additional name
|
||||
{
|
||||
"source_path" => Dir.pwd,
|
||||
"cache_path" => DEFAULT_CACHE_PATH
|
||||
}
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,100 @@
|
|||
# frozen_string_literal: true
|
||||
require "licensee"
|
||||
|
||||
module Licensed
|
||||
class Dependency < License
|
||||
LEGAL_FILES = /\A(AUTHORS|COPYING|NOTICE|LEGAL)(?:\..*)?\z/i
|
||||
|
||||
attr_reader :path
|
||||
attr_reader :search_root
|
||||
|
||||
def initialize(path, metadata = {})
|
||||
@path = path
|
||||
@search_root = metadata.delete("search_root")
|
||||
|
||||
# with licensee providing license_file[:dir],
|
||||
# enforcing absolute paths makes life much easier when determining
|
||||
# an absolute file path in notices
|
||||
unless Pathname.new(path).absolute?
|
||||
raise "Dependency path #{path} must be absolute"
|
||||
end
|
||||
|
||||
super metadata
|
||||
end
|
||||
|
||||
# Returns a Licensee::Projects::FSProject for the dependency path
|
||||
def project
|
||||
@project ||= Licensee::Projects::FSProject.new(path, search_root: search_root, detect_packages: true, detect_readme: true)
|
||||
end
|
||||
|
||||
# Detects license information and sets it on this dependency object.
|
||||
# After calling `detect_license!``, the license is set at
|
||||
# `dependency["license"]` and legal text is set to `dependency.text`
|
||||
def detect_license!
|
||||
self["license"] = license_key
|
||||
self.text = ([license_text] + self.notices).compact.join("\n" + "-" * 80 + "\n")
|
||||
end
|
||||
|
||||
# Extract legal notices from the dependency source
|
||||
def notices
|
||||
local_files.uniq.map { |f| File.read(f) }
|
||||
end
|
||||
|
||||
# Returns an array of file paths used to locate legal notices
|
||||
def local_files
|
||||
return [] unless Dir.exist?(path)
|
||||
|
||||
Dir.foreach(path).map do |file|
|
||||
next unless file.match(LEGAL_FILES)
|
||||
|
||||
file_path = File.join(path, file)
|
||||
next unless File.file?(file_path)
|
||||
|
||||
file_path
|
||||
end.compact
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
# Returns the Licensee::ProjectFile representing the matched_project_file
|
||||
# or remote_license_file
|
||||
def project_file
|
||||
matched_project_file || remote_license_file
|
||||
end
|
||||
|
||||
# Returns the Licensee::LicenseFile, Licensee::PackageManagerFile, or
|
||||
# Licensee::ReadmeFile with a matched license, in that order or nil
|
||||
# if no license file matched a known license
|
||||
def matched_project_file
|
||||
@matched_project_file ||= project.matched_files
|
||||
.select { |f| f.license && !f.license.other? }
|
||||
.first
|
||||
end
|
||||
|
||||
# Returns a Licensee::LicenseFile with the content of the license in the
|
||||
# dependency's repository to account for LICENSE files not being distributed
|
||||
def remote_license_file
|
||||
@remote_license_file ||= Licensed.from_github(self["homepage"])
|
||||
end
|
||||
|
||||
# Regardless of the license detected, try to pull the license content
|
||||
# from the local LICENSE, remote LICENSE, or the README, in that order
|
||||
def license_text
|
||||
content_file = project.license_file || remote_license_file || project.readme_file
|
||||
content_file.content if content_file
|
||||
end
|
||||
|
||||
# Returns a string representing the project's license
|
||||
# Note, this will be "other" if a license file was found but the license
|
||||
# could not be identified and "none" if no license file was found at all
|
||||
def license_key
|
||||
if project_file && project_file.license
|
||||
project_file.license.key
|
||||
elsif project.license_file || remote_license_file
|
||||
"other"
|
||||
else
|
||||
"none"
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,39 @@
|
|||
# frozen_string_literal: true
|
||||
module Licensed
|
||||
module Git
|
||||
class << self
|
||||
# Returns whether git commands are available
|
||||
def available?
|
||||
@git ||= Licensed::Shell.tool_available?("git")
|
||||
end
|
||||
|
||||
def repository_root
|
||||
return unless available?
|
||||
@root ||= Pathname.new(Licensed::Shell.execute("git", "rev-parse", "--show-toplevel"))
|
||||
end
|
||||
|
||||
# Returns the most recent git SHA for a file or directory
|
||||
# or nil if SHA is not available
|
||||
#
|
||||
# descriptor - file or directory to retrieve latest SHA for
|
||||
def version(descriptor)
|
||||
return unless available? && descriptor
|
||||
|
||||
dir = File.directory?(descriptor) ? descriptor : File.dirname(descriptor)
|
||||
file = File.directory?(descriptor) ? "." : File.basename(descriptor)
|
||||
|
||||
Dir.chdir dir do
|
||||
Licensed::Shell.execute("git", "rev-list", "-1", "HEAD", "--", file)
|
||||
end
|
||||
end
|
||||
|
||||
# Returns the commit date for the provided SHA as a timestamp
|
||||
#
|
||||
# sha - commit sha to retrieve date
|
||||
def commit_date(sha)
|
||||
return unless available? && sha
|
||||
Licensed::Shell.execute("git", "show", "-s", "-1", "--format=%ct", sha)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,43 @@
|
|||
# frozen_string_literal: true
|
||||
require "yaml"
|
||||
require "fileutils"
|
||||
require "forwardable"
|
||||
|
||||
module Licensed
|
||||
class License
|
||||
YAML_FRONTMATTER_PATTERN = /\A---\s*\n(.*?\n?)^---\s*$\n?(.*)\z/m
|
||||
LEGAL_FILES = /\A(COPYING|NOTICE|LEGAL)(?:\..*)?\z/i
|
||||
|
||||
# Read an existing license file
|
||||
#
|
||||
# filename - A String path to the file
|
||||
#
|
||||
# Returns a Licensed::License
|
||||
def self.read(filename)
|
||||
match = File.read(filename).scrub.match(YAML_FRONTMATTER_PATTERN)
|
||||
new(YAML.load(match[1]), match[2])
|
||||
end
|
||||
|
||||
extend Forwardable
|
||||
def_delegators :@metadata, :[], :[]=
|
||||
|
||||
# The license text and other legal notices
|
||||
attr_accessor :text
|
||||
|
||||
# Construct a new license
|
||||
#
|
||||
# filename - the String path of the file
|
||||
# metadata - a Hash of the metadata for the package
|
||||
# text - a String of the license text and other legal notices
|
||||
def initialize(metadata = {}, text = nil)
|
||||
@metadata = metadata
|
||||
@text = text
|
||||
end
|
||||
|
||||
# Save the metadata and license to a file
|
||||
def save(filename)
|
||||
FileUtils.mkdir_p(File.dirname(filename))
|
||||
File.write(filename, YAML.dump(@metadata) + "---\n#{text}")
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,28 @@
|
|||
# frozen_string_literal: true
|
||||
require "open3"
|
||||
|
||||
module Licensed
|
||||
module Shell
|
||||
# Executes a command, returning it's STDOUT on success. Returns an empty
|
||||
# string on failure
|
||||
def self.execute(cmd, *args)
|
||||
output, _, status = Open3.capture3(cmd, *args)
|
||||
return "" unless status.success?
|
||||
output.strip
|
||||
end
|
||||
|
||||
# Executes a command and returns a boolean value indicating if the command
|
||||
# was succesful
|
||||
def self.success?(cmd, *args)
|
||||
_, _, status = Open3.capture3(cmd, *args)
|
||||
status.success?
|
||||
end
|
||||
|
||||
# Returns a boolean indicating whether a CLI tool is available in the
|
||||
# current environment
|
||||
def self.tool_available?(tool)
|
||||
output, err, status = Open3.capture3("which", tool)
|
||||
status.success? && !output.strip.empty? && err.strip.empty?
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,53 @@
|
|||
# frozen_string_literal: true
|
||||
require "json"
|
||||
|
||||
module Licensed
|
||||
module Source
|
||||
class Bower
|
||||
def initialize(config)
|
||||
@config = config
|
||||
end
|
||||
|
||||
def type
|
||||
"bower"
|
||||
end
|
||||
|
||||
def enabled?
|
||||
return false unless @config.enabled?(type)
|
||||
|
||||
[@config.pwd.join(".bowerrc"), @config.pwd.join("bower.json")].any? do |path|
|
||||
File.exist?(path)
|
||||
end
|
||||
end
|
||||
|
||||
def dependencies
|
||||
@dependencies ||= Dir.glob(bower_path.join("*/.bower.json")).map do |file|
|
||||
package = JSON.parse(File.read(file))
|
||||
path = bower_path.join(file).dirname.to_path
|
||||
Dependency.new(path, {
|
||||
"type" => type,
|
||||
"name" => package["name"],
|
||||
"version" => package["version"] || package["_release"],
|
||||
"summary" => package["description"],
|
||||
"homepage" => package["homepage"]
|
||||
})
|
||||
end
|
||||
end
|
||||
|
||||
# Returns a parsed ".bowerrc" configuration, or an empty hash if not found
|
||||
def bower_config
|
||||
@bower_config ||= begin
|
||||
path = @config.pwd.join(".bowerrc")
|
||||
path.exist? ? JSON.parse(path.read) : {}
|
||||
end
|
||||
end
|
||||
|
||||
# Returns the expected path to bower components.
|
||||
# Note this does not validate that the returned path is valid
|
||||
def bower_path
|
||||
pwd = bower_config["cwd"] ? Pathname.new(bower_config["cwd"]).expand_path : @config.pwd
|
||||
pwd.join bower_config["directory"] || "bower_components"
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,53 @@
|
|||
# frozen_string_literal: true
|
||||
require "bundler"
|
||||
|
||||
module Licensed
|
||||
module Source
|
||||
class Bundler
|
||||
def initialize(config)
|
||||
@config = config
|
||||
end
|
||||
|
||||
def enabled?
|
||||
@config.enabled?(type) && File.exist?(lockfile_path)
|
||||
end
|
||||
|
||||
def type
|
||||
"rubygem"
|
||||
end
|
||||
|
||||
def dependencies
|
||||
@dependencies ||= definition.specs_for(groups).map do |spec|
|
||||
Dependency.new(spec.gem_dir, {
|
||||
"type" => type,
|
||||
"name" => spec.name,
|
||||
"version" => spec.version.to_s,
|
||||
"summary" => spec.summary,
|
||||
"homepage" => spec.homepage
|
||||
})
|
||||
end
|
||||
end
|
||||
|
||||
# Build the bundler definition
|
||||
def definition
|
||||
@definition ||= ::Bundler::Definition.build(gemfile_path, lockfile_path, nil)
|
||||
end
|
||||
|
||||
# Returns the bundle definition groups, excluding test and development
|
||||
def groups
|
||||
definition.groups - [:test, :development]
|
||||
end
|
||||
|
||||
# Returns the expected path to the Bundler Gemfile
|
||||
def gemfile_path
|
||||
@config.pwd.join ::Bundler.default_gemfile.basename.to_s
|
||||
end
|
||||
|
||||
# Returns the expected path to the Bundler Gemfile.lock
|
||||
def lockfile_path
|
||||
@config.pwd.join ::Bundler.default_lockfile.basename.to_s
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,176 @@
|
|||
# frozen_string_literal: true
|
||||
require "English"
|
||||
|
||||
module Licensed
|
||||
module Source
|
||||
class Cabal
|
||||
def initialize(config)
|
||||
@config = config
|
||||
end
|
||||
|
||||
def type
|
||||
"cabal"
|
||||
end
|
||||
|
||||
def enabled?
|
||||
@config.enabled?(type) && cabal_packages.any? && ghc?
|
||||
end
|
||||
|
||||
def dependencies
|
||||
@dependencies ||= package_ids.map do |id|
|
||||
package = package_info(id)
|
||||
|
||||
path, search_root = package_docs_dirs(package)
|
||||
Dependency.new(path, {
|
||||
"type" => type,
|
||||
"name" => package["name"],
|
||||
"version" => package["version"],
|
||||
"summary" => package["synopsis"],
|
||||
"homepage" => safe_homepage(package["homepage"]),
|
||||
"search_root" => search_root
|
||||
})
|
||||
end
|
||||
end
|
||||
|
||||
# Returns the packages document directory and search root directory
|
||||
# as an array
|
||||
def package_docs_dirs(package)
|
||||
unless package["haddock-html"]
|
||||
# default to a local vendor directory if haddock-html property
|
||||
# isn't available
|
||||
return [File.join(@config.pwd, "vendor", package["name"]), nil]
|
||||
end
|
||||
|
||||
html_dir = package["haddock-html"]
|
||||
data_dir = package["data-dir"]
|
||||
return [html_dir, nil] unless data_dir
|
||||
|
||||
# only allow data directories that are ancestors of the html directory
|
||||
unless Pathname.new(html_dir).fnmatch?(File.join(data_dir, "**"))
|
||||
data_dir = nil
|
||||
end
|
||||
|
||||
[html_dir, data_dir]
|
||||
end
|
||||
|
||||
# Returns a homepage url that enforces https and removes url fragments
|
||||
def safe_homepage(homepage)
|
||||
return unless homepage
|
||||
# use https and remove url fragment
|
||||
homepage.gsub(/http:/, "https:")
|
||||
.gsub(/#[^?]*\z/, "")
|
||||
end
|
||||
|
||||
# Returns a `Set` of the package ids for all cabal dependencies
|
||||
def package_ids
|
||||
deps = cabal_packages.flat_map { |n| package_dependencies(n, false) }
|
||||
recursive_dependencies(deps)
|
||||
end
|
||||
|
||||
# Recursively finds the dependencies for each cabal package.
|
||||
# Returns a `Set` containing the package names for all dependencies
|
||||
def recursive_dependencies(package_names, results = Set.new)
|
||||
return [] if package_names.nil? || package_names.empty?
|
||||
|
||||
new_packages = Set.new(package_names) - results.to_a
|
||||
return [] if new_packages.empty?
|
||||
|
||||
results.merge new_packages
|
||||
|
||||
dependencies = new_packages.flat_map { |n| package_dependencies(n) }
|
||||
.compact
|
||||
|
||||
return results if dependencies.empty?
|
||||
|
||||
results.merge recursive_dependencies(dependencies, results)
|
||||
end
|
||||
|
||||
# Returns an array of dependency package names for the cabal package
|
||||
# given by `id`
|
||||
def package_dependencies(id, full_id = true)
|
||||
package_dependencies_command(id, full_id).gsub("depends:", "")
|
||||
.split
|
||||
.map(&:strip)
|
||||
end
|
||||
|
||||
# Returns the output of running `ghc-pkg field depends` for a package id
|
||||
# Optionally allows for interpreting the given id as an
|
||||
# installed package id (`--ipid`)
|
||||
def package_dependencies_command(id, full_id)
|
||||
fields = %w(depends)
|
||||
|
||||
if full_id
|
||||
ghc_pkg_field_command(id, fields, "--ipid")
|
||||
else
|
||||
ghc_pkg_field_command(id, fields)
|
||||
end
|
||||
end
|
||||
|
||||
# Returns package information as a hash for the given id
|
||||
def package_info(id)
|
||||
package_info_command(id).lines.each_with_object({}) do |line, info|
|
||||
key, value = line.split(":", 2).map(&:strip)
|
||||
next unless key && value
|
||||
|
||||
info[key] = value
|
||||
end
|
||||
end
|
||||
|
||||
# Returns the output of running `ghc-pkg field` to obtain package information
|
||||
def package_info_command(id)
|
||||
fields = %w(name version synopsis homepage haddock-html data-dir)
|
||||
ghc_pkg_field_command(id, fields, "--ipid")
|
||||
end
|
||||
|
||||
# Runs a `ghc-pkg field` command for a given set of fields and arguments
|
||||
# Automatically includes ghc package DB locations in the command
|
||||
def ghc_pkg_field_command(id, fields, *args)
|
||||
Licensed::Shell.execute("ghc-pkg", "field", id, fields.join(","), *args, *package_db_args)
|
||||
end
|
||||
|
||||
# Returns an array of ghc package DB locations as specified in the app
|
||||
# configuration
|
||||
def package_db_args
|
||||
return [] unless @config["cabal"]
|
||||
Array(@config["cabal"]["ghc_package_db"]).map do |path|
|
||||
next "--#{path}" if %w(global user).include?(path)
|
||||
path = realized_ghc_package_path(path)
|
||||
path = File.expand_path(path, @config.pwd)
|
||||
|
||||
next unless File.exist?(path)
|
||||
"--package-db=#{path}"
|
||||
end.compact
|
||||
end
|
||||
|
||||
# Returns a ghc package path with template markers replaced by live
|
||||
# data
|
||||
def realized_ghc_package_path(path)
|
||||
path.gsub("<ghc_version>", ghc_version)
|
||||
end
|
||||
|
||||
# Return an array of the top-level cabal packages for the current app
|
||||
def cabal_packages
|
||||
cabal_files.map do |f|
|
||||
name_match = File.read(f).match(/^name:\s*(.*)$/)
|
||||
name_match[1] if name_match
|
||||
end.compact
|
||||
end
|
||||
|
||||
# Returns an array of the local directory cabal package files
|
||||
def cabal_files
|
||||
@cabal_files ||= Dir.glob(File.join(@config.pwd, "*.cabal"))
|
||||
end
|
||||
|
||||
# Returns the ghc cli tool version
|
||||
def ghc_version
|
||||
return unless ghc?
|
||||
@version ||= Licensed::Shell.execute("ghc", "--numeric-version")
|
||||
end
|
||||
|
||||
# Returns whether the ghc cli tool is available
|
||||
def ghc?
|
||||
@ghc ||= Licensed::Shell.tool_available?("ghc")
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,126 @@
|
|||
# frozen_string_literal: true
|
||||
require "json"
|
||||
require "English"
|
||||
|
||||
module Licensed
|
||||
module Source
|
||||
class Go
|
||||
def initialize(config)
|
||||
@config = config
|
||||
end
|
||||
|
||||
def type
|
||||
"go"
|
||||
end
|
||||
|
||||
def enabled?
|
||||
@config.enabled?(type) && go_source?
|
||||
end
|
||||
|
||||
def dependencies
|
||||
@dependencies ||= packages.map do |package_name|
|
||||
package = package_info(package_name)
|
||||
import_path = non_vendored_import_path(package_name)
|
||||
|
||||
if package.empty?
|
||||
next if @config.ignored?("type" => type, "name" => package_name)
|
||||
raise "couldn't find package for #{import_path}"
|
||||
end
|
||||
|
||||
package_dir = package["Dir"]
|
||||
Dependency.new(package_dir, {
|
||||
"type" => type,
|
||||
"name" => import_path,
|
||||
"summary" => package["Doc"],
|
||||
"homepage" => homepage(import_path),
|
||||
"search_root" => search_root(package_dir),
|
||||
"version" => Licensed::Git.version(package_dir)
|
||||
})
|
||||
end.compact
|
||||
end
|
||||
|
||||
# Returns the homepage for a package import_path. Assumes that the
|
||||
# import path itself is a url domain and path
|
||||
def homepage(import_path)
|
||||
return unless import_path
|
||||
|
||||
# hacky but generally works due to go packages looking like
|
||||
# "github.com/..." or "golang.org/..."
|
||||
"https://#{import_path}"
|
||||
end
|
||||
|
||||
# Returns an array of dependency package import paths
|
||||
def packages
|
||||
return [] unless root_package["Deps"]
|
||||
|
||||
# don't include go std packages
|
||||
# don't include packages under the root project that aren't vendored
|
||||
root_package["Deps"]
|
||||
.uniq
|
||||
.select { |d| !go_std_packages.include?(d) }
|
||||
.select { |d| !d.start_with?(root_package["ImportPath"]) || vendored_path?(d) }
|
||||
end
|
||||
|
||||
# Returns the root directory to search for a package license
|
||||
#
|
||||
# package - package object obtained from package_info
|
||||
def search_root(package_dir)
|
||||
# search root choices:
|
||||
# 1. vendor folder if package is vendored
|
||||
# 2. GOPATH
|
||||
# 3. nil (no search up directory hierarchy)
|
||||
return package_dir.match("^(.*/vendor)/.*$")[1] if vendored_path?(package_dir)
|
||||
ENV.fetch("GOPATH", nil)
|
||||
end
|
||||
|
||||
# Returns whether a package is vendored or not based on the package
|
||||
# import_path
|
||||
#
|
||||
# path - Package path to test
|
||||
def vendored_path?(path)
|
||||
path && path.include?("vendor/")
|
||||
end
|
||||
|
||||
# Returns the import path parameter without the vendor component
|
||||
#
|
||||
# import_path - Package import path with vendor component
|
||||
def non_vendored_import_path(import_path)
|
||||
return unless import_path
|
||||
return import_path unless vendored_path?(import_path)
|
||||
import_path.split("vendor/")[1]
|
||||
end
|
||||
|
||||
# Returns package information, or {} if package isn't found
|
||||
#
|
||||
# package - Go package import path
|
||||
def package_info(package = nil)
|
||||
info = package_info_command(package)
|
||||
return {} if info.empty?
|
||||
JSON.parse(info)
|
||||
end
|
||||
|
||||
# Returns package information as a JSON string
|
||||
#
|
||||
# package - Go package import path
|
||||
def package_info_command(package)
|
||||
package ||= ""
|
||||
Licensed::Shell.execute("go", "list", "-json", package)
|
||||
end
|
||||
|
||||
# Returns the info for the package under test
|
||||
def root_package
|
||||
@root_package ||= package_info
|
||||
end
|
||||
|
||||
# Returns whether go source is found
|
||||
def go_source?
|
||||
@go_source ||= Licensed::Shell.success?("go", "doc")
|
||||
end
|
||||
|
||||
# Returns a list of go standard packages
|
||||
def go_std_packages
|
||||
@std_packages ||= Licensed::Shell.execute("go", "list", "std").lines.map(&:strip)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,80 @@
|
|||
# frozen_string_literal: true
|
||||
require "pathname/common_prefix"
|
||||
|
||||
module Licensed
|
||||
module Source
|
||||
class Manifest
|
||||
def initialize(config)
|
||||
@config = config
|
||||
end
|
||||
|
||||
def enabled?
|
||||
@config.enabled?(type) && File.exist?(manifest_path)
|
||||
end
|
||||
|
||||
def type
|
||||
"manifest"
|
||||
end
|
||||
|
||||
def dependencies
|
||||
@dependencies ||= packages.map do |package_name, sources|
|
||||
Dependency.new(sources_license_path(sources), {
|
||||
"type" => type,
|
||||
"name" => package_name,
|
||||
"version" => package_version(sources)
|
||||
})
|
||||
end
|
||||
end
|
||||
|
||||
# Returns the top-most directory that is common to all paths in `sources`
|
||||
def sources_license_path(sources)
|
||||
common_prefix = Pathname.common_prefix(*sources).to_path
|
||||
|
||||
# don't allow the repo root to be used as common prefix
|
||||
# the project this is run for should be excluded from the manifest,
|
||||
# or ignored in the config. any license in the root should be ignored.
|
||||
return common_prefix if common_prefix != Licensed::Git.repository_root
|
||||
|
||||
# use the first source file as the license path.
|
||||
sources.first
|
||||
end
|
||||
|
||||
# Returns the latest git SHA available from `sources`
|
||||
def package_version(sources)
|
||||
return if sources.nil? || sources.empty?
|
||||
|
||||
sources.map { |s| Licensed::Git.version(s) }
|
||||
.compact
|
||||
.max_by { |sha| Licensed::Git.commit_date(sha) }
|
||||
end
|
||||
|
||||
# Returns a map of package names -> array of full source paths found
|
||||
# in the app manifest
|
||||
def packages
|
||||
manifest.each_with_object({}) do |(src, package_name), hsh|
|
||||
next if src.nil? || src.empty?
|
||||
hsh[package_name] ||= []
|
||||
hsh[package_name] << File.join(Licensed::Git.repository_root, src)
|
||||
end
|
||||
end
|
||||
|
||||
# Returns parsed manifest data for the app
|
||||
def manifest
|
||||
case manifest_path.extname.downcase.delete "."
|
||||
when "json"
|
||||
JSON.parse(File.read(manifest_path))
|
||||
when "yml", "yaml"
|
||||
YAML.load_file(manifest_path)
|
||||
end
|
||||
end
|
||||
|
||||
# Returns the manifest location for the app
|
||||
def manifest_path
|
||||
path = @config["manifest"]["path"] if @config["manifest"]
|
||||
return Licensed::Git.repository_root.join(path) if path
|
||||
|
||||
@config.cache_path.join("manifest.json")
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,70 @@
|
|||
# frozen_string_literal: true
|
||||
require "json"
|
||||
|
||||
module Licensed
|
||||
module Source
|
||||
class NPM
|
||||
def initialize(config)
|
||||
@config = config
|
||||
end
|
||||
|
||||
def type
|
||||
"npm"
|
||||
end
|
||||
|
||||
def enabled?
|
||||
@config.enabled?(type) && File.exist?(@config.pwd.join("package.json"))
|
||||
end
|
||||
|
||||
def dependencies
|
||||
return @dependencies if defined?(@dependencies)
|
||||
|
||||
locations = {}
|
||||
package_location_command.lines.each do |line|
|
||||
path, id = line.split(":")[0, 2]
|
||||
locations[id] ||= path
|
||||
end
|
||||
|
||||
packages = recursive_dependencies(JSON.parse(package_metadata_command)["dependencies"])
|
||||
|
||||
@dependencies = packages.map do |name, package|
|
||||
path = package["realPath"] || locations["#{package["name"]}@#{package["version"]}"]
|
||||
fail "couldn't locate #{name} under node_modules/" unless path
|
||||
Dependency.new(path, {
|
||||
"type" => type,
|
||||
"name" => package["name"],
|
||||
"version" => package["version"],
|
||||
"summary" => package["description"],
|
||||
"homepage" => package["homepage"]
|
||||
})
|
||||
end
|
||||
end
|
||||
|
||||
# Recursively parse dependency JSON data. Returns a hash mapping the
|
||||
# package name to it's metadata
|
||||
def recursive_dependencies(dependencies, result = {})
|
||||
dependencies.each do |name, dependency|
|
||||
(result[name] ||= {}).update(dependency)
|
||||
recursive_dependencies(dependency["dependencies"] || {}, result)
|
||||
end
|
||||
result
|
||||
end
|
||||
|
||||
# Returns the output from running `npm list` to get package paths
|
||||
def package_location_command
|
||||
npm_list_command("--parseable", "--production", "--long")
|
||||
end
|
||||
|
||||
# Returns the output from running `npm list` to get package metadata
|
||||
def package_metadata_command
|
||||
npm_list_command("--json", "--production", "--long")
|
||||
end
|
||||
|
||||
# Executes an `npm list` command with the provided args and returns the
|
||||
# output from stdout
|
||||
def npm_list_command(*args)
|
||||
Licensed::Shell.execute("npm", "list", *args)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,51 @@
|
|||
# frozen_string_literal: true
|
||||
require "thor"
|
||||
|
||||
module Licensed
|
||||
module UI
|
||||
class Shell
|
||||
LEVELS = %w(silent error warn confirm info debug)
|
||||
|
||||
def initialize
|
||||
@shell = STDOUT.tty? ? Thor::Base.shell.new : Thor::Shell::Basic.new
|
||||
@level = ENV["DEBUG"] ? "debug" : "info"
|
||||
end
|
||||
|
||||
def debug(msg, newline = true)
|
||||
@shell.say msg, nil, newline if level?("debug")
|
||||
end
|
||||
|
||||
def info(msg, newline = true)
|
||||
@shell.say msg, nil, newline if level?("info")
|
||||
end
|
||||
|
||||
def confirm(msg, newline = true)
|
||||
@shell.say msg, :green, newline if level?("confirm")
|
||||
end
|
||||
|
||||
def warn(msg, newline = true)
|
||||
@shell.say msg, :yellow, newline if level?("warn")
|
||||
end
|
||||
|
||||
def error(msg, newline = true)
|
||||
@shell.say msg, :red, newline if level?("error")
|
||||
end
|
||||
|
||||
def level=(level)
|
||||
raise ArgumentError unless LEVELS.include?(level.to_s)
|
||||
@level = level
|
||||
end
|
||||
|
||||
def level?(name = nil)
|
||||
name ? LEVELS.index(name) <= LEVELS.index(@level) : @level
|
||||
end
|
||||
|
||||
def silence
|
||||
old_level, @level = @level, "silent"
|
||||
yield
|
||||
ensure
|
||||
@level = old_level
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,4 @@
|
|||
# frozen_string_literal: true
|
||||
module Licensed
|
||||
VERSION = "1.0.0".freeze
|
||||
end
|
|
@ -0,0 +1,36 @@
|
|||
# coding: utf-8
|
||||
# frozen_string_literal: true
|
||||
lib = File.expand_path("../lib", __FILE__)
|
||||
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
||||
require "licensed/version"
|
||||
|
||||
Gem::Specification.new do |spec|
|
||||
spec.name = "licensed"
|
||||
spec.version = Licensed::VERSION
|
||||
spec.authors = ["GitHub"]
|
||||
spec.email = ["opensource+licensed@github.com"]
|
||||
|
||||
spec.summary = %q{Extract and validate the licenses of dependencies.}
|
||||
spec.description = "Licensed automates extracting and validating the licenses of dependencies."
|
||||
|
||||
spec.homepage = "https://github.com/github/licensed"
|
||||
spec.license = "MIT"
|
||||
|
||||
spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
|
||||
spec.bindir = "exe"
|
||||
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
||||
spec.require_paths = ["lib"]
|
||||
|
||||
spec.add_dependency "licensee", "~> 9.0"
|
||||
spec.add_dependency "thor", "~>0.19"
|
||||
spec.add_dependency "octokit", "~>4.0"
|
||||
spec.add_dependency "pathname-common_prefix", "~>0.0.1"
|
||||
|
||||
spec.add_development_dependency "bundler", "~> 1.10"
|
||||
spec.add_development_dependency "rake", "~> 10.0"
|
||||
spec.add_development_dependency "minitest", "~> 5.8"
|
||||
spec.add_development_dependency "vcr", "~> 2.9"
|
||||
spec.add_development_dependency "webmock", "~> 1.21"
|
||||
spec.add_development_dependency "rubocop", "~> 0.49"
|
||||
spec.add_development_dependency "rubocop-github", "~> 0.6"
|
||||
end
|
|
@ -0,0 +1,43 @@
|
|||
#!/bin/bash
|
||||
set -euo pipefail
|
||||
IFS=$'\n\t'
|
||||
|
||||
if [ -n "$(which bundle)" ]; then
|
||||
bundle install --path vendor/gems
|
||||
fi
|
||||
|
||||
cd test/fixtures
|
||||
|
||||
if [ -n "$(which bundle)" ]; then
|
||||
pushd bundler
|
||||
bundle install --path vendor/gems
|
||||
popd
|
||||
fi
|
||||
|
||||
# Install bower fixtures
|
||||
if [ -n "$(which bower)" ]; then
|
||||
pushd bower
|
||||
bower install
|
||||
popd
|
||||
fi
|
||||
|
||||
# Install npm fixtures
|
||||
if [ -n "$(which npm)" ]; then
|
||||
pushd npm
|
||||
npm install
|
||||
popd
|
||||
fi
|
||||
|
||||
if [ -n "$(which go)" ]; then
|
||||
export GOPATH="`pwd`/go"
|
||||
|
||||
pushd go/src/test
|
||||
go get || true
|
||||
popd
|
||||
fi
|
||||
|
||||
if [ -n "$(which cabal)" ]; then
|
||||
pushd haskell
|
||||
cabal new-build
|
||||
popd
|
||||
fi
|
|
@ -0,0 +1,7 @@
|
|||
#!/bin/sh
|
||||
|
||||
set -e
|
||||
|
||||
bundle exec rake test
|
||||
bundle exec rubocop -S -D
|
||||
gem build licensed.gemspec
|
|
@ -0,0 +1,15 @@
|
|||
#!/usr/bin/env ruby
|
||||
# frozen_string_literal: true
|
||||
|
||||
require "bundler/setup"
|
||||
require "licensed"
|
||||
|
||||
# You can add fixtures and/or initialization code here to make experimenting
|
||||
# with your gem easier. You can also use a different console, if you like.
|
||||
|
||||
# (If you use this, don't forget to add pry to your Gemfile!)
|
||||
# require "pry"
|
||||
# Pry.start
|
||||
|
||||
require "irb"
|
||||
IRB.start
|
|
@ -0,0 +1,71 @@
|
|||
# frozen_string_literal: true
|
||||
require "test_helper"
|
||||
|
||||
describe "licensed" do
|
||||
let (:root) { File.expand_path("../../", __FILE__) }
|
||||
let (:config_path) { File.join(root, "test/fixtures/config/.licensed.yml") }
|
||||
|
||||
before do
|
||||
Dir.chdir root
|
||||
end
|
||||
|
||||
describe "cache" do
|
||||
it "exits 0" do
|
||||
_, status = Open3.capture2 "bundle exec exe/licensed cache -c #{config_path}"
|
||||
assert status.success?
|
||||
end
|
||||
|
||||
it "exits 1 when a config file isn't found" do
|
||||
_, _, status = Open3.capture3 "bundle exec exe/licensed cache"
|
||||
refute status.success?
|
||||
end
|
||||
|
||||
it "accepts a path to a config file" do
|
||||
out, status = Open3.capture2 "bundle exec exe/licensed cache -c #{config_path}"
|
||||
refute out =~ /Usage/i
|
||||
|
||||
out, status = Open3.capture2 "bundle exec exe/licensed cache --config #{config_path}"
|
||||
refute out =~ /Usage/i
|
||||
end
|
||||
end
|
||||
|
||||
describe "status" do
|
||||
it "exits 1 when failing" do
|
||||
_, _, status = Open3.capture3 "bundle exec exe/licensed status -c #{config_path}"
|
||||
refute status.success?
|
||||
end
|
||||
|
||||
it "exits 1 when a config file isn't found" do
|
||||
_, _, status = Open3.capture3 "bundle exec exe/licensed status"
|
||||
refute status.success?
|
||||
end
|
||||
|
||||
it "accepts a path to a config file" do
|
||||
out, status = Open3.capture2 "bundle exec exe/licensed status -c #{config_path}"
|
||||
refute out =~ /Usage/i
|
||||
|
||||
out, status = Open3.capture2 "bundle exec exe/licensed status --config #{config_path}"
|
||||
refute out =~ /Usage/i
|
||||
end
|
||||
end
|
||||
|
||||
describe "list" do
|
||||
it "exits 0" do
|
||||
_, status = Open3.capture2 "bundle exec exe/licensed list -c #{config_path}"
|
||||
assert status.success?
|
||||
end
|
||||
|
||||
it "exits 1 when a config file isn't found" do
|
||||
_, _, status = Open3.capture3 "bundle exec exe/licensed list"
|
||||
refute status.success?
|
||||
end
|
||||
|
||||
it "accepts a path to a config file" do
|
||||
out, status = Open3.capture2 "bundle exec exe/licensed list -c #{config_path}"
|
||||
refute out =~ /Usage/i
|
||||
|
||||
out, status = Open3.capture2 "bundle exec exe/licensed list --config #{config_path}"
|
||||
refute out =~ /Usage/i
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,86 @@
|
|||
# frozen_string_literal: true
|
||||
require "test_helper"
|
||||
|
||||
describe Licensed::Command::Cache do
|
||||
let(:config) { Licensed::Configuration.new }
|
||||
let(:generator) { Licensed::Command::Cache.new(config) }
|
||||
|
||||
before do
|
||||
config.ui.level = "silent"
|
||||
FileUtils.rm_rf config.cache_path
|
||||
end
|
||||
|
||||
it "extracts license info for each ruby dep" do
|
||||
generator.run
|
||||
assert config.cache_path.join("rubygem/licensee.txt").exist?
|
||||
license = Licensed::License.read(config.cache_path.join("rubygem/licensee.txt"))
|
||||
assert_equal "licensee", license["name"]
|
||||
assert_equal "mit", license["license"]
|
||||
end
|
||||
|
||||
it "cleans up old dependencies" do
|
||||
FileUtils.mkdir_p config.cache_path.join("rubygem")
|
||||
File.write config.cache_path.join("rubygem/old_dep.txt"), ""
|
||||
generator.run
|
||||
refute config.cache_path.join("rubygem/old_dep.txt").exist?
|
||||
end
|
||||
|
||||
it "cleans up ignored dependencies" do
|
||||
FileUtils.mkdir_p config.cache_path.join("rubygem")
|
||||
File.write config.cache_path.join("rubygem/licensee.txt"), ""
|
||||
config.ignore "type" => "rubygem", "name" => "licensee"
|
||||
generator.run
|
||||
refute config.cache_path.join("rubygem/licensee.txt").exist?
|
||||
end
|
||||
|
||||
it "does not include ignored dependencies in dependency counts" do
|
||||
config.ui.level = "info"
|
||||
out, _ = capture_io { generator.run }
|
||||
count = out.match(/dependencies: (\d+)/)[1].to_i
|
||||
|
||||
FileUtils.mkdir_p config.cache_path.join("rubygem")
|
||||
File.write config.cache_path.join("rubygem/licensee.txt"), ""
|
||||
config.ignore "type" => "rubygem", "name" => "licensee"
|
||||
|
||||
out, _ = capture_io { generator.run }
|
||||
ignored_count = out.match(/dependencies: (\d+)/)[1].to_i
|
||||
assert_equal count - 1, ignored_count
|
||||
end
|
||||
|
||||
describe "with multiple apps" do
|
||||
let(:apps) do
|
||||
[
|
||||
{
|
||||
"name" => "app1",
|
||||
"cache_path" => "vendor/licenses/app1",
|
||||
"source_path" => Dir.pwd
|
||||
},
|
||||
{
|
||||
"name" => "app2",
|
||||
"cache_path" => "vendor/licenses/app2",
|
||||
"source_path" => Dir.pwd
|
||||
}
|
||||
]
|
||||
end
|
||||
let(:config) { Licensed::Configuration.new("apps" => apps) }
|
||||
|
||||
it "caches metadata for all apps" do
|
||||
generator.run
|
||||
assert config["apps"][0].cache_path.join("rubygem/licensee.txt").exist?
|
||||
assert config["apps"][1].cache_path.join("rubygem/licensee.txt").exist?
|
||||
end
|
||||
end
|
||||
|
||||
describe "with app.source_path" do
|
||||
let(:fixtures) { File.expand_path("../../fixtures/npm", __FILE__) }
|
||||
let(:config) { Licensed::Configuration.new("source_path" => fixtures) }
|
||||
|
||||
it "changes the current directory to app.source_path while running" do
|
||||
config.stub(:enabled?, ->(type) { type == "npm" }) do
|
||||
generator.run
|
||||
end
|
||||
|
||||
assert config.cache_path.join("npm/autoprefixer.txt").exist?
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,64 @@
|
|||
# frozen_string_literal: true
|
||||
require "test_helper"
|
||||
|
||||
describe Licensed::Command::List do
|
||||
let(:config) { Licensed::Configuration.new }
|
||||
let(:command) { Licensed::Command::List.new(config) }
|
||||
|
||||
it "lists dependencies for all source types" do
|
||||
out, = capture_io { command.run }
|
||||
config.sources.each do |s|
|
||||
assert_match(/#{s.type} dependencies:/, out)
|
||||
end
|
||||
end
|
||||
|
||||
it "does not include ignored dependencies" do
|
||||
out, = capture_io { command.run }
|
||||
assert_match(/licensee/, out)
|
||||
count = out.match(/dependencies: (\d+)/)[1].to_i
|
||||
|
||||
config.ignore("type" => "rubygem", "name" => "licensee")
|
||||
out, = capture_io { command.run }
|
||||
refute_match(/licensee/, out)
|
||||
ignored_count = out.match(/dependencies: (\d+)/)[1].to_i
|
||||
assert_equal count - 1, ignored_count
|
||||
end
|
||||
|
||||
describe "with multiple apps" do
|
||||
let(:apps) do
|
||||
[
|
||||
{
|
||||
"name" => "app1",
|
||||
"cache_path" => "vendor/licenses/app1",
|
||||
"source_path" => Dir.pwd
|
||||
},
|
||||
{
|
||||
"name" => "app2",
|
||||
"cache_path" => "vendor/licenses/app2",
|
||||
"source_path" => Dir.pwd
|
||||
}
|
||||
]
|
||||
end
|
||||
let(:config) { Licensed::Configuration.new("apps" => apps) }
|
||||
|
||||
it "lists dependencies for all apps" do
|
||||
out, = capture_io { command.run }
|
||||
config.apps.each do |app|
|
||||
assert_match(/Displaying dependencies for #{app['name']}/, out)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe "with app.source_path" do
|
||||
let(:fixtures) { File.expand_path("../../fixtures/npm", __FILE__) }
|
||||
let(:config) { Licensed::Configuration.new("source_path" => fixtures) }
|
||||
|
||||
it "changes the current directory to app.source_path while running" do
|
||||
out, = config.stub(:enabled?, ->(type) { type == "npm" }) do
|
||||
capture_io { command.run }
|
||||
end
|
||||
|
||||
assert_match(/Found autoprefixer/, out)
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,117 @@
|
|||
# frozen_string_literal: true
|
||||
require "test_helper"
|
||||
|
||||
describe Licensed::Command::Status do
|
||||
let(:config) { Licensed::Configuration.new }
|
||||
|
||||
before do
|
||||
config.ui.silence do
|
||||
Licensed::Command::Cache.new(config.dup).run(force: true)
|
||||
end
|
||||
@verifier = Licensed::Command::Status.new(config)
|
||||
end
|
||||
|
||||
it "warns if license is not allowed" do
|
||||
out, _ = capture_io { @verifier.run }
|
||||
assert_match(/license needs reviewed: mit/, out)
|
||||
end
|
||||
|
||||
it "does not warn if license is allowed" do
|
||||
config.allow "mit"
|
||||
out, _ = capture_io { @verifier.run }
|
||||
refute_match(/license needs reviewed: mit/, out)
|
||||
end
|
||||
|
||||
it "does not warn if dependency is ignored" do
|
||||
out, _ = capture_io { @verifier.run }
|
||||
assert_match(/licensee.txt/, out)
|
||||
|
||||
config.ignore "type" => "rubygem", "name" => "licensee"
|
||||
out, _ = capture_io { @verifier.run }
|
||||
refute_match(/licensee.txt/, out)
|
||||
end
|
||||
|
||||
it "does not warn if dependency is reviewed" do
|
||||
out, _ = capture_io { @verifier.run }
|
||||
assert_match(/licensee/, out)
|
||||
|
||||
config.review "type" => "rubygem", "name" => "licensee"
|
||||
out, _ = capture_io { @verifier.run }
|
||||
refute_match(/licensee/, out)
|
||||
end
|
||||
|
||||
it "warns if license is empty" do
|
||||
filename = config.cache_path.join("rubygem/licensee.txt")
|
||||
license = Licensed::License.read(filename)
|
||||
license.text = ""
|
||||
license.save(filename)
|
||||
|
||||
out, _ = capture_io { @verifier.run }
|
||||
assert_match(/missing license text/, out)
|
||||
end
|
||||
|
||||
it "warns if versions do not match" do
|
||||
@verifier.app_dependencies(config.apps.first).first["version"] = "nope"
|
||||
out, _ = capture_io { @verifier.run }
|
||||
assert_match(/cached license data out of date/, out)
|
||||
end
|
||||
|
||||
it "warns if cached license data missing" do
|
||||
FileUtils.rm config.cache_path.join("rubygem/licensee.txt")
|
||||
out, _ = capture_io { @verifier.run }
|
||||
assert_match(/cached license data missing/, out)
|
||||
end
|
||||
|
||||
it "does not warn if cached license data missing for ignored gem" do
|
||||
FileUtils.rm config.cache_path.join("rubygem/licensee.txt")
|
||||
config.ignore "type" => "rubygem", "name" => "licensee"
|
||||
|
||||
out, _ = capture_io { @verifier.run }
|
||||
refute_match(/licensee/, out)
|
||||
end
|
||||
|
||||
it "does not include ignored dependencies in dependency counts" do
|
||||
out, _ = capture_io { @verifier.run }
|
||||
count = out.match(/(\d+) dependencies checked/)[1].to_i
|
||||
|
||||
config.ignore "type" => "rubygem", "name" => "licensee"
|
||||
out, _ = capture_io { @verifier.run }
|
||||
ignored_count = out.match(/(\d+) dependencies checked/)[1].to_i
|
||||
assert_equal count - 1, ignored_count
|
||||
end
|
||||
|
||||
describe "with multiple apps" do
|
||||
let(:apps) do
|
||||
[
|
||||
{
|
||||
"name" => "app1",
|
||||
"cache_path" => "vendor/licenses/app1",
|
||||
"source_path" => Dir.pwd
|
||||
},
|
||||
{
|
||||
"name" => "app2",
|
||||
"cache_path" => "vendor/licenses/app2",
|
||||
"source_path" => Dir.pwd
|
||||
}
|
||||
]
|
||||
end
|
||||
let(:config) { Licensed::Configuration.new("apps" => apps) }
|
||||
|
||||
it "verifies dependencies for all apps" do
|
||||
out, _ = capture_io { @verifier.run }
|
||||
config.apps.each do |app|
|
||||
assert_match(/Checking licenses for #{app['name']}/, out)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe "with app.source_path" do
|
||||
let(:fixtures) { File.expand_path("../../fixtures/npm", __FILE__) }
|
||||
let(:config) { Licensed::Configuration.new("source_path" => fixtures) }
|
||||
|
||||
it "changes the current directory to app.source_path while running" do
|
||||
out, _ = capture_io { @verifier.run }
|
||||
assert_match(/autoprefixer.txt:\s+?- license needs reviewed/, out)
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,191 @@
|
|||
# frozen_string_literal: true
|
||||
require "test_helper"
|
||||
|
||||
describe Licensed::Configuration do
|
||||
let(:config) { Licensed::Configuration.new }
|
||||
|
||||
before do
|
||||
@package = {"type" => "rubygem", "name" => "bundler", "license" => "mit"}
|
||||
end
|
||||
|
||||
it "accepts a license directory path option" do
|
||||
config["cache_path"] = "path"
|
||||
assert_equal Licensed::Git.repository_root.join("path"), config.cache_path
|
||||
end
|
||||
|
||||
it "sets default values" do
|
||||
assert_equal Pathname.pwd, config.source_path
|
||||
assert_equal Licensed::Git.repository_root.join(".licenses"),
|
||||
config.cache_path
|
||||
assert_equal File.basename(Dir.pwd), config["name"]
|
||||
end
|
||||
|
||||
describe "load_from" do
|
||||
let(:fixtures) { File.expand_path("../fixtures/config", __FILE__) }
|
||||
|
||||
it "loads a config from a relative directory path" do
|
||||
relative_path = Pathname.new(fixtures).relative_path_from(Pathname.pwd)
|
||||
config = Licensed::Configuration.load_from(relative_path)
|
||||
assert_equal "licensed-yml", config["name"]
|
||||
end
|
||||
|
||||
it "loads a config from an absolute directory path" do
|
||||
config = Licensed::Configuration.load_from(fixtures)
|
||||
assert_equal "licensed-yml", config["name"]
|
||||
end
|
||||
|
||||
it "loads a config from a relative file path" do
|
||||
file = File.join(fixtures, "config.yml")
|
||||
relative_path = Pathname.new(file).relative_path_from(Pathname.pwd)
|
||||
config = Licensed::Configuration.load_from(relative_path)
|
||||
assert_equal "config-yml", config["name"]
|
||||
end
|
||||
|
||||
it "loads a config from an absolute file path" do
|
||||
file = File.join(fixtures, "config.yml")
|
||||
config = Licensed::Configuration.load_from(file)
|
||||
assert_equal "config-yml", config["name"]
|
||||
end
|
||||
|
||||
it "loads json configurations" do
|
||||
file = File.join(fixtures, ".licensed.json")
|
||||
config = Licensed::Configuration.load_from(file)
|
||||
assert_equal "licensed-json", config["name"]
|
||||
end
|
||||
|
||||
it "sets a default cache_path" do
|
||||
config = Licensed::Configuration.load_from(fixtures)
|
||||
assert_equal Pathname.pwd.join(".licenses"), config.cache_path
|
||||
end
|
||||
|
||||
it "raises an error if a default config file is not found" do
|
||||
Dir.mktmpdir do |dir|
|
||||
assert_raises ::Licensed::Configuration::LoadError do
|
||||
Licensed::Configuration.load_from(dir)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
it "raises an error if the config file type is not understood" do
|
||||
file = File.join(fixtures, ".licensed.unknown")
|
||||
assert_raises ::Licensed::Configuration::LoadError do
|
||||
Licensed::Configuration.load_from(file)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe "ignore" do
|
||||
it "marks the dependency as ignored" do
|
||||
refute config.ignored?(@package)
|
||||
config.ignore @package
|
||||
assert config.ignored?(@package)
|
||||
end
|
||||
end
|
||||
|
||||
describe "review" do
|
||||
it "marks the dependency as reviewed" do
|
||||
refute config.reviewed?(@package)
|
||||
config.review @package
|
||||
assert config.reviewed?(@package)
|
||||
end
|
||||
end
|
||||
|
||||
describe "allow" do
|
||||
it "marks the license as allowed" do
|
||||
refute config.allowed?(@package)
|
||||
config.allow "mit"
|
||||
assert config.allowed?(@package)
|
||||
end
|
||||
end
|
||||
|
||||
describe "enabled?" do
|
||||
it "defaults to true for unconfigured source" do
|
||||
assert config.enabled?("npm")
|
||||
end
|
||||
|
||||
it "is false if enabled in config" do
|
||||
config["sources"]["npm"] = true
|
||||
assert config.enabled?("npm")
|
||||
end
|
||||
|
||||
it "is false if disable in config" do
|
||||
config["sources"]["npm"] = false
|
||||
refute config.enabled?("npm")
|
||||
end
|
||||
end
|
||||
|
||||
describe "apps" do
|
||||
it "defaults to returning itself" do
|
||||
assert_equal [config], config.apps
|
||||
end
|
||||
|
||||
describe "from configuration options" do
|
||||
let(:apps) do
|
||||
[
|
||||
{
|
||||
"name" => "app1",
|
||||
"override" => "override",
|
||||
"cache_path" => "app1/vendor/licenses",
|
||||
"source_path" => File.expand_path("../../", __FILE__)
|
||||
},
|
||||
{
|
||||
"name" => "app2",
|
||||
"cache_path" => "app2/vendor/licenses",
|
||||
"source_path" => File.expand_path("../../", __FILE__)
|
||||
}
|
||||
]
|
||||
end
|
||||
let(:config) do
|
||||
Licensed::Configuration.new("apps" => apps,
|
||||
"override" => "default",
|
||||
"default" => "default")
|
||||
end
|
||||
|
||||
it "returns apps from configuration" do
|
||||
assert_equal 2, config.apps.size
|
||||
assert_equal "app1", config.apps[0]["name"]
|
||||
assert_equal "app2", config.apps[1]["name"]
|
||||
end
|
||||
|
||||
it "includes default options" do
|
||||
assert_equal "default", config.apps[0]["default"]
|
||||
assert_equal "default", config.apps[1]["default"]
|
||||
end
|
||||
|
||||
it "overrides default options" do
|
||||
assert_equal "default", config["override"]
|
||||
assert_equal "override", config.apps[0]["override"]
|
||||
end
|
||||
|
||||
it "uses a default name" do
|
||||
apps[0].delete("name")
|
||||
assert_equal "licensed", config.apps[0]["name"]
|
||||
end
|
||||
|
||||
it "uses a default cache path" do
|
||||
apps[0].delete("cache_path")
|
||||
assert_equal Licensed::Git.repository_root.join(".licenses/app1"),
|
||||
config.apps[0].cache_path
|
||||
end
|
||||
|
||||
it "appends the app name to an inherited cache path" do
|
||||
apps[0].delete("cache_path")
|
||||
config = Licensed::Configuration.new("apps" => apps,
|
||||
"cache_path" => "vendor/cache")
|
||||
assert_equal Licensed::Git.repository_root.join("vendor/cache/app1"),
|
||||
config.apps[0].cache_path
|
||||
end
|
||||
|
||||
it "does not append the app name to an explicit cache path" do
|
||||
refute config.apps[0].cache_path.to_s.end_with? config.apps[0]["name"]
|
||||
end
|
||||
|
||||
it "raises an error if source_path is not set on an app" do
|
||||
apps[0].delete("source_path")
|
||||
assert_raises ::Licensed::Configuration::LoadError do
|
||||
Licensed::Configuration.new("apps" => apps)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,143 @@
|
|||
# frozen_string_literal: true
|
||||
require "test_helper"
|
||||
require "tmpdir"
|
||||
|
||||
describe Licensed::Dependency do
|
||||
def mkproject(&block)
|
||||
Dir.mktmpdir do |dir|
|
||||
Dir.chdir dir do
|
||||
yield Licensed::Dependency.new(dir, {})
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe "detect_license!" do
|
||||
it "gets license from license file" do
|
||||
mkproject do |dependency|
|
||||
File.write "LICENSE", Licensee::License.find("mit").text
|
||||
dependency.detect_license!
|
||||
assert_equal "mit", dependency["license"]
|
||||
assert_match(/MIT License/, dependency.text)
|
||||
end
|
||||
end
|
||||
|
||||
it "gets license from github" do
|
||||
Licensed.use_github = true
|
||||
|
||||
VCR.use_cassette("sshirokov/csgtool/license") do
|
||||
dependency = Licensed::Dependency.new(Dir.tmpdir, {
|
||||
"homepage" => "https://github.com/sshirokov/csgtool"
|
||||
})
|
||||
dependency.detect_license!
|
||||
|
||||
assert_equal "mit", dependency["license"]
|
||||
assert_match(/Yaroslav Shirokov/, dependency.text)
|
||||
end
|
||||
end
|
||||
|
||||
it "gets license from package manager" do
|
||||
mkproject do |dependency|
|
||||
File.write "project.gemspec", "s.license = 'mit'"
|
||||
dependency.detect_license!
|
||||
|
||||
assert_equal "mit", dependency["license"]
|
||||
end
|
||||
end
|
||||
|
||||
it "gets license from readme" do
|
||||
mkproject do |dependency|
|
||||
File.write "README.md", "# License\n" + Licensee::License.find("mit").text
|
||||
dependency.detect_license!
|
||||
assert_equal "mit", dependency["license"]
|
||||
assert_match(/MIT License/, dependency.text)
|
||||
end
|
||||
end
|
||||
|
||||
it "pulls license from package manager if LICENSE file is other" do
|
||||
mkproject do |dependency|
|
||||
File.write "LICENSE.md", "See project.gemspec"
|
||||
File.write "project.gemspec", "s.license = 'mit'"
|
||||
dependency.detect_license!
|
||||
|
||||
assert_equal "mit", dependency["license"]
|
||||
end
|
||||
end
|
||||
|
||||
it "pulls license from README if LICENSE and package manager are other" do
|
||||
mkproject do |dependency|
|
||||
File.write "project.gemspec", "foo"
|
||||
File.write "README.md", "# License\n" + Licensee::License.find("mit").text
|
||||
dependency.detect_license!
|
||||
|
||||
assert_equal "mit", dependency["license"]
|
||||
assert_match(/MIT License/, dependency.text)
|
||||
end
|
||||
end
|
||||
|
||||
it "pulls license from GitHub if local sources are all other" do
|
||||
mkproject do |dependency|
|
||||
File.write "LICENSE.md", "See README"
|
||||
File.write "project.gemspec", "foo"
|
||||
File.write "README.txt", "# License\n" + "The Remote MIT license"
|
||||
Licensed.use_github = true
|
||||
|
||||
VCR.use_cassette("sshirokov/csgtool/license") do
|
||||
dependency = Licensed::Dependency.new(Dir.tmpdir, {
|
||||
"homepage" => "https://github.com/sshirokov/csgtool"
|
||||
})
|
||||
dependency.detect_license!
|
||||
|
||||
assert_equal "mit", dependency["license"]
|
||||
assert_match(/Yaroslav Shirokov/, dependency.text)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
it "extracts other legal notices" do
|
||||
mkproject do |dependency|
|
||||
File.write "AUTHORS", "authors"
|
||||
File.write "COPYING", "copying"
|
||||
File.write "NOTICE", "notice"
|
||||
File.write "LEGAL", "legal"
|
||||
|
||||
dependency.detect_license!
|
||||
|
||||
assert_match(/authors/, dependency.text)
|
||||
assert_match(/copying/, dependency.text)
|
||||
assert_match(/notice/, dependency.text)
|
||||
assert_match(/legal/, dependency.text)
|
||||
end
|
||||
end
|
||||
|
||||
it "sets license to other if undetected" do
|
||||
mkproject do |dependency|
|
||||
File.write "LICENSE", "some unknown license"
|
||||
dependency.detect_license!
|
||||
assert_equal "other", dependency["license"]
|
||||
end
|
||||
end
|
||||
|
||||
it "sets license to none if no license found" do
|
||||
mkproject do |dependency|
|
||||
dependency.detect_license!
|
||||
assert_equal "none", dependency["license"]
|
||||
end
|
||||
end
|
||||
|
||||
it "finds license content outside of the dependency path" do
|
||||
Dir.mktmpdir do |dir|
|
||||
Dir.chdir dir do
|
||||
File.write "LICENSE", "license"
|
||||
|
||||
Dir.mkdir "dependency"
|
||||
Dir.chdir "dependency" do
|
||||
dep = Licensed::Dependency.new(Dir.pwd, "search_root" => File.expand_path(".."))
|
||||
dep.detect_license!
|
||||
|
||||
assert_equal "license", dep.text
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,9 @@
|
|||
{
|
||||
"name": "licensed",
|
||||
"dependencies": {
|
||||
"jquery": "2.1.4"
|
||||
},
|
||||
"devDependencies": {
|
||||
"mocha": "2.3.3"
|
||||
}
|
||||
}
|
|
@ -0,0 +1,4 @@
|
|||
# frozen_string_literal: true
|
||||
source "https://rubygems.org"
|
||||
|
||||
gem "semantic", "1.6.0"
|
|
@ -0,0 +1,3 @@
|
|||
{
|
||||
"name": "licensed-json"
|
||||
}
|
|
@ -0,0 +1 @@
|
|||
name: licensed-yml
|
|
@ -0,0 +1 @@
|
|||
name: config-yml
|
|
@ -0,0 +1,6 @@
|
|||
package test
|
||||
|
||||
import (
|
||||
"github.com/hashicorp/golang-lru"
|
||||
"github.com/gorilla/context"
|
||||
)
|
7
test/fixtures/go/src/test/vendor/github.com/gorilla/context/.travis.yml
сгенерированный
поставляемый
Normal file
7
test/fixtures/go/src/test/vendor/github.com/gorilla/context/.travis.yml
сгенерированный
поставляемый
Normal file
|
@ -0,0 +1,7 @@
|
|||
language: go
|
||||
|
||||
go:
|
||||
- 1.0
|
||||
- 1.1
|
||||
- 1.2
|
||||
- tip
|
27
test/fixtures/go/src/test/vendor/github.com/gorilla/context/LICENSE
сгенерированный
поставляемый
Normal file
27
test/fixtures/go/src/test/vendor/github.com/gorilla/context/LICENSE
сгенерированный
поставляемый
Normal file
|
@ -0,0 +1,27 @@
|
|||
Copyright (c) 2012 Rodrigo Moraes. All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are
|
||||
met:
|
||||
|
||||
* Redistributions of source code must retain the above copyright
|
||||
notice, this list of conditions and the following disclaimer.
|
||||
* Redistributions in binary form must reproduce the above
|
||||
copyright notice, this list of conditions and the following disclaimer
|
||||
in the documentation and/or other materials provided with the
|
||||
distribution.
|
||||
* Neither the name of Google Inc. nor the names of its
|
||||
contributors may be used to endorse or promote products derived from
|
||||
this software without specific prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
7
test/fixtures/go/src/test/vendor/github.com/gorilla/context/README.md
сгенерированный
поставляемый
Normal file
7
test/fixtures/go/src/test/vendor/github.com/gorilla/context/README.md
сгенерированный
поставляемый
Normal file
|
@ -0,0 +1,7 @@
|
|||
context
|
||||
=======
|
||||
[![Build Status](https://travis-ci.org/gorilla/context.png?branch=master)](https://travis-ci.org/gorilla/context)
|
||||
|
||||
gorilla/context is a general purpose registry for global request variables.
|
||||
|
||||
Read the full documentation here: http://www.gorillatoolkit.org/pkg/context
|
143
test/fixtures/go/src/test/vendor/github.com/gorilla/context/context.go
сгенерированный
поставляемый
Normal file
143
test/fixtures/go/src/test/vendor/github.com/gorilla/context/context.go
сгенерированный
поставляемый
Normal file
|
@ -0,0 +1,143 @@
|
|||
// Copyright 2012 The Gorilla Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package context
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
var (
|
||||
mutex sync.RWMutex
|
||||
data = make(map[*http.Request]map[interface{}]interface{})
|
||||
datat = make(map[*http.Request]int64)
|
||||
)
|
||||
|
||||
// Set stores a value for a given key in a given request.
|
||||
func Set(r *http.Request, key, val interface{}) {
|
||||
mutex.Lock()
|
||||
if data[r] == nil {
|
||||
data[r] = make(map[interface{}]interface{})
|
||||
datat[r] = time.Now().Unix()
|
||||
}
|
||||
data[r][key] = val
|
||||
mutex.Unlock()
|
||||
}
|
||||
|
||||
// Get returns a value stored for a given key in a given request.
|
||||
func Get(r *http.Request, key interface{}) interface{} {
|
||||
mutex.RLock()
|
||||
if ctx := data[r]; ctx != nil {
|
||||
value := ctx[key]
|
||||
mutex.RUnlock()
|
||||
return value
|
||||
}
|
||||
mutex.RUnlock()
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetOk returns stored value and presence state like multi-value return of map access.
|
||||
func GetOk(r *http.Request, key interface{}) (interface{}, bool) {
|
||||
mutex.RLock()
|
||||
if _, ok := data[r]; ok {
|
||||
value, ok := data[r][key]
|
||||
mutex.RUnlock()
|
||||
return value, ok
|
||||
}
|
||||
mutex.RUnlock()
|
||||
return nil, false
|
||||
}
|
||||
|
||||
// GetAll returns all stored values for the request as a map. Nil is returned for invalid requests.
|
||||
func GetAll(r *http.Request) map[interface{}]interface{} {
|
||||
mutex.RLock()
|
||||
if context, ok := data[r]; ok {
|
||||
result := make(map[interface{}]interface{}, len(context))
|
||||
for k, v := range context {
|
||||
result[k] = v
|
||||
}
|
||||
mutex.RUnlock()
|
||||
return result
|
||||
}
|
||||
mutex.RUnlock()
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetAllOk returns all stored values for the request as a map and a boolean value that indicates if
|
||||
// the request was registered.
|
||||
func GetAllOk(r *http.Request) (map[interface{}]interface{}, bool) {
|
||||
mutex.RLock()
|
||||
context, ok := data[r]
|
||||
result := make(map[interface{}]interface{}, len(context))
|
||||
for k, v := range context {
|
||||
result[k] = v
|
||||
}
|
||||
mutex.RUnlock()
|
||||
return result, ok
|
||||
}
|
||||
|
||||
// Delete removes a value stored for a given key in a given request.
|
||||
func Delete(r *http.Request, key interface{}) {
|
||||
mutex.Lock()
|
||||
if data[r] != nil {
|
||||
delete(data[r], key)
|
||||
}
|
||||
mutex.Unlock()
|
||||
}
|
||||
|
||||
// Clear removes all values stored for a given request.
|
||||
//
|
||||
// This is usually called by a handler wrapper to clean up request
|
||||
// variables at the end of a request lifetime. See ClearHandler().
|
||||
func Clear(r *http.Request) {
|
||||
mutex.Lock()
|
||||
clear(r)
|
||||
mutex.Unlock()
|
||||
}
|
||||
|
||||
// clear is Clear without the lock.
|
||||
func clear(r *http.Request) {
|
||||
delete(data, r)
|
||||
delete(datat, r)
|
||||
}
|
||||
|
||||
// Purge removes request data stored for longer than maxAge, in seconds.
|
||||
// It returns the amount of requests removed.
|
||||
//
|
||||
// If maxAge <= 0, all request data is removed.
|
||||
//
|
||||
// This is only used for sanity check: in case context cleaning was not
|
||||
// properly set some request data can be kept forever, consuming an increasing
|
||||
// amount of memory. In case this is detected, Purge() must be called
|
||||
// periodically until the problem is fixed.
|
||||
func Purge(maxAge int) int {
|
||||
mutex.Lock()
|
||||
count := 0
|
||||
if maxAge <= 0 {
|
||||
count = len(data)
|
||||
data = make(map[*http.Request]map[interface{}]interface{})
|
||||
datat = make(map[*http.Request]int64)
|
||||
} else {
|
||||
min := time.Now().Unix() - int64(maxAge)
|
||||
for r := range data {
|
||||
if datat[r] < min {
|
||||
clear(r)
|
||||
count++
|
||||
}
|
||||
}
|
||||
}
|
||||
mutex.Unlock()
|
||||
return count
|
||||
}
|
||||
|
||||
// ClearHandler wraps an http.Handler and clears request values at the end
|
||||
// of a request lifetime.
|
||||
func ClearHandler(h http.Handler) http.Handler {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
defer Clear(r)
|
||||
h.ServeHTTP(w, r)
|
||||
})
|
||||
}
|
161
test/fixtures/go/src/test/vendor/github.com/gorilla/context/context_test.go
сгенерированный
поставляемый
Normal file
161
test/fixtures/go/src/test/vendor/github.com/gorilla/context/context_test.go
сгенерированный
поставляемый
Normal file
|
@ -0,0 +1,161 @@
|
|||
// Copyright 2012 The Gorilla Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package context
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"testing"
|
||||
)
|
||||
|
||||
type keyType int
|
||||
|
||||
const (
|
||||
key1 keyType = iota
|
||||
key2
|
||||
)
|
||||
|
||||
func TestContext(t *testing.T) {
|
||||
assertEqual := func(val interface{}, exp interface{}) {
|
||||
if val != exp {
|
||||
t.Errorf("Expected %v, got %v.", exp, val)
|
||||
}
|
||||
}
|
||||
|
||||
r, _ := http.NewRequest("GET", "http://localhost:8080/", nil)
|
||||
emptyR, _ := http.NewRequest("GET", "http://localhost:8080/", nil)
|
||||
|
||||
// Get()
|
||||
assertEqual(Get(r, key1), nil)
|
||||
|
||||
// Set()
|
||||
Set(r, key1, "1")
|
||||
assertEqual(Get(r, key1), "1")
|
||||
assertEqual(len(data[r]), 1)
|
||||
|
||||
Set(r, key2, "2")
|
||||
assertEqual(Get(r, key2), "2")
|
||||
assertEqual(len(data[r]), 2)
|
||||
|
||||
//GetOk
|
||||
value, ok := GetOk(r, key1)
|
||||
assertEqual(value, "1")
|
||||
assertEqual(ok, true)
|
||||
|
||||
value, ok = GetOk(r, "not exists")
|
||||
assertEqual(value, nil)
|
||||
assertEqual(ok, false)
|
||||
|
||||
Set(r, "nil value", nil)
|
||||
value, ok = GetOk(r, "nil value")
|
||||
assertEqual(value, nil)
|
||||
assertEqual(ok, true)
|
||||
|
||||
// GetAll()
|
||||
values := GetAll(r)
|
||||
assertEqual(len(values), 3)
|
||||
|
||||
// GetAll() for empty request
|
||||
values = GetAll(emptyR)
|
||||
if values != nil {
|
||||
t.Error("GetAll didn't return nil value for invalid request")
|
||||
}
|
||||
|
||||
// GetAllOk()
|
||||
values, ok = GetAllOk(r)
|
||||
assertEqual(len(values), 3)
|
||||
assertEqual(ok, true)
|
||||
|
||||
// GetAllOk() for empty request
|
||||
values, ok = GetAllOk(emptyR)
|
||||
assertEqual(value, nil)
|
||||
assertEqual(ok, false)
|
||||
|
||||
// Delete()
|
||||
Delete(r, key1)
|
||||
assertEqual(Get(r, key1), nil)
|
||||
assertEqual(len(data[r]), 2)
|
||||
|
||||
Delete(r, key2)
|
||||
assertEqual(Get(r, key2), nil)
|
||||
assertEqual(len(data[r]), 1)
|
||||
|
||||
// Clear()
|
||||
Clear(r)
|
||||
assertEqual(len(data), 0)
|
||||
}
|
||||
|
||||
func parallelReader(r *http.Request, key string, iterations int, wait, done chan struct{}) {
|
||||
<-wait
|
||||
for i := 0; i < iterations; i++ {
|
||||
Get(r, key)
|
||||
}
|
||||
done <- struct{}{}
|
||||
|
||||
}
|
||||
|
||||
func parallelWriter(r *http.Request, key, value string, iterations int, wait, done chan struct{}) {
|
||||
<-wait
|
||||
for i := 0; i < iterations; i++ {
|
||||
Get(r, key)
|
||||
}
|
||||
done <- struct{}{}
|
||||
|
||||
}
|
||||
|
||||
func benchmarkMutex(b *testing.B, numReaders, numWriters, iterations int) {
|
||||
|
||||
b.StopTimer()
|
||||
r, _ := http.NewRequest("GET", "http://localhost:8080/", nil)
|
||||
done := make(chan struct{})
|
||||
b.StartTimer()
|
||||
|
||||
for i := 0; i < b.N; i++ {
|
||||
wait := make(chan struct{})
|
||||
|
||||
for i := 0; i < numReaders; i++ {
|
||||
go parallelReader(r, "test", iterations, wait, done)
|
||||
}
|
||||
|
||||
for i := 0; i < numWriters; i++ {
|
||||
go parallelWriter(r, "test", "123", iterations, wait, done)
|
||||
}
|
||||
|
||||
close(wait)
|
||||
|
||||
for i := 0; i < numReaders+numWriters; i++ {
|
||||
<-done
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func BenchmarkMutexSameReadWrite1(b *testing.B) {
|
||||
benchmarkMutex(b, 1, 1, 32)
|
||||
}
|
||||
func BenchmarkMutexSameReadWrite2(b *testing.B) {
|
||||
benchmarkMutex(b, 2, 2, 32)
|
||||
}
|
||||
func BenchmarkMutexSameReadWrite4(b *testing.B) {
|
||||
benchmarkMutex(b, 4, 4, 32)
|
||||
}
|
||||
func BenchmarkMutex1(b *testing.B) {
|
||||
benchmarkMutex(b, 2, 8, 32)
|
||||
}
|
||||
func BenchmarkMutex2(b *testing.B) {
|
||||
benchmarkMutex(b, 16, 4, 64)
|
||||
}
|
||||
func BenchmarkMutex3(b *testing.B) {
|
||||
benchmarkMutex(b, 1, 2, 128)
|
||||
}
|
||||
func BenchmarkMutex4(b *testing.B) {
|
||||
benchmarkMutex(b, 128, 32, 256)
|
||||
}
|
||||
func BenchmarkMutex5(b *testing.B) {
|
||||
benchmarkMutex(b, 1024, 2048, 64)
|
||||
}
|
||||
func BenchmarkMutex6(b *testing.B) {
|
||||
benchmarkMutex(b, 2048, 1024, 512)
|
||||
}
|
82
test/fixtures/go/src/test/vendor/github.com/gorilla/context/doc.go
сгенерированный
поставляемый
Normal file
82
test/fixtures/go/src/test/vendor/github.com/gorilla/context/doc.go
сгенерированный
поставляемый
Normal file
|
@ -0,0 +1,82 @@
|
|||
// Copyright 2012 The Gorilla Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
/*
|
||||
Package context stores values shared during a request lifetime.
|
||||
|
||||
For example, a router can set variables extracted from the URL and later
|
||||
application handlers can access those values, or it can be used to store
|
||||
sessions values to be saved at the end of a request. There are several
|
||||
others common uses.
|
||||
|
||||
The idea was posted by Brad Fitzpatrick to the go-nuts mailing list:
|
||||
|
||||
http://groups.google.com/group/golang-nuts/msg/e2d679d303aa5d53
|
||||
|
||||
Here's the basic usage: first define the keys that you will need. The key
|
||||
type is interface{} so a key can be of any type that supports equality.
|
||||
Here we define a key using a custom int type to avoid name collisions:
|
||||
|
||||
package foo
|
||||
|
||||
import (
|
||||
"github.com/gorilla/context"
|
||||
)
|
||||
|
||||
type key int
|
||||
|
||||
const MyKey key = 0
|
||||
|
||||
Then set a variable. Variables are bound to an http.Request object, so you
|
||||
need a request instance to set a value:
|
||||
|
||||
context.Set(r, MyKey, "bar")
|
||||
|
||||
The application can later access the variable using the same key you provided:
|
||||
|
||||
func MyHandler(w http.ResponseWriter, r *http.Request) {
|
||||
// val is "bar".
|
||||
val := context.Get(r, foo.MyKey)
|
||||
|
||||
// returns ("bar", true)
|
||||
val, ok := context.GetOk(r, foo.MyKey)
|
||||
// ...
|
||||
}
|
||||
|
||||
And that's all about the basic usage. We discuss some other ideas below.
|
||||
|
||||
Any type can be stored in the context. To enforce a given type, make the key
|
||||
private and wrap Get() and Set() to accept and return values of a specific
|
||||
type:
|
||||
|
||||
type key int
|
||||
|
||||
const mykey key = 0
|
||||
|
||||
// GetMyKey returns a value for this package from the request values.
|
||||
func GetMyKey(r *http.Request) SomeType {
|
||||
if rv := context.Get(r, mykey); rv != nil {
|
||||
return rv.(SomeType)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// SetMyKey sets a value for this package in the request values.
|
||||
func SetMyKey(r *http.Request, val SomeType) {
|
||||
context.Set(r, mykey, val)
|
||||
}
|
||||
|
||||
Variables must be cleared at the end of a request, to remove all values
|
||||
that were stored. This can be done in an http.Handler, after a request was
|
||||
served. Just call Clear() passing the request:
|
||||
|
||||
context.Clear(r)
|
||||
|
||||
...or use ClearHandler(), which conveniently wraps an http.Handler to clear
|
||||
variables at the end of a request lifetime.
|
||||
|
||||
The Routers from the packages gorilla/mux and gorilla/pat call Clear()
|
||||
so if you are using either of them you don't need to clear the context manually.
|
||||
*/
|
||||
package context
|
|
@ -0,0 +1,14 @@
|
|||
name: app
|
||||
version: 0.0.1
|
||||
synopsis: Initial project template from stack
|
||||
description: Please see README.md
|
||||
copyright: 2016 GitHub
|
||||
category: Web
|
||||
build-type: Simple
|
||||
cabal-version: >=1.10
|
||||
|
||||
library
|
||||
hs-source-dirs: .
|
||||
build-depends: base >= 4.8 && < 5
|
||||
, text == 1.2.2.1
|
||||
default-language: Haskell2010
|
|
@ -0,0 +1,5 @@
|
|||
{
|
||||
"test/fixtures/manifest/test_1.c": "manifest_test",
|
||||
"test/fixtures/manifest/subfolder/test_2.c": "manifest_test",
|
||||
"script/console": "other"
|
||||
}
|
|
@ -0,0 +1,3 @@
|
|||
test/fixtures/manifest/test_1.c: manifest_test
|
||||
test/fixtures/manifest/subfolder/test_2.c: manifest_test
|
||||
script/console: other
|
|
@ -0,0 +1,6 @@
|
|||
#include <stdio.h>
|
||||
|
||||
int main()
|
||||
{
|
||||
printf("I'm a test!");
|
||||
}
|
|
@ -0,0 +1,6 @@
|
|||
#include <stdio.h>
|
||||
|
||||
int main()
|
||||
{
|
||||
printf("I'm a test!");
|
||||
}
|
|
@ -0,0 +1,10 @@
|
|||
{
|
||||
"name": "fixtures",
|
||||
"version": "1.0.0",
|
||||
"dependencies": {
|
||||
"autoprefixer": "5.2.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"string.prototype.startswith": "0.2.0"
|
||||
}
|
||||
}
|
|
@ -0,0 +1,65 @@
|
|||
---
|
||||
http_interactions:
|
||||
- request:
|
||||
method: get
|
||||
uri: https://api.github.com/repos/bkeepers/test/license
|
||||
body:
|
||||
encoding: US-ASCII
|
||||
string: ''
|
||||
headers:
|
||||
Accept:
|
||||
- application/vnd.github.drax-preview+json
|
||||
User-Agent:
|
||||
- Octokit Ruby Gem 4.1.1
|
||||
Content-Type:
|
||||
- application/json
|
||||
Accept-Encoding:
|
||||
- gzip;q=1.0,deflate;q=0.6,identity;q=0.3
|
||||
response:
|
||||
status:
|
||||
code: 404
|
||||
message: Not Found
|
||||
headers:
|
||||
Server:
|
||||
- GitHub.com
|
||||
Date:
|
||||
- Thu, 15 Oct 2015 15:18:18 GMT
|
||||
Content-Type:
|
||||
- application/json; charset=utf-8
|
||||
Transfer-Encoding:
|
||||
- chunked
|
||||
Status:
|
||||
- 404 Not Found
|
||||
X-Ratelimit-Limit:
|
||||
- '60'
|
||||
X-Ratelimit-Remaining:
|
||||
- '57'
|
||||
X-Ratelimit-Reset:
|
||||
- '1444925377'
|
||||
X-Github-Media-Type:
|
||||
- github.drax-preview; format=json
|
||||
X-Xss-Protection:
|
||||
- 1; mode=block
|
||||
X-Frame-Options:
|
||||
- deny
|
||||
Content-Security-Policy:
|
||||
- default-src 'none'
|
||||
Access-Control-Allow-Credentials:
|
||||
- 'true'
|
||||
Access-Control-Expose-Headers:
|
||||
- ETag, Link, X-GitHub-OTP, X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Reset,
|
||||
X-OAuth-Scopes, X-Accepted-OAuth-Scopes, X-Poll-Interval
|
||||
Access-Control-Allow-Origin:
|
||||
- "*"
|
||||
X-Github-Request-Id:
|
||||
- 6154E65A:161C7:6A4DE35:561FC3BA
|
||||
Strict-Transport-Security:
|
||||
- max-age=31536000; includeSubdomains; preload
|
||||
X-Content-Type-Options:
|
||||
- nosniff
|
||||
body:
|
||||
encoding: ASCII-8BIT
|
||||
string: '{"message":"Not Found","documentation_url":"https://developer.github.com/v3"}'
|
||||
http_version:
|
||||
recorded_at: Thu, 15 Oct 2015 15:18:18 GMT
|
||||
recorded_with: VCR 2.9.3
|
|
@ -0,0 +1,77 @@
|
|||
---
|
||||
http_interactions:
|
||||
- request:
|
||||
method: get
|
||||
uri: https://api.github.com/repos/sshirokov/csgtool/license
|
||||
body:
|
||||
encoding: US-ASCII
|
||||
string: ''
|
||||
headers:
|
||||
Accept:
|
||||
- application/vnd.github.drax-preview+json
|
||||
User-Agent:
|
||||
- Octokit Ruby Gem 4.1.1
|
||||
Content-Type:
|
||||
- application/json
|
||||
Accept-Encoding:
|
||||
- gzip;q=1.0,deflate;q=0.6,identity;q=0.3
|
||||
response:
|
||||
status:
|
||||
code: 200
|
||||
message: OK
|
||||
headers:
|
||||
Server:
|
||||
- GitHub.com
|
||||
Date:
|
||||
- Thu, 15 Oct 2015 05:38:09 GMT
|
||||
Content-Type:
|
||||
- application/json; charset=utf-8
|
||||
Transfer-Encoding:
|
||||
- chunked
|
||||
Status:
|
||||
- 200 OK
|
||||
X-Ratelimit-Limit:
|
||||
- '60'
|
||||
X-Ratelimit-Remaining:
|
||||
- '51'
|
||||
X-Ratelimit-Reset:
|
||||
- '1444890347'
|
||||
Cache-Control:
|
||||
- public, max-age=60, s-maxage=60
|
||||
Last-Modified:
|
||||
- Tue, 23 Sep 2014 21:48:52 GMT
|
||||
Etag:
|
||||
- W/"828e01cc4f5af9623076d67f7d144b43"
|
||||
Vary:
|
||||
- Accept
|
||||
- Accept-Encoding
|
||||
X-Github-Media-Type:
|
||||
- github.drax-preview; format=json
|
||||
X-Xss-Protection:
|
||||
- 1; mode=block
|
||||
X-Frame-Options:
|
||||
- deny
|
||||
Content-Security-Policy:
|
||||
- default-src 'none'
|
||||
Access-Control-Allow-Credentials:
|
||||
- 'true'
|
||||
Access-Control-Expose-Headers:
|
||||
- ETag, Link, X-GitHub-OTP, X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Reset,
|
||||
X-OAuth-Scopes, X-Accepted-OAuth-Scopes, X-Poll-Interval
|
||||
Access-Control-Allow-Origin:
|
||||
- "*"
|
||||
X-Github-Request-Id:
|
||||
- 6154E65A:AB2F:9E6711E:561F3BC1
|
||||
Strict-Transport-Security:
|
||||
- max-age=31536000; includeSubdomains; preload
|
||||
X-Content-Type-Options:
|
||||
- nosniff
|
||||
X-Served-By:
|
||||
- 13d09b732ebe76f892093130dc088652
|
||||
body:
|
||||
encoding: ASCII-8BIT
|
||||
string: '{"name":"LICENSE","path":"LICENSE","sha":"5a97308dbb5611d2a3a180da24c070f4c0389ce3","size":1061,"url":"https://api.github.com/repos/sshirokov/csgtool/contents/LICENSE?ref=master","html_url":"https://github.com/sshirokov/csgtool/blob/master/LICENSE","git_url":"https://api.github.com/repos/sshirokov/csgtool/git/blobs/5a97308dbb5611d2a3a180da24c070f4c0389ce3","download_url":"https://raw.githubusercontent.com/sshirokov/csgtool/master/LICENSE","type":"file","content":"Q29weXJpZ2h0IChjKSAyMDEzIFlhcm9zbGF2IFNoaXJva292CgpQZXJtaXNz\naW9uIGlzIGhlcmVieSBncmFudGVkLCBmcmVlIG9mIGNoYXJnZSwgdG8gYW55\nIHBlcnNvbiBvYnRhaW5pbmcgYSBjb3B5IG9mCnRoaXMgc29mdHdhcmUgYW5k\nIGFzc29jaWF0ZWQgZG9jdW1lbnRhdGlvbiBmaWxlcyAodGhlICJTb2Z0d2Fy\nZSIpLCB0byBkZWFsIGluCnRoZSBTb2Z0d2FyZSB3aXRob3V0IHJlc3RyaWN0\naW9uLCBpbmNsdWRpbmcgd2l0aG91dCBsaW1pdGF0aW9uIHRoZSByaWdodHMg\ndG8KdXNlLCBjb3B5LCBtb2RpZnksIG1lcmdlLCBwdWJsaXNoLCBkaXN0cmli\ndXRlLCBzdWJsaWNlbnNlLCBhbmQvb3Igc2VsbCBjb3BpZXMgb2YKdGhlIFNv\nZnR3YXJlLCBhbmQgdG8gcGVybWl0IHBlcnNvbnMgdG8gd2hvbSB0aGUgU29m\ndHdhcmUgaXMgZnVybmlzaGVkIHRvIGRvIHNvLApzdWJqZWN0IHRvIHRoZSBm\nb2xsb3dpbmcgY29uZGl0aW9uczoKClRoZSBhYm92ZSBjb3B5cmlnaHQgbm90\naWNlIGFuZCB0aGlzIHBlcm1pc3Npb24gbm90aWNlIHNoYWxsIGJlIGluY2x1\nZGVkIGluIGFsbApjb3BpZXMgb3Igc3Vic3RhbnRpYWwgcG9ydGlvbnMgb2Yg\ndGhlIFNvZnR3YXJlLgoKVEhFIFNPRlRXQVJFIElTIFBST1ZJREVEICJBUyBJ\nUyIsIFdJVEhPVVQgV0FSUkFOVFkgT0YgQU5ZIEtJTkQsIEVYUFJFU1MgT1IK\nSU1QTElFRCwgSU5DTFVESU5HIEJVVCBOT1QgTElNSVRFRCBUTyBUSEUgV0FS\nUkFOVElFUyBPRiBNRVJDSEFOVEFCSUxJVFksIEZJVE5FU1MKRk9SIEEgUEFS\nVElDVUxBUiBQVVJQT1NFIEFORCBOT05JTkZSSU5HRU1FTlQuIElOIE5PIEVW\nRU5UIFNIQUxMIFRIRSBBVVRIT1JTIE9SCkNPUFlSSUdIVCBIT0xERVJTIEJF\nIExJQUJMRSBGT1IgQU5ZIENMQUlNLCBEQU1BR0VTIE9SIE9USEVSIExJQUJJ\nTElUWSwgV0hFVEhFUgpJTiBBTiBBQ1RJT04gT0YgQ09OVFJBQ1QsIFRPUlQg\nT1IgT1RIRVJXSVNFLCBBUklTSU5HIEZST00sIE9VVCBPRiBPUiBJTgpDT05O\nRUNUSU9OIFdJVEggVEhFIFNPRlRXQVJFIE9SIFRIRSBVU0UgT1IgT1RIRVIg\nREVBTElOR1MgSU4gVEhFIFNPRlRXQVJFLgo=\n","encoding":"base64","_links":{"self":"https://api.github.com/repos/sshirokov/csgtool/contents/LICENSE?ref=master","git":"https://api.github.com/repos/sshirokov/csgtool/git/blobs/5a97308dbb5611d2a3a180da24c070f4c0389ce3","html":"https://github.com/sshirokov/csgtool/blob/master/LICENSE"},"license":{"key":"mit","name":"MIT
|
||||
License","url":"https://api.github.com/licenses/mit","featured":true}}'
|
||||
http_version:
|
||||
recorded_at: Thu, 15 Oct 2015 05:38:08 GMT
|
||||
recorded_with: VCR 2.9.3
|
|
@ -0,0 +1,26 @@
|
|||
# frozen_string_literal: true
|
||||
require "test_helper"
|
||||
require "tempfile"
|
||||
|
||||
describe Licensed::License do
|
||||
it "acts like a hash" do
|
||||
license = Licensed::License.new("name" => "test")
|
||||
assert_equal "test", license["name"]
|
||||
license["name"] = "changed"
|
||||
assert_equal "changed", license["name"]
|
||||
end
|
||||
|
||||
describe "save" do
|
||||
before do
|
||||
@filename = Tempfile.new("license").path
|
||||
end
|
||||
|
||||
it "writes text and metadata" do
|
||||
license = Licensed::License.new({"name" => "test"}, "content")
|
||||
license.save(@filename)
|
||||
assert_equal "---\nname: test\n---\ncontent", File.read(@filename)
|
||||
|
||||
Licensed::License.read(@filename)
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,47 @@
|
|||
# frozen_string_literal: true
|
||||
require "test_helper"
|
||||
|
||||
describe Licensed::Dependency do
|
||||
describe "from_github" do
|
||||
before do
|
||||
Licensed.use_github = true
|
||||
end
|
||||
|
||||
it "gets license from github" do
|
||||
VCR.use_cassette("sshirokov/csgtool/license") do
|
||||
license_file = Licensed.from_github("https://github.com/sshirokov/csgtool")
|
||||
|
||||
assert license_file.license
|
||||
assert_equal "mit", license_file.license.key
|
||||
assert_match(/Yaroslav Shirokov/, license_file.content)
|
||||
assert_equal Encoding::UTF_8, license_file.content.encoding
|
||||
end
|
||||
end
|
||||
|
||||
it "works with anchored github link" do
|
||||
VCR.use_cassette("sshirokov/csgtool/license") do
|
||||
license_file = Licensed.from_github("https://github.com/sshirokov/csgtool#readme")
|
||||
|
||||
assert license_file.license
|
||||
assert_equal "mit", license_file.license.key
|
||||
assert_match(/Yaroslav Shirokov/, license_file.content)
|
||||
end
|
||||
end
|
||||
|
||||
it "returns nil if repository does not have license" do
|
||||
VCR.use_cassette("bkeepers/test/license") do
|
||||
assert_nil Licensed.from_github("https://github.com/bkeepers/test")
|
||||
end
|
||||
end
|
||||
|
||||
it "returns nil if url is nil" do
|
||||
assert_nil Licensed.from_github(nil)
|
||||
end
|
||||
|
||||
it "returns nil if url is not a github repository" do
|
||||
assert_nil Licensed.from_github("https://github.com")
|
||||
assert_nil Licensed.from_github("https://github.com/bkeepers")
|
||||
assert_nil Licensed.from_github("https://example.com/foo/bar")
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,51 @@
|
|||
# frozen_string_literal: true
|
||||
require "test_helper"
|
||||
require "tmpdir"
|
||||
|
||||
if Licensed::Shell.tool_available?("bower")
|
||||
describe Licensed::Source::Bower do
|
||||
let(:fixtures) { File.expand_path("../../fixtures/bower", __FILE__) }
|
||||
let(:config) { Licensed::Configuration.new }
|
||||
let(:source) { Licensed::Source::Bower.new(config) }
|
||||
|
||||
describe "enabled?" do
|
||||
it "is true if .bowerrc exists" do
|
||||
Dir.mktmpdir do |dir|
|
||||
Dir.chdir(dir) do
|
||||
File.write ".bowerrc", ""
|
||||
assert source.enabled?
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
it "is true if bower.json exists" do
|
||||
Dir.chdir(fixtures) do
|
||||
assert source.enabled?
|
||||
end
|
||||
end
|
||||
|
||||
it "is false if bower is disabled" do
|
||||
Dir.chdir(fixtures) do
|
||||
config["sources"]["bower"] = false
|
||||
refute source.enabled?
|
||||
end
|
||||
end
|
||||
|
||||
it "is false no bower configs exist" do
|
||||
Dir.chdir(Dir.tmpdir) do
|
||||
refute source.enabled?
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe "dependencies" do
|
||||
it "finds bower dependencies" do
|
||||
Dir.chdir(fixtures) do
|
||||
dep = source.dependencies.find { |d| d["name"] == "jquery" }
|
||||
assert dep
|
||||
assert_equal "2.1.4", dep["version"]
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,86 @@
|
|||
# frozen_string_literal: true
|
||||
require "test_helper"
|
||||
require "tmpdir"
|
||||
|
||||
if Licensed::Shell.tool_available?("bundle")
|
||||
module Bundler
|
||||
class << self
|
||||
# helper to clear all bundler environment around a yielded block
|
||||
def with_local_configuration
|
||||
# with the clean, original environment
|
||||
with_original_env do
|
||||
# reset all bundler configuration
|
||||
reset!
|
||||
# and re-configure with settings for current directory
|
||||
configure
|
||||
|
||||
yield
|
||||
end
|
||||
ensure
|
||||
# restore bundler configuration
|
||||
reset!
|
||||
configure
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe Licensed::Source::Bundler do
|
||||
let(:fixtures) { File.expand_path("../../fixtures/bundler", __FILE__) }
|
||||
let(:config) { Licensed::Configuration.new }
|
||||
let(:source) { Licensed::Source::Bundler.new(config) }
|
||||
|
||||
describe "enabled?" do
|
||||
it "is true if Gemfile.lock exists" do
|
||||
Dir.chdir(fixtures) do
|
||||
assert source.enabled?
|
||||
end
|
||||
end
|
||||
|
||||
it "is false no Gemfile.lock exists" do
|
||||
Dir.chdir(Dir.tmpdir) do
|
||||
refute source.enabled?
|
||||
end
|
||||
end
|
||||
|
||||
it "is false if disabled" do
|
||||
Dir.chdir(fixtures) do
|
||||
assert source.enabled?
|
||||
config["sources"][source.type] = false
|
||||
refute source.enabled?
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe "gemfile_path" do
|
||||
it "returns a default gemfile path" do
|
||||
gemfile_path = source.gemfile_path
|
||||
default_gemfile = ::Bundler.default_gemfile.basename.to_s
|
||||
|
||||
assert_equal Pathname, gemfile_path.class
|
||||
assert_match(/#{Regexp.quote(default_gemfile)}$/, gemfile_path.to_s)
|
||||
end
|
||||
end
|
||||
|
||||
describe "lockfile_path" do
|
||||
it "returns a default lockfile path" do
|
||||
lockfile_path = source.lockfile_path
|
||||
default_lockfile = ::Bundler.default_lockfile.basename.to_s
|
||||
|
||||
assert_equal Pathname, lockfile_path.class
|
||||
assert_match(/#{Regexp.quote(default_lockfile)}$/, lockfile_path.to_s)
|
||||
end
|
||||
end
|
||||
|
||||
describe "dependencies" do
|
||||
it "finds dependencies from Gemfile" do
|
||||
Dir.chdir(fixtures) do
|
||||
::Bundler.with_local_configuration do
|
||||
dep = source.dependencies.find { |d| d["name"] == "semantic" }
|
||||
assert dep
|
||||
assert_equal "1.6.0", dep["version"]
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,73 @@
|
|||
# frozen_string_literal: true
|
||||
require "test_helper"
|
||||
require "tmpdir"
|
||||
|
||||
if Licensed::Shell.tool_available?("ghc")
|
||||
describe Licensed::Source::Cabal do
|
||||
let(:fixtures) { File.expand_path("../../fixtures/haskell", __FILE__) }
|
||||
let(:config) { Licensed::Configuration.new }
|
||||
let(:source) { Licensed::Source::Cabal.new(config) }
|
||||
|
||||
describe "enabled?" do
|
||||
it "is true if cabal packages exist" do
|
||||
Dir.chdir(fixtures) do
|
||||
assert source.enabled?
|
||||
end
|
||||
end
|
||||
|
||||
it "is false if cabal packages exist" do
|
||||
Dir.chdir(Dir.tmpdir) do
|
||||
refute source.enabled?
|
||||
end
|
||||
end
|
||||
|
||||
it "is false if disabled" do
|
||||
Dir.chdir(fixtures) do
|
||||
config["sources"][source.type] = false
|
||||
refute source.enabled?
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe "dependencies" do
|
||||
let(:local_db) { "dist-newstyle/packagedb/ghc-<ghc_version>" }
|
||||
let(:user_db) { "~/.cabal/store/ghc-<ghc_version>/package.db" }
|
||||
|
||||
describe "without configured package dbs" do
|
||||
it "does not find dependencies" do
|
||||
Dir.chdir(fixtures) do
|
||||
dep = nil
|
||||
capture_subprocess_io do
|
||||
dep = source.dependencies.detect { |d| d["name"] == "text" }
|
||||
end
|
||||
refute dep
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
it "finds indirect dependencies" do
|
||||
config["cabal"] = { "ghc_package_db" => ["global", user_db, local_db] }
|
||||
Dir.chdir(fixtures) do
|
||||
dep = source.dependencies.detect { |d| d["name"] == "bytestring" }
|
||||
assert dep
|
||||
assert_equal "cabal", dep["type"]
|
||||
assert_equal "0.10.8.2", dep["version"]
|
||||
assert dep["homepage"]
|
||||
assert dep["summary"]
|
||||
end
|
||||
end
|
||||
|
||||
it "finds direct dependencies" do
|
||||
config["cabal"] = { "ghc_package_db" => ["global", user_db, local_db] }
|
||||
Dir.chdir(fixtures) do
|
||||
dep = source.dependencies.detect { |d| d["name"] == "text" }
|
||||
assert dep
|
||||
assert_equal "cabal", dep["type"]
|
||||
assert_equal "1.2.2.1", dep["version"]
|
||||
assert dep["homepage"]
|
||||
assert dep["summary"]
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,141 @@
|
|||
# frozen_string_literal: true
|
||||
require "test_helper"
|
||||
require "tmpdir"
|
||||
|
||||
if Licensed::Shell.tool_available?("go")
|
||||
describe Licensed::Source::Go do
|
||||
let(:go_path) { File.expand_path("../../fixtures/go", __FILE__) }
|
||||
let(:fixtures) { File.join(go_path, "src/test") }
|
||||
|
||||
before do
|
||||
ENV["GOPATH"] = go_path
|
||||
@config = Licensed::Configuration.new
|
||||
@source = Licensed::Source::Go.new(@config)
|
||||
end
|
||||
|
||||
describe "enabled?" do
|
||||
it "is true if go source is available" do
|
||||
Dir.chdir(fixtures) do
|
||||
assert @source.enabled?
|
||||
end
|
||||
end
|
||||
|
||||
it "is false if go source is not available" do
|
||||
Dir.mktmpdir do |dir|
|
||||
Dir.chdir(dir) do
|
||||
refute @source.enabled?
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
it "is false if disabled" do
|
||||
Dir.chdir(fixtures) do
|
||||
assert @source.enabled?
|
||||
@config["sources"][@source.type] = false
|
||||
refute @source.enabled?
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe "dependencies" do
|
||||
it "includes direct dependencies" do
|
||||
Dir.chdir fixtures do
|
||||
dep = @source.dependencies.detect { |d| d["name"] == "github.com/hashicorp/golang-lru" }
|
||||
assert dep
|
||||
assert_equal "go", dep["type"]
|
||||
assert dep["homepage"]
|
||||
assert dep["summary"]
|
||||
end
|
||||
end
|
||||
|
||||
it "includes indirect dependencies" do
|
||||
Dir.chdir fixtures do
|
||||
dep = @source.dependencies.detect { |d| d["name"] == "github.com/hashicorp/golang-lru/simplelru" }
|
||||
assert dep
|
||||
assert_equal "go", dep["type"]
|
||||
assert dep["homepage"]
|
||||
end
|
||||
end
|
||||
|
||||
it "doesn't include depenencies from the go std library" do
|
||||
Dir.chdir fixtures do
|
||||
refute @source.dependencies.any? { |d| d["name"] == "runtime" }
|
||||
end
|
||||
end
|
||||
|
||||
describe "with unavailable packages" do
|
||||
before do
|
||||
@tmpdir = Dir.mktmpdir
|
||||
FileUtils.mkdir File.join(@tmpdir, "src")
|
||||
ENV["GOPATH"] = @tmpdir
|
||||
|
||||
@tmpfixtures = File.join(@tmpdir, "src/test")
|
||||
FileUtils.cp_r File.join(go_path, "src/test"), @tmpfixtures
|
||||
|
||||
# the tests are expected to print errors from `go list` which
|
||||
# should not be hidden during normal usage. hide that output during
|
||||
# the test execution
|
||||
@previous_stderr = $stderr
|
||||
$stderr.reopen(File.new("/dev/null", "w"))
|
||||
end
|
||||
|
||||
after do
|
||||
$stderr.reopen(@previous_stderr)
|
||||
FileUtils.rm_rf @tmpdir
|
||||
end
|
||||
|
||||
it "do not raise an error if ignored" do
|
||||
@config.ignore("type" => "go", "name" => "github.com/hashicorp/golang-lru")
|
||||
|
||||
Dir.chdir @tmpfixtures do
|
||||
@source.dependencies
|
||||
end
|
||||
end
|
||||
|
||||
it "raises an error" do
|
||||
Dir.chdir @tmpfixtures do
|
||||
assert_raises RuntimeError do
|
||||
@source.dependencies
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe "search root" do
|
||||
it "is set to the vendor path for vendored packages" do
|
||||
Dir.chdir fixtures do
|
||||
dep = @source.dependencies.detect { |d| d["name"] == "github.com/gorilla/context" }
|
||||
assert dep
|
||||
assert_equal File.join(fixtures, "vendor"), dep.search_root
|
||||
end
|
||||
end
|
||||
|
||||
it "is set to GOPATH if available" do
|
||||
Dir.chdir fixtures do
|
||||
dep = @source.dependencies.detect { |d| d["name"] == "github.com/hashicorp/golang-lru" }
|
||||
assert dep
|
||||
assert_equal go_path, dep.search_root
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe "package version" do
|
||||
it "is nil when git is unavailable" do
|
||||
Dir.chdir fixtures do
|
||||
Licensed::Git.stub(:available?, false) do
|
||||
dep = @source.dependencies.detect { |d| d["name"] == "github.com/gorilla/context" }
|
||||
assert_nil dep["version"]
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
it "is the latest git SHA of the package directory" do
|
||||
Dir.chdir fixtures do
|
||||
dep = @source.dependencies.detect { |d| d["name"] == "github.com/gorilla/context" }
|
||||
assert_match(/[a-f0-9]{40}/, dep["version"])
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,78 @@
|
|||
# frozen_string_literal: true
|
||||
require "test_helper"
|
||||
require "tmpdir"
|
||||
|
||||
describe Licensed::Source::Manifest do
|
||||
let(:fixtures) { File.expand_path("../../fixtures/manifest", __FILE__) }
|
||||
let(:config) { Licensed::Configuration.new("cache_path" => fixtures) }
|
||||
let(:source) { Licensed::Source::Manifest.new(config) }
|
||||
|
||||
describe "enabled?" do
|
||||
it "is true if manifest.json exists in license directory" do
|
||||
assert source.enabled?
|
||||
end
|
||||
|
||||
it "is false if manifest.json does not exist in license directory" do
|
||||
config["cache_path"] = Dir.tmpdir
|
||||
refute source.enabled?
|
||||
end
|
||||
|
||||
it "is false if disabled" do
|
||||
config["sources"][source.type] = false
|
||||
refute source.enabled?
|
||||
end
|
||||
end
|
||||
|
||||
describe "dependencies" do
|
||||
it "includes dependencies from the manifest" do
|
||||
dep = source.dependencies.detect { |d| d["name"] == "manifest_test" }
|
||||
assert dep
|
||||
assert_equal "manifest", dep["type"]
|
||||
assert dep["version"] # version comes from git, just make sure its there
|
||||
end
|
||||
|
||||
describe "paths" do
|
||||
it "finds the common folder path for the dependency" do
|
||||
dep = source.dependencies.detect { |d| d["name"] == "manifest_test" }
|
||||
assert_equal fixtures, dep.path
|
||||
end
|
||||
|
||||
it "uses the first source if there is no common path" do
|
||||
dep = source.dependencies.detect { |d| d["name"] == "other" }
|
||||
assert dep.path.end_with?("script/console")
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe "manifest" do
|
||||
it "loads json" do
|
||||
manifest_path = File.join(fixtures, "manifest.json")
|
||||
config["manifest"] = { "path" => manifest_path }
|
||||
|
||||
assert source.manifest && !source.manifest.empty?
|
||||
end
|
||||
|
||||
it "loads yaml" do
|
||||
manifest_path = File.join(fixtures, "manifest.yml")
|
||||
config["manifest"] = { "path" => manifest_path }
|
||||
|
||||
assert source.manifest && !source.manifest.empty?
|
||||
end
|
||||
end
|
||||
|
||||
describe "manifest_path" do
|
||||
it "defaults to cache_path/manifest.json" do
|
||||
assert_equal Pathname.new(fixtures).join("manifest.json"),
|
||||
source.manifest_path
|
||||
end
|
||||
|
||||
it "can be set in configuration" do
|
||||
config["cache_path"] = Dir.tmpdir
|
||||
|
||||
manifest_path = File.join(fixtures, "manifest.json")
|
||||
config["manifest"] = { "path" => manifest_path }
|
||||
|
||||
assert_equal Pathname.new(manifest_path), source.manifest_path
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,69 @@
|
|||
# frozen_string_literal: true
|
||||
require "test_helper"
|
||||
require "tmpdir"
|
||||
|
||||
if Licensed::Shell.tool_available?("npm")
|
||||
describe Licensed::Source::NPM do
|
||||
before do
|
||||
@config = Licensed::Configuration.new
|
||||
@config.ui.level = "silent"
|
||||
@source = Licensed::Source::NPM.new(@config)
|
||||
end
|
||||
|
||||
describe "enabled?" do
|
||||
it "is true if package.json exists" do
|
||||
Dir.mktmpdir do |dir|
|
||||
Dir.chdir(dir) do
|
||||
File.write "package.json", ""
|
||||
assert @source.enabled?
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
it "is false no npm configs exist" do
|
||||
Dir.chdir(Dir.tmpdir) do
|
||||
refute @source.enabled?
|
||||
end
|
||||
end
|
||||
|
||||
it "is false if disabled" do
|
||||
Dir.mktmpdir do |dir|
|
||||
Dir.chdir(dir) do
|
||||
File.write "package.json", ""
|
||||
|
||||
assert @source.enabled?
|
||||
@config["sources"][@source.type] = false
|
||||
refute @source.enabled?
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe "dependencies" do
|
||||
let(:fixtures) { File.expand_path("../../fixtures/npm", __FILE__) }
|
||||
|
||||
it "includes declared dependencies" do
|
||||
Dir.chdir fixtures do
|
||||
dep = @source.dependencies.detect { |d| d["name"] == "autoprefixer" }
|
||||
assert dep
|
||||
assert_equal "npm", dep["type"]
|
||||
assert_equal "5.2.0", dep["version"]
|
||||
assert dep["homepage"]
|
||||
assert dep["summary"]
|
||||
end
|
||||
end
|
||||
|
||||
it "includes transient dependencies" do
|
||||
Dir.chdir fixtures do
|
||||
assert @source.dependencies.detect { |dep| dep["name"] == "autoprefixer" }
|
||||
end
|
||||
end
|
||||
|
||||
it "does not include dev dependencies" do
|
||||
Dir.chdir fixtures do
|
||||
refute @source.dependencies.detect { |dep| dep["name"] == "string.prototype.startswith" }
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,20 @@
|
|||
# frozen_string_literal: true
|
||||
require "bundler/setup"
|
||||
require "minitest/autorun"
|
||||
require "licensed"
|
||||
require "English"
|
||||
|
||||
# Make sure this doesn't get recorded in VCR responses
|
||||
ENV["GITHUB_TOKEN"] = nil
|
||||
|
||||
require "vcr"
|
||||
VCR.configure do |config|
|
||||
config.cassette_library_dir = File.expand_path("../fixtures/vcr", __FILE__)
|
||||
config.hook_into :webmock
|
||||
end
|
||||
|
||||
Minitest::Spec.class_eval do
|
||||
before do
|
||||
Licensed.use_github = false
|
||||
end
|
||||
end
|
Загрузка…
Ссылка в новой задаче