Skip to content

Commit

Permalink
Merge pull request #3390 from gjtorikian/go-fund-me
Browse files Browse the repository at this point in the history
Add `bundle fund` command
  • Loading branch information
deivid-rodriguez committed Oct 1, 2020
2 parents 9860c26 + 664db8e commit ce27b98
Show file tree
Hide file tree
Showing 13 changed files with 282 additions and 12 deletions.
1 change: 1 addition & 0 deletions Manifest.txt
Expand Up @@ -32,6 +32,7 @@ bundler/lib/bundler/cli/config.rb
bundler/lib/bundler/cli/console.rb
bundler/lib/bundler/cli/doctor.rb
bundler/lib/bundler/cli/exec.rb
bundler/lib/bundler/cli/fund.rb
bundler/lib/bundler/cli/gem.rb
bundler/lib/bundler/cli/info.rb
bundler/lib/bundler/cli/init.rb
Expand Down
8 changes: 8 additions & 0 deletions bundler/lib/bundler/cli.rb
Expand Up @@ -439,6 +439,14 @@ def outdated(*gems)
Outdated.new(options, gems).run
end

desc "fund [OPTIONS]", "Lists information about gems seeking funding assistance"
method_option "group", :aliases => "-g", :type => :array, :banner =>
"Fetch funding information for a specific group"
def fund
require_relative "cli/fund"
Fund.new(options).run
end

desc "cache [OPTIONS]", "Locks and then caches all of the gems into vendor/cache"
method_option "all", :type => :boolean,
:default => Bundler.feature_flag.cache_all?,
Expand Down
14 changes: 14 additions & 0 deletions bundler/lib/bundler/cli/common.rb
Expand Up @@ -14,6 +14,20 @@ def self.print_post_install_message(name, msg)
Bundler.ui.info msg
end

def self.output_fund_metadata_summary
definition = Bundler.definition
current_dependencies = definition.requested_dependencies
current_specs = definition.specs

count = current_dependencies.count {|dep| current_specs[dep.name].first.metadata.key?("funding_uri") }

return if count.zero?

intro = count > 1 ? "#{count} installed gems you directly depend on are" : "#{count} installed gem you directly depend on is"
message = "#{intro} looking for funding.\n Run `bundle fund` for details"
Bundler.ui.info message
end

def self.output_without_groups_message(command)
return if Bundler.settings[:without].empty?
Bundler.ui.confirm without_groups_message(command)
Expand Down
36 changes: 36 additions & 0 deletions bundler/lib/bundler/cli/fund.rb
@@ -0,0 +1,36 @@
# frozen_string_literal: true

module Bundler
class CLI::Fund
attr_reader :options

def initialize(options)
@options = options
end

def run
Bundler.definition.validate_runtime!

groups = Array(options[:group]).map(&:to_sym)

deps = if groups.any?
Bundler.definition.dependencies_for(groups)
else
Bundler.definition.current_dependencies
end

fund_info = deps.each_with_object([]) do |dep, arr|
spec = Bundler.definition.specs[dep.name].first
if spec.metadata.key?("funding_uri")
arr << "* #{spec.name} (#{spec.version})\n Funding: #{spec.metadata["funding_uri"]}"
end
end

if fund_info.empty?
Bundler.ui.info "None of the installed gems you directly depend on are looking for funding."
else
Bundler.ui.info fund_info.join("\n")
end
end
end
end
1 change: 1 addition & 0 deletions bundler/lib/bundler/cli/info.rb
Expand Up @@ -60,6 +60,7 @@ def print_gem_info(spec)
gem_info << "\tHomepage: #{spec.homepage}\n" if spec.homepage
gem_info << "\tDocumentation: #{metadata["documentation_uri"]}\n" if metadata.key?("documentation_uri")
gem_info << "\tSource Code: #{metadata["source_code_uri"]}\n" if metadata.key?("source_code_uri")
gem_info << "\tFunding: #{metadata["funding_uri"]}\n" if metadata.key?("funding_uri")
gem_info << "\tWiki: #{metadata["wiki_uri"]}\n" if metadata.key?("wiki_uri")
gem_info << "\tChangelog: #{metadata["changelog_uri"]}\n" if metadata.key?("changelog_uri")
gem_info << "\tBug Tracker: #{metadata["bug_tracker_uri"]}\n" if metadata.key?("bug_tracker_uri")
Expand Down
2 changes: 2 additions & 0 deletions bundler/lib/bundler/cli/install.rb
Expand Up @@ -82,6 +82,8 @@ def run
require_relative "clean"
Bundler::CLI::Clean.new(options).run
end

Bundler::CLI::Common.output_fund_metadata_summary
rescue GemNotFound, VersionConflict => e
if options[:local] && Bundler.app_cache.exist?
Bundler.ui.warn "Some gems seem to be missing from your #{Bundler.settings.app_cache_path} directory."
Expand Down
2 changes: 2 additions & 0 deletions bundler/lib/bundler/cli/update.rb
Expand Up @@ -106,6 +106,8 @@ def run
Bundler.ui.confirm "Bundle updated!"
Bundler::CLI::Common.output_without_groups_message(:update)
Bundler::CLI::Common.output_post_install_messages installer.post_install_messages

Bundler::CLI::Common.output_fund_metadata_summary
end
end
end
24 changes: 12 additions & 12 deletions bundler/lib/bundler/definition.rb
Expand Up @@ -233,6 +233,12 @@ def requested_specs
end
end

def requested_dependencies
groups = requested_groups
groups.map!(&:to_sym)
dependencies_for(groups)
end

def current_dependencies
dependencies.select do |d|
d.should_include? && !d.gem_platforms(@platforms).empty?
Expand All @@ -244,6 +250,12 @@ def specs_for(groups)
specs.for(expand_dependencies(deps))
end

def dependencies_for(groups)
current_dependencies.reject do |d|
(d.groups & groups).empty?
end
end

# Resolve all the dependencies specified in Gemfile. It ensures that
# dependencies that have been already resolved via locked file and are fresh
# are reused when resolving dependencies
Expand Down Expand Up @@ -898,18 +910,6 @@ def expand_dependency_with_platforms(dep, platforms)
end
end

def dependencies_for(groups)
current_dependencies.reject do |d|
(d.groups & groups).empty?
end
end

def requested_dependencies
groups = requested_groups
groups.map!(&:to_sym)
dependencies_for(groups)
end

def source_requirements
# Load all specs from remote sources
index
Expand Down
55 changes: 55 additions & 0 deletions bundler/spec/commands/fund_spec.rb
@@ -0,0 +1,55 @@
# frozen_string_literal: true

RSpec.describe "bundle fund" do
it "prints fund information for all gems in the bundle" do
install_gemfile <<-G
source "#{file_uri_for(gem_repo1)}"
gem 'has_metadata'
gem 'has_funding'
gem 'rack-obama'
G

bundle "fund"

expect(out).to include("* has_metadata (1.0)\n Funding: https://example.com/has_metadata/funding")
expect(out).to include("* has_funding (1.2.3)\n Funding: https://example.com/has_funding/funding")
expect(out).to_not include("rack-obama")
end

it "does not consider fund information for gem dependencies" do
install_gemfile <<-G
source "#{file_uri_for(gem_repo1)}"
gem 'gem_with_dependent_funding'
G

bundle "fund"

expect(out).to_not include("* has_funding (1.2.3)\n Funding: https://example.com/has_funding/funding")
expect(out).to_not include("gem_with_dependent_funding")
end

it "prints message if none of the gems have fund information" do
install_gemfile <<-G
source "#{file_uri_for(gem_repo1)}"
gem 'rack-obama'
G

bundle "fund"

expect(out).to include("None of the installed gems you directly depend on are looking for funding.")
end

describe "with --group option" do
it "prints fund message for only specified group gems" do
install_gemfile <<-G
source "#{file_uri_for(gem_repo1)}"
gem 'has_metadata', :group => :development
gem 'has_funding'
G

bundle "fund --group development"
expect(out).to include("* has_metadata (1.0)\n Funding: https://example.com/has_metadata/funding")
expect(out).to_not include("* has_funding (1.2.3)\n Funding: https://example.com/has_funding/funding")
end
end
end
1 change: 1 addition & 0 deletions bundler/spec/commands/info_spec.rb
Expand Up @@ -66,6 +66,7 @@
\tHomepage: http://example.com
\tDocumentation: https://www.example.info/gems/bestgemever/0.0.1
\tSource Code: https://example.com/user/bestgemever
\tFunding: https://example.com/has_metadata/funding
\tWiki: https://example.com/user/bestgemever/wiki
\tChangelog: https://example.com/user/bestgemever/CHANGELOG.md
\tBug Tracker: https://example.com/user/bestgemever/issues
Expand Down
110 changes: 110 additions & 0 deletions bundler/spec/install/gems/fund_spec.rb
@@ -0,0 +1,110 @@
# frozen_string_literal: true

RSpec.describe "bundle install" do
context "with gem sources" do
context "when gems include a fund URI" do
it "displays the plural fund message after installing" do
install_gemfile <<-G
source "#{file_uri_for(gem_repo1)}"
gem 'has_metadata'
gem 'has_funding'
gem 'rack-obama'
G

expect(out).to include("2 installed gems you directly depend on are looking for funding.")
end

it "displays the singular fund message after installing" do
install_gemfile <<-G
source "#{file_uri_for(gem_repo1)}"
gem 'has_funding'
gem 'rack-obama'
G

expect(out).to include("1 installed gem you directly depend on is looking for funding.")
end
end

context "when gems do not include fund messages" do
it "does not display any fund messages" do
install_gemfile <<-G
source "#{file_uri_for(gem_repo1)}"
gem "activesupport"
G

expect(out).not_to include("gem you depend on")
end
end

context "when a dependency includes a fund message" do
it "does not display the fund message" do
install_gemfile <<-G
source "#{file_uri_for(gem_repo1)}"
gem 'gem_with_dependent_funding'
G

expect(out).not_to include("gem you depend on")
end
end
end

context "with git sources" do
context "when gems include fund URI" do
it "displays the fund message after installing" do
build_git "also_has_funding" do |s|
s.metadata = {
"funding_uri" => "https://example.com/also_has_funding/funding",
}
end
install_gemfile <<-G
source "#{file_uri_for(gem_repo1)}"
gem 'also_has_funding', :git => '#{lib_path("also_has_funding-1.0")}'
G

expect(out).to include("1 installed gem you directly depend on is looking for funding.")
end

it "displays the fund message if repo is updated" do
build_git "also_has_funding" do |s|
s.metadata = {
"funding_uri" => "https://example.com/also_has_funding/funding",
}
end
install_gemfile <<-G
source "#{file_uri_for(gem_repo1)}"
gem 'also_has_funding', :git => '#{lib_path("also_has_funding-1.0")}'
G

build_git "also_has_funding", "1.1" do |s|
s.metadata = {
"funding_uri" => "https://example.com/also_has_funding/funding",
}
end
install_gemfile <<-G
source "#{file_uri_for(gem_repo1)}"
gem 'also_has_funding', :git => '#{lib_path("also_has_funding-1.1")}'
G

expect(out).to include("1 installed gem you directly depend on is looking for funding.")
end

it "displays the fund message if repo is not updated" do
build_git "also_has_funding" do |s|
s.metadata = {
"funding_uri" => "https://example.com/also_has_funding/funding",
}
end
gemfile <<-G
source "#{file_uri_for(gem_repo1)}"
gem 'also_has_funding', :git => '#{lib_path("also_has_funding-1.0")}'
G

bundle :install
expect(out).to include("1 installed gem you directly depend on is looking for funding.")

bundle :install
expect(out).to include("1 installed gem you directly depend on is looking for funding.")
end
end
end
end
11 changes: 11 additions & 0 deletions bundler/spec/support/builders.rb
Expand Up @@ -322,10 +322,21 @@ def __pry__
"documentation_uri" => "https://www.example.info/gems/bestgemever/0.0.1",
"homepage_uri" => "https://bestgemever.example.io",
"mailing_list_uri" => "https://groups.example.com/bestgemever",
"funding_uri" => "https://example.com/has_metadata/funding",
"source_code_uri" => "https://example.com/user/bestgemever",
"wiki_uri" => "https://example.com/user/bestgemever/wiki",
}
end

build_gem "has_funding", "1.2.3" do |s|
s.metadata = {
"funding_uri" => "https://example.com/has_funding/funding",
}
end

build_gem "gem_with_dependent_funding", "1.0" do |s|
s.add_dependency "has_funding"
end
end
end

Expand Down
29 changes: 29 additions & 0 deletions bundler/spec/update/gems/fund_spec.rb
@@ -0,0 +1,29 @@
# frozen_string_literal: true

RSpec.describe "bundle update" do
before do
gemfile <<-G
source "#{file_uri_for(gem_repo1)}"
gem 'has_metadata'
gem 'has_funding', '< 2.0'
G

bundle :install
end

context "when listed gems are updated" do
before do
gemfile <<-G
source "#{file_uri_for(gem_repo1)}"
gem 'has_metadata'
gem 'has_funding'
G

bundle :update, :all => true
end

it "displays fund message" do
expect(out).to include("2 installed gems you directly depend on are looking for funding.")
end
end
end

0 comments on commit ce27b98

Please sign in to comment.