Skip to content

Commit

Permalink
Provide help when there is a typo in a command (#138)
Browse files Browse the repository at this point in the history
Add a basic spell checker to make suggestions when the command could have a typo.
  • Loading branch information
benoittgt authored Sep 21, 2024
1 parent 83a1d44 commit 326a5ea
Show file tree
Hide file tree
Showing 5 changed files with 91 additions and 5 deletions.
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

0 comments on commit 326a5ea

Please sign in to comment.