blob: c82852403c80a284617dd29315e46ec6fb853139 [file] [log] [blame]
/**
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.commons.cli;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.Iterator;
import java.util.List;
/**
* A formatter of help messages for the current command line options
*
* @author Slawek Zachcial
* @author John Keyes (john at integralsource.com)
* @version $Revision$, $Date$
*/
public class HelpFormatter
{
// --------------------------------------------------------------- Constants
/** default number of characters per line */
public static final int DEFAULT_WIDTH = 74;
/** default padding to the left of each line */
public static final int DEFAULT_LEFT_PAD = 1;
/**
* the number of characters of padding to be prefixed
* to each description line
*/
public static final int DEFAULT_DESC_PAD = 3;
/** the string to display at the beginning of the usage statement */
public static final String DEFAULT_SYNTAX_PREFIX = "usage: ";
/** default prefix for shortOpts */
public static final String DEFAULT_OPT_PREFIX = "-";
/** default prefix for long Option */
public static final String DEFAULT_LONG_OPT_PREFIX = "--";
/** default name for an argument */
public static final String DEFAULT_ARG_NAME = "arg";
// -------------------------------------------------------------- Attributes
/**
* number of characters per line
*
* @deprecated Scope will be made private for next major version
* - use get/setWidth methods instead.
*/
public int defaultWidth = DEFAULT_WIDTH;
/**
* amount of padding to the left of each line
*
* @deprecated Scope will be made private for next major version
* - use get/setLeftPadding methods instead.
*/
public int defaultLeftPad = DEFAULT_LEFT_PAD;
/**
* the number of characters of padding to be prefixed
* to each description line
*
* @deprecated Scope will be made private for next major version
* - use get/setDescPadding methods instead.
*/
public int defaultDescPad = DEFAULT_DESC_PAD;
/**
* the string to display at the begining of the usage statement
*
* @deprecated Scope will be made private for next major version
* - use get/setSyntaxPrefix methods instead.
*/
public String defaultSyntaxPrefix = DEFAULT_SYNTAX_PREFIX;
/**
* the new line string
*
* @deprecated Scope will be made private for next major version
* - use get/setNewLine methods instead.
*/
public String defaultNewLine = System.getProperty("line.separator");
/**
* the shortOpt prefix
*
* @deprecated Scope will be made private for next major version
* - use get/setOptPrefix methods instead.
*/
public String defaultOptPrefix = DEFAULT_OPT_PREFIX;
/**
* the long Opt prefix
*
* @deprecated Scope will be made private for next major version
* - use get/setLongOptPrefix methods instead.
*/
public String defaultLongOptPrefix = DEFAULT_LONG_OPT_PREFIX;
/**
* the name of the argument
*
* @deprecated Scope will be made private for next major version
* - use get/setArgName methods instead.
*/
public String defaultArgName = DEFAULT_ARG_NAME;
/**
* Comparator used to sort the options when they output in help text
*
* Defaults to case-insensitive alphabetical sorting by option key
*/
protected Comparator optionComparator = new OptionComparator();
/**
* Sets the 'width'.
*
* @param width the new value of 'width'
*/
public void setWidth(int width)
{
this.defaultWidth = width;
}
/**
* Returns the 'width'.
*
* @return the 'width'
*/
public int getWidth()
{
return defaultWidth;
}
/**
* Sets the 'leftPadding'.
*
* @param padding the new value of 'leftPadding'
*/
public void setLeftPadding(int padding)
{
this.defaultLeftPad = padding;
}
/**
* Returns the 'leftPadding'.
*
* @return the 'leftPadding'
*/
public int getLeftPadding()
{
return defaultLeftPad;
}
/**
* Sets the 'descPadding'.
*
* @param padding the new value of 'descPadding'
*/
public void setDescPadding(int padding)
{
this.defaultDescPad = padding;
}
/**
* Returns the 'descPadding'.
*
* @return the 'descPadding'
*/
public int getDescPadding()
{
return defaultDescPad;
}
/**
* Sets the 'syntaxPrefix'.
*
* @param prefix the new value of 'syntaxPrefix'
*/
public void setSyntaxPrefix(String prefix)
{
this.defaultSyntaxPrefix = prefix;
}
/**
* Returns the 'syntaxPrefix'.
*
* @return the 'syntaxPrefix'
*/
public String getSyntaxPrefix()
{
return defaultSyntaxPrefix;
}
/**
* Sets the 'newLine'.
*
* @param newline the new value of 'newLine'
*/
public void setNewLine(String newline)
{
this.defaultNewLine = newline;
}
/**
* Returns the 'newLine'.
*
* @return the 'newLine'
*/
public String getNewLine()
{
return defaultNewLine;
}
/**
* Sets the 'optPrefix'.
*
* @param prefix the new value of 'optPrefix'
*/
public void setOptPrefix(String prefix)
{
this.defaultOptPrefix = prefix;
}
/**
* Returns the 'optPrefix'.
*
* @return the 'optPrefix'
*/
public String getOptPrefix()
{
return defaultOptPrefix;
}
/**
* Sets the 'longOptPrefix'.
*
* @param prefix the new value of 'longOptPrefix'
*/
public void setLongOptPrefix(String prefix)
{
this.defaultLongOptPrefix = prefix;
}
/**
* Returns the 'longOptPrefix'.
*
* @return the 'longOptPrefix'
*/
public String getLongOptPrefix()
{
return defaultLongOptPrefix;
}
/**
* Sets the 'argName'.
*
* @param name the new value of 'argName'
*/
public void setArgName(String name)
{
this.defaultArgName = name;
}
/**
* Returns the 'argName'.
*
* @return the 'argName'
*/
public String getArgName()
{
return defaultArgName;
}
/**
* Comparator used to sort the options when they output in help text
*
* Defaults to case-insensitive alphabetical sorting by option key
*/
public Comparator getOptionComparator()
{
return optionComparator;
}
/**
* Set the comparator used to sort the options when they output in help text
*
* Passing in a null parameter will set the ordering to the default mode
*/
public void setOptionComparator(Comparator comparator)
{
if (comparator == null)
{
this.optionComparator = new OptionComparator();
}
else
{
this.optionComparator = comparator;
}
}
/**
* Print the help for <code>options</code> with the specified
* command line syntax. This method prints help information to
* System.out.
*
* @param cmdLineSyntax the syntax for this application
* @param options the Options instance
*/
public void printHelp(String cmdLineSyntax, Options options)
{
printHelp(defaultWidth, cmdLineSyntax, null, options, null, false);
}
/**
* Print the help for <code>options</code> with the specified
* command line syntax. This method prints help information to
* System.out.
*
* @param cmdLineSyntax the syntax for this application
* @param options the Options instance
* @param autoUsage whether to print an automatically generated
* usage statement
*/
public void printHelp(String cmdLineSyntax, Options options, boolean autoUsage)
{
printHelp(defaultWidth, cmdLineSyntax, null, options, null, autoUsage);
}
/**
* Print the help for <code>options</code> with the specified
* command line syntax. This method prints help information to
* System.out.
*
* @param cmdLineSyntax the syntax for this application
* @param header the banner to display at the begining of the help
* @param options the Options instance
* @param footer the banner to display at the end of the help
*/
public void printHelp(String cmdLineSyntax, String header, Options options, String footer)
{
printHelp(cmdLineSyntax, header, options, footer, false);
}
/**
* Print the help for <code>options</code> with the specified
* command line syntax. This method prints help information to
* System.out.
*
* @param cmdLineSyntax the syntax for this application
* @param header the banner to display at the begining of the help
* @param options the Options instance
* @param footer the banner to display at the end of the help
* @param autoUsage whether to print an automatically generated
* usage statement
*/
public void printHelp(String cmdLineSyntax, String header, Options options, String footer, boolean autoUsage)
{
printHelp(defaultWidth, cmdLineSyntax, header, options, footer, autoUsage);
}
/**
* Print the help for <code>options</code> with the specified
* command line syntax. This method prints help information to
* System.out.
*
* @param width the number of characters to be displayed on each line
* @param cmdLineSyntax the syntax for this application
* @param header the banner to display at the beginning of the help
* @param options the Options instance
* @param footer the banner to display at the end of the help
*/
public void printHelp(int width, String cmdLineSyntax, String header, Options options, String footer)
{
printHelp(width, cmdLineSyntax, header, options, footer, false);
}
/**
* Print the help for <code>options</code> with the specified
* command line syntax. This method prints help information to
* System.out.
*
* @param width the number of characters to be displayed on each line
* @param cmdLineSyntax the syntax for this application
* @param header the banner to display at the begining of the help
* @param options the Options instance
* @param footer the banner to display at the end of the help
* @param autoUsage whether to print an automatically generated
* usage statement
*/
public void printHelp(int width, String cmdLineSyntax, String header,
Options options, String footer, boolean autoUsage)
{
PrintWriter pw = new PrintWriter(System.out);
printHelp(pw, width, cmdLineSyntax, header, options, defaultLeftPad, defaultDescPad, footer, autoUsage);
pw.flush();
}
/**
* Print the help for <code>options</code> with the specified
* command line syntax.
*
* @param pw the writer to which the help will be written
* @param width the number of characters to be displayed on each line
* @param cmdLineSyntax the syntax for this application
* @param header the banner to display at the begining of the help
* @param options the Options instance
* @param leftPad the number of characters of padding to be prefixed
* to each line
* @param descPad the number of characters of padding to be prefixed
* to each description line
* @param footer the banner to display at the end of the help
*
* @throws IllegalStateException if there is no room to print a line
*/
public void printHelp(PrintWriter pw, int width, String cmdLineSyntax,
String header, Options options, int leftPad,
int descPad, String footer)
{
printHelp(pw, width, cmdLineSyntax, header, options, leftPad, descPad, footer, false);
}
/**
* Print the help for <code>options</code> with the specified
* command line syntax.
*
* @param pw the writer to which the help will be written
* @param width the number of characters to be displayed on each line
* @param cmdLineSyntax the syntax for this application
* @param header the banner to display at the begining of the help
* @param options the Options instance
* @param leftPad the number of characters of padding to be prefixed
* to each line
* @param descPad the number of characters of padding to be prefixed
* to each description line
* @param footer the banner to display at the end of the help
* @param autoUsage whether to print an automatically generated
* usage statement
*
* @throws IllegalStateException if there is no room to print a line
*/
public void printHelp(PrintWriter pw, int width, String cmdLineSyntax,
String header, Options options, int leftPad,
int descPad, String footer, boolean autoUsage)
{
if ((cmdLineSyntax == null) || (cmdLineSyntax.length() == 0))
{
throw new IllegalArgumentException("cmdLineSyntax not provided");
}
if (autoUsage)
{
printUsage(pw, width, cmdLineSyntax, options);
}
else
{
printUsage(pw, width, cmdLineSyntax);
}
if ((header != null) && (header.trim().length() > 0))
{
printWrapped(pw, width, header);
}
printOptions(pw, width, options, leftPad, descPad);
if ((footer != null) && (footer.trim().length() > 0))
{
printWrapped(pw, width, footer);
}
}
/**
* <p>Prints the usage statement for the specified application.</p>
*
* @param pw The PrintWriter to print the usage statement
* @param width The number of characters to display per line
* @param app The application name
* @param options The command line Options
*
*/
public void printUsage(PrintWriter pw, int width, String app, Options options)
{
// initialise the string buffer
StringBuffer buff = new StringBuffer(defaultSyntaxPrefix).append(app).append(" ");
// create a list for processed option groups
final Collection processedGroups = new ArrayList();
// temp variable
Option option;
List optList = new ArrayList(options.getOptions());
Collections.sort(optList, getOptionComparator());
// iterate over the options
for (Iterator i = optList.iterator(); i.hasNext();)
{
// get the next Option
option = (Option) i.next();
// check if the option is part of an OptionGroup
OptionGroup group = options.getOptionGroup(option);
// if the option is part of a group
if (group != null)
{
// and if the group has not already been processed
if (!processedGroups.contains(group))
{
// add the group to the processed list
processedGroups.add(group);
// add the usage clause
appendOptionGroup(buff, group);
}
// otherwise the option was displayed in the group
// previously so ignore it.
}
// if the Option is not part of an OptionGroup
else
{
appendOption(buff, option, option.isRequired());
}
if (i.hasNext())
{
buff.append(" ");
}
}
// call printWrapped
printWrapped(pw, width, buff.toString().indexOf(' ') + 1, buff.toString());
}
/**
* Appends the usage clause for an OptionGroup to a StringBuffer.
* The clause is wrapped in square brackets if the group is required.
* The display of the options is handled by appendOption
* @param buff the StringBuffer to append to
* @param group the group to append
* @see #appendOption(StringBuffer,Option,boolean)
*/
private void appendOptionGroup(final StringBuffer buff, final OptionGroup group)
{
if (!group.isRequired())
{
buff.append("[");
}
List optList = new ArrayList(group.getOptions());
Collections.sort(optList, getOptionComparator());
// for each option in the OptionGroup
for (Iterator i = optList.iterator(); i.hasNext();)
{
// whether the option is required or not is handled at group level
appendOption(buff, (Option) i.next(), true);
if (i.hasNext())
{
buff.append(" | ");
}
}
if (!group.isRequired())
{
buff.append("]");
}
}
/**
* Appends the usage clause for an Option to a StringBuffer.
*
* @param buff the StringBuffer to append to
* @param option the Option to append
* @param required whether the Option is required or not
*/
private static void appendOption(final StringBuffer buff, final Option option, final boolean required)
{
if (!required)
{
buff.append("[");
}
if (option.getOpt() != null)
{
buff.append("-").append(option.getOpt());
}
else
{
buff.append("--").append(option.getLongOpt());
}
// if the Option has a value
if (option.hasArg() && option.hasArgName())
{
buff.append(" <").append(option.getArgName()).append(">");
}
// if the Option is not a required option
if (!required)
{
buff.append("]");
}
}
/**
* Print the cmdLineSyntax to the specified writer, using the
* specified width.
*
* @param pw The printWriter to write the help to
* @param width The number of characters per line for the usage statement.
* @param cmdLineSyntax The usage statement.
*/
public void printUsage(PrintWriter pw, int width, String cmdLineSyntax)
{
int argPos = cmdLineSyntax.indexOf(' ') + 1;
printWrapped(pw, width, defaultSyntaxPrefix.length() + argPos, defaultSyntaxPrefix + cmdLineSyntax);
}
/**
* <p>Print the help for the specified Options to the specified writer,
* using the specified width, left padding and description padding.</p>
*
* @param pw The printWriter to write the help to
* @param width The number of characters to display per line
* @param options The command line Options
* @param leftPad the number of characters of padding to be prefixed
* to each line
* @param descPad the number of characters of padding to be prefixed
* to each description line
*/
public void printOptions(PrintWriter pw, int width, Options options,
int leftPad, int descPad)
{
StringBuffer sb = new StringBuffer();
renderOptions(sb, width, options, leftPad, descPad);
pw.println(sb.toString());
}
/**
* Print the specified text to the specified PrintWriter.
*
* @param pw The printWriter to write the help to
* @param width The number of characters to display per line
* @param text The text to be written to the PrintWriter
*/
public void printWrapped(PrintWriter pw, int width, String text)
{
printWrapped(pw, width, 0, text);
}
/**
* Print the specified text to the specified PrintWriter.
*
* @param pw The printWriter to write the help to
* @param width The number of characters to display per line
* @param nextLineTabStop The position on the next line for the first tab.
* @param text The text to be written to the PrintWriter
*/
public void printWrapped(PrintWriter pw, int width, int nextLineTabStop, String text)
{
StringBuffer sb = new StringBuffer(text.length());
renderWrappedText(sb, width, nextLineTabStop, text);
pw.println(sb.toString());
}
// --------------------------------------------------------------- Protected
/**
* Render the specified Options and return the rendered Options
* in a StringBuffer.
*
* @param sb The StringBuffer to place the rendered Options into.
* @param width The number of characters to display per line
* @param options The command line Options
* @param leftPad the number of characters of padding to be prefixed
* to each line
* @param descPad the number of characters of padding to be prefixed
* to each description line
*
* @return the StringBuffer with the rendered Options contents.
*/
protected StringBuffer renderOptions(StringBuffer sb, int width, Options options, int leftPad, int descPad)
{
final String lpad = createPadding(leftPad);
final String dpad = createPadding(descPad);
// first create list containing only <lpad>-a,--aaa where
// -a is opt and --aaa is long opt; in parallel look for
// the longest opt string this list will be then used to
// sort options ascending
int max = 0;
StringBuffer optBuf;
List prefixList = new ArrayList();
List optList = options.helpOptions();
Collections.sort(optList, getOptionComparator());
for (Iterator i = optList.iterator(); i.hasNext();)
{
Option option = (Option) i.next();
optBuf = new StringBuffer(8);
if (option.getOpt() == null)
{
optBuf.append(lpad).append(" " + defaultLongOptPrefix).append(option.getLongOpt());
}
else
{
optBuf.append(lpad).append(defaultOptPrefix).append(option.getOpt());
if (option.hasLongOpt())
{
optBuf.append(',').append(defaultLongOptPrefix).append(option.getLongOpt());
}
}
if (option.hasArg())
{
if (option.hasArgName())
{
optBuf.append(" <").append(option.getArgName()).append(">");
}
else
{
optBuf.append(' ');
}
}
prefixList.add(optBuf);
max = (optBuf.length() > max) ? optBuf.length() : max;
}
int x = 0;
for (Iterator i = optList.iterator(); i.hasNext();)
{
Option option = (Option) i.next();
optBuf = new StringBuffer(prefixList.get(x++).toString());
if (optBuf.length() < max)
{
optBuf.append(createPadding(max - optBuf.length()));
}
optBuf.append(dpad);
int nextLineTabStop = max + descPad;
if (option.getDescription() != null)
{
optBuf.append(option.getDescription());
}
renderWrappedText(sb, width, nextLineTabStop, optBuf.toString());
if (i.hasNext())
{
sb.append(defaultNewLine);
}
}
return sb;
}
/**
* Render the specified text and return the rendered Options
* in a StringBuffer.
*
* @param sb The StringBuffer to place the rendered text into.
* @param width The number of characters to display per line
* @param nextLineTabStop The position on the next line for the first tab.
* @param text The text to be rendered.
*
* @return the StringBuffer with the rendered Options contents.
*/
protected StringBuffer renderWrappedText(StringBuffer sb, int width,
int nextLineTabStop, String text)
{
int pos = findWrapPos(text, width, 0);
if (pos == -1)
{
sb.append(rtrim(text));
return sb;
}
sb.append(rtrim(text.substring(0, pos))).append(defaultNewLine);
if (nextLineTabStop >= width)
{
// stops infinite loop happening
nextLineTabStop = 1;
}
// all following lines must be padded with nextLineTabStop space
// characters
final String padding = createPadding(nextLineTabStop);
while (true)
{
text = padding + text.substring(pos).trim();
pos = findWrapPos(text, width, 0);
if (pos == -1)
{
sb.append(text);
return sb;
}
if ( (text.length() > width) && (pos == nextLineTabStop - 1) )
{
pos = width;
}
sb.append(rtrim(text.substring(0, pos))).append(defaultNewLine);
}
}
/**
* Finds the next text wrap position after <code>startPos</code> for the
* text in <code>text</code> with the column width <code>width</code>.
* The wrap point is the last postion before startPos+width having a
* whitespace character (space, \n, \r).
*
* @param text The text being searched for the wrap position
* @param width width of the wrapped text
* @param startPos position from which to start the lookup whitespace
* character
* @return postion on which the text must be wrapped or -1 if the wrap
* position is at the end of the text
*/
protected int findWrapPos(String text, int width, int startPos)
{
int pos = -1;
// the line ends before the max wrap pos or a new line char found
if (((pos = text.indexOf('\n', startPos)) != -1 && pos <= width)
|| ((pos = text.indexOf('\t', startPos)) != -1 && pos <= width))
{
return pos + 1;
}
else if (startPos + width >= text.length())
{
return -1;
}
// look for the last whitespace character before startPos+width
pos = startPos + width;
char c;
while ((pos >= startPos) && ((c = text.charAt(pos)) != ' ')
&& (c != '\n') && (c != '\r'))
{
--pos;
}
// if we found it - just return
if (pos > startPos)
{
return pos;
}
// must look for the first whitespace chearacter after startPos
// + width
pos = startPos + width;
while ((pos <= text.length()) && ((c = text.charAt(pos)) != ' ')
&& (c != '\n') && (c != '\r'))
{
++pos;
}
return (pos == text.length()) ? (-1) : pos;
}
/**
* Return a String of padding of length <code>len</code>.
*
* @param len The length of the String of padding to create.
*
* @return The String of padding
*/
protected String createPadding(int len)
{
StringBuffer sb = new StringBuffer(len);
for (int i = 0; i < len; ++i)
{
sb.append(' ');
}
return sb.toString();
}
/**
* Remove the trailing whitespace from the specified String.
*
* @param s The String to remove the trailing padding from.
*
* @return The String of without the trailing padding
*/
protected String rtrim(String s)
{
if ((s == null) || (s.length() == 0))
{
return s;
}
int pos = s.length();
while ((pos > 0) && Character.isWhitespace(s.charAt(pos - 1)))
{
--pos;
}
return s.substring(0, pos);
}
// ------------------------------------------------------ Package protected
// ---------------------------------------------------------------- Private
// ---------------------------------------------------------- Inner classes
/**
* This class implements the <code>Comparator</code> interface
* for comparing Options.
*/
private static class OptionComparator implements Comparator
{
/**
* Compares its two arguments for order. Returns a negative
* integer, zero, or a positive integer as the first argument
* is less than, equal to, or greater than the second.
*
* @param o1 The first Option to be compared.
* @param o2 The second Option to be compared.
* @return a negative integer, zero, or a positive integer as
* the first argument is less than, equal to, or greater than the
* second.
*/
public int compare(Object o1, Object o2)
{
Option opt1 = (Option) o1;
Option opt2 = (Option) o2;
return opt1.getKey().compareToIgnoreCase(opt2.getKey());
}
}
}