diff --git a/lib/license_finder/package_manager.rb b/lib/license_finder/package_manager.rb index 190e44102..c428f68c6 100644 --- a/lib/license_finder/package_manager.rb +++ b/lib/license_finder/package_manager.rb @@ -152,6 +152,7 @@ def log_to_file(contents) require 'license_finder/package_managers/npm' require 'license_finder/package_managers/yarn' require 'license_finder/package_managers/pip' +require 'license_finder/package_managers/pipenv' require 'license_finder/package_managers/maven' require 'license_finder/package_managers/mix' require 'license_finder/package_managers/cocoa_pods' diff --git a/lib/license_finder/package_managers/pipenv.rb b/lib/license_finder/package_managers/pipenv.rb new file mode 100644 index 000000000..0c7091f47 --- /dev/null +++ b/lib/license_finder/package_managers/pipenv.rb @@ -0,0 +1,41 @@ +# frozen_string_literal: true + +require 'json' + +module LicenseFinder + class Pipenv < PackageManager + def initialize(options = {}) + super + @lockfile = Pathname('Pipfile.lock') + end + + def current_packages + content = IO.read(detected_package_path) + dependencies = JSON.parse(content) + dependencies['default'].map do |name, value| + version = value['version'].sub(/^==/, '') + PipPackage.new(name, version, pypi_def(name, version)) + end + end + + def possible_package_paths + project_path ? [project_path.join(@lockfile)] : [@lockfile] + end + + private + + def pypi_def(name, version) + response = pypi_request("https://pypi.org/pypi/#{name}/#{version}/json") + response.is_a?(Net::HTTPSuccess) ? JSON.parse(response.body).fetch('info', {}) : {} + end + + def pypi_request(location, limit = 10) + uri = URI(location) + http = Net::HTTP.new(uri.host, uri.port) + http.use_ssl = true + response = http.get(uri.request_uri).response + + response.is_a?(Net::HTTPRedirection) && limit.positive? ? pypi_request(response['location'], limit - 1) : response + end + end +end diff --git a/lib/license_finder/scanner.rb b/lib/license_finder/scanner.rb index 15ef1517a..4b3f03a8d 100644 --- a/lib/license_finder/scanner.rb +++ b/lib/license_finder/scanner.rb @@ -2,8 +2,10 @@ module LicenseFinder class Scanner - PACKAGE_MANAGERS = [GoModules, GoDep, GoWorkspace, Go15VendorExperiment, Glide, Gvt, Govendor, Trash, Dep, Bundler, NPM, Pip, - Yarn, Bower, Maven, Gradle, CocoaPods, Rebar, Nuget, Carthage, Mix, Conan, Sbt, Cargo, Dotnet, Composer].freeze + PACKAGE_MANAGERS = [ + GoModules, GoDep, GoWorkspace, Go15VendorExperiment, Glide, Gvt, Govendor, Trash, Dep, Bundler, NPM, Pip, + Yarn, Bower, Maven, Gradle, CocoaPods, Rebar, Nuget, Carthage, Mix, Conan, Sbt, Cargo, Dotnet, Composer, Pipenv + ].freeze class << self def remove_subprojects(paths) diff --git a/spec/fixtures/all_pms/Pipfile.lock b/spec/fixtures/all_pms/Pipfile.lock new file mode 100644 index 000000000..e69de29bb diff --git a/spec/fixtures/pipenv-with-lockfile/Pipfile b/spec/fixtures/pipenv-with-lockfile/Pipfile new file mode 100644 index 000000000..35e85f92a --- /dev/null +++ b/spec/fixtures/pipenv-with-lockfile/Pipfile @@ -0,0 +1,12 @@ +[[source]] +name = "pypi" +url = "https://pypi.org/simple" +verify_ssl = true + +[dev-packages] + +[packages] +six = "*" + +[requires] +python_version = "3.8" diff --git a/spec/fixtures/pipenv-with-lockfile/Pipfile.lock b/spec/fixtures/pipenv-with-lockfile/Pipfile.lock new file mode 100644 index 000000000..eb64a7693 --- /dev/null +++ b/spec/fixtures/pipenv-with-lockfile/Pipfile.lock @@ -0,0 +1,29 @@ +{ + "_meta": { + "hash": { + "sha256": "d9b5cc506fc4feb9bf1ae7cadfd3737d5a0bd2b2d6c3fbcf0de3458bab34ad89" + }, + "pipfile-spec": 6, + "requires": { + "python_version": "3.8" + }, + "sources": [ + { + "name": "pypi", + "url": "https://pypi.org/simple", + "verify_ssl": true + } + ] + }, + "default": { + "six": { + "hashes": [ + "sha256:1f1b7d42e254082a9db6279deae68afb421ceba6158efa6131de7b3003ee93fd", + "sha256:30f610279e8b2578cab6db20741130331735c781b56053c59c4076da27f06b66" + ], + "index": "pypi", + "version": "==1.13.0" + } + }, + "develop": {} +} diff --git a/spec/lib/license_finder/package_managers/pipenv_spec.rb b/spec/lib/license_finder/package_managers/pipenv_spec.rb new file mode 100644 index 000000000..3183a0a10 --- /dev/null +++ b/spec/lib/license_finder/package_managers/pipenv_spec.rb @@ -0,0 +1,50 @@ +# frozen_string_literal: true + +require 'spec_helper' +require 'fakefs/spec_helpers' + +module LicenseFinder + describe Pipenv do + let(:root) { fixture_path('pipenv-with-lockfile') } + let(:pipenv) { Pipenv.new(project_path: root) } + it_behaves_like 'a PackageManager' + + describe '#current_packages' do + let(:response_body) do + <<~RAW +{ + "info": { + "author": "Benjamin Peterson", + "classifiers": [ + "Development Status :: 5 - Production/Stable", + "Intended Audience :: Developers", + "License :: OSI Approved :: MIT License", + "Programming Language :: Python :: 2", + "Programming Language :: Python :: 3", + "Topic :: Software Development :: Libraries", + "Topic :: Utilities" + ], + "home_page": "https://github.com/benjaminp/six", + "license": "MIT", + "name": "six", + "summary": "Python 2 and 3 compatibility utilities", + "version": "1.13.0" + } +} + RAW + end + + before do + stub_request(:get, "https://pypi.org/pypi/six/1.13.0/json") + .to_return(status: 200, body: response_body) + end + + it 'fetches data for pipenv' do + results = pipenv.current_packages.map do |package| + [package.name, package.version, package.licenses.map { |x| x.send(:short_name) }] + end + expect(results).to match_array([ ['six', '1.13.0', ['MIT']] ]) + end + end + end +end