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

Support case-insensitive (configurable) option name parsing #9

Closed
remkop opened this issue Feb 4, 2017 · 10 comments
Closed

Support case-insensitive (configurable) option name parsing #9

remkop opened this issue Feb 4, 2017 · 10 comments
Labels
theme: parser An issue or change related to the parser type: API 🔌 type: enhancement ✨
Milestone

Comments

@remkop
Copy link
Owner

remkop commented Feb 4, 2017

No description provided.

@remkop remkop changed the title Implement usage online help Support case-insensitive (configurable) option name parsing Feb 4, 2017
@remkop remkop modified the milestone: 0.4.0 advanced option parsing Feb 5, 2017
@remkop remkop modified the milestones: backlog, 3.7 Aug 27, 2018
@remkop remkop modified the milestones: 3.7, 3.8 Oct 19, 2018
@remkop remkop modified the milestones: 4.2, 4.3 Jan 29, 2020
@ifsheldon
Copy link

Hi! I'm considering to contribute to this project, and I'd like to know whether this issue is still opened to PR. It seems that this has not been implemented yet. If the issue is still valid, have you got any advice on how to implement this?

@remkop
Copy link
Owner Author

remkop commented Mar 28, 2020

Hi, thank you for considering contributing!
Yes, PRs are welcome, especially ones with tests and documentation. :-)

About this particular feature, I haven’t analyzed what would be involved.

I was also planning to add support for abbreviated options and commands in the near future. That feature and this feature both increase the probability that options are ambiguous, and we need to be careful about that.

For example, picocli currently throws an InitializationException if a command has two options with the same name. If case-insensitive mode is enabled, this exception should also be thrown if two option names only differ in their (upper/lower) case.

Also, the ParseResult class allows applications to query if an option was specified on the command line (and what its value was). If case-insensitive mode is enabled, should this query also become case-insensitive?

There may be other areas where the expected behaviour needs to be clarified.

@remkop remkop added the theme: parser An issue or change related to the parser label Apr 2, 2020
@remkop remkop modified the milestones: 4.3, 4.4 Apr 14, 2020
@NewbieOrange
Copy link
Contributor

NewbieOrange commented May 2, 2020

Hello, @remkop ! I am evaluating different approaches to implement case-insensitive support and would love to hear some feedbacks.

  1. The least modification way seems to be add setCaseInsensitive and isCaseInsensitive methods to the CommandSpec (and also adding an alias in CommandLine?) class, since the class holds the map from string to OptionSpec / CommandLine, it would be relatively easy to replace LinkedHashMap to a simple case-aware LinkedMap implementation. It would only require minimum changes to the current code, and will be fully backward compatible. It may not be the most intuitive, and it only allows case-(in)sensitive be changed at (sub)command level, not per option. (but is that really needed?)

  2. We can also add the aforementioned methods to both OptionSpec and CommandSpec for individual control over each option / (sub)command. However, is it reasonable to have both case-sensitive and case-insensitive options in one command? This is the most flexible solution, but it would probably be harder to implement and possibly slow at runtime.

  3. ParserSpec seems to be another place for such options to be toggled at a parser level, but users will be unable to toggle for each command, thus not flexible.

@remkop
Copy link
Owner Author

remkop commented May 2, 2020

Hello @ifsheldon and @NewbieOrange, I noticed you both submitted a pull request for the same issue. First, thank you both for helping making picocli a better library! 👍

I would like to make this feature work as follows:

  • separate support for making options and subcommands case-insensitive (no special treatment needed for mixins)
  • enabling case-insensitive mode should be on a per-command basis (not per-option)
  • when options are case-insensitive, then negatable options and posix (single character) options also become case-insensitive
  • when options are case-insensitive, the ParseResult methods like hasMatchedOption(char), hasMatchedOption(String), matchedOption and matchedOptionValue should also be case-insensitive
  • if this information is stored in ParserSpec, we need setters like ParserSpec::optionsCaseInsensitive(boolean) and ParserSpec::subcommandsCaseInsensitive(boolean), and corresponding getters. Similarly if this information is stored in CommandSpec (or in the custom maps), then these accessors could be on CommandSpec (that would also be fine)
  • the CommandLine facade needs getters and setters for these properties, which are then applied to all subcommands, similar to CommandLine::setCaseInsensitiveEnumValuesAllowed

Both proposals are part of the way there, but not fully.

@ifsheldon and @NewbieOrange, how would you like to proceed? One idea is that you collaborate on a solution together, on a branch. Another option is that you work on different parts of the solution, and contribute each part separately. And there may be other ways to do this.

Thoughts?

@NewbieOrange
Copy link
Contributor

NewbieOrange commented May 3, 2020

Hi @remkop , thanks for your feedbacks!

Since ParseResult.matchedOption() calls static OptionSpec findOption(char shortName, Iterable<OptionSpec> options) in CommandSpec class, which does not pass any information about a CommandSpec instance, and findOption is a static method, it seems difficult to get any information about the case-insensitivity.

Also, since ParseResult is mainly used by the developers (not the application users), and the query name is written in code, is it necessary to support case-insensitive in such query? (Is there any use case where the query is generated at runtime?)

@remkop
Copy link
Owner Author

remkop commented May 3, 2020

Hi @NewbieOrange, here is an example:

Suppose that a case-insensitive command has an option @Option(names = "--option"). The end user specified --OPTION=123 on the command line.

The application may have code like String value = parseResult.matchedOptionValue("--option", -1). This should return value 123.

Implementation-wise, we can modify the static OptionSpec findOption(char shortName, Iterable<OptionSpec> options) method to take an additional boolean, and pass this.commandSpec().caseInsensitiveOptions() to that method.

@NewbieOrange
Copy link
Contributor

NewbieOrange commented May 3, 2020

Currently my PR should have parseResult.matchedOptionValue("--option", -1) return 123, I was thinking you asked parseResult.matchedOptionValue("--OPTION", -1) to also return 123. :)

I have already updated my fork to match all the features you have mentioned, if there is anything missing, please let me know!

Edit: The new test for case-insensitive options

    class App {
        @Option(names = "-h")
        boolean helpMessage;

        @Option(names = "-hElLo")
        boolean helloWorld;
    }
    CommandLine commandLine = new CommandLine(new App());
    commandLine.setCaseInsensitive(true);
    ParseResult result = commandLine.parseArgs("-h", "-HeLLO");

    assertTrue(result.hasMatchedOption("-h"));
    assertTrue(result.hasMatchedOption("-hElLo"));
    assertFalse(result.hasMatchedOption("-H"));
    assertFalse(result.hasMatchedOption("-hello"));
    assertFalse(result.hasMatchedOption("-HELLO"));
    assertFalse(result.hasMatchedOption("-HeLLO"));

@ifsheldon
Copy link

Indeed we are working together to contribute. Since @NewbieOrange has proposed a complete solution, it is unnecessary to propose another one, so it's fine that you just check and merge his branch.

@remkop
Copy link
Owner Author

remkop commented May 3, 2020

@ifsheldon Understood. I agree, your PR is already helping clarify the requirements and improving the solution. Many thanks! 👍


@NewbieOrange I guess it makes sense for applications to query the ParseResult only with the option name that was used in the option definition. I will think about this some more.

Different topic: I don't think that the convenience methods CommandSpec::caseInsensitive and CommandLine::setCaseInsensitive are needed. I prefer that applications explicitly specify both setCaseInsensitiveOptions and setCaseInsensitiveCommands.

Also, did you get a chance to add tests for case insensitive subcommands (and sub-subcommands)?

@NewbieOrange
Copy link
Contributor

NewbieOrange commented May 3, 2020

@remkop The convenience methods are removed and tests for (sub-)subcommands are added!

Edit: I also have changed setCaseInsensitiveCommands to setCaseInsensitiveSubcommands to match the naming of other methods.

remkop added a commit that referenced this issue May 4, 2020
jerrylususu added a commit to jerrylususu/picocli that referenced this issue May 4, 2020
remkop added a commit that referenced this issue May 4, 2020
* update RELEASE-NOTES.md
* simplify user manual section on Case Sensitivity
* throw DuplicateNameException instead of IllegalStateException in CaseInsensitiveLinkedMap
* throw DuplicateNameException instead of InitializationException in addSubcommand
* suppress warnings for unchecked cast
* minor improvements to POSIXOptionResembleDemo
remkop added a commit that referenced this issue May 4, 2020
…edMap; made class package-protected to facilitate testing
@remkop remkop closed this as completed May 5, 2020
remkop added a commit that referenced this issue May 6, 2020
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
theme: parser An issue or change related to the parser type: API 🔌 type: enhancement ✨
Projects
None yet
Development

No branches or pull requests

3 participants