如何让两个Thor任务共享选项?

使用Thor,可以使用method_option为特定任务设置选项。 要为类中的所有任务设置选项,可以使用class_option 。 但是,如果想要一个class级的某些任务,而不是所有的任务,分享选项呢?

在以下任务1和任务2共享选项但它们不共享所有选项,并且它们不与task3共享任何选项。

 require 'thor' class Cli  :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) 

对于method_option :type, :type => :string, :required => true, :default => 'foo'method_option :type, :type => :string, :required => true, :default => 'foo' ,说明method_option :type, :type => :string, :required => true, :default => 'foo'的问题在于它违反了DRY原则 。 是否有一种惯用的处理方式?

method_optionmethod_option中定义,它根据文档采用以下参数:

  • name::参数的名称。
  • options::如下所述。

知道了这一点,您可以将参数存储在一个数组中的method_option中,并在调用method_option数组扩展为单独的参数 。

 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) 

我不知道这是否是惯用的,我不认为它是那么优雅。 不过,它比违反DRY原则更好。

我会使用这样的超类:

 require 'thor' class CliBase < Thor def self.shared_options method_option :verbose, :aliases => '-v', :type => :boolean, :desc => 'Verbose', :default => false, :required => false end end 

…然后子类如下:

 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) 

我有同样的问题,我使用了NN回答的问题。 但我发现了一些问题:

如果您想要共享多个选项,如示例中所示,它不能很好地工作。 想象一下,你想要分享:value task2和task3之间的:value 。 您可以创建另一个shared_options ,也可以使用共享选项创建一个数组,并使用shared_option名称访问它。

这有效,但它很冗长,难以阅读。 我已经实现了一些小东西来分享选项。

 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 

这将创建一个散列,其中选项名称为键,“定义”(必需,默认等)作为值(这是一个散列)。 之后很容易获得。

有了这个,您可以执行以下操作:

 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) 

对我来说,它看起来更具可读性,如果命令数量大于3或4,这是一个很大的改进。

所以现在有一个很好的干燥方法,但可能不会落入惯用的要求,虽然我想为寻找更新答案的人提及它。

您可以首先使用class_options在方法之间设置大多数共享选项:

 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 

关于这一点最好的部分是,它适用于声明后的所有方法。 将这些设置为必需,您可以看到第一个方法需要greetname ,但是foo只需要名称。

为了不是一直输入“shared_options”,你也可以这样做:

 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 

或者如果你不想要带方法别名的杂技,你可以定义自己的方法“my_desc”并调用它而不是“desc”。