How to make two thor tasks share options?

前端 未结 5 1305
予麋鹿
予麋鹿 2021-02-14 04:37

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 t

相关标签:
5条回答
  • 2021-02-14 04:59

    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.

    0 讨论(0)
  • 2021-02-14 05:01

    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.

    0 讨论(0)
  • 2021-02-14 05:05

    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".

    0 讨论(0)
  • 2021-02-14 05:08

    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.

    0 讨论(0)
  • 2021-02-14 05:11

    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)
    
    0 讨论(0)
提交回复
热议问题