I made a custom IHelpSectionRenderer as advised. In case someone is interested:
@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 "";
}
}