diff --git a/bundle_update_interactive.gemspec b/bundle_update_interactive.gemspec index 836d0fe..b942d9b 100644 --- a/bundle_update_interactive.gemspec +++ b/bundle_update_interactive.gemspec @@ -30,6 +30,7 @@ Gem::Specification.new do |spec| # Runtime dependencies spec.add_dependency "bundler", "~> 2.0" spec.add_dependency "bundler-audit", ">= 0.9.1" + spec.add_dependency "launchy", ">= 2.5.0" spec.add_dependency "pastel", ">= 0.8.0" spec.add_dependency "tty-prompt", ">= 0.23.1" spec.add_dependency "tty-screen", ">= 0.8.2" diff --git a/images/update-interactive.png b/images/update-interactive.png index 64503e0..630ae56 100644 Binary files a/images/update-interactive.png and b/images/update-interactive.png differ diff --git a/lib/bundle_update_interactive/cli/multi_select.rb b/lib/bundle_update_interactive/cli/multi_select.rb index 0a0211b..3a6ee57 100644 --- a/lib/bundle_update_interactive/cli/multi_select.rb +++ b/lib/bundle_update_interactive/cli/multi_select.rb @@ -1,5 +1,6 @@ # frozen_string_literal: true +require "launchy" require "pastel" require "tty/prompt" require "tty/screen" @@ -8,6 +9,7 @@ class BundleUpdateInteractive::CLI class MultiSelect class List < TTY::Prompt::MultiList def initialize(prompt, **options) + @opener = options.delete(:opener) defaults = { cycle: true, help_color: :itself.to_proc, @@ -34,20 +36,30 @@ def keypress(event) when "j", "n" then keydown when "a" then select_all when "r" then reverse_selection + when "o" then opener&.call(choices[@active - 1].value) end end + + private + + attr_reader :opener end def self.prompt_for_gems_to_update(outdated_gems, prompt: nil) table = Table.new(outdated_gems) title = "#{outdated_gems.length} gems can be updated." - chosen = new(title: title, table: table, prompt: prompt).prompt + opener = lambda do |gem| + url = outdated_gems[gem].changelog_uri + Launchy.open(url) unless url.nil? + end + chosen = new(title: title, table: table, prompt: prompt, opener: opener).prompt outdated_gems.slice(*chosen) end - def initialize(title:, table:, prompt: nil) + def initialize(title:, table:, opener: nil, prompt: nil) @title = title @table = table + @opener = opener @tty_prompt = prompt || TTY::Prompt.new( interrupt: lambda { puts @@ -59,16 +71,16 @@ def initialize(title:, table:, prompt: nil) def prompt choices = table.gem_names.to_h { |name| [table.render_gem(name), name] } - tty_prompt.invoke_select(List, title, choices, help: help) + tty_prompt.invoke_select(List, title, choices, help: help, opener: opener) end private - attr_reader :pastel, :table, :tty_prompt, :title + attr_reader :pastel, :table, :opener, :tty_prompt, :title def help [ - pastel.dim("\nPress to select, ↑/↓ move, all, reverse, to finish."), + pastel.dim("\nPress to select, ↑/↓ move, all, reverse, open url, to finish."), "\n ", table.render_header ].join diff --git a/lib/bundle_update_interactive/outdated_gem.rb b/lib/bundle_update_interactive/outdated_gem.rb index f54ceca..3366be3 100644 --- a/lib/bundle_update_interactive/outdated_gem.rb +++ b/lib/bundle_update_interactive/outdated_gem.rb @@ -10,7 +10,7 @@ class OutdatedGem :updated_version, :updated_git_version - attr_writer :rubygems_source, :vulnerable + attr_writer :changelog_uri, :rubygems_source, :vulnerable def initialize(**attrs) @vulnerable = nil diff --git a/test/bundle_update_interactive/cli/multi_select_test.rb b/test/bundle_update_interactive/cli/multi_select_test.rb index 245b2da..9e8f80a 100644 --- a/test/bundle_update_interactive/cli/multi_select_test.rb +++ b/test/bundle_update_interactive/cli/multi_select_test.rb @@ -1,6 +1,7 @@ # frozen_string_literal: true require "test_helper" +require "launchy" require "tty/prompt/test" class BundleUpdateInteractive::CLI @@ -12,9 +13,9 @@ class MultiSelectTest < Minitest::Test def setup @outdated_gems = { - "a" => build(:outdated_gem, name: "a", rubygems_source: false), - "b" => build(:outdated_gem, name: "b", rubygems_source: false), - "c" => build(:outdated_gem, name: "c", rubygems_source: false) + "a" => build(:outdated_gem, name: "a", changelog_uri: nil), + "b" => build(:outdated_gem, name: "b", changelog_uri: "https://b.example.com/"), + "c" => build(:outdated_gem, name: "c", changelog_uri: "https://c.example.com/") } end @@ -66,6 +67,18 @@ def test_pressing_ctrl_r_has_no_effect assert_empty selected end + def test_pressing_down_then_o_opens_changelog_uri_of_second_gem_in_browser + Launchy.expects(:open).with("https://b.example.com/").once + + use_menu_with_keypress ARROW_DOWN, "o" + end + + def test_pressing_o_for_gem_with_no_changelog_does_nothing + Launchy.expects(:open).never + + use_menu_with_keypress "o" + end + private def use_menu_with_keypress(*keys)