问题
With Thor one can use method_option to set the options for a particular task. To set the options for all tasks in a class one can use class_option
. But what about the case where one wants some tasks of a class, but not all, to share options?
In the following task1
and task2
shares options but they do not share all options and they share no options with task3
.
require 'thor'
class Cli < Thor
desc 'task1', 'Task 1'
method_option :type, :type => :string, :required => true, :default => 'foo'
def task1
end
desc 'task2', 'Task 2'
method_option :type, :type => :string, :required => true, :default => 'foo'
method_option :value, :type => :numeric
def task2
end
desc 'task3', 'Task 3'
method_option :verbose, :type => :boolean, :aliases => '-v'
def task3
end
end
Cli.start(ARGV)
The problem with stating method_option :type, :type => :string, :required => true, :default => 'foo'
for both task1
and task2
is that it violates the DRY principle. Is there an idiomatic way of handling this?
回答1:
method_option
is defined in thor.rb and it takes the following parameters according to the documentation:
name<Symbol>::
The name of the argument.options<Hash>::
Described below.
Knowing this you can store the parameters to method_option
in an array and expand that array into separate parameters as method_option
is called.
require 'thor'
class Cli < Thor
shared_options = [:type, {:type => :string, :required => true, :default => 'foo'}]
desc 'task1', 'Task 1'
method_option *shared_options
def task1
end
desc 'task2', 'Task 2'
method_option *shared_options
method_option :value, :type => :numeric
def task2
end
desc 'task3', 'Task 3'
method_option :verbose, :type => :boolean, :aliases => '-v'
def task3
end
end
Cli.start(ARGV)
I have no idea if this is idiomatic and I do not think it is that elegant. Still, it is better than violating the DRY principle.
回答2:
I would just use a superclass like this:
require 'thor'
class CliBase < Thor
def self.shared_options
method_option :verbose,
:aliases => '-v',
:type => :boolean,
:desc => 'Verbose',
:default => false,
:required => false
end
end
... then subclass as follows:
require 'cli_base'
class Cli < CliBase
desc 'task1', 'Task 1'
shared_options
def task1
end
desc 'task2', 'Task 2'
shared_options
method_option :value, :type => :numeric
def task2
end
desc 'task3', 'Task 3'
method_option :colors, :type => :boolean, :aliases => '-c'
def task3
end
end
Cli.start(ARGV)
回答3:
So there is a nice dry way to do this now, but may not fall into the requirements of being as idiomatic, though I wanted to mention it for anyone looking for a more recent answer.
You can start by using the class_options to set the majority of shared options between your methods:
module MyModule
class Hello < Thor
class_option :name, :desc => "name", :required => true
class_option :greet, :desc => "greeting to use", :required => true
desc "Hello", "Saying hello"
def say
puts "#{options[:greet]}, #{options[:name]}!"
end
desc "Say", "Saying anything"
remove_class_option :greet
def hello
puts "Hello, #{options[:name]}!"
end
def foo
puts "Foo, #{options[:name]}!"
end
end
end
The best part about this, is that it pertains to all methods after the declaration. With these set to required, you can see that the first method requires both greet and name, but say and foo only require the name.
回答4:
I had the same problem and I used what N.N. answered. But I found some problems:
If you want to share more than one option as in the example, it doesn't work very well. Imagine you want to share :value
between task2 and task3. You could create another shared_options
or you could create an array with the shared options and access it with the shared_option name.
This works but it's verbose and hard to read. I've implemented something small to be able to share options.
Cli < Thor
class << self
def add_shared_option(name, options = {})
@shared_options = {} if @shared_options.nil?
@shared_options[name] = options
end
def shared_options(*option_names)
option_names.each do |option_name|
opt = @shared_options[option_name]
raise "Tried to access shared option '#{option_name}' but it was not previously defined" if opt.nil?
option option_name, opt
end
end
end
#...commands
end
This creates a hash with the option name as key, and the 'definition' (required, default, etc) as value (which is a hash). This is easily accessible afterwards.
With this, you can do the following:
require 'thor'
class Cli < Thor
add_shared_option :type, :type => :string, :required => true, :default => 'foo'
add_shared_option :value, :type => :numeric
desc 'task1', 'Task 1'
shared_options :type
def task1
end
desc 'task2', 'Task 2'
shared_options :type, :value
def task2
end
desc 'task3', 'Task 3'
shared_options :value
def task3
end
end
Cli.start(ARGV)
For me it looks more readable, and if the number of commands is bigger than 3 or 4 it's a great improvement.
回答5:
So as not to type "shared_options" all the time, you can also do this:
require 'thor'
class Cli < Thor
class << self
private
def shared_options!
# list your shared options here
method_option :opt1, type: :boolean
method_option :opt2, type: :numeric
# etc
end
# alias original desc so we can call it from inside new desc
alias_method :orig_desc, :desc
# redefine desc, calling original desc, and then applying shared_options!
def desc(*args)
orig_desc(*args)
shared_options!
end
end
desc 'task1', 'Task 1'
def task1
end
desc 'task2', 'Task 2'
def task2
end
desc 'task3', 'Task 3'
def task3
end
end
Or if you don't want acrobatics with method aliasing, you could just define your own method "my_desc" and call that instead of "desc".
来源:https://stackoverflow.com/questions/14346285/how-to-make-two-thor-tasks-share-options