Skip to content

Commit

Permalink
Only parse arguments once to enable proper validation
Browse files Browse the repository at this point in the history
  • Loading branch information
spacecowboy committed Mar 15, 2017
1 parent 5d3489c commit b765d51
Show file tree
Hide file tree
Showing 13 changed files with 209 additions and 40 deletions.
Expand Up @@ -26,15 +26,20 @@
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Optional;
import java.util.function.Function;
import java.util.stream.Collectors;

import org.apache.commons.lang3.text.WordUtils;

import org.neo4j.commandline.admin.IncorrectUsage;
import org.neo4j.commandline.arguments.common.Database;
import org.neo4j.commandline.arguments.common.MandatoryCanonicalPath;
import org.neo4j.commandline.arguments.common.OptionalCanonicalPath;
import org.neo4j.helpers.Args;

import static java.lang.String.format;

/**
* Builder class for constructing a suitable arguments-string for displaying in help messages and alike.
Expand All @@ -48,6 +53,7 @@ public class Arguments
private static final String NEWLINE = System.getProperty( "line.separator" );
private final Map<String,NamedArgument> namedArgs;
private final ArrayList<PositionalArgument> positionalArgs;
private Args parsedArgs;

public Arguments()
{
Expand Down Expand Up @@ -151,11 +157,11 @@ public static String wrapText( final String text, final int lineLength )

public String formatArgumentDescription( final int longestAlignmentLength, final NamedArgument argument )
{
final String left = String.format( " %s", argument.optionsListing() );
final String left = format( " %s", argument.optionsListing() );
final String right;
if ( argument instanceof OptionalNamedArg )
{
right = String.format( "%s [default:%s]", argument.description(),
right = format( "%s [default:%s]", argument.description(),
((OptionalNamedArg) argument).defaultValue() );
}
else
Expand All @@ -180,11 +186,11 @@ public static String rightColumnFormatted( final String leftText, final String r
final String[] rightLines = wrapText( rightText, rightWidth ).split( NEWLINE );

final String fmt = "%-" + (startOnNewLine ? newLineIndent : rightAlignIndex) + "s%s";
String firstLine = String.format( fmt, leftText, startOnNewLine ? "" : rightLines[0] );
String firstLine = format( fmt, leftText, startOnNewLine ? "" : rightLines[0] );

String rest = Arrays.stream( rightLines )
.skip( startOnNewLine ? 0 : 1 )
.map( l -> String.format( fmt, "", l ) )
.map( l -> format( fmt, "", l ) )
.collect( Collectors.joining( NEWLINE ) );

if ( rest.isEmpty() )
Expand All @@ -197,23 +203,70 @@ public static String rightColumnFormatted( final String leftText, final String r
}
}

public String parse( String argName, String[] args )
public Arguments parse( String[] args ) throws IncorrectUsage
{
// Get boolean flags
List<String> flags = namedArgs.entrySet().stream()
.filter( e -> e.getValue() instanceof OptionalBooleanArg )
.map( Entry::getKey )
.collect( Collectors.toList() );

parsedArgs = Args.withFlags( flags.toArray( new String[flags.size()] ) ).parse( args );
validate();
return this;
}

public String get( int pos )
{
if ( pos >= 0 && pos < positionalArgs.size() )
{
return positionalArgs.get( pos ).parse( parsedArgs );
}
throw new IllegalArgumentException( format( "Positional argument '%d' not specified.", pos ) );
}

public String get( String argName )
{
if ( namedArgs.containsKey( argName ) )
{
return namedArgs.get( argName ).parse( args );
return namedArgs.get( argName ).parse( parsedArgs );
}
throw new IllegalArgumentException( "No such argument available to be parsed: " + argName );
}

public boolean parseBoolean( String argName, String[] args )
private void validate() throws IncorrectUsage
{
return parse( argName, Boolean::parseBoolean, args );
for ( String o : parsedArgs.asMap().keySet() )
{
if ( !namedArgs.containsKey( o ) )
{
throw new IncorrectUsage( format( "unrecognized option: '%s'", o ) );
}
}
long mandatoryPositionalArgs = positionalArgs.stream()
.filter( o -> o instanceof MandatoryPositionalArgument )
.count();
if ( parsedArgs.orphans().size() < mandatoryPositionalArgs )
{
throw new IncorrectUsage( "not enough arguments" );
}
String excessArgs = parsedArgs.orphans().stream()
.skip( positionalArgs.size() )
.collect( Collectors.joining( " " ) );
if ( !excessArgs.isEmpty() )
{
throw new IncorrectUsage( format( "unrecognized arguments: '%s'", excessArgs ) );
}
}

public Optional<Path> parseOptionalPath( String argName, String[] args )
public boolean getBoolean( String argName )
{
String p = parse( argName, args );
return get( argName, Boolean::parseBoolean );
}

public Optional<Path> getOptionalPath( String argName )
{
String p = get( argName );

if ( p.isEmpty() )
{
Expand All @@ -223,18 +276,31 @@ public Optional<Path> parseOptionalPath( String argName, String[] args )
return Optional.of( Paths.get( p ) );
}

public Path parseMandatoryPath( String argName, String[] args )
public Path getMandatoryPath( String argName )
{
Optional<Path> p = parseOptionalPath( argName, args );
Optional<Path> p = getOptionalPath( argName );
if ( p.isPresent() )
{
return p.get();
}
throw new IllegalArgumentException( String.format( "Missing exampleValue for '%s'", argName ) );
throw new IllegalArgumentException( format( "Missing exampleValue for '%s'", argName ) );
}

public <T> T parse( String argName, Function<String,T> converter, String[] args )
public <T> T get( String argName, Function<String,T> converter )
{
return converter.apply( parse( argName, args ) );
return converter.apply( get( argName ) );
}

/**
* @param argName name of argument
* @return true if argName was given as an explicit argument, false otherwise
*/
public boolean has( String argName )
{
if ( namedArgs.containsKey( argName ) )
{
return namedArgs.get( argName ).has( parsedArgs );
}
throw new IllegalArgumentException( "No such argument available: " + argName );
}
}
Expand Up @@ -69,8 +69,8 @@ public String exampleValue()
}

@Override
public String parse( String... args )
public String parse( Args parsedArgs )
{
return Args.parse( args ).interpretOption( name, mandatory(), identity() );
return parsedArgs.interpretOption( name, mandatory(), identity() );
}
}
Expand Up @@ -19,6 +19,8 @@
*/
package org.neo4j.commandline.arguments;

import org.neo4j.helpers.Args;

public interface NamedArgument
{
/**
Expand Down Expand Up @@ -49,5 +51,13 @@ public interface NamedArgument
/**
* Parses the option (or possible default value) out of program arguments.
*/
String parse( String... args );
String parse( Args parsedArgs );

/**
* Returns true if this argument was given explicitly on the command line
*/
default boolean has( Args parsedArgs )
{
return parsedArgs.has( name() );
}
}
Expand Up @@ -38,10 +38,8 @@ public String usage()
}

@Override
public String parse( String... args )
public String parse( Args parsedArgs )
{
return Boolean.toString( Args.withFlags( name() )
.parse( args )
.getBoolean( name, Boolean.parseBoolean( defaultValue ) ) );
return Boolean.toString( parsedArgs.getBoolean( name, Boolean.parseBoolean( defaultValue ) ) );
}
}
Expand Up @@ -86,9 +86,9 @@ public String defaultValue()
}

@Override
public String parse( String... args )
public String parse( Args parsedArgs )
{
String value = Args.parse( args ).interpretOption( name, withDefault( defaultValue ), identity() );
String value = parsedArgs.interpretOption( name, withDefault( defaultValue ), identity() );
if ( allowedValues.length > 0 )
{
for ( String allowedValue : allowedValues )
Expand Down
Expand Up @@ -19,6 +19,8 @@
*/
package org.neo4j.commandline.arguments;

import org.neo4j.helpers.Args;

public class OptionalPositionalArgument implements PositionalArgument
{
protected final String value;
Expand All @@ -41,4 +43,10 @@ public String usage()
{
return String.format( "[<%s>]", value );
}

@Override
public String parse( Args parsedArgs )
{
return parsedArgs.orphans().get( position );
}
}
Expand Up @@ -19,6 +19,8 @@
*/
package org.neo4j.commandline.arguments;

import org.neo4j.helpers.Args;

public interface PositionalArgument
{
/**
Expand All @@ -30,4 +32,9 @@ public interface PositionalArgument
* Represents the option in the usage string.
*/
String usage();

/**
* Parses the option (or possible default value) out of program arguments.
*/
String parse( Args parsedArgs );
}
Expand Up @@ -23,6 +23,7 @@
import java.io.File;

import org.neo4j.commandline.arguments.OptionalNamedArg;
import org.neo4j.helpers.Args;

public class Database extends OptionalNamedArg
{
Expand All @@ -42,8 +43,8 @@ private static String validate( String dbName )
}

@Override
public String parse( String... args )
public String parse( Args parsedArgs )
{
return validate( super.parse( args ) );
return validate( super.parse( parsedArgs ) );
}
}
Expand Up @@ -22,6 +22,7 @@

import org.neo4j.commandline.Util;
import org.neo4j.commandline.arguments.MandatoryNamedArg;
import org.neo4j.helpers.Args;

public class MandatoryCanonicalPath extends MandatoryNamedArg
{
Expand All @@ -42,8 +43,8 @@ private static String canonicalize( String path )
}

@Override
public String parse( String... args )
public String parse( Args parsedArgs )
{
return canonicalize( super.parse( args ) );
return canonicalize( super.parse( parsedArgs ) );
}
}
Expand Up @@ -22,6 +22,7 @@

import org.neo4j.commandline.Util;
import org.neo4j.commandline.arguments.OptionalNamedArg;
import org.neo4j.helpers.Args;

public class OptionalCanonicalPath extends OptionalNamedArg
{
Expand All @@ -42,8 +43,8 @@ private static String canonicalize( String path )
}

@Override
public String parse( String... args )
public String parse( Args parsedArgs )
{
return canonicalize( super.parse( args ) );
return canonicalize( super.parse( parsedArgs ) );
}
}

0 comments on commit b765d51

Please sign in to comment.