diff options
Diffstat (limited to 'third_party/java/jopt-simple/src/main/java/joptsimple/BuiltinHelpFormatter.java')
-rw-r--r-- | third_party/java/jopt-simple/src/main/java/joptsimple/BuiltinHelpFormatter.java | 565 |
1 files changed, 565 insertions, 0 deletions
diff --git a/third_party/java/jopt-simple/src/main/java/joptsimple/BuiltinHelpFormatter.java b/third_party/java/jopt-simple/src/main/java/joptsimple/BuiltinHelpFormatter.java new file mode 100644 index 0000000000..51ec603e6f --- /dev/null +++ b/third_party/java/jopt-simple/src/main/java/joptsimple/BuiltinHelpFormatter.java @@ -0,0 +1,565 @@ +/* + The MIT License + + Copyright (c) 2004-2015 Paul R. Holser, Jr. + + Permission is hereby granted, free of charge, to any person obtaining + a copy of this software and associated documentation files (the + "Software"), to deal in the Software without restriction, including + without limitation the rights to use, copy, modify, merge, publish, + distribute, sublicense, and/or sell copies of the Software, and to + permit persons to whom the Software is furnished to do so, subject to + the following conditions: + + The above copyright notice and this permission notice shall be + included in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +*/ + +package joptsimple; + +import java.util.*; + +import joptsimple.internal.Messages; +import joptsimple.internal.Rows; +import joptsimple.internal.Strings; + +import static joptsimple.ParserRules.*; +import static joptsimple.internal.Classes.*; +import static joptsimple.internal.Strings.*; + +/** + * <p>A help formatter that allows configuration of overall row width and column separator width.</p> + * + * <p>The formatter produces output in two sections: one for the options, and one for non-option arguments.</p> + * + * <p>The options section has two columns: the left column for the options, and the right column for their + * descriptions. The formatter will allow as much space as possible for the descriptions, by minimizing the option + * column's width, no greater than slightly less than half the overall desired width.</p> + * + * <p>The non-option arguments section is one column, occupying as much width as it can.</p> + * + * <p>Subclasses are free to override bits of this implementation as they see fit. Inspect the code + * carefully to understand the flow of control that this implementation guarantees.</p> + * + * @author <a href="mailto:pholser@alumni.rice.edu">Paul Holser</a> + */ +public class BuiltinHelpFormatter implements HelpFormatter { + private final Rows nonOptionRows; + private final Rows optionRows; + + /** + * Makes a formatter with a pre-configured overall row width and column separator width. + */ + BuiltinHelpFormatter() { + this( 80, 2 ); + } + + /** + * Makes a formatter with a given overall row width and column separator width. + * + * @param desiredOverallWidth how many characters wide to make the overall help display + * @param desiredColumnSeparatorWidth how many characters wide to make the separation between option column and + * description column + */ + public BuiltinHelpFormatter( int desiredOverallWidth, int desiredColumnSeparatorWidth ) { + nonOptionRows = new Rows( desiredOverallWidth * 2, 0 ); + optionRows = new Rows( desiredOverallWidth, desiredColumnSeparatorWidth ); + } + + /** + * {@inheritDoc} + * + * <p>This implementation:</p> + * <ul> + * <li>Sorts the given descriptors by their first elements of {@link OptionDescriptor#options()}</li> + * <li>Passes the resulting sorted set to {@link #addRows(java.util.Collection)}</li> + * <li>Returns the result of {@link #formattedHelpOutput()}</li> + * </ul> + */ + public String format( Map<String, ? extends OptionDescriptor> options ) { + optionRows.reset(); + nonOptionRows.reset(); + + Comparator<OptionDescriptor> comparator = + new Comparator<OptionDescriptor>() { + public int compare( OptionDescriptor first, OptionDescriptor second ) { + return first.options().iterator().next().compareTo( second.options().iterator().next() ); + } + }; + + Set<OptionDescriptor> sorted = new TreeSet<>( comparator ); + sorted.addAll( options.values() ); + + addRows( sorted ); + + return formattedHelpOutput(); + } + + /** + * Adds a row of option help output in the left column, with empty space in the right column. + * + * @param single text to put in the left column + */ + protected void addOptionRow( String single ) { + addOptionRow( single, "" ); + } + + /** + * Adds a row of option help output in the left and right columns. + * + * @param left text to put in the left column + * @param right text to put in the right column + */ + protected void addOptionRow( String left, String right ) { + optionRows.add( left, right ); + } + + /** + * Adds a single row of non-option argument help. + * + * @param single single row of non-option argument help text + */ + protected void addNonOptionRow( String single ) { + nonOptionRows.add( single, "" ); + } + + /** + * Resizes the columns of all the rows to be no wider than the widest element in that column. + */ + protected void fitRowsToWidth() { + nonOptionRows.fitToWidth(); + optionRows.fitToWidth(); + } + + /** + * Produces non-option argument help. + * + * @return non-option argument help + */ + protected String nonOptionOutput() { + return nonOptionRows.render(); + } + + /** + * Produces help for options and their descriptions. + * + * @return option help + */ + protected String optionOutput() { + return optionRows.render(); + } + + /** + * <p>Produces help output for an entire set of options and non-option arguments.</p> + * + * <p>This implementation concatenates:</p> + * <ul> + * <li>the result of {@link #nonOptionOutput()}</li> + * <li>if there is non-option output, a line separator</li> + * <li>the result of {@link #optionOutput()}</li> + * </ul> + * + * @return help output for entire set of options and non-option arguments + */ + protected String formattedHelpOutput() { + StringBuilder formatted = new StringBuilder(); + String nonOptionDisplay = nonOptionOutput(); + if ( !Strings.isNullOrEmpty( nonOptionDisplay ) ) + formatted.append( nonOptionDisplay ).append( LINE_SEPARATOR ); + formatted.append( optionOutput() ); + + return formatted.toString(); + } + + /** + * <p>Adds rows of help output for the given options.</p> + * + * <p>This implementation:</p> + * <ul> + * <li>Calls {@link #addNonOptionsDescription(java.util.Collection)} with the options as the argument</li> + * <li>If there are no options, calls {@link #addOptionRow(String)} with an argument that indicates + * that no options are specified.</li> + * <li>Otherwise, calls {@link #addHeaders(java.util.Collection)} with the options as the argument, + * followed by {@link #addOptions(java.util.Collection)} with the options as the argument.</li> + * <li>Calls {@link #fitRowsToWidth()}.</li> + * </ul> + * + * @param options descriptors for the configured options of a parser + */ + protected void addRows( Collection<? extends OptionDescriptor> options ) { + addNonOptionsDescription( options ); + + if ( options.isEmpty() ) + addOptionRow( message( "no.options.specified" ) ); + else { + addHeaders( options ); + addOptions( options ); + } + + fitRowsToWidth(); + } + + /** + * <p>Adds non-option arguments descriptions to the help output.</p> + * + * <p>This implementation:</p> + * <ul> + * <li>{@linkplain #findAndRemoveNonOptionsSpec(java.util.Collection) Finds and removes the non-option + * arguments descriptor}</li> + * <li>{@linkplain #shouldShowNonOptionArgumentDisplay(OptionDescriptor) Decides whether there is + * anything to show for non-option arguments}</li> + * <li>If there is, {@linkplain #addNonOptionRow(String) adds a header row} and + * {@linkplain #addNonOptionRow(String) adds a} + * {@linkplain #createNonOptionArgumentsDisplay(OptionDescriptor) non-option arguments description} </li> + * </ul> + * + * @param options descriptors for the configured options of a parser + */ + protected void addNonOptionsDescription( Collection<? extends OptionDescriptor> options ) { + OptionDescriptor nonOptions = findAndRemoveNonOptionsSpec( options ); + if ( shouldShowNonOptionArgumentDisplay( nonOptions ) ) { + addNonOptionRow( message( "non.option.arguments.header" ) ); + addNonOptionRow( createNonOptionArgumentsDisplay( nonOptions ) ); + } + } + + /** + * <p>Decides whether or not to show a non-option arguments help.</p> + * + * <p>This implementation responds with {@code true} if the non-option descriptor has a non-{@code null}, + * non-empty value for any of {@link OptionDescriptor#description()}, + * {@link OptionDescriptor#argumentTypeIndicator()}, or {@link OptionDescriptor#argumentDescription()}.</p> + * + * @param nonOptionDescriptor non-option argument descriptor + * @return {@code true} if non-options argument help should be shown + */ + protected boolean shouldShowNonOptionArgumentDisplay( OptionDescriptor nonOptionDescriptor ) { + return !Strings.isNullOrEmpty( nonOptionDescriptor.description() ) + || !Strings.isNullOrEmpty( nonOptionDescriptor.argumentTypeIndicator() ) + || !Strings.isNullOrEmpty( nonOptionDescriptor.argumentDescription() ); + } + + /** + * <p>Creates a non-options argument help string.</p> + * + * <p>This implementation creates an empty string buffer and calls + * {@link #maybeAppendOptionInfo(StringBuilder, OptionDescriptor)} + * and {@link #maybeAppendNonOptionsDescription(StringBuilder, OptionDescriptor)}, passing them the + * buffer and the non-option arguments descriptor.</p> + * + * @param nonOptionDescriptor non-option argument descriptor + * @return help string for non-options + */ + protected String createNonOptionArgumentsDisplay( OptionDescriptor nonOptionDescriptor ) { + StringBuilder buffer = new StringBuilder(); + maybeAppendOptionInfo( buffer, nonOptionDescriptor ); + maybeAppendNonOptionsDescription( buffer, nonOptionDescriptor ); + + return buffer.toString(); + } + + /** + * <p>Appends help for the given non-option arguments descriptor to the given buffer.</p> + * + * <p>This implementation appends {@code " -- "} if the buffer has text in it and the non-option arguments + * descriptor has a {@link OptionDescriptor#description()}; followed by the + * {@link OptionDescriptor#description()}.</p> + * + * @param buffer string buffer + * @param nonOptions non-option arguments descriptor + */ + protected void maybeAppendNonOptionsDescription( StringBuilder buffer, OptionDescriptor nonOptions ) { + buffer.append( buffer.length() > 0 && !Strings.isNullOrEmpty( nonOptions.description() ) ? " -- " : "" ) + .append( nonOptions.description() ); + } + + /** + * Finds the non-option arguments descriptor in the given collection, removes it, and returns it. + * + * @param options descriptors for the configured options of a parser + * @return the non-option arguments descriptor + */ + protected OptionDescriptor findAndRemoveNonOptionsSpec( Collection<? extends OptionDescriptor> options ) { + for ( Iterator<? extends OptionDescriptor> it = options.iterator(); it.hasNext(); ) { + OptionDescriptor next = it.next(); + if ( next.representsNonOptions() ) { + it.remove(); + return next; + } + } + + throw new AssertionError( "no non-options argument spec" ); + } + + /** + * <p>Adds help row headers for option help columns.</p> + * + * <p>This implementation uses the headers {@code "Option"} and {@code "Description"}. If the options contain + * a "required" option, the {@code "Option"} header looks like {@code "Option (* = required)}. Both headers + * are "underlined" using {@code "-"}.</p> + * + * @param options descriptors for the configured options of a parser + */ + protected void addHeaders( Collection<? extends OptionDescriptor> options ) { + if ( hasRequiredOption( options ) ) { + addOptionRow( message( "option.header.with.required.indicator" ), message( "description.header" ) ); + addOptionRow( message( "option.divider.with.required.indicator" ), message( "description.divider" ) ); + } else { + addOptionRow( message( "option.header" ), message( "description.header" ) ); + addOptionRow( message( "option.divider" ), message( "description.divider" ) ); + } + } + + /** + * Tells whether the given option descriptors contain a "required" option. + * + * @param options descriptors for the configured options of a parser + * @return {@code true} if at least one of the options is "required" + */ + protected final boolean hasRequiredOption( Collection<? extends OptionDescriptor> options ) { + for ( OptionDescriptor each : options ) { + if ( each.isRequired() ) + return true; + } + + return false; + } + + /** + * <p>Adds help rows for the given options.</p> + * + * <p>This implementation loops over the given options, and for each, calls {@link #addOptionRow(String, String)} + * using the results of {@link #createOptionDisplay(OptionDescriptor)} and + * {@link #createDescriptionDisplay(OptionDescriptor)}, respectively, as arguments.</p> + * + * @param options descriptors for the configured options of a parser + */ + protected void addOptions( Collection<? extends OptionDescriptor> options ) { + for ( OptionDescriptor each : options ) { + if ( !each.representsNonOptions() ) + addOptionRow( createOptionDisplay( each ), createDescriptionDisplay( each ) ); + } + } + + /** + * <p>Creates a string for how the given option descriptor is to be represented in help.</p> + * + * <p>This implementation gives a string consisting of the concatenation of:</p> + * <ul> + * <li>{@code "* "} for "required" options, otherwise {@code ""}</li> + * <li>For each of the {@link OptionDescriptor#options()} of the descriptor, separated by {@code ", "}: + * <ul> + * <li>{@link #optionLeader(String)} of the option</li> + * <li>the option</li> + * </ul> + * </li> + * <li>the result of {@link #maybeAppendOptionInfo(StringBuilder, OptionDescriptor)}</li> + * </ul> + * + * @param descriptor a descriptor for a configured option of a parser + * @return help string + */ + protected String createOptionDisplay( OptionDescriptor descriptor ) { + StringBuilder buffer = new StringBuilder( descriptor.isRequired() ? "* " : "" ); + + for ( Iterator<String> i = descriptor.options().iterator(); i.hasNext(); ) { + String option = i.next(); + buffer.append( optionLeader( option ) ); + buffer.append( option ); + + if ( i.hasNext() ) + buffer.append( ", " ); + } + + maybeAppendOptionInfo( buffer, descriptor ); + + return buffer.toString(); + } + + /** + * <p>Gives a string that represents the given option's "option leader" in help.</p> + * + * <p>This implementation answers with {@code "--"} for options of length greater than one; otherwise answers + * with {@code "-"}.</p> + * + * @param option a string option + * @return an "option leader" string + */ + protected String optionLeader( String option ) { + return option.length() > 1 ? DOUBLE_HYPHEN : HYPHEN; + } + + /** + * <p>Appends additional info about the given option to the given buffer.</p> + * + * <p>This implementation:</p> + * <ul> + * <li>calls {@link #extractTypeIndicator(OptionDescriptor)} for the descriptor</li> + * <li>calls {@link joptsimple.OptionDescriptor#argumentDescription()} for the descriptor</li> + * <li>if either of the above is present, calls + * {@link #appendOptionHelp(StringBuilder, String, String, boolean)}</li> + * </ul> + * + * @param buffer string buffer + * @param descriptor a descriptor for a configured option of a parser + */ + protected void maybeAppendOptionInfo( StringBuilder buffer, OptionDescriptor descriptor ) { + String indicator = extractTypeIndicator( descriptor ); + String description = descriptor.argumentDescription(); + if ( descriptor.acceptsArguments() + || !isNullOrEmpty( description ) + || descriptor.representsNonOptions() ) { + + appendOptionHelp( buffer, indicator, description, descriptor.requiresArgument() ); + } + } + + /** + * <p>Gives an indicator of the type of arguments of the option described by the given descriptor, + * for use in help.</p> + * + * <p>This implementation asks for the {@link OptionDescriptor#argumentTypeIndicator()} of the given + * descriptor, and if it is present and not {@code "java.lang.String"}, parses it as a fully qualified + * class name and returns the base name of that class; otherwise returns {@code "String"}.</p> + * + * @param descriptor a descriptor for a configured option of a parser + * @return type indicator text + */ + protected String extractTypeIndicator( OptionDescriptor descriptor ) { + String indicator = descriptor.argumentTypeIndicator(); + + if ( !isNullOrEmpty( indicator ) && !String.class.getName().equals( indicator ) ) + return shortNameOf( indicator ); + + return "String"; + } + + /** + * <p>Appends info about an option's argument to the given buffer.</p> + * + * <p>This implementation calls {@link #appendTypeIndicator(StringBuilder, String, String, char, char)} with + * the surrounding characters {@code '<'} and {@code '>'} for options with {@code required} arguments, and + * with the surrounding characters {@code '['} and {@code ']'} for options with optional arguments.</p> + * + * @param buffer string buffer + * @param typeIndicator type indicator + * @param description type description + * @param required indicator of "required"-ness of the argument of the option + */ + protected void appendOptionHelp( StringBuilder buffer, String typeIndicator, String description, + boolean required ) { + if ( required ) + appendTypeIndicator( buffer, typeIndicator, description, '<', '>' ); + else + appendTypeIndicator( buffer, typeIndicator, description, '[', ']' ); + } + + /** + * <p>Appends a type indicator for an option's argument to the given buffer.</p> + * + * <p>This implementation appends, in order:</p> + * <ul> + * <li>{@code ' '}</li> + * <li>{@code start}</li> + * <li>the type indicator, if not {@code null}</li> + * <li>if the description is present, then {@code ": "} plus the description if the type indicator is + * present; otherwise the description only</li> + * <li>{@code end}</li> + * </ul> + * + * @param buffer string buffer + * @param typeIndicator type indicator + * @param description type description + * @param start starting character + * @param end ending character + */ + protected void appendTypeIndicator( StringBuilder buffer, String typeIndicator, String description, + char start, char end ) { + buffer.append( ' ' ).append( start ); + if ( typeIndicator != null ) + buffer.append( typeIndicator ); + + if ( !Strings.isNullOrEmpty( description ) ) { + if ( typeIndicator != null ) + buffer.append( ": " ); + + buffer.append( description ); + } + + buffer.append( end ); + } + + /** + * <p>Gives a string representing a description of the option with the given descriptor.</p> + * + * <p>This implementation:</p> + * <ul> + * <li>Asks for the descriptor's {@link OptionDescriptor#defaultValues()}</li> + * <li>If they're not present, answers the descriptor's {@link OptionDescriptor#description()}.</li> + * <li>If they are present, concatenates and returns: + * <ul> + * <li>the descriptor's {@link OptionDescriptor#description()}</li> + * <li>{@code ' '}</li> + * <li>{@code "default: "} plus the result of {@link #createDefaultValuesDisplay(java.util.List)}, + * surrounded by parentheses</li> + * </ul> + * </li> + * </ul> + * + * @param descriptor a descriptor for a configured option of a parser + * @return display text for the option's description + */ + protected String createDescriptionDisplay( OptionDescriptor descriptor ) { + List<?> defaultValues = descriptor.defaultValues(); + if ( defaultValues.isEmpty() ) + return descriptor.description(); + + String defaultValuesDisplay = createDefaultValuesDisplay( defaultValues ); + return ( descriptor.description() + + ' ' + + surround( message( "default.value.header" ) + ' ' + defaultValuesDisplay, '(', ')' ) + ).trim(); + } + + /** + * <p>Gives a display string for the default values of an option's argument.</p> + * + * <p>This implementation gives the {@link Object#toString()} of the first value if there is only one value, + * otherwise gives the {@link Object#toString()} of the whole list.</p> + * + * @param defaultValues some default values for a given option's argument + * @return a display string for those default values + */ + protected String createDefaultValuesDisplay( List<?> defaultValues ) { + return defaultValues.size() == 1 ? defaultValues.get( 0 ).toString() : defaultValues.toString(); + } + + /** + * <p>Looks up and gives a resource bundle message.</p> + * + * <p>This implementation looks in the bundle {@code "joptsimple.HelpFormatterMessages"} in the default + * locale, using a key that is the concatenation of this class's fully qualified name, {@code '.'}, + * and the given key suffix, formats the corresponding value using the given arguments, and returns + * the result.</p> + * + * @param keySuffix suffix to use when looking up the bundle message + * @param args arguments to fill in the message template with + * @return a formatted localized message + */ + protected String message( String keySuffix, Object... args ) { + return Messages.message( + Locale.getDefault(), + "joptsimple.HelpFormatterMessages", + BuiltinHelpFormatter.class, + keySuffix, + args ); + } +} |