Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Browse files

Automatically create aliases for unambiguous substrings of commands

Inspired by the code in RubyGems, this code goes beyond that basic case
to correctly resolve ambiguous aliases that map to the same command.

Closes #158.
Closes #160.
Closes #162.
  • Loading branch information...
commit a0e3e20c7ea902f0678d741e8451093c36f5d912 1 parent 1fbea01
@sferik sferik authored
View
2  LICENSE.md
@@ -1,4 +1,4 @@
-Copyright (c) 2008 Yehuda Katz
+Copyright (c) 2008 Yehuda Katz, Eric Hodel, et al.
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
View
43 lib/thor.rb
@@ -311,7 +311,6 @@ def initialize_added #:nodoc:
# Retrieve the task name from given args.
def retrieve_task_name(args) #:nodoc:
meth = args.first.to_s unless args.empty?
-
if meth && (map[meth] || meth !~ /^\-/)
args.shift
else
@@ -319,11 +318,45 @@ def retrieve_task_name(args) #:nodoc:
end
end
- # Receives a task name (can be nil), and try to get a map from it.
- # If a map can't be found use the sent name or the default task.
+ # receives a (possibly nil) task name and returns a name that is in
+ # the tasks hash. In addition to normalizing aliases, this logic
+ # will determine if a shortened command is an unambiguous prefix of
+ # a task or alias.
+ #
+ # +normalize_task_name+ also converts names like +animal-prison+
+ # into +animal_prison+.
def normalize_task_name(meth) #:nodoc:
- meth = map[meth.to_s] || meth || default_task
- meth.to_s.gsub('-','_') # treat foo-bar > foo_bar
+ return default_task.to_s.gsub('-', '_') unless meth
+
+ possibilities = find_task_possibilities(meth)
+ if possibilities.size > 1
+ raise ArgumentError, "Ambiguous task #{meth} matches [#{possibilities.join(', ')}]"
+ elsif possibilities.size < 1
+ meth = meth || default_task
+ elsif map[meth]
+ meth = map[meth]
+ else
+ meth = possibilities.first
+ end
+
+ meth.to_s.gsub('-','_') # treat foo-bar as foo_bar
+ end
+
+ # this is the logic that takes the task name passed in by the user
+ # and determines whether it is an unambiguous prefix of a task or
+ # alias name.
+ def find_task_possibilities(meth)
+ len = meth.length
+ possibilities = all_tasks.merge(map).keys.select { |n| meth == n[0, len] }.sort
+ unique_possibilities = possibilities.map { |k| map[k] || k }.uniq
+
+ if possibilities.include?(meth)
+ [meth]
+ elsif unique_possibilities.size == 1
+ unique_possibilities
+ else
+ possibilities
+ end
end
def subcommand_help(cmd)
View
6 spec/fixtures/script.thor
@@ -10,6 +10,8 @@ class MyScript < Thor
map "-T" => :animal, ["-f", "--foo"] => :foo
+ map "animal_prison" => "zoo"
+
desc "zoo", "zoo around"
def zoo
true
@@ -26,11 +28,15 @@ class MyScript < Thor
[type]
end
+ map "hid" => "hidden"
+
desc "hidden TYPE", "this is hidden", :hide => true
def hidden(type)
[type]
end
+ map "fu" => "zoo"
+
desc "foo BAR", <<END
do some fooing
This is more info!
View
25 spec/thor_spec.rb
@@ -195,6 +195,31 @@
it "raises when an exception happens within the task call" do
lambda { MyScript.start(["call_myself_with_wrong_arity"]) }.should raise_error(ArgumentError)
end
+
+ context "when the user enters an unambiguous substring of a command" do
+ it "should invoke a command" do
+ MyScript.start(["z"]).should == MyScript.start(["zoo"])
+ end
+
+ it "should invoke a command, even when there's an alias the resolves to the same command" do
+ MyScript.start(["hi"]).should == MyScript.start(["hidden"])
+ end
+
+ it "should invoke an alias" do
+ MyScript.start(["animal_pri"]).should == MyScript.start(["zoo"])
+ end
+ end
+
+ context "when the user enters an ambiguous substring of a command" do
+ it "should raise an exception that explains the ambiguity" do
+ lambda { MyScript.start(["call"]) }.should raise_error(ArgumentError, 'Ambiguous task call matches [call_myself_with_wrong_arity, call_unexistent_method]')
+ end
+
+ it "should raise an exception when there is an alias" do
+ lambda { MyScript.start(["f"]) }.should raise_error(ArgumentError, 'Ambiguous task f matches [foo, fu]')
+ end
+ end
+
end
describe "#subcommand" do
Please sign in to comment.
Something went wrong with that request. Please try again.