crystal
crystal copied to clipboard
OptionParser should be able to parse options partially
Had that implemented, it would be possible to write commands with own options clearly:
require "option_parser"
options = {} of Symbol => String | Bool | Nil
subtext = <<-HELP
Commonly used command are:
foo : does something awesome
baz : does something fantastic
See "opt.rb COMMAND --help" for more information on a specific command.
HELP
global_options = OptionParser.new do |opts|
opts.banner = "Usage: opt.rb [options] [command [options]]"
opts.on("-v", "--verbose", "Run verbosely") { |v| options[:verbose] = v }
opts.on("-h", "--help", "Show this help") { puts opts }
opts.separator
opts.separator subtext
end
commands = {
"foo" => OptionParser.new do |opts|
opts.banner = "Usage: foo [options]"
opts.on("-f", "--force", "force verbosely") { |v| options[:force] = v }
opts.on("-h", "--help", "Show this help") { puts opts }
end,
"baz" => OptionParser.new do |opts|
opts.banner = "Usage: baz [options]"
opts.on("-q", "--quiet", "quietly run ") { |v| options[:quiet] = v }
opts.on("-h", "--help", "Show this help") { puts opts }
end,
}
global_options.partial_parse!
command = ARGV.shift?
commands[command].partial_parse! if command
It seems in the linked methods this is implemented in such a way that global options need come first, then the command and then command-specific options. This means only app -v foo works, but app foo -v won't. Is this your intention as well?
I don't think it would particularly useful if you had to know for each flag if it belongs before or after the command when calling a program.
@straight-shoota this is very standard behaviour. Almost all programs with subcommands have this. Including crystal itself (-h and -v).
#4809 ?
@RX14 The crystal compiler has no global flags (except --help and --version which are essentially commands) and the first argument is always interpreted as a command.
But yes, this is used for example with git, which has a few options that can be used to specify essential configuration like --git-dir - they go directly after the program name (git) and before the command. For this, you would need partial parsing. But these options-before-command are quite rare. As I already mentioned, this forces users to a specific order and that's generally not the best in terms of usability.
But yes, it can be appropriate if the program needs to know about some essential configuration before deciding on parsing any command. But to my knowledge that should not very often the case and it begs the question whether it suits to fit into the stdlib.
If it is sufficiently implemented by #4809, it is probably fine. I just wanted to note that this behaviour can be confusing and should be avoided when possible. But I'm not really sure if the OP had this in mind. Otherwise the given example code is just bad at illustrating it because the global options --verbose should definitly go after the command and --help is essentially a command, not a flag.
Python standard library's module argparse also provides the ability to define sub-options. Сonsequently it is not so uncommon.
@straight-shoota sorry for delay.
But I'm not really sure if the OP had this in mind.
You got it right, the intention was to have global options (come before any command) and command options (come after a command).
I agree, --verbose and --help are not the best examples to illustrate, so the OP example is quite abstract. I believe that I understand your concern about implementing this relatively rare used feature, but I think crystal should have it, since crystal might be a very good language not for web-apps only, but for system tools too, where more flexible OptionParser would be welcome.
A different use of ordered mode is to explicitly turn option flags into regular positional arguments. This is useful for crystal eval. Consider the following:
$ crystal eval puts %w{a -t b}
["a", "b"]
Execute: 00:00:00.079870500
Here -t is excluded from the string array literal if and only if it is a valid flag; by using ordered mode, puts is neither a subcommand nor a flag, so -t is always preserved. (I'd argue that https://github.com/crystal-lang/crystal/issues/10947#issuecomment-880535124 presents a better interface than this particular use case.)
Following #11537 we could expose this feature as a constructor parameter called ordered or posixly_correct. Then we could implement crystal eval with something like:
OptionParser.parse(@options, ordered: true) do |opts|
opts.on("-h", "--help") { ... }
opts.unknown_args do |before, after|
case before.shift?
when "eval"
# here `@options` contains the rest of the arguments, including the `--` if any
OptionParser.parse(@options, ordered: true) do |opts|
opts.on("-h", "--help") { ... }
opts.unknown_args do |before, after|
program_source = before.join " "
program_args = after
# ...
end
end
end
end
end