denisdefreyne / cri

A tool for building commandline applications
MIT License
120 stars 19 forks source link

FREQ: a #skip_subcommand_parsing method to allow options but arbitrary parameters #69

Closed RoUS closed 6 years ago

RoUS commented 6 years ago

The #skip_option_parsing is good as far as it goes, but (IMHO) it addresses only half of the issue.

As far as I can tell, there is no way to define a command that will accept ab arbitrary number of arbitrary parameters without trying to treat them as subcommands. For examine, I don't see any way to do this:

configure set -v user=foo server=http://example.com/

because it gritches about user=foo not being a valid subcommand.

Is there a trick to this, or is it actually not currently supported?

Thanks!

denisdefreyne commented 6 years ago

At the moment, Cri commands either accept subcommands, or accept arguments (an arbitrary number of them).

Do you need to have a command that can take both? (I imagine that could get confusing.)

RoUS commented 6 years ago

My use-case is a command configure with known subcommands (user, system, etc.). It's the subcommands that need to take arbitrary arguments -- otherwise I end up with something ugly like

configure user --option=foo --value=bar
configure user --option=format --value-yaml

as opposed to something like

configure user foo=bar format=yaml

This is oversimplified and somewhat contrived, but I hope it gets my idea across.

denisdefreyne commented 6 years ago

@RoUS Would something along these lines work?

# frozen_string_literal: true

require 'cri'

config = {
  'animal' => 'cat',
  'sound' => 'meow',
}

config_cmd = Cri::Command.define do
  name        'conf'
  usage       'conf [subcommand] …'
  summary     'manage configuration'
  description '…'
end

set_cmd = Cri::Command.define do
  name        'set'
  usage       'set key=value ...'
  summary     'set config values'
  description '…'

  run do |opts, args|
    args.each do |string|
      key, value = string.split('=')
      config[key] = value
    end
  end
end

unset_cmd = Cri::Command.define do
  name        'unset'
  usage       'unset key ...'
  summary     'unset config values'
  description '…'

  run do |opts, args|
    args.each do |key|
      config.delete(key)
    end
  end
end

config_cmd.add_command(set_cmd)
config_cmd.add_command(unset_cmd)

config_cmd.run(ARGV)

pp config
% ruby conf.rb unset animal
{"sound"=>"meow"}
% ruby conf.rb unset asdf
{"animal"=>"cat", "sound"=>"meow"}
% ruby conf.rb set fn=Denis ln=Defreyne
{"animal"=>"cat", "sound"=>"meow", "fn"=>"Denis", "ln"=>"Defreyne"}
RoUS commented 6 years ago

Yes, I think that would do it; I totally missed the concept of using #run in the command definition. Thanks!

denisdefreyne commented 6 years ago

Cool! I’ll close this issue now.