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

Synopsis should respect order if specified (was: SortOption=false does not work when mixing "attributes" and "setter" Options) #1408

Closed
sbernard31 opened this issue Aug 13, 2021 · 7 comments
Labels
theme: usagehelp An issue or change related to the usage help message type: bug 🐛
Milestone

Comments

@sbernard31
Copy link
Contributor

The documentation§14.4. Unsorted Option List says :

By default the options list displays options in alphabetical order. Use the sortOptions = false attribute to display options in the order they are declared in your class.
@Command(sortOptions = false)

I want to sort my options in the order they are declared and so use sortOptions = false.

I faced a situation where the described behavior is not respected.
I firstly thought that I maybe find a bug, after lot of time trying to understand, I isolated the issue.

It seems it does not really works if you mixed "methods" and "attribitutes"
Knowing that I'm not so sure if this could really fixed so if this is really a bug.

Here is the code to reproduce :

@Command(name = "myCommand",
         mixinStandardHelpOptions = true,
         description = "A command with not well ordered option.",
         sortOptions = false)
public class Example {

    @Option(names = { "-d", "--option-d" }, defaultValue = "Default D", description = "A first level option D")
    private void setD(String value) {
        this.d = value;
    }
    private String d;
    
    @Option(names = { "-c", "--option-c" }, defaultValue = "Default C", description = "A first level option C")
    private void setC(String value) {
        this.c = value;
    }
    private String c;

    @Option(names = { "-b", "--option-b" }, defaultValue = "Default B", description = "A first level option B")
    private String b;

    @Option(names = { "-a", "--option-a" }, defaultValue = "Default A", description = "A first level option A")
    private String a;

    public static void main(String... args) {
        Example example = new Example();
        int exitCode = new CommandLine(example).execute(args);

        System.exit(exitCode);
    }
}

And the output you get :

Usage: myCommand [-hV] [-a=<a>] [-b=<b>] [-c=<c>] [-d=<d>]
A command with not well ordered option.
  -b, --option-b=<b>   Should be 3rd
  -a, --option-a=<a>   Should be 4th
  -d, --option-d=<d>   Should be 1st
  -c, --option-c=<c>   Should be 2nd
  -h, --help           Show this help message and exit.
  -V, --version        Print version information and exit.

You can see that :

  • option is not ordered by declaration order. (maybe not possible to do better)
  • synopsis is not ordered like option list (this one looks more like a bug ?)

The workaround is using @Option(order = <int>) (see 14.5. Reordering Options)

@sbernard31 sbernard31 changed the title SortOption=false with option declared as method SortOption=false does not work when mixing "attributes" and "setter" Options Aug 13, 2021
@remkop
Copy link
Owner

remkop commented Aug 14, 2021

Hi @sbernard31 you are correct, and this behaviour is documented in the user manual (14.5. Reordering Options):

When mixing @option methods and @option fields, options do not reliably appear in declaration order.

Glad you already found the solution: you can sort options using @Option(order = <int>).

(When you do that, I expect that the synopsis ordering will line up with the option list ordering.)

@remkop
Copy link
Owner

remkop commented Aug 14, 2021

Is there any action you want me to take related to this ticket?

@sbernard31
Copy link
Contributor Author

this behaviour is documented in the user manual (14.5. Reordering Options)

When mixing @option methods and @option fields, options do not reliably appear in declaration order.

I totally missed it ? 🤦‍♂️

(When you do that, I expect that the synopsis ordering will line up with the option list ordering.)

Nope it looks like this :

Usage: myCommand [-hV] [-a=<a>] [-b=<b>] [-c=<c>] [-d=<d>]
A command with not well ordered option.
  -h, --help           Show this help message and exit.
  -V, --version        Print version information and exit.
  -d, --option-d=<d>   Should be 1st
  -c, --option-c=<c>   Should be 2nd
  -b, --option-b=<b>   Should be 3rd
  -a, --option-a=<a>   Should be 4th

So finally maybe there is bug with the synopsis order ?

I share the code if you want to try to reproduce on your own :

@Command(name = "myCommand",
         mixinStandardHelpOptions = true,
         description = "A command with not well ordered option.",
         sortOptions = false)
public class Example {

    @Option(names = { "-d", "--option-d" }, defaultValue = "Default D", description = "Should be 1st", order = 1)
    private void setD(String value) {
        this.d = value;
    }
    private String d;
    
    @Option(names = { "-c", "--option-c" }, defaultValue = "Default C", description = "Should be 2nd",order = 2)
    private void setC(String value) {
        this.c = value;
    }
    private String c;

    @Option(names = { "-b", "--option-b" }, defaultValue = "Default B", description = "Should be 3rd", order = 3)
    private String b;

    @Option(names = { "-a", "--option-a" }, defaultValue = "Default A", description = "Should be 4th", order = 4)
    private String a;
    
    
   /* @Option(names = { "-d", "--option-d" }, defaultValue = "Default D", description = "Should be 1st")
    private void setD(String value) {
        this.d = value;
    }
    private String d;
    
    @Option(names = { "-c", "--option-c" }, defaultValue = "Default C", description = "Should be 2nd")
    private void setC(String value) {
        this.c = value;
    }
    private String c;

    @Option(names = { "-b", "--option-b" }, defaultValue = "Default B", description = "Should be 3rd")
    private String b;

    @Option(names = { "-a", "--option-a" }, defaultValue = "Default A", description = "Should be 4th")
    private String a;*/

    public static void main(String... args) {
        Example example = new Example();
        int exitCode = new CommandLine(example).execute(args);

        System.exit(exitCode);
    }
}

@remkop
Copy link
Owner

remkop commented Aug 19, 2021

Looks like for the synopsis, picocli by default sorts options using the Comparator returned by Help::createShortOptionArityAndNameComparator, where for the options list, options are sorted by the Comparator returned by Help::createDefaultOptionSort.

You are correct, the current implementation always sorts the synopsis options alphabetically and does not take sortOptions=false into account.

You can change this via the Help API: similar to this example, you can have a custom IHelpFactory like this:

    static class MyHelpFactory implements IHelpFactory {
        @Override
        public Help create(CommandSpec commandSpec, ColorScheme colorScheme) {
            return new Help(commandSpec, colorScheme) {
                @Override
                    public String synopsis(int synopsisHeadingLength) {

                        // the simplest thing is to just do this:
                        return detailedSynopsis(synopsisHeadingLength, 
                                createOrderComparatorIfNecessary(commandSpec.options()), true);

                        // but if you want to keep it more generic you can do this:
/*
                        if (!empty(commandSpec.usageMessage().customSynopsis())) { return customSynopsis(); }

                        Comparator<OptionSpec> optionSort = 
                                return commandSpec.usageMessage().sortOptions()
                                        ? createShortOptionArityAndNameComparator()
                                        : createOrderComparatorIfNecessary(commandSpec.options());

                        return commandSpec.usageMessage().abbreviateSynopsis() ? abbreviatedSynopsis()
                                : detailedSynopsis(synopsisHeadingLength, optionSort, true);
*/
                    }
            };
        }
    }

And you use it like this:

    public static void main(String[] args) {
        CommandLine cmd = new CommandLine(new App());
        cmd.setHelpFactory(new MyHelpFactory());
        cmd.execute(args);
    }

@remkop remkop added this to the 4.7 milestone Feb 11, 2022
@remkop remkop added theme: usagehelp An issue or change related to the usage help message type: bug 🐛 labels Feb 12, 2022
@remkop
Copy link
Owner

remkop commented Feb 12, 2022

Note to self: maybe related to #964.

@remkop remkop changed the title SortOption=false does not work when mixing "attributes" and "setter" Options Synopsis should respect order if specified (was: SortOption=false does not work when mixing "attributes" and "setter" Options) Feb 14, 2022
@remkop remkop closed this as completed in 84fa2c2 Feb 14, 2022
@remkop
Copy link
Owner

remkop commented Feb 14, 2022

A fix for the synopsis order has been implemented.

This requires the @Command(sortSynopsis = false) annotation to be set.
See also #1574.

@remkop
Copy link
Owner

remkop commented Feb 15, 2022

I also clarified the user manual, many people ran into the same confusion, because the information was kind of hidden in the next section. Hopefully this CAUTION call-out will clarify things. I also mention it in the javadocs now.

image

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
theme: usagehelp An issue or change related to the usage help message type: bug 🐛
Projects
None yet
Development

No branches or pull requests

2 participants