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