From 871d13219f6db0f5f0401943acc6d59f7bacc2e6 Mon Sep 17 00:00:00 2001 From: takmar Date: Fri, 22 Mar 2024 21:40:49 +0900 Subject: [PATCH 1/2] Add command_exists? method to Thor and Thor::Group classes --- lib/thor.rb | 11 +++++++++++ lib/thor/group.rb | 11 +++++++++++ spec/fixtures/script.thor | 3 +++ spec/group_spec.rb | 10 ++++++++++ spec/thor_spec.rb | 12 ++++++++++++ 5 files changed, 47 insertions(+) diff --git a/lib/thor.rb b/lib/thor.rb index cf999e6e..3ae943c1 100644 --- a/lib/thor.rb +++ b/lib/thor.rb @@ -439,6 +439,17 @@ def disable_required_check?(command) #:nodoc: command && disable_required_check.include?(command.name.to_sym) end + # Checks if a specified command exists. + # + # ==== Parameters + # command_name:: The name of the command to check for existence. + # + # ==== Returns + # Boolean:: +true+ if the command exists, +false+ otherwise. + def command_exists?(command_name) #:nodoc: + commands.keys.include?(normalize_command_name(command_name)) + end + protected # Returns this class exclusive options array set. diff --git a/lib/thor/group.rb b/lib/thor/group.rb index 263ccfe6..48df46b9 100644 --- a/lib/thor/group.rb +++ b/lib/thor/group.rb @@ -211,6 +211,17 @@ def handle_argument_error(command, error, _args, arity) #:nodoc: raise error, msg end + # Checks if a specified command exists. + # + # ==== Parameters + # command_name:: The name of the command to check for existence. + # + # ==== Returns + # Boolean:: +true+ if the command exists, +false+ otherwise. + def command_exists?(command_name) #:nodoc: + commands.keys.include?(command_name) + end + protected # The method responsible for dispatching given the args. diff --git a/spec/fixtures/script.thor b/spec/fixtures/script.thor index 92fb0cb0..2fbdd097 100644 --- a/spec/fixtures/script.thor +++ b/spec/fixtures/script.thor @@ -264,12 +264,15 @@ end class Apple < Thor namespace :fruits desc 'apple', 'apple'; def apple; end + desc 'rotten-apple', 'rotten apple'; def rotten_apple; end + map "ra" => :rotten_apple end class Pear < Thor namespace :fruits desc 'pear', 'pear'; def pear; end end + class MyClassOptionScript < Thor class_option :free diff --git a/spec/group_spec.rb b/spec/group_spec.rb index 6d5a65ee..b308c914 100644 --- a/spec/group_spec.rb +++ b/spec/group_spec.rb @@ -186,6 +186,16 @@ end end + describe "#command_exists?" do + it "returns true for a command that is defined in the class" do + expect(MyCounter.command_exists?("one")).to be true + end + + it "returns false for a command that is not defined in the class" do + expect(MyCounter.command_exists?("zero")).to be false + end + end + describe "edge-cases" do it "can handle boolean options followed by arguments" do klass = Class.new(Thor::Group) do diff --git a/spec/thor_spec.rb b/spec/thor_spec.rb index 6d03eb41..f9fd0fe0 100644 --- a/spec/thor_spec.rb +++ b/spec/thor_spec.rb @@ -340,6 +340,18 @@ def self.exit_on_failure? end end + describe "#command_exists?" do + it "returns true for a command that is defined in the class" do + expect(MyScript.command_exists?("zoo")).to be true + expect(MyScript.command_exists?("name-with-dashes")).to be true + expect(MyScript.command_exists?("animal_prison")).to be true + end + + it "returns false for a command that is not defined in the class" do + expect(MyScript.command_exists?("animal_heaven")).to be false + end + end + describe "#map" do it "calls the alias of a method if one is provided" do expect(MyScript.start(%w(-T fish))).to eq(%w(fish)) From 382165767952c68c907624c9b08e9b12ab680303 Mon Sep 17 00:00:00 2001 From: takmar Date: Fri, 22 Mar 2024 21:44:38 +0900 Subject: [PATCH 2/2] Fix find_class_and_command_by_namespace to correctly identify hyphenated and alias command names --- lib/thor/util.rb | 2 +- spec/util_spec.rb | 8 ++++++++ 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/lib/thor/util.rb b/lib/thor/util.rb index 8a2ccef1..6fa0b3d9 100644 --- a/lib/thor/util.rb +++ b/lib/thor/util.rb @@ -133,7 +133,7 @@ def find_class_and_command_by_namespace(namespace, fallback = true) *pieces, command = namespace.split(":") namespace = pieces.join(":") namespace = "default" if namespace.empty? - klass = Thor::Base.subclasses.detect { |thor| thor.namespace == namespace && thor.commands.keys.include?(command) } + klass = Thor::Base.subclasses.detect { |thor| thor.namespace == namespace && thor.command_exists?(command) } end unless klass # look for a Thor::Group with the right name klass = Thor::Util.find_by_namespace(namespace) diff --git a/spec/util_spec.rb b/spec/util_spec.rb index 80665edc..54cc051b 100644 --- a/spec/util_spec.rb +++ b/spec/util_spec.rb @@ -114,6 +114,14 @@ def self.clear_user_home! expect(Thor::Util.find_class_and_command_by_namespace("fruits:apple")).to eq([Apple, "apple"]) expect(Thor::Util.find_class_and_command_by_namespace("fruits:pear")).to eq([Pear, "pear"]) end + + it "returns correct Thor class and the command name with hypen when shared namespaces" do + expect(Thor::Util.find_class_and_command_by_namespace("fruits:rotten-apple")).to eq([Apple, "rotten-apple"]) + end + + it "returns correct Thor class and the associated alias command name when shared namespaces" do + expect(Thor::Util.find_class_and_command_by_namespace("fruits:ra")).to eq([Apple, "ra"]) + end end describe "#thor_classes_in" do