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

Show/Hide command on specific conditions #1052

Closed
charphi opened this issue May 18, 2020 · 8 comments
Closed

Show/Hide command on specific conditions #1052

charphi opened this issue May 18, 2020 · 8 comments
Labels
theme: usagehelp An issue or change related to the usage help message type: API 🔌 type: enhancement ✨
Milestone

Comments

@charphi
Copy link
Contributor

charphi commented May 18, 2020

The current way to show/hide a command is to set the parameter at compile time.
Similar to IVersionProvider, it could be useful to show/hide a command if certain conditions are met such as platform, OS, resources, ...

@remkop remkop added type: API 🔌 type: enhancement ✨ theme: usagehelp An issue or change related to the usage help message labels May 18, 2020
@remkop
Copy link
Owner

remkop commented May 18, 2020

Yes, this is currently not easy to achieve. There is an API to customize the help: help section renderers, but for fine-grained changes like you describe it would mean re-implementing large pieces of logic in the Help.listCommands(), Help.listOptions() and Help.listParameters() methods.

One reason this is not easy is that these methods currently query the CommandSpec model directly. What we could do instead is add methods that take a list of the objects to display.

We could change the Help class to something like this:

// CommandLine.Help class

public String commandList() {
    return commandList(subcommands());
}

// NEW method: only specified subcommands are shown
public String commandList(Map<String, Help> subcommands) {
    // logic for creating COMMAND_LIST section of usage help
}

public String optionList() {
    return optionList(commandSpec.options());
}

// NEW method: only specified OptionSpec objects are shown
public String optionList(List<OptionSpec> options) {
    // logic for creating OPTION_LIST section of usage help
}

public String parameterList() {
    return parameterList(commandSpec.positionalParameters());
}

// NEW method: only specified PositionalParamSpec objects are shown
public String parameterList(List<PositionalParamSpec> positionals) {
    // logic for creating PARAMETER_LIST section of usage help
}

This would allow you to customize the usage help message:

IHelpSectionRenderer renderer = (Help help) -> {
    Map<String, Help> subcommands = new LinkedHashMap<>(help.subcommands());
    // remove subcommands that you don't want to show...
    return help.commandList(subcommands);
};
CommandLine cmd= new CommandLine(new MyApp());
cmd.getHelpSectionMap().put(UsageMessageSpec.SECTION_KEY_COMMAND_LIST, renderer);

Requirements like #983 could then be implemented like this:

IHelpSectionRenderer renderer = (Help help) -> {
    // don't show inherited options...
    return help.optionList(help.commandSpec().options().stream()
            .filter(option -> !option.inherited())
            .collect(Collectors.toList()));
};
CommandLine cmd= new CommandLine(new MyApp());
cmd.getHelpSectionMap().put(UsageMessageSpec.SECTION_KEY_OPTION_LIST, renderer);

@remkop remkop added this to the 4.4 milestone May 23, 2020
remkop added a commit that referenced this issue Jun 1, 2020
…#1085][#1084][#1096][#1081] improve Help API and impl

* [#1052] API: Show/Hide commands in usage help on specific conditions. Thanks to [Philippe Charles](https://github.com/charphi) for raising this.
* [#1088] API: Add method `Help::allSubcommands` to return all subcommands, including hidden ones.
* [#1090] API: Add methods `Help::optionListExcludingGroups` to return a String with the rendered section of the usage help containing only the specified options, including hidden ones.
* [#1092] API: Add method `Help::parameterList(List<PositionalParamSpec>)` to return a String with the rendered section of the usage help containing only the specified positional parameters, including hidden ones.
* [#1093] API: Add method `Help::commandList(Map<String, Help>)` to return a String with the rendered section of the usage help containing only the specified subcommands, including hidden ones.
* [#1091] API: Add method `Help::optionListGroupSections` to return a String with the rendered section of the usage help containing only the option groups.
* [#1089] API: Add method `Help::createDefaultOptionSort` to create a `Comparator` that follows the command and options' configuration.
* [#1094] API: Add method `Help::createDefaultLayout(List<OptionSpec>, List<PositionalParamSpec>, ColorScheme)` to create a layout for the specified options and positionals.
* [#1087] API: Add methods `ArgumentGroupSpec::allOptionsNested` and `ArgumentGroupSpec::allPositionalParametersNested`.
* [#1086] API: add methods `Help.Layout::addAllOptions` and `Help.Layout::addAllPositionals`, to show all specified options, including hidden ones.
* [#1085] API: Add method `Help::optionSectionGroups` to get argument groups with a header.
* [#1084] API: Add API to create layout for a list of options/positional params.
* [#1096] Enhancement: Override `Help.Column` `equals`, `hashCode` and `toString` methods to facilitate testing.
* [#1081] Bugfix: `CommandLine.Help` constructor no longer calls overridable methods `addAllSubcommands` and `createDefaultParamLabelRenderer`.

Closes #1088, closes #1090, closes #1092, closes #1093, closes #1091, closes #1089, closes #1094, closes #1087, closes #1086, closes #1085, closes #1084, closes #1096, closes #1081
@remkop
Copy link
Owner

remkop commented Jun 1, 2020

I pushed a commit to master that should give you the tools to fix this. Specifically, these two new methods:

  • Help.allSubcommands
  • Help.commandList(Map)

To show all subcommands, including any hidden commands, do this:

IHelpSectionRenderer renderer = (Help help) -> {
    return help.commandList(help.allSubcommands());
};

CommandLine cmd= new CommandLine(new MyApp());
cmd.getHelpSectionMap().put(UsageMessageSpec.SECTION_KEY_COMMAND_LIST, renderer);

Can you try this to see if it meets your requirements?

@charphi
Copy link
Contributor Author

charphi commented Jun 2, 2020

I've just tested it and it is ok.

Note that I only use help.subcommands() instead of help.allSubcommands() since I consider that an annotated command with hidden parameter set to true should always be hidden.

@remkop
Copy link
Owner

remkop commented Jun 2, 2020

Great, thanks!

Can you share code for your use case?
I would like to show an example in the release notes of how this feature would be useful.

@remkop
Copy link
Owner

remkop commented Jun 6, 2020

I’ve added some examples for the different (but similar) use case of hiding inherited options in the usage help message of subcommands:

If you have some code you can share that demonstrates your use case, that would be great!

@charphi
Copy link
Contributor Author

charphi commented Jun 8, 2020

Ok, I will share something as soon as possible.

@charphi
Copy link
Contributor Author

charphi commented Jun 8, 2020

Here is a simplified example. Is the intent clear enough?

public class Example {

    @Command(name = "myApp")
    static class App {

        @Command(
                name = "do-something",
                description = "Do something useful."
        )
        void doSomething() {
        }

        @Command(
                name = "generate-cmd-launcher",
                description = "Generate a command prompt launcher (${PARENT-COMMAND-NAME}.cmd)."
        )
        void generateCmdLauncher() {
        }
    }

    public static void main(String[] args) {

        IHelpSectionRenderer renderer = (CommandLine.Help help) -> {
            LinkedHashMap<String, Help> map = new LinkedHashMap<>(help.subcommands());
            if (!isWindows()) {
                map.remove("generate-cmd-launcher");
            }
            return help.commandList(map);
        };

        CommandLine cmd = new CommandLine(new App());
        cmd.getHelpSectionMap().put(UsageMessageSpec.SECTION_KEY_COMMAND_LIST, renderer);
        System.out.println(cmd.getUsageMessage());
    }

    static boolean isWindows() {
        return System.getProperty("os.name").toLowerCase().contains("win");
    }
}

@remkop
Copy link
Owner

remkop commented Jun 8, 2020

Thank you! Very helpful. I added it to the examples.

@remkop remkop closed this as completed Jun 19, 2020
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: API 🔌 type: enhancement ✨
Projects
None yet
Development

No branches or pull requests

2 participants