From 59527e6ac7a2a7387b400c5dfb5f4448d17b3153 Mon Sep 17 00:00:00 2001 From: George Dietrich Date: Fri, 21 Jun 2024 23:33:09 -0400 Subject: [PATCH] Improve output when using a namespace as a command (athena-framework/athena#427) --- spec/application_spec.cr | 32 +++++++++++++ .../text/application_filtered_namespace.txt | 2 +- spec/spec_helper.cr | 31 ++++++++++++ src/application.cr | 47 +++++++++++++------ src/descriptor/text.cr | 2 +- 5 files changed, 98 insertions(+), 16 deletions(-) diff --git a/spec/application_spec.cr b/spec/application_spec.cr index f2cd333..8a05d82 100644 --- a/spec/application_spec.cr +++ b/spec/application_spec.cr @@ -374,6 +374,38 @@ struct ApplicationTest < ASPEC::TestCase output.should contain "Do you want to run 'foo' instead? (yes/no) [no]:" end + def test_run_namespace : Nil + ENV["COLUMNS"] = "120" + app = ACON::Application.new "foo" + app.auto_exit = false + app.add FooCommand.new + app.add Foo1Command.new + app.add Foo2Command.new + + tester = ACON::Spec::ApplicationTester.new app + tester.run(command: "foo", decorated: false) # .should eq ACON::Command::Status::FAILURE + + output = tester.display true + output.should contain "Available commands for the 'foo' namespace:" + output.should contain "The foo:bar command" + output.should contain "The foo:bar1 command" + end + + def test_run_with_find_error : Nil + app = ACON::Application.new "foo" + app.auto_exit = false + app.catch_exceptions = false + app.command_loader = MockCommandLoader.new( + command_or_exception: FooCommand.new, + has: false, + names: ::Exception.new("Oh noes") + ) + + expect_raises ::Exception, "Oh noes" do + ACON::Spec::ApplicationTester.new(app).run command: "blah" + end + end + def test_find_alternative_exception_message_multiple : Nil ENV["COLUMNS"] = "120" app = ACON::Application.new "foo" diff --git a/spec/fixtures/text/application_filtered_namespace.txt b/spec/fixtures/text/application_filtered_namespace.txt index 1c358ce..2897d0b 100644 --- a/spec/fixtures/text/application_filtered_namespace.txt +++ b/spec/fixtures/text/application_filtered_namespace.txt @@ -11,5 +11,5 @@ My Athena application 1.0.0 -n, --no-interaction Do not ask any interactive question -v|vv|vvv, --verbose Increase the verbosity of messages: 1 for normal output, 2 for more verbose output and 3 for debug -Available commands for the "command4" namespace: +Available commands for the 'command4' namespace: command4:descriptor diff --git a/spec/spec_helper.cr b/spec/spec_helper.cr index 581d7e7..38e3bae 100644 --- a/spec/spec_helper.cr +++ b/spec/spec_helper.cr @@ -13,4 +13,35 @@ require "./fixtures/**" # Override that given there are specs based on ansi output. Colorize.enabled = true +struct MockCommandLoader + include Athena::Console::Loader::Interface + + def initialize( + *, + @command_or_exception : ACON::Command | ::Exception? = nil, + @has : Bool = true, + @names : Array(String) | ::Exception = [] of String + ) + end + + def get(name : String) : ACON::Command + case v = @command_or_exception + in ::Exception then raise v + in ACON::Command then v + in Nil then raise "BUG: no command or exception was set" + end + end + + def has?(name : String) : Bool + @has + end + + def names : Array(String) + case v = @names + in ::Exception then raise v + in Array(String) then v + end + end +end + ASPEC.run_all diff --git a/src/application.cr b/src/application.cr index de6f2fb..6bc75b2 100644 --- a/src/application.cr +++ b/src/application.cr @@ -599,27 +599,46 @@ class Athena::Console::Application command = self.find command_name rescue ex : Exception - if !(ex.is_a?(ACON::Exceptions::CommandNotFound) && !ex.is_a?(ACON::Exceptions::NamespaceNotFound)) || - 1 != (alternatives = ex.alternatives).size || - !input.interactive? - # TODO: Handle dispatching - - raise ex - end + if (ex.is_a?(ACON::Exceptions::CommandNotFound) && !ex.is_a?(ACON::Exceptions::NamespaceNotFound)) && + 1 == (alternatives = ex.alternatives).size && + input.interactive? + alternative = alternatives.not_nil!.first - alternative = alternatives.not_nil!.first + style = ACON::Style::Athena.new input, output + output.puts "" + output.puts ACON::Helper::Formatter.new.format_block "Command '#{command_name}' is not defined.", "error", true - style = ACON::Style::Athena.new input, output + unless style.confirm "Do you want to run '#{alternative}' instead?", false + # TODO: Handle dispatching - style.block "\nCommand '#{command_name}' is not defined.\n", style: "error" + return ACON::Command::Status::FAILURE + end - unless style.confirm "Do you want to run '#{alternative}' instead?", false + command = self.find alternative + else # TODO: Handle dispatching - return ACON::Command::Status::FAILURE - end + begin + if ex.is_a?(ACON::Exceptions::CommandNotFound) && (namespace = self.find_namespace command_name) + ACON::Helper::Descriptor.new.describe( + output.is_a?(ACON::Output::ConsoleOutputInterface) ? output.error_output : output, + self, + ACON::Descriptor::Context.new( + format: "txt", + raw_text: false, + namespace: namespace, + short: false + ) + ) + + return ACON::Command::Status::SUCCESS + end - command = self.find alternative + raise ex + rescue ACON::Exceptions::NamespaceNotFound + raise ex + end + end end @running_command = command diff --git a/src/descriptor/text.cr b/src/descriptor/text.cr index 236cc8e..7e54ca2 100644 --- a/src/descriptor/text.cr +++ b/src/descriptor/text.cr @@ -45,7 +45,7 @@ class Athena::Console::Descriptor::Text < Athena::Console::Descriptor ) if described_namespace - self.write_text %(Available commands for the "#{described_namespace}" namespace:), context + self.write_text %(Available commands for the '#{described_namespace}' namespace:), context else self.write_text "Available commands:", context end