rails / thor

Thor is a toolkit for building powerful command-line interfaces.
http://whatisthor.com/
MIT License
5.12k stars 553 forks source link

Get list of all commands of a class inheriting from Thor #731

Open RonLek opened 4 years ago

RonLek commented 4 years ago

I'm trying to get a list of all commands, subcommands and options of a class that inherits from Thor. For example, the module CLI below inherits from Thor and has two commands actor and service as shown below:

actor.rb

module CLI
  module Commands
    class Actor < CLI::Commands
      desc "monitor <AppName>", "Monitor the current app"
      def monitor(app_name)
      # do something
      end

      desc "new <AppName>", "New App"
      def new(app_name)
      # do something
      end
  end
end

service.rb

module CLI
  module Commands
    class Service < CLI::Commands
      desc "deploy <SERVICE_NAME>", "Deploy service"
      def deploy(name)
        # do something
       end
  end
end

I'm looking for a command list_commands that could list all commands and their methods and subcommands in a JSON like format

~ cli list_commands
cli: [
      actor: [ monitor, new ]
      service: [deploy]
]

Currently, kind of get what I what by putting help in front of each command/subcommand to get the next subcommand. My intention to use the output of the above command is to auto-generate a bash-completion script as new commands are added into the Commands module that inherits from CLI which inherits from Thor. Suggestions for any other approaches using some inbuilt mechanism in Thor are also welcome!

PS: Also take a look at a similar SO question

tristanmorgan commented 3 years ago

I took a slightly different approach to auto complete using complete -C /path/to/command command where instead of a script generated, bash calls the command directly to ask what should be used.

Related to #415

My code to fetch some of the data was:

  # list command names
  def list_commands
    commands = self.class.all_commands.keys.map { |elem| elem.tr('_', '-') }
    commands.reject! { |elem| %w[autocomplete default].include?(elem) }
  end

  # list flags for a command
  def  list_arguments(command:)
    options = self.class.all_commands[command].options.values
    exit 1 if options.empty?

    options.map(&:aliases).flatten! +
      options.map(&:switch_name)
  end