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.
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
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