subcommand parser
I was just looking to suggest something like this, after using the Crystal OptionParser library. :+1:
What do you think about the ability to make .on support a bare-word subcommand, which doesn't necessitate a sub-parser or another method?
Here's the example from the Crystal website:
verbose = false
salute = false
welcome = false
name = "World"
parser = OptionParser.new do |parser|
parser.banner = "Usage: example [subcommand] [arguments]"
parser.on("salute", "Salute a name") do
salute = true
parser.banner = "Usage: example salute [arguments]"
parser.on("-t NAME", "--to=NAME", "Specify the name to salute") { |_name| name = _name }
end
parser.on("welcome", "Print a greeting message") do # This UX is very symmetric with the rest
welcome = true
parser.banner = "Usage: example welcome" # Re-uses original parser in scope - which might affect visitor pattern of Ruby's optparse?
end
parser.on("-v", "--verbose", "Enabled verbose output") { verbose = true }
parser.on("-h", "--help", "Show this help") do
puts parser
exit
end
end
parser.parse
if salute
STDERR.puts "Saluting #{name}" if verbose
puts "Hello #{name}"
elsif welcome
STDERR.puts "Welcoming #{name}" if verbose
puts "Welcome!"
else
puts parser
exit(1)
end
I think that it is NOT a good idea to support subcommand functionality in OptionParser. Subcommand functionality is not a feature of command option parser library such as OptionParser, but is a feature of command-line application framework. If you need subcommand functionality, use (or create) a command-line application framework (ex: Benry-CmdApp framework).
I strongly recommend for OptionParser to focus on its role as command option parsing.
I released cliapp gem which is a small framework for CLI application similar to Git or Docker. I created it in order to insist that subcommand functionality should be covered by framework, not by option parser library. Because the cliapp gem is so small, it is a good example of how to design a CLI application framework which has subcommand functionality.
Example code of cliapp (filename: sample):
#!/usr/bin/env ruby
require 'cliapp'
## create an application object
app = CLIApp.new("Sample", "Sample Application", version: "1.0.0")
app.global_options({
:help => ["-h", "--help" , "print help message"],
:version => [ "--version" , "print version number"],
:list => ["-l", "--list" , "list action names"],
})
## 'hello' action
app.action("hello", "greeting message", {
:lang => ["-l", "--lang=<en|fr|it>", "language", ["en", "it", "fr"]],
}) do |name="world", lang: "en"|
case lang
when "en" ; puts "Hello, #{name}!"
when "fr" ; puts "Bonjour, #{name}!"
when "it" ; puts "Chao, #{name}!"
else raise "** internal error: lang=#{lang.inspect}"
end
end
## main
begin
app.run(*ARGV)
exit 0
rescue OptionParser::ParseError, CLIApp::ActionError => exc
$stderr.puts "[ERROR] #{exc.message}"
exit 1
end
Example output:
$ chmod ./sample
$ ./sample hello # subcommand example
Hello, world!
$ ./sample hello --lang=fr # subcommand option example
Bonjour, world!
$ ./sample hello Alice --lang=fr # subcommand argument example
Bonjour, Alice!
Again, I strongly recommend for OptionParser to focus on its role as command option parsing.