Skip to content

Broken option parsing in MainCommand when adding SubCommands #21

@foxxx0

Description

@foxxx0

First off, I've just started using this shard due to the stdlib OptionParser lacking some considerable customization options and flexibility. Thanks for putting this together.

However, I have run into an issue when adding subcommands. This could totally be just me "holding it wrong", but I was unable to figure out how to do it properly.

Use-Case

Share common options with SubCommands

This is most useful for things like a config file. I'd like to specify this :single type option only in the MainCommand and have all SubCommands simply inherit it.

Allow inherting global flag options

It would very beneficial to have flag options like -v/--verbose or -d/--debug in the global scope of the MainCommand to be inheritable by SubCommands.

Expected Behaviour

Options defined in MainCommand should always be parseable and usable, regardless of whether any SubCommands are added.

Example

Options defined in MainCommand specified in front of the SubCommand.

# my_cli_tool [options] <subcommand>

Options:
-c, --config    Config file to use (default: /my/default/path/config.yml)

Actual Behavior

Subcommands with @inherit_options=false

Any non-flag options in MainCommand result in a NilAssertionError at runtime, e.g.:

# my_cli_tool -c config.yml

Unhandled exception: Error while executing command error handler:
Cling::Parser::Result#value cannot be nil (Cling::ExecutionError)
  from lib/cling/src/cling/executor.cr:51:9 in 'handle'
  from lib/cling/src/cling/command.cr:172:5 in 'execute'
  from broken_parsing.cr:41:1 in '__crystal_main'
  from /usr/lib/crystal/crystal/main.cr:129:5 in 'main_user_code'
  from /usr/lib/crystal/crystal/main.cr:115:7 in 'main'
  from /usr/lib/crystal/crystal/system/unix/main.cr:9:3 in 'main'
  from /usr/lib/libc.so.6 in '??'
  from /usr/lib/libc.so.6 in '__libc_start_main'
  from ./broken_parsing in '_start'
  from ???
Caused by: Cling::Parser::Result#value cannot be nil (NilAssertionError)
  from lib/cling/src/cling/parser.cr:35:7 in 'value'
  from lib/cling/src/cling/executor.cr:46:55 in 'handle'
  from lib/cling/src/cling/command.cr:172:5 in 'execute'
  from broken_parsing.cr:41:1 in '__crystal_main'
  from /usr/lib/crystal/crystal/main.cr:129:5 in 'main_user_code'
  from /usr/lib/crystal/crystal/main.cr:115:7 in 'main'
  from /usr/lib/crystal/crystal/system/unix/main.cr:9:3 in 'main'
  from /usr/lib/libc.so.6 in '??'
  from /usr/lib/libc.so.6 in '__libc_start_main'
  from ./broken_parsing in '_start'
  from ???

Subcommands with @inherit_options=true

Now the inherited non-flag options can only be used after the SubCommand, e.g.:

# my_cli_tool <subcommand> -c config.yml foobar

Which also renders them completely unusable outside of a SubCommand context.
Trying to specify them like # my_cli_tool -c config.yml <subcommand> foobar results in the same NilAssertionError.

Minimal Example for Reproduction

require "cling"

class SubCommand < Cling::Command
  def setup : Nil
    @name = "subcommand"
    @inherit_options = true
    add_argument "name", description: "some name", required: true
  end

  def run(arguments : Cling::Arguments, options : Cling::Options) : Nil
    puts "subcommand run ; name = #{arguments.get("name")}"
  end
end

class MainCommand < Cling::Command
  def setup : Nil
    @summary = @description = "some description"
    @name = "foobar"
    add_option 'h', "help", description: "Show usage details"
    add_option 'c', "config",
      description: "Config file to use",
      type: :single,
      default: "/my/default/path/config.yml"

    add_command  SubCommand.new
  end

  def pre_run(arguments : Cling::Arguments, options : Cling::Options) : Nil
    if options.has? "help"
      puts help_template
      exit_program 0
    end
  end

  def run(arguments : Cling::Arguments, options : Cling::Options) : Nil
    puts "main run ; config = #{options.get("config")}"
  end
end

cli = MainCommand.new
cli.execute ARGV

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions