我如何构建帮助功能以将子命令分成具有单独描述性 headers 的组?

How can I construct the help functionality to divide subcommands into groups with separate descriptive headers?

我目前正在使用的一个工具有很多子命令,这使得 picocli 的帮助输出不清楚。我的代码如下所示:

@Command(name = "toolName",
    version = "version",
    sortOptions = false,
    parameterListHeading = "%nParameters:%n",
    optionListHeading = "%nOptions:%n",
    commandListHeading = "%nThese are common commands:%n%n",
    subcommands = {
    command1.class,
    command2.class,
    command3.class,
    etc.class }

帮助如下:

@Option(names = {"-h", "--help"}, scope = ScopeType.INHERIT, usageHelp = true,
        description = "Display this help message.")
boolean usageHelpRequested;

我希望帮助输出看起来类似于 git 的:

git bash --help output

这里的子命令被分成不同的功能(比如启动一个工作区)并带有描述。这可能使用 picocli 吗?

是的,通过深入了解 picocli 的帮助 API。

一个想法是自定义 IHelpSectionRenderer,请参阅此示例:https://github.com/remkop/picocli/blob/main/picocli-examples/src/main/java/picocli/examples/customhelp/GroupingDemo.java

我按照建议定制了一个 IHelpSectionRenderer。如果有人感兴趣:

@Command(name = "help", helpCommand = true)
class Help implements Runnable {
    public static Map<String, String[]> commandGroups;
    static {
        commandGroups = new HashMap<>();
        commandGroups.put("group1", new String[]{
            "function1", "function2", "function3"});
        commandGroups.put("group2", new String[]{
            "function3", "function4", "function5"});
}

@Override
public void run() {
    CommandLine cmd = new CommandLine(new Tool());

    cmd.getHelpSectionMap().remove(SECTION_KEY_COMMAND_LIST_HEADING);
    cmd.getHelpSectionMap().remove(SECTION_KEY_COMMAND_LIST);
    List<String> keys = new ArrayList<>(cmd.getHelpSectionKeys());

    for (String group : commandGroups.keySet()) {
        String headerSection = "SECTION_KEY_" + group + "_HEADING";
        String commandSection = "SECTION_KEY_" + group;
        cmd.getHelpSectionMap().put(headerSection,
                help -> help.createHeading("%n" + group + ":%n"));
        cmd.getHelpSectionMap().put(commandSection,
                new CommandListRenderer(commandGroups.get(group)));
        keys.add(headerSection);
        keys.add(commandSection);
    }

    cmd.setHelpSectionKeys(keys);
    cmd.usage(System.out);

    }
}

class CommandListRenderer implements IHelpSectionRenderer {
    String[] functions;


    public CommandListRenderer(String[] f) {
        functions = f;
    }

    //@Override
    public String render(Help help) {
        CommandSpec spec = help.commandSpec();
        if (spec.subcommands().isEmpty()) { return ""; }

        // prepare layout: two columns
        // the left column overflows, the right column wraps if text is too long
        Help.TextTable textTable = 
        Help.TextTable.forColumns(help.colorScheme(),
            new Help.Column(30, 2, Help.Column.Overflow.SPAN),
            new Help.Column(spec.usageMessage().width() - 30, 2, 
        Help.Column.Overflow.TRUNCATE));

        for (String f : functions) {
            CommandLine subcommand = spec.subcommands().get(f);
            if (subcommand == null) {
                throw new NotFoundException("Function not found: " + f);
            }
            addCommand(subcommand, textTable);
        }
        return textTable.toString();
    }

public void addCommand(CommandLine cmd, Help.TextTable textTable) {
    // create comma-separated list of command name and aliases
    String names = cmd.getCommandSpec().names().toString();
    names = names.substring(1, names.length() - 1); // remove leading '[' and trailing ']'

    // command description is taken from header or description
    String description = description(cmd.getCommandSpec().usageMessage());

    // add a line for this command to the layout
    textTable.addRowValues(names, description);
}

private String description(Model.UsageMessageSpec usageMessage) {
    if (usageMessage.header().length > 0) {
        return usageMessage.header()[0];
    }
    if (usageMessage.description().length > 0) {
        return usageMessage.description()[0];
    }
    return "";
}

}