Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

OptionParser should be able to parse options partially #5845

Open
sudo-nice opened this issue Mar 20, 2018 · 7 comments
Open

OptionParser should be able to parse options partially #5845

sudo-nice opened this issue Mar 20, 2018 · 7 comments

Comments

@sudo-nice
Copy link

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

E.g. Ruby and Elixir have such ability.

@straight-shoota
Copy link
Member

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.

@RX14
Copy link
Contributor

RX14 commented Mar 21, 2018

@straight-shoota this is very standard behaviour. Almost all programs with subcommands have this. Including crystal itself (-h and -v).

@makenowjust
Copy link
Contributor

#4809 ?

@straight-shoota
Copy link
Member

straight-shoota commented Mar 21, 2018

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

@Obirvalger
Copy link

Python standard library's module argparse also provides the ability to define sub-options. Сonsequently it is not so uncommon.

@sudo-nice
Copy link
Author

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

@HertzDevil
Copy link
Contributor

HertzDevil commented Feb 4, 2022

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 #10947 (comment) 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

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

6 participants