-
Notifications
You must be signed in to change notification settings - Fork 420
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
[#530] Usage message customization (2) #567
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -38,7 +38,7 @@ | |
import java.util.*; | ||
import java.util.concurrent.Callable; | ||
import java.util.regex.Pattern; | ||
|
||
import java.util.stream.Collectors; | ||
import picocli.CommandLine.Help.Ansi.IStyle; | ||
import picocli.CommandLine.Help.Ansi.Style; | ||
import picocli.CommandLine.Help.Ansi.Text; | ||
|
@@ -140,14 +140,50 @@ | |
* </p> | ||
*/ | ||
public class CommandLine { | ||
|
||
/** Predefined section keys. */ | ||
public static final String HEADER_HEADING = "headerHeading"; | ||
public static final String HEADER = "header"; | ||
public static final String SYNOPSIS_HEADING = "synopsisHeading"; | ||
public static final String SYNOPSIS = "synopsis"; | ||
public static final String DESCRIPTION_HEADING = "descriptionHeading"; | ||
public static final String DESCRIPTION = "description"; | ||
public static final String PARAMETER_LIST_HEADING = "parameterListHeading"; | ||
public static final String PARAMETER_LIST = "parameterList"; | ||
public static final String OPTION_LIST_HEADING = "optionListHeading"; | ||
public static final String OPTION_LIST = "optionList"; | ||
public static final String COMMAND_LIST_HEADING = "commandListHeading"; | ||
public static final String COMMAND_LIST = "commandList"; | ||
public static final String FOOTER_HEADING = "footerHeading"; | ||
public static final String FOOTER = "footer"; | ||
|
||
/** This is picocli version {@value}. */ | ||
public static final String VERSION = "4.0.0-SNAPSHOT"; | ||
|
||
private final Tracer tracer = new Tracer(); | ||
private final CommandSpec commandSpec; | ||
private final Interpreter interpreter; | ||
private final IFactory factory; | ||
|
||
private IHelpFactory helpFactory; | ||
|
||
private List<String> sectionKeys = Collections.unmodifiableList(Arrays.asList( | ||
HEADER_HEADING, | ||
HEADER, | ||
SYNOPSIS_HEADING, | ||
SYNOPSIS, | ||
DESCRIPTION_HEADING, | ||
DESCRIPTION, | ||
PARAMETER_LIST_HEADING, | ||
PARAMETER_LIST, | ||
OPTION_LIST_HEADING, | ||
OPTION_LIST, | ||
COMMAND_LIST_HEADING, | ||
COMMAND_LIST, | ||
FOOTER_HEADING, | ||
FOOTER)); | ||
|
||
private Map<String, IHelpSectionRenderer> helpSectionRendererMap = createHelpSectionRendererMap(); | ||
|
||
/** | ||
* 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. | ||
* <p>The specified object may be a {@link CommandSpec CommandSpec} object, or it may be a {@code @Command}-annotated | ||
|
@@ -1467,6 +1503,18 @@ public static void usage(Object command, PrintStream out, Help.ColorScheme color | |
* @since 3.0 */ | ||
public void usage(PrintWriter writer, Help.Ansi ansi) { usage(writer, Help.defaultColorScheme(ansi)); } | ||
|
||
public CommandLine setHelpFactory(IHelpFactory helpFactory) { | ||
this.helpFactory = helpFactory; | ||
return this; | ||
} | ||
|
||
public IHelpFactory getHelpFactory() { | ||
if (helpFactory == null) { | ||
helpFactory = new DefaultHelpFactory(); | ||
} | ||
return helpFactory; | ||
} | ||
|
||
/** | ||
* Prints a usage help message for the annotated command class to the specified {@code PrintStream}. | ||
* Delegates construction of the usage help message to the {@link Help} inner class and is equivalent to: | ||
|
@@ -1500,45 +1548,107 @@ public static void usage(Object command, PrintStream out, Help.ColorScheme color | |
* @param colorScheme the {@code ColorScheme} defining the styles for options, parameters and commands when ANSI is enabled | ||
*/ | ||
public void usage(PrintStream out, Help.ColorScheme colorScheme) { | ||
out.print(usage(new StringBuilder(), new Help(getCommandSpec(), colorScheme))); | ||
out.print(usage(new StringBuilder(), getHelpFactory().create(getCommandSpec(), colorScheme))); | ||
} | ||
/** Similar to {@link #usage(PrintStream, Help.ColorScheme)}, but with the specified {@code PrintWriter} instead of a {@code PrintStream}. | ||
* @since 3.0 */ | ||
public void usage(PrintWriter writer, Help.ColorScheme colorScheme) { | ||
writer.print(usage(new StringBuilder(), new Help(getCommandSpec(), colorScheme))); | ||
writer.print(usage(new StringBuilder(), getHelpFactory().create(getCommandSpec(), colorScheme))); | ||
} | ||
/** Similar to {@link #usage(PrintStream)}, but returns the usage help message as a String instead of printing it to the {@code PrintStream}. | ||
* @since 3.2 */ | ||
public String getUsageMessage() { | ||
return usage(new StringBuilder(), new Help(getCommandSpec())).toString(); | ||
return usage(new StringBuilder(), getHelpFactory().create(getCommandSpec(), Help.defaultColorScheme(Help.Ansi.AUTO))).toString(); | ||
} | ||
/** Similar to {@link #usage(PrintStream, Help.Ansi)}, but returns the usage help message as a String instead of printing it to the {@code PrintStream}. | ||
* @since 3.2 */ | ||
public String getUsageMessage(Help.Ansi ansi) { | ||
return usage(new StringBuilder(), new Help(getCommandSpec(), ansi)).toString(); | ||
return usage(new StringBuilder(), getHelpFactory().create(getCommandSpec(), Help.defaultColorScheme(ansi))).toString(); | ||
} | ||
/** Similar to {@link #usage(PrintStream, Help.ColorScheme)}, but returns the usage help message as a String instead of printing it to the {@code PrintStream}. | ||
* @since 3.2 */ | ||
public String getUsageMessage(Help.ColorScheme colorScheme) { | ||
return usage(new StringBuilder(), new Help(getCommandSpec(), colorScheme)).toString(); | ||
} | ||
private static StringBuilder usage(StringBuilder sb, Help help) { | ||
return sb.append(help.headerHeading()) | ||
.append(help.header()) | ||
.append(help.synopsisHeading()) //e.g. Usage: | ||
.append(help.synopsis(help.synopsisHeadingLength())) //e.g. <main class> [OPTIONS] <command> [COMMAND-OPTIONS] [ARGUMENTS] | ||
.append(help.descriptionHeading()) //e.g. %nDescription:%n%n | ||
.append(help.description()) //e.g. {"Converts foos to bars.", "Use options to control conversion mode."} | ||
.append(help.parameterListHeading()) //e.g. %nPositional parameters:%n%n | ||
.append(help.parameterList()) //e.g. [FILE...] the files to convert | ||
.append(help.optionListHeading()) //e.g. %nOptions:%n%n | ||
.append(help.optionList()) //e.g. -h, --help displays this help and exits | ||
.append(help.commandListHeading()) //e.g. %nCommands:%n%n | ||
.append(help.commandList()) //e.g. add adds the frup to the frooble | ||
.append(help.footerHeading()) | ||
.append(help.footer()); | ||
return usage(new StringBuilder(), getHelpFactory().create(getCommandSpec(), colorScheme)).toString(); | ||
} | ||
|
||
private StringBuilder usage(StringBuilder sb, Help help) { | ||
for (String key : getSectionKeys()) { | ||
IHelpSectionRenderer renderer = helpSectionRendererMap.get(key); | ||
if (renderer != null) { sb.append(renderer.render(help)); } | ||
} | ||
return sb; | ||
} | ||
|
||
/** | ||
* Returns the section keys in the order that the usage help message should render the sections. | ||
* This ordering may be modified with {@link #setSectionKeys(List) setSectionKeys}. The default keys are: | ||
* <pre> | ||
* "headerHeading", | ||
* "header", | ||
* "synopsisHeading", | ||
* "synopsis", | ||
* "descriptionHeading", | ||
* "description", | ||
* "parameterListHeading", | ||
* "parameterList", | ||
* "optionListHeading", | ||
* "optionList", | ||
* "commandListHeading", | ||
* "commandList", | ||
* "footerHeading", | ||
* "footer" | ||
* </pre> | ||
* @since 3.9 | ||
*/ | ||
public List<String> getSectionKeys() { return sectionKeys; } | ||
|
||
/** | ||
* Sets the section keys in the order that the usage help message should render the sections. | ||
* @see #getSectionKeys | ||
* @since 3.9 | ||
*/ | ||
public void setSectionKeys(List<String> keys) { sectionKeys = Collections.unmodifiableList(keys); } | ||
|
||
/** Returns the help section renderers for the predefined section keys. see: {@link #getSectionKeys()} */ | ||
private Map<String, IHelpSectionRenderer> createHelpSectionRendererMap() { | ||
Map<String, IHelpSectionRenderer> result = new HashMap<String, IHelpSectionRenderer>(); | ||
|
||
result.put(HEADER_HEADING, new IHelpSectionRenderer() { public String render(Help help) { return help.headerHeading(); } }); | ||
result.put(HEADER, new IHelpSectionRenderer() { public String render(Help help) { return help.header(); } }); | ||
//e.g. Usage: | ||
result.put(SYNOPSIS_HEADING, new IHelpSectionRenderer() { public String render(Help help) { return help.synopsisHeading(); } }); | ||
//e.g. <main class> [OPTIONS] <command> [COMMAND-OPTIONS] [ARGUMENTS] | ||
result.put(SYNOPSIS, new IHelpSectionRenderer() { public String render(Help help) { return help.synopsis(help.synopsisHeadingLength()); } }); | ||
//e.g. %nDescription:%n%n | ||
result.put(DESCRIPTION_HEADING, new IHelpSectionRenderer() { public String render(Help help) { return help.descriptionHeading(); } }); | ||
//e.g. {"Converts foos to bars.", "Use options to control conversion mode."} | ||
result.put(DESCRIPTION, new IHelpSectionRenderer() { public String render(Help help) { return help.description(); } }); | ||
//e.g. %nPositional parameters:%n%n | ||
result.put(PARAMETER_LIST_HEADING, new IHelpSectionRenderer() { public String render(Help help) { return help.parameterListHeading(); } }); | ||
//e.g. [FILE...] the files to convert | ||
result.put(PARAMETER_LIST, new IHelpSectionRenderer() { public String render(Help help) { return help.parameterList(); } }); | ||
//e.g. %nOptions:%n%n | ||
result.put(OPTION_LIST_HEADING, new IHelpSectionRenderer() { public String render(Help help) { return help.optionListHeading(); } }); | ||
//e.g. -h, --help displays this help and exits | ||
result.put(OPTION_LIST, new IHelpSectionRenderer() { public String render(Help help) { return help.optionList(); } }); | ||
//e.g. %nCommands:%n%n | ||
result.put(COMMAND_LIST_HEADING, new IHelpSectionRenderer() { public String render(Help help) { return help.commandListHeading(); } }); | ||
//e.g. add adds the frup to the frooble | ||
result.put(COMMAND_LIST, new IHelpSectionRenderer() { public String render(Help help) { return help.commandList(); } }); | ||
result.put(FOOTER_HEADING, new IHelpSectionRenderer() { public String render(Help help) { return help.footerHeading(); } }); | ||
result.put(FOOTER, new IHelpSectionRenderer() { public String render(Help help) { return help.footer(); } }); | ||
return result; | ||
} | ||
|
||
/** | ||
* Returns the map of section keys and renderers used to construct the usage help message. | ||
* The usage help message can be customized by adding, replacing and removing section renderers from this map. | ||
* Sections can be reordered with {@link #setSectionKeys(List) setSectionKeys}. | ||
* Sections that are either not in this map or not in the list returned by {@link #getSectionKeys() getSectionKeys} are omitted. | ||
* @since 3.9 | ||
*/ | ||
public Map<String, IHelpSectionRenderer> getSectionMap() { return helpSectionRendererMap; } | ||
|
||
/** | ||
* Delegates to {@link #printVersionHelp(PrintStream, Help.Ansi)} with the {@linkplain Help.Ansi#AUTO platform default}. | ||
* @param out the printStream to print to | ||
|
@@ -3214,6 +3324,18 @@ private static class NoDefaultProvider implements IDefaultValueProvider { | |
public String defaultValue(ArgSpec argSpec) { throw new UnsupportedOperationException(); } | ||
} | ||
|
||
public interface IHelpFactory { | ||
Help create(CommandSpec commandSpec, Help.ColorScheme colorScheme); | ||
} | ||
|
||
public static class DefaultHelpFactory implements IHelpFactory { | ||
|
||
public Help create(CommandSpec commandSpec, Help.ColorScheme colorScheme) { | ||
return new Help(commandSpec, colorScheme); | ||
} | ||
|
||
} | ||
|
||
/** | ||
* Factory for instantiating classes that are registered declaratively with annotation attributes, like | ||
* {@link Command#subcommands()}, {@link Option#converter()}, {@link Parameters#converter()} and {@link Command#versionProvider()}. | ||
|
@@ -7925,6 +8047,18 @@ public static interface IHelpCommandInitializable { | |
void init(CommandLine helpCommandLine, Help.Ansi ansi, PrintStream out, PrintStream err); | ||
} | ||
|
||
|
||
public interface IHelpSectionRenderer { | ||
|
||
/** | ||
* Renders a section of the usage help, like header heading, header, synopsis heading, | ||
* synopsis, description heading, description, etc. | ||
* @since 3.9 | ||
*/ | ||
String render(Help help); | ||
|
||
} | ||
|
||
/** | ||
* A collection of methods and inner classes that provide fine-grained control over the contents and layout of | ||
* the usage help message to display to end users when help is requested or invalid input values were specified. | ||
|
@@ -7975,6 +8109,7 @@ public static class Help { | |
private final ColorScheme colorScheme; | ||
private final Map<String, Help> commands = new LinkedHashMap<String, Help>(); | ||
private List<String> aliases = Collections.emptyList(); | ||
private IHelpFactory helpFactory; | ||
|
||
private IParamLabelRenderer parameterLabelRenderer; | ||
|
||
|
@@ -8010,7 +8145,8 @@ public Help(CommandSpec commandSpec, ColorScheme colorScheme) { | |
this.aliases.add(0, commandSpec.name()); | ||
this.colorScheme = Assert.notNull(colorScheme, "colorScheme").applySystemProperties(); | ||
parameterLabelRenderer = createDefaultParamLabelRenderer(); // uses help separator | ||
|
||
this.helpFactory = commandSpec.commandLine() != null ? commandSpec.commandLine().getHelpFactory() : new DefaultHelpFactory(); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Not sure if this is the right way to get the helpfactory. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yes, that looks fine. You raise a good point though: the |
||
|
||
this.addAllSubcommands(commandSpec.subcommands()); | ||
} | ||
|
||
|
@@ -8023,13 +8159,17 @@ public Help(CommandSpec commandSpec, ColorScheme colorScheme) { | |
/** Returns the {@code ColorScheme} model that this Help was constructed with. | ||
* @since 3.0 */ | ||
public ColorScheme colorScheme() { return colorScheme; } | ||
|
||
/** Returns the {@code ColorScheme} model that this Help was constructed with. | ||
* @since 2.9 */ | ||
private IHelpFactory getHelpFactory() { return helpFactory; } | ||
|
||
/** Option and positional parameter value label renderer used for the synopsis line(s) and the option list. | ||
* By default initialized to the result of {@link #createDefaultParamLabelRenderer()}, which takes a snapshot | ||
* of the {@link ParserSpec#separator()} at construction time. If the separator is modified after Help construction, you | ||
* may need to re-initialize this field by calling {@link #createDefaultParamLabelRenderer()} again. */ | ||
public IParamLabelRenderer parameterLabelRenderer() {return parameterLabelRenderer;} | ||
|
||
SysLord marked this conversation as resolved.
Show resolved
Hide resolved
|
||
/** Registers all specified subcommands with this Help. | ||
* @param commands maps the command names to the associated CommandLine object | ||
* @return this Help instance (for method chaining) | ||
|
@@ -8071,7 +8211,7 @@ public Help addAllSubcommands(Map<String, CommandLine> commands) { | |
* @return this Help instance (for method chaining) */ | ||
Help addSubcommand(List<String> commandNames, CommandLine commandLine) { | ||
String all = commandNames.toString(); | ||
commands.put(all.substring(1, all.length() - 1), new Help(commandLine.commandSpec, colorScheme).withCommandNames(commandNames)); | ||
commands.put(all.substring(1, all.length() - 1), getHelpFactory().create(commandLine.commandSpec, colorScheme).withCommandNames(commandNames)); | ||
return this; | ||
} | ||
|
||
|
@@ -8082,7 +8222,8 @@ Help addSubcommand(List<String> commandNames, CommandLine commandLine) { | |
* @deprecated | ||
*/ | ||
@Deprecated public Help addSubcommand(String commandName, Object command) { | ||
commands.put(commandName, new Help(CommandSpec.forAnnotatedObject(command, commandSpec.commandLine().factory))); | ||
commands.put(commandName, | ||
getHelpFactory().create(CommandSpec.forAnnotatedObject(command, commandSpec.commandLine().factory), defaultColorScheme(Ansi.AUTO))); | ||
return this; | ||
} | ||
|
||
|
@@ -10137,8 +10278,8 @@ public static class OverwrittenOptionException extends ParameterException { | |
private static final long serialVersionUID = 1338029208271055776L; | ||
private final ArgSpec overwrittenArg; | ||
public OverwrittenOptionException(CommandLine commandLine, ArgSpec overwritten, String msg) { | ||
super(commandLine, msg); | ||
overwrittenArg = overwritten; | ||
super(commandLine, msg); | ||
overwrittenArg = overwritten; | ||
} | ||
/** Returns the {@link ArgSpec} for the option which was being overwritten. | ||
* @since 3.8 */ | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks good. We need to think of some javadoc, but I can add that later.