Miscellaneous cleanup. Better display of help output. Better exception subtyping. More thought-out access routines.
git-svn-id: file:///humgen/gsa-scr1/gsa-engineering/svn_contents/trunk@608 348d0f76-0448-11de-a6fe-93d51630548a
This commit is contained in:
parent
5be75e0ae6
commit
62e7e46754
|
|
@ -9,6 +9,7 @@ import java.util.Collection;
|
|||
import java.util.List;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Iterator;
|
||||
import java.util.Collections;
|
||||
|
||||
/**
|
||||
* Created by IntelliJ IDEA.
|
||||
|
|
@ -34,23 +35,28 @@ class ArgumentDefinitions implements Iterable<ArgumentDefinition> {
|
|||
private Set<ArgumentDefinition> argumentDefinitions = new HashSet<ArgumentDefinition>();
|
||||
|
||||
/**
|
||||
* Adds an argument to the this argument definition list.
|
||||
* @param argument The argument to add.
|
||||
* @param sourceClass Class where the argument was defined.
|
||||
* @param sourceField Field in which the argument was defined.
|
||||
* The groupings of argument definitions. Used mainly for help output.
|
||||
*/
|
||||
public void add( Argument argument, Class sourceClass, Field sourceField ) {
|
||||
ArgumentDefinition definition = new ArgumentDefinition( argument, sourceClass, sourceField );
|
||||
if( definition.fullName.length() == 0 ) {
|
||||
throw new IllegalArgumentException( "Argument cannot have 0-length fullname." );
|
||||
private Set<ArgumentDefinitionGroup> argumentDefinitionGroups = new HashSet<ArgumentDefinitionGroup>();
|
||||
|
||||
/**
|
||||
* Adds an argument to the this argument definition list.
|
||||
* @param argumentDefinitionGroup The group of arguments to add.
|
||||
*/
|
||||
public void add( ArgumentDefinitionGroup argumentDefinitionGroup ) {
|
||||
for( ArgumentDefinition definition: argumentDefinitionGroup ) {
|
||||
// Do some basic validation before adding the definition.
|
||||
if( definition.fullName.length() == 0 )
|
||||
throw new IllegalArgumentException( "Argument cannot have 0-length fullname." );
|
||||
if( hasArgumentDefinition( definition.fullName, FullNameDefinitionMatcher ) )
|
||||
throw new StingException("Duplicate definition of argument with full name: " + definition.fullName);
|
||||
if( hasArgumentDefinition( definition.shortName, ShortNameDefinitionMatcher ) )
|
||||
throw new StingException("Duplicate definition of argument with short name: " + definition.shortName);
|
||||
|
||||
argumentDefinitions.add( definition );
|
||||
}
|
||||
|
||||
if( hasArgumentDefinition( definition.fullName, FullNameDefinitionMatcher ) )
|
||||
throw new StingException("Duplicate definition of argument with full name: " + definition.fullName);
|
||||
if( hasArgumentDefinition( definition.shortName, ShortNameDefinitionMatcher ) )
|
||||
throw new StingException("Duplicate definition of argument with short name: " + definition.shortName);
|
||||
|
||||
argumentDefinitions.add( definition );
|
||||
argumentDefinitionGroups.add( argumentDefinitionGroup );
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -96,9 +102,17 @@ class ArgumentDefinitions implements Iterable<ArgumentDefinition> {
|
|||
return selectedArgumentDefinitions;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a list of the available argument groups.
|
||||
* @return All the argument groups that have been added.
|
||||
*/
|
||||
Collection<ArgumentDefinitionGroup> getArgumentDefinitionGroups() {
|
||||
return argumentDefinitionGroups;
|
||||
}
|
||||
|
||||
/**
|
||||
* Iterates through all command-line arguments.
|
||||
* @return
|
||||
* @return an iterator over command-line arguments.
|
||||
*/
|
||||
public Iterator<ArgumentDefinition> iterator() {
|
||||
return argumentDefinitions.iterator();
|
||||
|
|
@ -107,7 +121,7 @@ class ArgumentDefinitions implements Iterable<ArgumentDefinition> {
|
|||
/**
|
||||
* Match the full name of a definition.
|
||||
*/
|
||||
public static DefinitionMatcher FullNameDefinitionMatcher = new DefinitionMatcher() {
|
||||
static DefinitionMatcher FullNameDefinitionMatcher = new DefinitionMatcher() {
|
||||
public boolean matches( ArgumentDefinition definition, Object key ) {
|
||||
if( definition.fullName == null )
|
||||
return key == null;
|
||||
|
|
@ -119,7 +133,7 @@ class ArgumentDefinitions implements Iterable<ArgumentDefinition> {
|
|||
/**
|
||||
* Match the short name of a definition.
|
||||
*/
|
||||
public static DefinitionMatcher ShortNameDefinitionMatcher = new DefinitionMatcher() {
|
||||
static DefinitionMatcher ShortNameDefinitionMatcher = new DefinitionMatcher() {
|
||||
public boolean matches( ArgumentDefinition definition, Object key ) {
|
||||
if( definition.shortName == null )
|
||||
return key == null;
|
||||
|
|
@ -128,7 +142,7 @@ class ArgumentDefinitions implements Iterable<ArgumentDefinition> {
|
|||
}
|
||||
};
|
||||
|
||||
public static AliasProvider ShortNameAliasProvider = new AliasProvider() {
|
||||
static AliasProvider ShortNameAliasProvider = new AliasProvider() {
|
||||
/**
|
||||
* Short names can come in the form -Ofoo.txt, -O foo.txt, or -out (multi-character short name).
|
||||
* Given the argument name and built-in provided, see if these can be formed into some other argument
|
||||
|
|
@ -159,7 +173,7 @@ class ArgumentDefinitions implements Iterable<ArgumentDefinition> {
|
|||
/**
|
||||
* Find all required definitions.
|
||||
*/
|
||||
public static DefinitionMatcher RequiredDefinitionMatcher = new DefinitionMatcher() {
|
||||
static DefinitionMatcher RequiredDefinitionMatcher = new DefinitionMatcher() {
|
||||
public boolean matches( ArgumentDefinition definition, Object key ) {
|
||||
if( !(key instanceof Boolean) )
|
||||
throw new IllegalArgumentException("RequiredDefinitionMatcher requires boolean key");
|
||||
|
|
@ -168,6 +182,34 @@ class ArgumentDefinitions implements Iterable<ArgumentDefinition> {
|
|||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* A group of argument definitions.
|
||||
*/
|
||||
class ArgumentDefinitionGroup implements Iterable<ArgumentDefinition> {
|
||||
/**
|
||||
* Name of this group.
|
||||
*/
|
||||
public final String groupName;
|
||||
|
||||
/**
|
||||
* The argument definitions associated with this group.
|
||||
*/
|
||||
public final Collection<ArgumentDefinition> argumentDefinitions;
|
||||
|
||||
public ArgumentDefinitionGroup( String groupName, Collection<ArgumentDefinition> argumentDefinitions ) {
|
||||
this.groupName = groupName;
|
||||
this.argumentDefinitions = Collections.unmodifiableCollection( argumentDefinitions );
|
||||
}
|
||||
|
||||
/**
|
||||
* Iterate over the arguments in an argument definition group.
|
||||
* @return
|
||||
*/
|
||||
public Iterator<ArgumentDefinition> iterator() {
|
||||
return argumentDefinitions.iterator();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A specific argument definition. Maps one-to-one with a field in some class.
|
||||
*/
|
||||
|
|
|
|||
|
|
@ -73,7 +73,6 @@ public abstract class CommandLineProgram {
|
|||
required=false)
|
||||
protected Boolean debugMode = false;
|
||||
|
||||
|
||||
/**
|
||||
* our logging output patterns
|
||||
*/
|
||||
|
|
@ -145,7 +144,7 @@ public abstract class CommandLineProgram {
|
|||
|
||||
Class[] argumentSources = clp.getArgumentSources();
|
||||
for( Class argumentSource: argumentSources )
|
||||
parser.addArgumentSource( argumentSource );
|
||||
parser.addArgumentSource( clp.getArgumentSourceName(argumentSource), argumentSource );
|
||||
parser.parse(args);
|
||||
parser.validate();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,11 +1,20 @@
|
|||
package org.broadinstitute.sting.utils.cmdLine;
|
||||
|
||||
import org.broadinstitute.sting.utils.StingException;
|
||||
|
||||
import java.util.Formatter;
|
||||
import java.util.Locale;
|
||||
import java.util.Formattable;
|
||||
import java.util.List;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Iterator;
|
||||
import java.util.Map;
|
||||
import java.util.HashMap;
|
||||
import java.util.Comparator;
|
||||
import java.util.Set;
|
||||
import java.util.TreeSet;
|
||||
import java.util.SortedSet;
|
||||
import java.util.Collection;
|
||||
import java.util.regex.Pattern;
|
||||
import java.util.regex.Matcher;
|
||||
/**
|
||||
|
|
@ -37,21 +46,67 @@ public class HelpFormatter {
|
|||
* @param argumentDefinitions Argument definitions for which help should be printed.
|
||||
*/
|
||||
public void printHelp( ArgumentDefinitions argumentDefinitions ) {
|
||||
System.out.printf("%s%n%n%s%n", getSynopsis(argumentDefinitions), getDetailed(argumentDefinitions) );
|
||||
SortedSet<ArgumentDefinition> mainArguments = getMainArguments( argumentDefinitions );
|
||||
Map<String,SortedSet<ArgumentDefinition>> pluginsByGroup = getPluginArguments( argumentDefinitions );
|
||||
|
||||
System.out.printf("%s%s%n",
|
||||
getSynopsis(mainArguments,pluginsByGroup),
|
||||
getDetailed(mainArguments,pluginsByGroup) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve a sorted set of the command-line arguments for the main application from the ArgumentDefinitions object.
|
||||
* @param argumentDefinitions Predefined argument definitions.
|
||||
* @return A list of argument definitions.
|
||||
*/
|
||||
private SortedSet<ArgumentDefinition> getMainArguments( ArgumentDefinitions argumentDefinitions ) {
|
||||
for( ArgumentDefinitionGroup argumentGroup: argumentDefinitions.getArgumentDefinitionGroups() ) {
|
||||
// A null groupName is the signal that these are the application's main arguments.
|
||||
if( argumentGroup.groupName == null ){
|
||||
SortedSet<ArgumentDefinition> sortedDefinitions = new TreeSet<ArgumentDefinition>( new ArgumentDefinitionComparator() );
|
||||
sortedDefinitions.addAll( argumentGroup.argumentDefinitions );
|
||||
return sortedDefinitions;
|
||||
}
|
||||
}
|
||||
throw new StingException( "Unable to retrieve main command-line arguments." );
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve a sorted set of the command-line arguments for application plugin objects.
|
||||
* @param argumentDefinitions Predefined argument definitions.
|
||||
* @return A map of argument group name -> a list of argument definitions.
|
||||
*/
|
||||
private Map<String,SortedSet<ArgumentDefinition>> getPluginArguments( ArgumentDefinitions argumentDefinitions ) {
|
||||
Map<String,SortedSet<ArgumentDefinition>> pluginsByGroup = new HashMap<String,SortedSet<ArgumentDefinition>>();
|
||||
for( ArgumentDefinitionGroup argumentGroup: argumentDefinitions.getArgumentDefinitionGroups() ) {
|
||||
// A null groupName is the signal that these are the application's main arguments.
|
||||
if( argumentGroup.groupName == null )
|
||||
continue;
|
||||
SortedSet<ArgumentDefinition> sortedDefinitions = new TreeSet<ArgumentDefinition>( new ArgumentDefinitionComparator() );
|
||||
sortedDefinitions.addAll( argumentGroup.argumentDefinitions );
|
||||
pluginsByGroup.put( argumentGroup.groupName, sortedDefinitions );
|
||||
}
|
||||
return pluginsByGroup;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the synopsis: the actual command to run.
|
||||
* @param argumentDefinitions Argument definitions for which help should be printed.
|
||||
* @param mainArguments Main program arguments.
|
||||
* @praam pluginArgumentGroups Groups of plugin arguments
|
||||
* @return A synopsis line.
|
||||
*/
|
||||
private String getSynopsis( ArgumentDefinitions argumentDefinitions ) {
|
||||
private String getSynopsis( SortedSet<ArgumentDefinition> mainArguments, Map<String,SortedSet<ArgumentDefinition>> pluginArgumentGroups ) {
|
||||
// Build out the synopsis all as one long line.
|
||||
StringBuilder lineBuilder = new StringBuilder();
|
||||
Formatter lineFormatter = new Formatter( lineBuilder );
|
||||
|
||||
lineFormatter.format("java -jar dist/GenomeAnalysisTK.jar");
|
||||
|
||||
List<ArgumentDefinition> argumentDefinitions = new ArrayList<ArgumentDefinition>();
|
||||
argumentDefinitions.addAll( mainArguments );
|
||||
for( SortedSet pluginArguments: pluginArgumentGroups.values() )
|
||||
argumentDefinitions.addAll( pluginArguments );
|
||||
|
||||
for( ArgumentDefinition argumentDefinition: argumentDefinitions ) {
|
||||
lineFormatter.format(" ");
|
||||
if( !argumentDefinition.required ) lineFormatter.format("[");
|
||||
|
|
@ -82,21 +137,34 @@ public class HelpFormatter {
|
|||
|
||||
/**
|
||||
* Gets detailed output about each argument type.
|
||||
* @param argumentDefinitions Argument definitions for which help should be printed.
|
||||
* @param mainArguments Main program arguments.
|
||||
* @param pluginArgumentGroups Groups of plugin arguments
|
||||
* @return Detailed text about all arguments.
|
||||
*/
|
||||
private String getDetailed( ArgumentDefinitions argumentDefinitions ) {
|
||||
private String getDetailed( SortedSet<ArgumentDefinition> mainArguments, Map<String,SortedSet<ArgumentDefinition>> pluginArgumentGroups ) {
|
||||
StringBuilder builder = new StringBuilder();
|
||||
|
||||
builder.append( getDetailForGroup( mainArguments ) );
|
||||
|
||||
for( String groupName: pluginArgumentGroups.keySet() ) {
|
||||
builder.append( String.format("Arguments for %s:%n", groupName ) );
|
||||
builder.append( getDetailForGroup( pluginArgumentGroups.get(groupName) ) );
|
||||
}
|
||||
return builder.toString();
|
||||
}
|
||||
|
||||
private String getDetailForGroup( SortedSet<ArgumentDefinition> argumentDefinitions ) {
|
||||
StringBuilder builder = new StringBuilder();
|
||||
Formatter formatter = new Formatter( builder );
|
||||
|
||||
// Try to fit the entire argument definition across the screen, but impose an arbitrary cap of 3/4 *
|
||||
// LINE_WIDTH in case the length of the arguments gets out of control.
|
||||
// LINE_WIDTH in case the length of the arguments gets out of control.
|
||||
int argWidth = Math.min( findLongestArgumentCallingInfo(argumentDefinitions), (LINE_WIDTH*3)/4 - ARG_DOC_SEPARATION_WIDTH );
|
||||
int docWidth = LINE_WIDTH - argWidth - ARG_DOC_SEPARATION_WIDTH;
|
||||
|
||||
for( ArgumentDefinition argumentDefinition: argumentDefinitions ) {
|
||||
Iterator<String> wordWrappedArgs = wordWrap( getArgumentCallingInfo(argumentDefinition), argWidth ).iterator();
|
||||
Iterator<String> wordWrappedDoc = wordWrap( argumentDefinition.doc, docWidth ).iterator();
|
||||
Iterator<String> wordWrappedDoc = wordWrap( argumentDefinition.doc, docWidth ).iterator();
|
||||
|
||||
while( wordWrappedArgs.hasNext() || wordWrappedDoc.hasNext() ) {
|
||||
String arg = wordWrappedArgs.hasNext() ? wordWrappedArgs.next() : "";
|
||||
|
|
@ -135,7 +203,7 @@ public class HelpFormatter {
|
|||
* @param argumentDefinitions argument definitions to inspect.
|
||||
* @return longest argument length.
|
||||
*/
|
||||
private int findLongestArgumentCallingInfo( ArgumentDefinitions argumentDefinitions ) {
|
||||
private int findLongestArgumentCallingInfo( Collection<ArgumentDefinition> argumentDefinitions ) {
|
||||
int longest = 0;
|
||||
for( ArgumentDefinition argumentDefinition: argumentDefinitions ) {
|
||||
String argumentText = getArgumentCallingInfo( argumentDefinition );
|
||||
|
|
@ -167,4 +235,23 @@ public class HelpFormatter {
|
|||
}
|
||||
return wrapped;
|
||||
}
|
||||
|
||||
/**
|
||||
* A comparator to reorder arguments in alphabetical order.
|
||||
*/
|
||||
private class ArgumentDefinitionComparator implements Comparator<ArgumentDefinition> {
|
||||
public int compare( ArgumentDefinition lhs, ArgumentDefinition rhs ) {
|
||||
if( lhs.shortName == null && rhs.shortName == null )
|
||||
return lhs.fullName.compareTo( rhs.fullName );
|
||||
// short names always come before long names.
|
||||
else if ( lhs.shortName == null )
|
||||
return -1;
|
||||
else if ( rhs.shortName == null )
|
||||
return 1;
|
||||
else if ( lhs.shortName.equals(rhs.shortName) )
|
||||
return lhs.fullName.compareTo( rhs.fullName );
|
||||
else
|
||||
return lhs.shortName.compareTo( rhs.shortName );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -66,21 +66,33 @@ public class ParsingEngine {
|
|||
}
|
||||
|
||||
/**
|
||||
* Add an argument source. Argument sources are expected to have
|
||||
* Add a main argument source. Argument sources are expected to have
|
||||
* any number of fields with an @Argument annotation attached.
|
||||
* @param source An argument source from which to extract
|
||||
* command-line arguments.
|
||||
* @param source An argument source from which to extract command-line arguments.
|
||||
*/
|
||||
public void addArgumentSource( Class source ) {
|
||||
do {
|
||||
addArgumentSource(null, source);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add an argument source. Argument sources are expected to have
|
||||
* any number of fields with an @Argument annotation attached.
|
||||
* @param sourceName name for this argument source. 'Null' indicates that this source should be treated
|
||||
* as the main module.
|
||||
* @param source An argument source from which to extract command-line arguments.
|
||||
*/
|
||||
public void addArgumentSource( String sourceName, Class source ) {
|
||||
Collection<ArgumentDefinition> argumentsFromSource = new ArrayList<ArgumentDefinition>();
|
||||
while( source != null ) {
|
||||
Field[] fields = source.getDeclaredFields();
|
||||
for( Field field: fields ) {
|
||||
Argument argument = field.getAnnotation(Argument.class);
|
||||
if(argument != null)
|
||||
argumentDefinitions.add( argument, source, field );
|
||||
argumentsFromSource.add( new ArgumentDefinition(argument,source,field) );
|
||||
}
|
||||
source = source.getSuperclass();
|
||||
} while( source != null );
|
||||
}
|
||||
argumentDefinitions.add( new ArgumentDefinitionGroup(sourceName, argumentsFromSource) );
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -456,10 +468,19 @@ public class ParsingEngine {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Generic class for handling misc parsing exceptions.
|
||||
*/
|
||||
class ParseException extends StingException {
|
||||
public ParseException( String message ) {
|
||||
super( message );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* An exception indicating that some required arguments are missing.
|
||||
*/
|
||||
class MissingArgumentException extends StingException {
|
||||
class MissingArgumentException extends ParseException {
|
||||
public MissingArgumentException( Collection<ArgumentDefinition> missingArguments ) {
|
||||
super( formatArguments(missingArguments) );
|
||||
}
|
||||
|
|
@ -467,17 +488,11 @@ class MissingArgumentException extends StingException {
|
|||
private static String formatArguments( Collection<ArgumentDefinition> missingArguments ) {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
for( ArgumentDefinition missingArgument: missingArguments )
|
||||
sb.append( String.format("Argument with name '%s' is missing.", missingArgument.fullName) );
|
||||
sb.append( String.format("Argument with name '%s' is missing.%n", missingArgument.fullName) );
|
||||
return sb.toString();
|
||||
}
|
||||
}
|
||||
|
||||
class ParseException extends StingException {
|
||||
public ParseException( String message ) {
|
||||
super( message );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* An exception for undefined arguments.
|
||||
*/
|
||||
|
|
@ -489,7 +504,7 @@ class InvalidArgumentException extends ParseException {
|
|||
private static String formatArguments( Collection<ArgumentMatch> invalidArguments ) {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
for( ArgumentMatch invalidArgument: invalidArguments )
|
||||
sb.append( String.format("Argument with name '%s' isn't defined.", invalidArgument.label) );
|
||||
sb.append( String.format("Argument with name '%s' isn't defined.%n", invalidArgument.label) );
|
||||
return sb.toString();
|
||||
}
|
||||
}
|
||||
|
|
@ -506,7 +521,7 @@ class InvalidArgumentValueException extends ParseException {
|
|||
StringBuilder sb = new StringBuilder();
|
||||
for( int index: invalidValues.indices.keySet() )
|
||||
for( String value: invalidValues.indices.get(index) )
|
||||
sb.append( String.format("Invalid argument value '%s' at position %d", value, index) );
|
||||
sb.append( String.format("Invalid argument value '%s' at position %d.%n", value, index) );
|
||||
return sb.toString();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue