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
@@ -1535,23 +1570,85 @@ public String getUsageMessage(Help.Ansi ansi) {
15351570 public String getUsageMessage(Help.ColorScheme colorScheme) {
15361571 return usage(new StringBuilder(), getHelpFactory().create(getCommandSpec(), colorScheme)).toString();
15371572 }
1538- private static StringBuilder usage(StringBuilder sb, Help help) {
1539- return sb.append(help.headerHeading())
1540- .append(help.header())
1541- .append(help.synopsisHeading()) //e.g. Usage:
1542- .append(help.synopsis(help.synopsisHeadingLength())) //e.g. <main class> [OPTIONS] <command> [COMMAND-OPTIONS] [ARGUMENTS]
1543- .append(help.descriptionHeading()) //e.g. %nDescription:%n%n
1544- .append(help.description()) //e.g. {"Converts foos to bars.", "Use options to control conversion mode."}
1545- .append(help.parameterListHeading()) //e.g. %nPositional parameters:%n%n
1546- .append(help.parameterList()) //e.g. [FILE...] the files to convert
1547- .append(help.optionListHeading()) //e.g. %nOptions:%n%n
1548- .append(help.optionList()) //e.g. -h, --help displays this help and exits
1549- .append(help.commandListHeading()) //e.g. %nCommands:%n%n
1550- .append(help.commandList()) //e.g. add adds the frup to the frooble
1551- .append(help.footerHeading())
1552- .append(help.footer());
1573+
1574+ private StringBuilder usage(StringBuilder sb, Help help) {
1575+ for (String key : getSectionKeys()) {
1576+ IHelpSectionRenderer renderer = helpSectionRendererMap.get(key);
1577+ if (renderer != null) { sb.append(renderer.render(help)); }
1578+ }
1579+ return sb;
1580+ }
1581+
1582+ /**
1583+ * Returns the section keys in the order that the usage help message should render the sections.
1584+ * This ordering may be modified with {@link #setSectionKeys(List) setSectionKeys}. The default keys are:
1585+ * <pre>
1586+ * "headerHeading",
1587+ * "header",
1588+ * "synopsisHeading",
1589+ * "synopsis",
1590+ * "descriptionHeading",
1591+ * "description",
1592+ * "parameterListHeading",
1593+ * "parameterList",
1594+ * "optionListHeading",
1595+ * "optionList",
1596+ * "commandListHeading",
1597+ * "commandList",
1598+ * "footerHeading",
1599+ * "footer"
1600+ * </pre>
1601+ * @since 3.9
1602+ */
1603+ public List<String> getSectionKeys() { return sectionKeys; }
1604+
1605+ /**
1606+ * Sets the section keys in the order that the usage help message should render the sections.
1607+ * @see #getSectionKeys
1608+ * @since 3.9
1609+ */
1610+ public void setSectionKeys(List<String> keys) { sectionKeys = Collections.unmodifiableList(keys); }
1611+
1612+ /** Returns the help section renderers for the predefined section keys. see: {@link #getSectionKeys()} */
1613+ private Map<String, IHelpSectionRenderer> createHelpSectionRendererMap() {
1614+ Map<String, IHelpSectionRenderer> result = new HashMap<String, IHelpSectionRenderer>();
1615+
1616+ result.put(HEADER_HEADING, new IHelpSectionRenderer() { public String render(Help help) { return help.headerHeading(); } });
1617+ result.put(HEADER, new IHelpSectionRenderer() { public String render(Help help) { return help.header(); } });
1618+ //e.g. Usage:
1619+ result.put(SYNOPSIS_HEADING, new IHelpSectionRenderer() { public String render(Help help) { return help.synopsisHeading(); } });
1620+ //e.g. <main class> [OPTIONS] <command> [COMMAND-OPTIONS] [ARGUMENTS]
1621+ result.put(SYNOPSIS, new IHelpSectionRenderer() { public String render(Help help) { return help.synopsis(help.synopsisHeadingLength()); } });
1622+ //e.g. %nDescription:%n%n
1623+ result.put(DESCRIPTION_HEADING, new IHelpSectionRenderer() { public String render(Help help) { return help.descriptionHeading(); } });
1624+ //e.g. {"Converts foos to bars.", "Use options to control conversion mode."}
1625+ result.put(DESCRIPTION, new IHelpSectionRenderer() { public String render(Help help) { return help.description(); } });
1626+ //e.g. %nPositional parameters:%n%n
1627+ result.put(PARAMETER_LIST_HEADING, new IHelpSectionRenderer() { public String render(Help help) { return help.parameterListHeading(); } });
1628+ //e.g. [FILE...] the files to convert
1629+ result.put(PARAMETER_LIST, new IHelpSectionRenderer() { public String render(Help help) { return help.parameterList(); } });
1630+ //e.g. %nOptions:%n%n
1631+ result.put(OPTION_LIST_HEADING, new IHelpSectionRenderer() { public String render(Help help) { return help.optionListHeading(); } });
1632+ //e.g. -h, --help displays this help and exits
1633+ result.put(OPTION_LIST, new IHelpSectionRenderer() { public String render(Help help) { return help.optionList(); } });
1634+ //e.g. %nCommands:%n%n
1635+ result.put(COMMAND_LIST_HEADING, new IHelpSectionRenderer() { public String render(Help help) { return help.commandListHeading(); } });
1636+ //e.g. add adds the frup to the frooble
1637+ result.put(COMMAND_LIST, new IHelpSectionRenderer() { public String render(Help help) { return help.commandList(); } });
1638+ result.put(FOOTER_HEADING, new IHelpSectionRenderer() { public String render(Help help) { return help.footerHeading(); } });
1639+ result.put(FOOTER, new IHelpSectionRenderer() { public String render(Help help) { return help.footer(); } });
1640+ return result;
15531641 }
15541642
1643+ /**
1644+ * Returns the map of section keys and renderers used to construct the usage help message.
1645+ * The usage help message can be customized by adding, replacing and removing section renderers from this map.
1646+ * Sections can be reordered with {@link #setSectionKeys(List) setSectionKeys}.
1647+ * Sections that are either not in this map or not in the list returned by {@link #getSectionKeys() getSectionKeys} are omitted.
1648+ * @since 3.9
1649+ */
1650+ public Map<String, IHelpSectionRenderer> getSectionMap() { return helpSectionRendererMap; }
1651+
15551652 /**
15561653 * Delegates to {@link #printVersionHelp(PrintStream, Help.Ansi)} with the {@linkplain Help.Ansi#AUTO platform default}.
15571654 * @param out the printStream to print to
@@ -7950,6 +8047,18 @@ public static interface IHelpCommandInitializable {
79508047 void init(CommandLine helpCommandLine, Help.Ansi ansi, PrintStream out, PrintStream err);
79518048 }
79528049
8050+
8051+ public interface IHelpSectionRenderer {
8052+
8053+ /**
8054+ * Renders a section of the usage help, like header heading, header, synopsis heading,
8055+ * synopsis, description heading, description, etc.
8056+ * @since 3.9
8057+ */
8058+ String render(Help help);
8059+
8060+ }
8061+
79538062 /**
79548063 * A collection of methods and inner classes that provide fine-grained control over the contents and layout of
79558064 * the usage help message to display to end users when help is requested or invalid input values were specified.
@@ -8054,7 +8163,7 @@ public Help(CommandSpec commandSpec, ColorScheme colorScheme) {
80548163 * of the {@link ParserSpec#separator()} at construction time. If the separator is modified after Help construction, you
80558164 * may need to re-initialize this field by calling {@link #createDefaultParamLabelRenderer()} again. */
80568165 public IParamLabelRenderer parameterLabelRenderer() {return parameterLabelRenderer;}
8057-
8166+
80588167 /** Registers all specified subcommands with this Help.
80598168 * @param commands maps the command names to the associated CommandLine object
80608169 * @return this Help instance (for method chaining)
@@ -10162,8 +10271,8 @@ public static class OverwrittenOptionException extends ParameterException {
1016210271 private static final long serialVersionUID = 1338029208271055776L;
1016310272 private final ArgSpec overwrittenArg;
1016410273 public OverwrittenOptionException(CommandLine commandLine, ArgSpec overwritten, String msg) {
10165- super(commandLine, msg);
10166- overwrittenArg = overwritten;
10274+ super(commandLine, msg);
10275+ overwrittenArg = overwritten;
1016710276 }
1016810277 /** Returns the {@link ArgSpec} for the option which was being overwritten.
1016910278 * @since 3.8 */
0 commit comments