Restricting options in CLI app

A CLI app I am working on has the following syntax in the command line:
appname command subcommand args

I’m using OptionParser and I’d wand to restrict some option at app level or the command level, for example:
appname --option1 command --option2 subcommand

so that I’d prevent option1 to be used after command or subcommand, and option2 used before command.

Any idea ?

I think you are looking for subcommands OptionParser - Crystal 1.18.2

1 Like

Sub commands only enhance the option parser configuration ad hoc. But that means the previously defined --option1 would still be allowed after command:

require "option_parser"

parser = OptionParser.new do |parser|
  parser.on("command", "") do
    puts "command"
  	parser.on("subcommand", "") do
      puts "subcommand"
    end
    parser.on("--option2", "") { puts "option2"}
  end
  parser.on("--option1", "") { puts "option1" }
end

parser.parse(%w[command --option1 --option2 subcommand])
# output:
# command
# option1
# option2
# subcommand

Clear separation can be achieved with multiple, layered parser instances.

require "option_parser"

command = nil
main_parser = OptionParser.new do |parser|
  parser.on("command", "") do
    command = "command"
    parser.stop
  end
  parser.on("--option1", "") { puts "option1" }
end

command_parser = OptionParser.new do |parser|
  parser.on("subcommand", "") do
    puts "subcommand"
  end
  parser.on("--option2", "") { puts "option2"}
end

# success
args = %w[--option1 command --option2 subcommand]
parser.parse(args)
if command == "command"
  parser_command.parse(args)
end

# failure
args = %w[command --option1 --option2 subcommand]
parser.parse(args)
if command == "command"
  parser_command.parse(args) # OptionParser::InvalidOption: Invalid option: --option1
end
1 Like

Crystal’s standard OptionParser is transparent, so once parse completes, the help text can’t be accessed anymore.
Because of this behavior, adding a --help option to subcommands requires a bit of extra care.

It’s possible to print the help and exit from inside parse.
But sometimes you may want to run the parsing step first and decide afterward whether to show the help message.
In that case, the help text needs to be saved manually.

The macro below is an example of how to add this behavior to multiple subcommands.

macro _on_help_
  on("-h", "--help", "Show this help") do
    opt.action = Action::Help
  end

  # OptionParser resets its internal state after parsing with
  # `with_preserved_state`, which also resets @flags.
  # We store the help text here so subcommands can use it later.
  @help_message = self.to_s
end

There may be better approaches as well.

1 Like

Thanks @straight-shoota , your suggestion was very helpful and did solve my problem.

1 Like