| /* |
| 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 ); |
| } |
| } |