davetron5000 / gli

Make awesome command-line applications the easy way
http://davetron5000.github.io/gli
Apache License 2.0
1.26k stars 102 forks source link

Is there a way to have subcommands entirely described in a folder structure? #205

Closed mrjcleaver closed 3 years ago

mrjcleaver commented 9 years ago

Hi, I was thinking to have contributors add to my cli by them contributing configuration files.

I was wondering if there was a way for me to avoid having to continually add to the main config file by having GLI pull in the descriptors as distributed across a folder structure.

I'd assume the folder structure had filenames that corresponded to subcommands, such that the presence of the filename is enough to make the subcommand available.

Thanks, Martin.

davetron5000 commented 9 years ago

You could do this in your app by walking a pre-configured directory and doing a commands_from for each of those.

So, suppose you told your users that if they put commands in ~/.my_app/commands, you'd load them as custom commands.

Suppose a user created this set-up:

~/.my_app/commands/foo.rb
~/.my_app/commands/other_commands/bar.rb
~/.my_app/commands/yet/more/deep/commands/baz.rb

Your app could do something like:

Dir.glob(File.join(ENV['HOME'],"~/.my_app","commands")) do |file_or_dir|
  if File.directory?(file_or_dir)
    commands_from file_or_dir
  end
end

That would basically load whatever it found at runtime. Could be very slow for big locations, but might do what you are looking for.

logicminds commented 6 years ago

I was wanting the same structure and went about it a different way. I just noticed this issue and wanted to comment in case someone else needed help.

My method lazy loads the command files given the current command and looks for plugins or relative commands within the same gem and external gems.

There is a bit more to my code than what I provided but essentially it just finds the commands relative to the current command name and evals them within the current context. I don't know if eval_instance was the best choice here, but it works and is fast. Debugging which command file is currently evaluated is still an issue because of the eval. I am also loading commands from third party plugins because I wanted the ability for someone to provide a subcommand at any level if needed. However, they are not allowed to override "core" commands.

The internal files and external files are scoped to the directory structure of the current command.

module GLI
  class Command
    # loads the command file in the context of the this command
    def load_sub_commands
      (internal_files + external_files).map do |file|
        # evals the file under the command instance of the parent command
        instance_eval(File.read(file))
        file
      end
    end
end
# lib/cli/cu/volt.rb

desc 'Shows a list of compute units voltages and tables'
command :volt do |volt|
  volt.default_command :help
  volt.load_sub_commands
end

# lib/cli/cu/volt/core.rb
desc 'Shows a list of compute unit voltages'
command :core do |core|
  core.action do |_global_options, options, _args|
    client.run_action -> do
      require 'crossbelt/commands/compute_unit/volt'
      Crossbelt::Command::ComputeUnit::Volt.new(options).core_voltage
    end
  end
end

File structure.

lib/crossbelt/cli
├── cu
│   ├── clocks.rb
│   ├── cost.rb
│   ├── info.rb
│   ├── list.rb
│   ├── mem.rb
│   ├── power.rb
│   ├── profit.rb
│   ├── profitcost.rb
│   ├── volt
│   │   ├── core.rb
│   │   └── table.rb
│   └── volt.rb
├── cu.rb

I would be happy to contribute some of this code to make this part of GLI but I still have some minor issues to work out and static values specific to my app. I believe the only issue right now is the -h flag doesn't seem to respect the current scope for deeper level subcommands.

davetron5000 commented 3 years ago

closing as kinda old. I think command_from is a fine solution to this.