Search code examples
rubyrubygemsthor

How do I register a Thor::Group as a subcommand with arguments


This question started out here. But changed significantly as I learned more about Thor.

I'm trying to make a Thor::Group subcommand that takes an argument. Oddly, it works if there are no arguments.

Can I use a Thor::Group as a sub-command?

This works when I type: foo counter

foo/bin/foo

module Foo
  class CLI < Thor
    register(Counter, 'counter', 'counter', 'Count up from the 1.')
  end

  class Counter < Thor::Group
    desc "Prints 1 2"

    def one
      puts 1
    end

    def two
      puts 2
    end

  end

end

Foo::CLI.start

But this doesn't work when I type: foo counter 5

module Foo
  class CLI < Thor
    register(Counter, 'counter', 'counter <number>', 'Count up from the input.')
  end

  class Counter < Thor::Group
    argument :number, :type => :numeric, :desc => "The number to start counting"
    desc "Prints 2 numbers based on input"

    def one
      puts number + 0
    end

    def two
      puts number + 1
    end

  end


end

Foo::CLI.start

It replies: counter was called incorrectly. Call as foo counter number


Solution

  • I have a solution. Instead of using Thor::Group I'm using Invocations

    bin/foo looks like this:

    #!/usr/bin/env ruby
    
    require 'foo'
    
    Foo::CLI.start
    

    lib/cli.rb - registers 'generate' as a subtask of of the base command, foo:

    module Foo
      class CLI < Thor
        register(Generate, 'generate', 'generate [something]', 'Type foo generate for more help.')
      end
    end
    

    lib/generate.rb looks like this:

    module Foo
    
      class Generate < Thor
    
        desc "project [name]", "Prints the project making step"
        def project(name)
          puts "making first project file #{name}"
          invoke :config
          invoke :project_sub
        end
    
        desc "config [name]", "Prints the config making step"
        def config(name)
          puts "making first config file #{name}"
          invoke :project_sub
        end
    
        desc "project_sub [name]", "Prints the project_sub making step"
        def project_sub(name)
          puts "making subsystem file #{name}"
        end
    
        def self.banner(task, namespace = false, subcommand = true)
           task.formatted_usage(self, true, subcommand).split(':').join(' ')
        end
    
      end
    
    end
    

    Now I can type: foo generate project fred

    and it will output:

    > making first project file fred
    > making first config file fred
    > making subsystem file fred
    

    Notice the banner override. It means that typing: foo generate project with invalid or missing args will give the correct help message:

    "project" was called incorrectly. Call as "foo generate project [name]".
    

    as opposed to

    "project" was called incorrectly. Call as "foo project [name]".