Rewrite LightOptionParser to support multiple parameter arguments.
* LightOptionParser will only make one pass through the arguments array. * Make LOP support multiple instances of an option (-i in1 -i in2) * Make LOP support indeterminate option argument lengths (using arguments: "variable" in the definition). * Add unit tests for LightOptionParser and a testing phase during the `package` build target.
This commit is contained in:
parent
8ab35b27bd
commit
4ab4e5876b
@ -4,7 +4,7 @@
|
||||
|
||||
<import file="jdb-build-1.10.xml"/>
|
||||
|
||||
<target name="package" depends="-build-modular">
|
||||
<target name="package" depends="run-tests,-build-modular">
|
||||
<copy
|
||||
file="${build.dir}/${name}-${version}.${build.number}.jar"
|
||||
tofile="${build.dir}/${name}-${version}.jar"/>
|
||||
|
@ -1,6 +1,6 @@
|
||||
#Thu, 25 Sep 2014 02:29:18 -0500
|
||||
#Tue, 28 Oct 2014 01:58:09 -0500
|
||||
name=jdb-util
|
||||
version=2.4
|
||||
version=3.0
|
||||
lib.local=true
|
||||
|
||||
build.number=1
|
||||
|
@ -11,77 +11,107 @@ public class LightOptionParser {
|
||||
|
||||
public static def parseOptions(def optionDefinitions, List args) {
|
||||
|
||||
def returnOpts = [:]
|
||||
def foundOpts = [:]
|
||||
def optionArgIndices = []
|
||||
def returnOpts = [args:[]]
|
||||
|
||||
/// Find all the options.
|
||||
args.eachWithIndex { arg, idx ->
|
||||
if (arg.startsWith('--')) foundOpts[arg.substring(2)] = [idx: idx]
|
||||
else if (arg.startsWith('-')) foundOpts[arg.substring(1)] = [idx: idx] }
|
||||
/// Look through each of the arguments to see if it is an option.
|
||||
/// Note that we are manually advancing the index in the loop.
|
||||
for (int i = 0; i < args.size();) {
|
||||
|
||||
/// Look for option arguments.
|
||||
foundOpts.each { foundName, optInfo ->
|
||||
def retVal = false
|
||||
def optName = false
|
||||
|
||||
def retVal
|
||||
if (args[i].startsWith('--')) optName = args[i].substring(2)
|
||||
else if (args[i].startsWith('-')) optName = args[i].substring(1)
|
||||
|
||||
/// This was recognized as an option, try to find the definition
|
||||
/// and read in any arguments.
|
||||
if (optName) {
|
||||
|
||||
/// Find the definition for this option.
|
||||
def optDef = optionDefinitions.find {
|
||||
it.key == foundName || it.value.longName == foundName }
|
||||
it.key == optName || it.value.longName == optName }
|
||||
|
||||
if (!optDef) throw new IllegalArgumentException(
|
||||
"Unrecognized option: '${args[optInfo.idx]}.")
|
||||
"Unrecognized option: '${args[i]}'.")
|
||||
|
||||
def optName = optDef.key
|
||||
optName = optDef.key
|
||||
optDef = optDef.value
|
||||
|
||||
/// Remember the option index for later.
|
||||
optionArgIndices << optInfo.idx
|
||||
/// If there are no arguments, this is a flag. Set the value
|
||||
/// and advance the index.
|
||||
if ((optDef.arguments ?: 0) == 0) { retVal = true; i++ }
|
||||
|
||||
/// If there are no arguments, this is a flag.
|
||||
if ((optDef.arguments ?: 0) == 0) retVal = true
|
||||
/// If there are a pre-determined number of arguments, read them
|
||||
/// in.
|
||||
else if (optDef.arguments &&
|
||||
optDef.arguments instanceof Number &&
|
||||
optDef.arguments > 0) {
|
||||
|
||||
/// Otherwise, read in the arguments
|
||||
if (optDef.arguments && optDef.arguments > 0) {
|
||||
retVal = []
|
||||
|
||||
/// Not enough arguments left
|
||||
if ((optInfo.idx + optDef.arguments) >= args.size()) {
|
||||
throw new Exception("Option '${args[optInfo.idx]}' " +
|
||||
if ((i + optDef.arguments) >= args.size()) {
|
||||
throw new Exception("Option '${args[i]}' " +
|
||||
"expects ${optDef.arguments} arguments.") }
|
||||
|
||||
int firstArgIdx = optInfo.idx + 1
|
||||
/// Advance past the option onto the first argument.
|
||||
i++
|
||||
|
||||
/// Case of only one argument
|
||||
if (optDef.arguments == 1)
|
||||
retVal = args[firstArgIdx]
|
||||
/// Case of multiple arguments
|
||||
else retVal = args[firstArgIdx..<(firstArgIdx + optDef.arguments)]
|
||||
/// Copy the arguments
|
||||
retVal += args[i..<(i + optDef.arguments)]
|
||||
|
||||
/// Remember all the option argument indices for later.
|
||||
(firstArgIdx..<(firstArgIdx + optDef.arguments)).each {
|
||||
optionArgIndices << it }}
|
||||
/// Advance the index past end of the arguements
|
||||
i += optDef.arguments }
|
||||
|
||||
/// Store the value in the returnOpts map
|
||||
/// If there are a variable number of arguments, treat all
|
||||
/// arguments until the next argument or the end of options as
|
||||
/// arguments for this option
|
||||
else if (optDef.arguments == 'variable') {
|
||||
|
||||
retVal = []
|
||||
|
||||
/// Advance past the option to the first argument
|
||||
i++
|
||||
|
||||
/// As long as we have not hit another option or the end of
|
||||
/// arguments, keep adding arguments to the list for this
|
||||
/// option.
|
||||
for(;i < args.size() && !args[i].startsWith('-'); i++)
|
||||
retVal << args[i] }
|
||||
|
||||
else {
|
||||
throw new Exception("Invalid number of arguments " +
|
||||
"defined for option ${optName}. The number of " +
|
||||
"arguments must be either an integer or the value " +
|
||||
"'variable'") }
|
||||
|
||||
/// Set the value on the option.
|
||||
if (retVal instanceof Boolean) {
|
||||
returnOpts[optName] = retVal
|
||||
if (optDef.longName) returnOpts[optDef.longName] = retVal }
|
||||
|
||||
else {
|
||||
if (!returnOpts.containsKey(optName))
|
||||
returnOpts[optName] = []
|
||||
returnOpts[optName] += retVal
|
||||
|
||||
if (optDef.longName) {
|
||||
if (!returnOpts.containsKey(optDef.longName))
|
||||
returnOpts[optDef.longName] = []
|
||||
returnOpts[optDef.longName] += retVal } } }
|
||||
|
||||
/// This was not as option, it is an unclaomed argument.
|
||||
else { returnOpts.args << args[i]; i++ } }
|
||||
|
||||
/// Check that all required options have been found.
|
||||
optionDefinitions.each { optName, optDef ->
|
||||
/// If this is a required field
|
||||
if (optDef.required &&
|
||||
/// and it has not been found, by either it's short or long name.
|
||||
!(returnOpts[optName] ||
|
||||
(optDef.longName && returnOpts[longName])))
|
||||
(optDef.longName && returnOpts[optDef.longName])))
|
||||
|
||||
throw new Exception("Missing required option: '-${optName}'.") }
|
||||
|
||||
/// Remove all the option arguments from the args list and return just
|
||||
/// the non-option arguments.
|
||||
optionArgIndices.sort().reverse().each { args.remove(it) }
|
||||
|
||||
//optionArgIndices = optionArgIndices.collect { args[it] }
|
||||
//args.removeAll(optionArgIndices)
|
||||
|
||||
returnOpts.args = args
|
||||
return returnOpts }
|
||||
}
|
||||
|
65
src/test/com/jdbernard/util/LightOptionParserTests.groovy
Normal file
65
src/test/com/jdbernard/util/LightOptionParserTests.groovy
Normal file
@ -0,0 +1,65 @@
|
||||
package com.jdbernard.util
|
||||
|
||||
import groovy.util.GroovyTestCase
|
||||
|
||||
import org.junit.Test
|
||||
|
||||
import static com.jdbernard.util.LightOptionParser.parseOptions
|
||||
|
||||
public class LightOptionParserTests extends GroovyTestCase {
|
||||
|
||||
def helpDef = ['h': [longName: 'help']]
|
||||
def confDef = ['c': [longName: 'config', required: true, arguments: 1]]
|
||||
def fullDef = [
|
||||
h: [longName: 'help'],
|
||||
c: [longName: 'config-file', required: true, arguments: 1],
|
||||
i: [longName: 'input-file', arguments: 'variable'],
|
||||
o: [longName: 'output-file2', arguments: 2]]
|
||||
|
||||
void testShortFlagPresent1() { assert parseOptions(helpDef, ["-h"]).h }
|
||||
void testShortFlagPresent2() { assert parseOptions(helpDef, ["-h"]).help }
|
||||
void testLongFlagPresent() { assert parseOptions(helpDef, ["--help"]).h}
|
||||
void testShortFlagPresent() { assert parseOptions(helpDef, ["--help"]).help }
|
||||
|
||||
void testFlagAbsent1() { assert !parseOptions(helpDef, ["arg"]).h }
|
||||
void testFlagAbsent2() { assert !parseOptions(helpDef, ["arg"]).help }
|
||||
|
||||
void testRequiredOptionMissing() {
|
||||
try {
|
||||
parseOptions(confDef, ["arg"])
|
||||
assert false }
|
||||
catch (Exception e) {} }
|
||||
|
||||
void testSingleArg1() {
|
||||
assert parseOptions(confDef, ["-c", "confFile"]).c == ["confFile"] }
|
||||
|
||||
void testSingleArg2() {
|
||||
assert parseOptions(confDef, ["-c", "confFile"]).config == ["confFile"] }
|
||||
|
||||
void testUnclaimedArgsAndFlag() {
|
||||
def opts = parseOptions(helpDef, ["arg1", "-h", "arg2"])
|
||||
assert opts.args == ["arg1", "arg2"] }
|
||||
|
||||
void testUnclaimedAndClaimedArgs() {
|
||||
def opts = parseOptions(fullDef, ["-c", "confFile", "arg1"])
|
||||
assert opts.args == ["arg1"]
|
||||
assert opts.c == ["confFile"] }
|
||||
|
||||
/*void testMultipleArgs1() {
|
||||
def opts = parseOptions(fullDef, ["-c", "confFile", ""])
|
||||
assert .conf == ["confFile"] }*/
|
||||
|
||||
void testFull() {
|
||||
def opts = parseOptions(fullDef,
|
||||
["-c", "cfgFile", "arg1", "-i", "in1", "in2", "in3",
|
||||
"-o", "out1", "out2", "arg2", "-h", "-i", "in4"])
|
||||
|
||||
assert opts.h
|
||||
assert opts.c == ["cfgFile"]
|
||||
assert opts['config-file'] == ["cfgFile"]
|
||||
assert opts.args == ["arg1", "arg2"]
|
||||
assert opts.i == ["in1", "in2", "in3", "in4"]
|
||||
assert opts["input-file"] == ["in1", "in2", "in3", "in4"]
|
||||
assert opts.o == ["out1", "out2"]
|
||||
assert opts["output-file2"] == ["out1", "out2"] }
|
||||
}
|
Loading…
Reference in New Issue
Block a user