3838import java.util.*;
3939import java.util.concurrent.Callable;
4040import java.util.regex.Pattern;
41-
41+ import java.util.stream.Collectors;
4242import picocli.CommandLine.Help.Ansi.IStyle;
4343import picocli.CommandLine.Help.Ansi.Style;
4444import picocli.CommandLine.Help.Ansi.Text;
140140 * </p>
141141 */
142142public class CommandLine {
143+
144+ /** Predefined section keys. */
145+ public static final String HEADER_HEADING = "headerHeading";
146+ public static final String HEADER = "header";
147+ public static final String SYNOPSIS_HEADING = "synopsisHeading";
148+ public static final String SYNOPSIS = "synopsis";
149+ public static final String DESCRIPTION_HEADING = "descriptionHeading";
150+ public static final String DESCRIPTION = "description";
151+ public static final String PARAMETER_LIST_HEADING = "parameterListHeading";
152+ public static final String PARAMETER_LIST = "parameterList";
153+ public static final String OPTION_LIST_HEADING = "optionListHeading";
154+ public static final String OPTION_LIST = "optionList";
155+ public static final String COMMAND_LIST_HEADING = "commandListHeading";
156+ public static final String COMMAND_LIST = "commandList";
157+ public static final String FOOTER_HEADING = "footerHeading";
158+ public static final String FOOTER = "footer";
159+
143160 /** This is picocli version {@value}. */
144161 public static final String VERSION = "4.0.0-SNAPSHOT";
145162
@@ -149,6 +166,24 @@ public class CommandLine {
149166 private final IFactory factory;
150167 private IHelpFactory helpFactory;
151168
169+ private List<String> sectionKeys = Collections.unmodifiableList(Arrays.asList(
170+ HEADER_HEADING,
171+ HEADER,
172+ SYNOPSIS_HEADING,
173+ SYNOPSIS,
174+ DESCRIPTION_HEADING,
175+ DESCRIPTION,
176+ PARAMETER_LIST_HEADING,
177+ PARAMETER_LIST,
178+ OPTION_LIST_HEADING,
179+ OPTION_LIST,
180+ COMMAND_LIST_HEADING,
181+ COMMAND_LIST,
182+ FOOTER_HEADING,
183+ FOOTER));
184+
185+ private Map<String, IHelpSectionRenderer> helpSectionRendererMap = createHelpSectionRendererMap();
186+
152187 /**
153188 * Constructs a new {@code CommandLine} interpreter with the specified object (which may be an annotated user object or a {@link CommandSpec CommandSpec}) and a default subcommand factory.
154189 * <p>The specified object may be a {@link CommandSpec CommandSpec} object, or it may be a {@code @Command}-annotated
@@ -1549,23 +1584,85 @@ public String getUsageMessage(Help.Ansi ansi) {
15491584 public String getUsageMessage(Help.ColorScheme colorScheme) {
15501585 return usage(new StringBuilder(), getHelpFactory().create(getCommandSpec(), colorScheme)).toString();
15511586 }
1552- private static StringBuilder usage(StringBuilder sb, Help help) {
1553- return sb.append(help.headerHeading())
1554- .append(help.header())
1555- .append(help.synopsisHeading()) //e.g. Usage:
1556- .append(help.synopsis(help.synopsisHeadingLength())) //e.g. <main class> [OPTIONS] <command> [COMMAND-OPTIONS] [ARGUMENTS]
1557- .append(help.descriptionHeading()) //e.g. %nDescription:%n%n
1558- .append(help.description()) //e.g. {"Converts foos to bars.", "Use options to control conversion mode."}
1559- .append(help.parameterListHeading()) //e.g. %nPositional parameters:%n%n
1560- .append(help.parameterList()) //e.g. [FILE...] the files to convert
1561- .append(help.optionListHeading()) //e.g. %nOptions:%n%n
1562- .append(help.optionList()) //e.g. -h, --help displays this help and exits
1563- .append(help.commandListHeading()) //e.g. %nCommands:%n%n
1564- .append(help.commandList()) //e.g. add adds the frup to the frooble
1565- .append(help.footerHeading())
1566- .append(help.footer());
1587+
1588+ private StringBuilder usage(StringBuilder sb, Help help) {
1589+ for (String key : getSectionKeys()) {
1590+ IHelpSectionRenderer renderer = helpSectionRendererMap.get(key);
1591+ if (renderer != null) { sb.append(renderer.render(help)); }
1592+ }
1593+ return sb;
1594+ }
1595+
1596+ /**
1597+ * Returns the section keys in the order that the usage help message should render the sections.
1598+ * This ordering may be modified with {@link #setSectionKeys(List) setSectionKeys}. The default keys are:
1599+ * <pre>
1600+ * "headerHeading",
1601+ * "header",
1602+ * "synopsisHeading",
1603+ * "synopsis",
1604+ * "descriptionHeading",
1605+ * "description",
1606+ * "parameterListHeading",
1607+ * "parameterList",
1608+ * "optionListHeading",
1609+ * "optionList",
1610+ * "commandListHeading",
1611+ * "commandList",
1612+ * "footerHeading",
1613+ * "footer"
1614+ * </pre>
1615+ * @since 3.9
1616+ */
1617+ public List<String> getSectionKeys() { return sectionKeys; }
1618+
1619+ /**
1620+ * Sets the section keys in the order that the usage help message should render the sections.
1621+ * @see #getSectionKeys
1622+ * @since 3.9
1623+ */
1624+ public void setSectionKeys(List<String> keys) { sectionKeys = Collections.unmodifiableList(keys); }
1625+
1626+ /** Returns the help section renderers for the predefined section keys. see: {@link #getSectionKeys()} */
1627+ private Map<String, IHelpSectionRenderer> createHelpSectionRendererMap() {
1628+ Map<String, IHelpSectionRenderer> result = new HashMap<String, IHelpSectionRenderer>();
1629+
1630+ result.put(HEADER_HEADING, new IHelpSectionRenderer() { public String render(Help help) { return help.headerHeading(); } });
1631+ result.put(HEADER, new IHelpSectionRenderer() { public String render(Help help) { return help.header(); } });
1632+ //e.g. Usage:
1633+ result.put(SYNOPSIS_HEADING, new IHelpSectionRenderer() { public String render(Help help) { return help.synopsisHeading(); } });
1634+ //e.g. <main class> [OPTIONS] <command> [COMMAND-OPTIONS] [ARGUMENTS]
1635+ result.put(SYNOPSIS, new IHelpSectionRenderer() { public String render(Help help) { return help.synopsis(help.synopsisHeadingLength()); } });
1636+ //e.g. %nDescription:%n%n
1637+ result.put(DESCRIPTION_HEADING, new IHelpSectionRenderer() { public String render(Help help) { return help.descriptionHeading(); } });
1638+ //e.g. {"Converts foos to bars.", "Use options to control conversion mode."}
1639+ result.put(DESCRIPTION, new IHelpSectionRenderer() { public String render(Help help) { return help.description(); } });
1640+ //e.g. %nPositional parameters:%n%n
1641+ result.put(PARAMETER_LIST_HEADING, new IHelpSectionRenderer() { public String render(Help help) { return help.parameterListHeading(); } });
1642+ //e.g. [FILE...] the files to convert
1643+ result.put(PARAMETER_LIST, new IHelpSectionRenderer() { public String render(Help help) { return help.parameterList(); } });
1644+ //e.g. %nOptions:%n%n
1645+ result.put(OPTION_LIST_HEADING, new IHelpSectionRenderer() { public String render(Help help) { return help.optionListHeading(); } });
1646+ //e.g. -h, --help displays this help and exits
1647+ result.put(OPTION_LIST, new IHelpSectionRenderer() { public String render(Help help) { return help.optionList(); } });
1648+ //e.g. %nCommands:%n%n
1649+ result.put(COMMAND_LIST_HEADING, new IHelpSectionRenderer() { public String render(Help help) { return help.commandListHeading(); } });
1650+ //e.g. add adds the frup to the frooble
1651+ result.put(COMMAND_LIST, new IHelpSectionRenderer() { public String render(Help help) { return help.commandList(); } });
1652+ result.put(FOOTER_HEADING, new IHelpSectionRenderer() { public String render(Help help) { return help.footerHeading(); } });
1653+ result.put(FOOTER, new IHelpSectionRenderer() { public String render(Help help) { return help.footer(); } });
1654+ return result;
15671655 }
15681656
1657+ /**
1658+ * Returns the map of section keys and renderers used to construct the usage help message.
1659+ * The usage help message can be customized by adding, replacing and removing section renderers from this map.
1660+ * Sections can be reordered with {@link #setSectionKeys(List) setSectionKeys}.
1661+ * Sections that are either not in this map or not in the list returned by {@link #getSectionKeys() getSectionKeys} are omitted.
1662+ * @since 3.9
1663+ */
1664+ public Map<String, IHelpSectionRenderer> getSectionMap() { return helpSectionRendererMap; }
1665+
15691666 /**
15701667 * Delegates to {@link #printVersionHelp(PrintStream, Help.Ansi)} with the {@linkplain Help.Ansi#AUTO platform default}.
15711668 * @param out the printStream to print to
@@ -7996,6 +8093,18 @@ public static interface IHelpCommandInitializable {
79968093 void init(CommandLine helpCommandLine, Help.Ansi ansi, PrintStream out, PrintStream err);
79978094 }
79988095
8096+
8097+ public interface IHelpSectionRenderer {
8098+
8099+ /**
8100+ * Renders a section of the usage help, like header heading, header, synopsis heading,
8101+ * synopsis, description heading, description, etc.
8102+ * @since 3.9
8103+ */
8104+ String render(Help help);
8105+
8106+ }
8107+
79998108 /**
80008109 * A collection of methods and inner classes that provide fine-grained control over the contents and layout of
80018110 * the usage help message to display to end users when help is requested or invalid input values were specified.
@@ -8100,7 +8209,7 @@ public Help(CommandSpec commandSpec, ColorScheme colorScheme) {
81008209 * of the {@link ParserSpec#separator()} at construction time. If the separator is modified after Help construction, you
81018210 * may need to re-initialize this field by calling {@link #createDefaultParamLabelRenderer()} again. */
81028211 public IParamLabelRenderer parameterLabelRenderer() {return parameterLabelRenderer;}
8103-
8212+
81048213 /** Registers all specified subcommands with this Help.
81058214 * @param commands maps the command names to the associated CommandLine object
81068215 * @return this Help instance (for method chaining)
0 commit comments