Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Provide help when there is a typo in a command #138

Merged
merged 5 commits into from
Sep 21, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion Gemfile
Original file line number Diff line number Diff line change
Expand Up @@ -9,5 +9,5 @@ gemspec
gem "backports", "~> 3.15.0", require: false

unless ENV["CI"]
gem "yard", require: false
gem "yard", require: false
end
11 changes: 7 additions & 4 deletions lib/dry/cli.rb
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ class CLI
require "dry/cli/registry"
require "dry/cli/parser"
require "dry/cli/usage"
require "dry/cli/spell_checker"
require "dry/cli/banner"
require "dry/cli/inflector"

Expand Down Expand Up @@ -108,7 +109,7 @@ def perform_command(arguments)
# @api private
def perform_registry(arguments)
result = registry.get(arguments)
return usage(result) unless result.found?
return spell_checker(result, arguments) unless result.found?

command, args = parse(result.command, result.arguments, result.names)

Expand Down Expand Up @@ -161,9 +162,11 @@ def error(result)
exit(1)
end

# @since 0.1.0
# @api private
def usage(result)
# @since 1.1.1
def spell_checker(result, arguments)
spell_checker = SpellChecker.call(result, arguments)
err.puts spell_checker if spell_checker
puts
err.puts Usage.call(result)
exit(1)
end
Expand Down
38 changes: 38 additions & 0 deletions lib/dry/cli/spell_checker.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
# frozen_string_literal: true

require "dry/cli/program_name"
require "did_you_mean"

module Dry
class CLI
# Command(s) usage
#
# @since 1.1.1
# @api private
module SpellChecker
# @since 1.1.1
# @api private
def self.call(result, arguments)
commands = result.children.keys
cmd = cmd_to_spell(arguments, result.names)

suggestions = DidYouMean::SpellChecker.new(dictionary: commands).correct(cmd.first)
if suggestions.any?
"I don't know how to '#{cmd.join(" ")}'. Did you mean: '#{suggestions.first}' ?"
end
end

# @since 1.1.1
# @api private
def self.cmd_to_spell(arguments, result_names)
arguments - result_names
end

# @since 1.1.1
# @api private
def self.ignore?(cmd)
cmd.empty? || cmd.first.start_with?("-")
end
end
end
end
1 change: 1 addition & 0 deletions spec/integration/inherited_commands_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
based logs APP # Display recent log output
based run APP CMD # Run a one-off process inside your app
based subrun APP CMD

OUT
expect(output).to eq(expected)
end
Expand Down
44 changes: 44 additions & 0 deletions spec/integration/spell_checker_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
# frozen_string_literal: true

require "open3"

RSpec.describe "Spell checker" do
it "print similar command when there is a command with a typo" do
_, stderr, = Open3.capture3("foo routs")

expected = <<~DESC
I don't know how to 'routs'. Did you mean: 'routes' ?
Commands:
foo assets [SUBCOMMAND]
foo callbacks DIR # Command with callbacks
foo console # Starts Foo console
foo db [SUBCOMMAND]
foo destroy [SUBCOMMAND]
foo exec TASK [DIRS] # Execute a task
foo generate [SUBCOMMAND]
foo greeting [RESPONSE]
foo hello # Print a greeting
foo new PROJECT # Generate a new Foo project
foo root-command [ARGUMENT|SUBCOMMAND] # Root command with arguments and subcommands
foo routes # Print routes
foo server # Start Foo server (only for development)
foo sub [SUBCOMMAND]
foo variadic [SUBCOMMAND]
foo version # Print Foo version
DESC

expect(stderr).to eq(expected)
end

it "handles typos in subcommands" do
_, stderr, = Open3.capture3("foo sub comand")

expected = <<~DESC
I don't know how to 'comand'. Did you mean: 'command' ?
Commands:
foo sub command # Override a subcommand
DESC

expect(stderr).to eq(expected)
end
end
Loading