From e8ff551a3ad66f9335e32dbf2d4b90650f44b313 Mon Sep 17 00:00:00 2001 From: Imran Rashid Date: Thu, 16 Aug 2012 14:14:21 -0700 Subject: [PATCH 01/50] options are not lazy, write some basic tests --- src/main/scala/application.scala | 27 +++++++------ src/test/scala/optional/ApplicationTest.scala | 39 +++++++++++++++++++ 2 files changed, 52 insertions(+), 14 deletions(-) create mode 100644 src/test/scala/optional/ApplicationTest.scala diff --git a/src/main/scala/application.scala b/src/main/scala/application.scala index bf07352..cd06b2a 100644 --- a/src/main/scala/application.scala +++ b/src/main/scala/application.scala @@ -108,8 +108,8 @@ trait Application { /** Public methods. */ - def getRawArgs() = opts.rawArgs - def getArgs() = opts.args + def getRawArgs() = _opts.rawArgs + def getArgs() = _opts.args /** These methods can be overridden to modify application behavior. */ @@ -212,20 +212,18 @@ trait Application } private var _opts: Options = null - lazy val opts = _opts - + private val argInfos = new HashSet[ArgInfo]() def callWithOptions(): Unit = { - import opts._ def missing(s: String) = usageError("Missing required option '%s'".format(s)) // verify minimum quantity of positional arguments - if (args.size < posArgCount) - usageError("too few arguments: expected %d, got %d".format(posArgCount, args.size)) + if (_opts.args.size < posArgCount) + usageError("too few arguments: expected %d, got %d".format(posArgCount, _opts.args.size)) // verify all required options are present - val missingArgs = reqArgs filter (x => !(options contains x.name) && !(x.name matches """^arg\d+$""")) + val missingArgs = reqArgs filter (x => !(_opts.options contains x.name) && !(x.name matches """^arg\d+$""")) if (!missingArgs.isEmpty) { val missingStr = missingArgs map ("--" + _.name) mkString " " val s = if (missingArgs.size == 1) "" else "s" @@ -235,15 +233,15 @@ trait Application def determineValue(ma: MainArg): AnyRef = { val MainArg(name, _, tpe) = ma - def isPresent = options contains name + def isPresent = _opts.options contains name - if (ma.isPositional) coerceTo(name, tpe)(args(ma.pos - 1)) - else if (isPresent) coerceTo(name, tpe)(options(name)) + if (ma.isPositional) coerceTo(name, tpe)(_opts.args(ma.pos - 1)) + else if (isPresent) coerceTo(name, tpe)(_opts.options(name)) else if (ma.isBoolean) jl.Boolean.FALSE else if (ma.isOptional) None else missing(name) } - + mainMethod.invoke(this, (mainArgs map determineValue).toArray : _*) } @@ -254,9 +252,10 @@ trait Application callWithOptions() } catch { - case UsageError(msg) => - println("Error: " + msg) + case ex:UsageError => + println("Error: " + ex.getMessage) println(usageMessage) + throw ex } } } diff --git a/src/test/scala/optional/ApplicationTest.scala b/src/test/scala/optional/ApplicationTest.scala new file mode 100644 index 0000000..fd43815 --- /dev/null +++ b/src/test/scala/optional/ApplicationTest.scala @@ -0,0 +1,39 @@ +package optional + +import org.scalatest.FunSuite +import org.scalatest.matchers.ShouldMatchers + +/** + * + */ + +class ApplicationTest extends FunSuite with ShouldMatchers { + + test("parseArgs") { + simpleArgs.main(Array("-a", "7", "-b", "18", "--longerName", "some string", "--lousyConflictingName", "another string")) + simpleArgs.args._1 should be (7) + simpleArgs.args._2 should be (18) + simpleArgs.args._3 should be ("some string") + simpleArgs.args._4 should be ("another string") + + simpleArgs.main(Array("--a", "9", "--b", "10", "--longerName", "abcde", "--lousyConflictingName", "efgh")) + simpleArgs.args._1 should be (9) + simpleArgs.args._2 should be (10) + simpleArgs.args._3 should be ("abcde") + simpleArgs.args._4 should be ("efgh") + + + evaluating { + simpleArgs.main(Array("-a", "90", "-b", "100", "-l", "xyz", "--lousyConflictingName", "pqr")) + } should produce [Exception] + } + +} + +object simpleArgs extends optional.Application { + var args : (Int, Int, String, String) = _ + def main(a: Int, b : Int, longerName: String, lousyConflictingName: String) { + args = (a, b, longerName, lousyConflictingName) + } +} + From 0c8aaac5887f3d01ac5e39ac63bde7aad210d7a2 Mon Sep 17 00:00:00 2001 From: Imran Rashid Date: Thu, 16 Aug 2012 14:15:20 -0700 Subject: [PATCH 02/50] remove reliance on old sbt & scala, setup sbt for testing --- project/Build.scala | 37 +++++++++++++++++++++++++++++++++++++ project/build.properties | 2 -- 2 files changed, 37 insertions(+), 2 deletions(-) create mode 100644 project/Build.scala diff --git a/project/Build.scala b/project/Build.scala new file mode 100644 index 0000000..3c99d81 --- /dev/null +++ b/project/Build.scala @@ -0,0 +1,37 @@ +import sbt._ +import Keys._ + +object SparkBuild extends Build { + lazy val core = Project("core", file("."), settings = coreSettings) + + def sharedSettings = Defaults.defaultSettings ++ Seq( + version := "0.1", + scalaVersion := "2.9.1", + scalacOptions := Seq(/*"-deprecation",*/ "-unchecked", "-optimize"), // -deprecation is too noisy due to usage of old Hadoop API, enable it once that's no longer an issue + unmanagedJars in Compile <<= baseDirectory map { base => (base / "lib" ** "*.jar").classpath }, + retrieveManaged := true, + transitiveClassifiers in Scope.GlobalScope := Seq("sources"), + publishTo <<= baseDirectory { base => Some(Resolver.file("Local", base / "target" / "maven" asFile)(Patterns(true, Resolver.mavenStyleBasePattern))) }, + libraryDependencies ++= Seq( + "org.eclipse.jetty" % "jetty-server" % "7.5.3.v20111011", + "org.scalatest" %% "scalatest" % "1.6.1" % "test", + "org.scalacheck" %% "scalacheck" % "1.9" % "test" + ) + ) + + val slf4jVersion = "1.6.1" + + def coreSettings = sharedSettings ++ Seq( + name := "sblaj-core", + resolvers ++= Seq( + "Typesafe Repository" at "http://repo.typesafe.com/typesafe/releases/", + "JBoss Repository" at "http://repository.jboss.org/nexus/content/repositories/releases/" + ), + libraryDependencies ++= Seq( + "log4j" % "log4j" % "1.2.16", + "org.slf4j" % "slf4j-api" % slf4jVersion, + "org.slf4j" % "slf4j-log4j12" % slf4jVersion + ) + ) + +} diff --git a/project/build.properties b/project/build.properties index 17f8f34..27a69f9 100644 --- a/project/build.properties +++ b/project/build.properties @@ -2,6 +2,4 @@ #Wed Aug 05 14:26:51 PDT 2009 project.organization=empty project.name=optional -sbt.version=0.5.2 project.version=0.1 -scala.version=2.7.5 From 6af8d7a2516fd54f0b0715e336ccbcae2bb465dc Mon Sep 17 00:00:00 2001 From: Imran Rashid Date: Thu, 16 Aug 2012 14:17:08 -0700 Subject: [PATCH 03/50] update .gitignore --- .gitignore | 19 ++++++++++++++++--- 1 file changed, 16 insertions(+), 3 deletions(-) diff --git a/.gitignore b/.gitignore index becaacf..e11bc91 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,17 @@ +*.class +*.log +build + +# sbt specific +dist/* +target/ +lib_managed/ +src_managed/ +project/boot/ +project/plugins/project/ + +# Scala-IDE specific +.scala_dependencies +.idea *.iml -*.ipr -*.iws -target-* + From d533f6b45632ea1a99262b5a509ffe1aecba09ca Mon Sep 17 00:00:00 2001 From: Imran Rashid Date: Sun, 19 Aug 2012 22:50:22 -0700 Subject: [PATCH 04/50] work in progress. mostly discovering how broken the existing package is, adding test cases --- src/main/scala/application.scala | 6 +- src/main/scala/options.scala | 20 +++ src/test/scala/optional/ApplicationTest.scala | 118 +++++++++++++++--- 3 files changed, 125 insertions(+), 19 deletions(-) diff --git a/src/main/scala/application.scala b/src/main/scala/application.scala index cd06b2a..f423495 100644 --- a/src/main/scala/application.scala +++ b/src/main/scala/application.scala @@ -192,7 +192,8 @@ trait Application def fail = designError("Could not create type '%s' from String".format(tpe)) def mismatch = usageError("option --%s expects arg of type '%s' but was given '%s'".format(name, stringForType(tpe), value)) def surprise = usageError("Unexpected type: %s (%s)".format(tpe, tpe.getClass)) - + + println("trying to coerce " + name + " to " + tpe) tpe match { case CString => value case CArrayString => value split separator @@ -216,6 +217,8 @@ trait Application private val argInfos = new HashSet[ArgInfo]() def callWithOptions(): Unit = { + println(parameterTypes) + def missing(s: String) = usageError("Missing required option '%s'".format(s)) // verify minimum quantity of positional arguments @@ -249,6 +252,7 @@ trait Application def main(cmdline: Array[String]) { try { _opts = Options.parse(argInfos, cmdline: _*) + println("_opts = " + _opts) callWithOptions() } catch { diff --git a/src/main/scala/options.scala b/src/main/scala/options.scala index ef59e8d..794a50a 100644 --- a/src/main/scala/options.scala +++ b/src/main/scala/options.scala @@ -71,3 +71,23 @@ object Options Options(options, arguments.toList, args.toList) } } + +object AutoArgInfo { + /** + * guess a short form of the arguments from the long names. + * + * + */ + def guessFromNames(names: Iterable[String]) : Seq[ArgInfo] = { + val usedLetters = mutable.Set[Char]() + names.map { n => + val l = n.charAt(0) + if (usedLetters.contains(l)) { + ArgInfo(l,n,false, null) //TODO + } + else { + ArgInfo(l, n, false, null) + } + }.toSeq + } +} diff --git a/src/test/scala/optional/ApplicationTest.scala b/src/test/scala/optional/ApplicationTest.scala index fd43815..2169bd4 100644 --- a/src/test/scala/optional/ApplicationTest.scala +++ b/src/test/scala/optional/ApplicationTest.scala @@ -9,31 +9,113 @@ import org.scalatest.matchers.ShouldMatchers class ApplicationTest extends FunSuite with ShouldMatchers { - test("parseArgs") { - simpleArgs.main(Array("-a", "7", "-b", "18", "--longerName", "some string", "--lousyConflictingName", "another string")) - simpleArgs.args._1 should be (7) - simpleArgs.args._2 should be (18) - simpleArgs.args._3 should be ("some string") - simpleArgs.args._4 should be ("another string") - - simpleArgs.main(Array("--a", "9", "--b", "10", "--longerName", "abcde", "--lousyConflictingName", "efgh")) - simpleArgs.args._1 should be (9) - simpleArgs.args._2 should be (10) - simpleArgs.args._3 should be ("abcde") - simpleArgs.args._4 should be ("efgh") - - - evaluating { - simpleArgs.main(Array("-a", "90", "-b", "100", "-l", "xyz", "--lousyConflictingName", "pqr")) - } should produce [Exception] +// test("readme parse args") { +// //straight from the Readme +// MyAwesomeCommandLineTool.main(Array("--count", "5", "quux")) +// MyAwesomeCommandLineTool.args._1 should be (Some(5)) +// MyAwesomeCommandLineTool.args._2 should be (None) +// MyAwesomeCommandLineTool.args._3 should be ("quux") +// } +// +// test("longNames"){ +// easy.main(Array("--aOne", "1", "--bTwo", "2", "--myDouble", "2.9", "--floatOptional", "2.3")) +// easy.args._1 should be (1) +// easy.args._2 should be (2) +// easy.args._3 should be (2.9) +// easy.args._4 should be (Some(2.3f)) +// } +// +// test("conflicting short names"){ +// +// conflictingNames.main(Array("-a", "7", "-b", "18", "--longerName", "some string", "--lousyConflictingName", "another string")) +// conflictingNames.args._1 should be (7) +// conflictingNames.args._2 should be (18) +// conflictingNames.args._3 should be ("some string") +// conflictingNames.args._4 should be ("another string") +// +// conflictingNames.main(Array("--a", "9", "--b", "10", "--longerName", "abcde", "--lousyConflictingName", "efgh")) +// conflictingNames.args._1 should be (9) +// conflictingNames.args._2 should be (10) +// conflictingNames.args._3 should be ("abcde") +// conflictingNames.args._4 should be ("efgh") +// +// +// evaluating { +// conflictingNames.main(Array("-a", "90", "-b", "100", "-l", "xyz", "--lousyConflictingName", "pqr")) +// } should produce [Exception] +// } + + + test("register single char args") { + registeredSingleChars.main(Array("-c", "5", "-d", "7.2")) + registeredSingleChars.args._1 should be (5) + registeredSingleChars.args._2 should be (7.2) } +// +// test("short param from long variables") { +// easy.main(Array("-a", "1", "-b", "2", "-m", "2.9", "-f", "2.3")) +// easy.args._1 should be (1) +// easy.args._2 should be (2) +// easy.args._3 should be (2.9) +// easy.args._4 should be (Some(2.3f)) +// } +// +// +// //won't work as long as we use java reflection! +// test("optional primitives") { +// primitiveOptions.main(Array("--count", "5", "--double", "7.6")) +// primitiveOptions.args._1 should be (Some(5)) +// primitiveOptions.args._2 should be (Some(7.6)) +// +// primitiveOptions.main(Array("--count", "1")) +// primitiveOptions.args._1 should be (Some(1)) +// primitiveOptions.args._2 should be (None) +// +// primitiveOptions.main(Array("--double", "13.9")) +// primitiveOptions.args._1 should be (None) +// primitiveOptions.args._2 should be (Some(13.9)) +// +// primitiveOptions.main(Array()) +// primitiveOptions.args._1 should be (None) +// primitiveOptions.args._2 should be (None) +// } } -object simpleArgs extends optional.Application { +object MyAwesomeCommandLineTool extends optional.Application { + //unfortunately, b/c this uses the java reflection api, it doesn't support Option for primitives + var args: (Option[java.lang.Integer], Option[java.io.File], String) = _ + def main(count: Option[java.lang.Integer], file: Option[java.io.File], arg1: String) { + args = (count, file, arg1) + } +} + +object easy extends optional.Application { + var args : (Int, Int, Double, Option[java.lang.Float]) = _ + def main(aOne: Int, bTwo: Int, myDouble: Double, floatOptional: Option[java.lang.Float]) { + args = (aOne, bTwo, myDouble, floatOptional) + } +} + +object conflictingNames extends optional.Application { var args : (Int, Int, String, String) = _ def main(a: Int, b : Int, longerName: String, lousyConflictingName: String) { args = (a, b, longerName, lousyConflictingName) } } +object primitiveOptions extends optional.Application { + var args: (Option[Int], Option[Double]) = _ + def main(count: Option[Int], double: Option[Double]) { + args = (count, double) + } +} + +object registeredSingleChars extends optional.Application { + var args: (Option[Int], Option[Double]) = _ + register(ArgInfo('c', "count", false, "the count"), ArgInfo('d', "double", false, "a double")) + def main(count: Option[Int], double: Option[Double]) { + args = (count, double) + } + +} \ No newline at end of file From 725b7b09d866982405363d9a1a1872235b96cc4c Mon Sep 17 00:00:00 2001 From: Imran Rashid Date: Tue, 2 Oct 2012 22:23:37 -0700 Subject: [PATCH 05/50] pretty much a total rewrite ... still in progress though --- src/main/scala/ArgumentParser.scala | 42 +++++++++ src/main/scala/ObjectToArgs.scala | 29 ++++++ src/main/scala/Parser.scala | 93 +++++++++++++++++++ src/main/scala/ReflectionUtils.scala | 23 +++++ src/main/scala/application.scala | 2 + .../scala/optional/ArgumentParserTest.scala | 40 ++++++++ .../scala/optional/ObjectToArgsTest.scala | 51 ++++++++++ src/test/scala/optional/ParserTest.scala | 40 ++++++++ 8 files changed, 320 insertions(+) create mode 100644 src/main/scala/ArgumentParser.scala create mode 100644 src/main/scala/ObjectToArgs.scala create mode 100644 src/main/scala/Parser.scala create mode 100644 src/main/scala/ReflectionUtils.scala create mode 100644 src/test/scala/optional/ArgumentParserTest.scala create mode 100644 src/test/scala/optional/ObjectToArgsTest.scala create mode 100644 src/test/scala/optional/ParserTest.scala diff --git a/src/main/scala/ArgumentParser.scala b/src/main/scala/ArgumentParser.scala new file mode 100644 index 0000000..b087994 --- /dev/null +++ b/src/main/scala/ArgumentParser.scala @@ -0,0 +1,42 @@ +package optional + +import java.lang.reflect.{Type,Field} +import scala.collection._ + +class ArgumentParser[T <: ArgAssignable](val argHolders: Seq[T]) { + + val nameToHolder = argHolders.map{h => h.getName -> h}.toMap + + def parse(args: Seq[String]) : Map[T, ValueHolder[_]] = { + parse(args.toArray) + } + + def parse(args: Array[String]) : Map[T, ValueHolder[_]] = { + var idx = 0 + val result = mutable.Map[T, ValueHolder[_]]() + while (idx < args.length) { + val arg : String = args(idx) + val argName = + if (arg.startsWith("--")) + arg.substring(2) + else + throw new RuntimeException("arg at position " + idx + " does not begin w/ --") + val holder = nameToHolder.get(argName) + if (holder.isEmpty) + throw new RuntimeException("no place to put argument with name " + argName) + result(holder.get) = ParseHelper.parseInto(args(idx + 1), holder.get.getType).get + idx += 2 + } + result + } +} + +trait ArgAssignable { + def getName: String + def getType: Type +} + +class FieldArgAssignable(val field: Field) extends ArgAssignable { + def getName = field.getName + def getType = field.getGenericType +} \ No newline at end of file diff --git a/src/main/scala/ObjectToArgs.scala b/src/main/scala/ObjectToArgs.scala new file mode 100644 index 0000000..326dd05 --- /dev/null +++ b/src/main/scala/ObjectToArgs.scala @@ -0,0 +1,29 @@ +package optional + +/** + * + */ + +class ObjectToArgs(val obj: Object) { + val argParser = new ArgumentParser[FieldArgAssignable]( + ReflectionUtils.getAllDeclaredFields(obj.getClass).map{f => new FieldArgAssignable(f)} + ) + + def parse(args: Array[String]) { + println(argParser.nameToHolder) + val parsed = argParser.parse(args) + parsed.foreach{ + kv => + val field = kv._1.field + field.setAccessible(true) + field.set(obj, kv._2.value) + } + } +} + +trait FieldParsing { + lazy val parser = new ObjectToArgs(this) + def parse(args: Array[String]) { + parser.parse(args) + } +} \ No newline at end of file diff --git a/src/main/scala/Parser.scala b/src/main/scala/Parser.scala new file mode 100644 index 0000000..96b7103 --- /dev/null +++ b/src/main/scala/Parser.scala @@ -0,0 +1,93 @@ +package optional + +import java.lang.reflect.{Type, ParameterizedType} + +trait Parser[T] { + def parse(s: String, tpe: Type): T + + /** + * return true if this parser knows how to parse the given type + * @param tpe + * @return + */ + def canParse(tpe: Type): Boolean +} + +trait SimpleParser[T] extends Parser[T] { + def getKnownTypes() : Set[Class[_]] + def canParse(tpe: Type) = { + if (tpe.isInstanceOf[Class[_]]) + getKnownTypes()(tpe.asInstanceOf[Class[_]]) + else + false + } + def parse(s: String, tpe:Type) = parse(s) + def parse(s:String) :T +} + +trait CompoundParser[T] extends Parser[T] + + +object StringParser extends SimpleParser[String] { + val knownTypes : Set[Class[_]] = Set(classOf[String]) + def getKnownTypes() = knownTypes + def parse(s:String) = s +} + +object IntParser extends SimpleParser[Int] { + val knownTypes : Set[Class[_]] = Set(classOf[Int], classOf[java.lang.Integer]) + def getKnownTypes() = knownTypes + def parse(s: String) = s.toInt +} + +object DoubleParser extends SimpleParser[Double] { + val knownTypes : Set[Class[_]] = Set(classOf[Double], classOf[java.lang.Double]) + def getKnownTypes() = knownTypes + def parse(s: String) = s.toDouble +} + +object ListParser extends CompoundParser[List[_]] { + + def canParse(tpe: Type) = { + val clz = if (tpe.isInstanceOf[Class[_]]) + tpe.asInstanceOf[Class[_]] + else if (tpe.isInstanceOf[ParameterizedType]) + tpe.asInstanceOf[ParameterizedType].getRawType.asInstanceOf[Class[_]] + else + classOf[Int] //just need something that won't match + classOf[List[_]].isAssignableFrom(clz) + } + + def parse(s: String, tpe: Type) = { + if (tpe.isInstanceOf[ParameterizedType]) { + val ptpe = tpe.asInstanceOf[ParameterizedType] + val subtype = ptpe.getActualTypeArguments()(0) + val subParser = ParseHelper.findParser(subtype).get //TODO need to handle cases where its a list, but can't parse subtype + val parts = s.split(",") + parts.map{sub => subParser.parse(sub, subtype)}.toList + } + else + List() + } + +} + + +object ParseHelper { + val parsers = Seq(StringParser, IntParser, DoubleParser, ListParser) + + def findParser(tpe: Type) : Option[Parser[_]] = { + for (p <- parsers) { + if (p.canParse(tpe)) + return Some(p) + } + None + } + + def parseInto[T](s: String, tpe: Type) : Option[ValueHolder[T]] = { + //could change this to be a map, at least for the simple types + findParser(tpe).map{parser => ValueHolder[T](parser.parse(s, tpe).asInstanceOf[T], tpe)} + } +} + +case class ValueHolder[T](value: T, tpe: Type) \ No newline at end of file diff --git a/src/main/scala/ReflectionUtils.scala b/src/main/scala/ReflectionUtils.scala new file mode 100644 index 0000000..d6af141 --- /dev/null +++ b/src/main/scala/ReflectionUtils.scala @@ -0,0 +1,23 @@ +package optional + +import scala.collection._ +import java.lang.reflect.Field + +/** + * + */ + +object ReflectionUtils { + def getAllDeclaredFields(cls: Class[_]) : mutable.Buffer[Field]= { + val fields = mutable.Buffer[Field]() + getAllDeclaredFields(cls, fields) + fields + } + + def getAllDeclaredFields(cls: Class[_], holder: mutable.Buffer[Field]) : Unit = { + holder ++= cls.getDeclaredFields + val superCls = cls.getSuperclass + if (superCls != null) + getAllDeclaredFields(superCls, holder) + } +} diff --git a/src/main/scala/application.scala b/src/main/scala/application.scala index f423495..cb7a03f 100644 --- a/src/main/scala/application.scala +++ b/src/main/scala/application.scala @@ -263,3 +263,5 @@ trait Application } } } + +case class OneArg(val longName: String, val shortName: Char, val isSwitch: Boolean) diff --git a/src/test/scala/optional/ArgumentParserTest.scala b/src/test/scala/optional/ArgumentParserTest.scala new file mode 100644 index 0000000..bf1acde --- /dev/null +++ b/src/test/scala/optional/ArgumentParserTest.scala @@ -0,0 +1,40 @@ +package optional + +import org.scalatest.FunSuite +import org.scalatest.matchers.ShouldMatchers +import scala.collection._ + +class ArgumentParserTest extends FunSuite with ShouldMatchers { + + test("parse") { + val fieldArgs = classOf[SimpleClass].getDeclaredFields.map{f => new FieldArgAssignable(f)} + val argParser = new ArgumentParser(fieldArgs) + + { + val parsed = getSimpleNameToArgMap(argParser.parse(Array("--name", "foo"))) + parsed.size should be (1) + parsed should contain key ("name") + parsed("name") should be ("foo") + } + + + { + val parsed = getSimpleNameToArgMap(argParser.parse(Array("--count", "5", "--dummy", "7.4e3", "--name", "ooga"))) + parsed.size should be (3) + parsed should contain key ("count") + parsed("count") should be (5) + parsed should contain key ("dummy") + parsed("dummy") should be (7.4e3) + parsed should contain key ("name") + parsed("name") should be ("ooga") + } + } + + + def getSimpleNameToArgMap(parsedArgs : Map[_ <: ArgAssignable, ValueHolder[_]]) = { + parsedArgs.map{kv => kv._1.getName -> kv._2.value}.toMap[String, Any] + } +} + + +case class SimpleClass(val name: String, val count: Int, val dummy: Double, val count2: Int) \ No newline at end of file diff --git a/src/test/scala/optional/ObjectToArgsTest.scala b/src/test/scala/optional/ObjectToArgsTest.scala new file mode 100644 index 0000000..492bca9 --- /dev/null +++ b/src/test/scala/optional/ObjectToArgsTest.scala @@ -0,0 +1,51 @@ +package optional + +import org.scalatest.FunSuite +import org.scalatest.matchers.ShouldMatchers + +/** + * + */ + +class ObjectToArgsTest extends FunSuite with ShouldMatchers { + + test("parseStrings") { + val o = new StringHolder(null, null) + val parser = new ObjectToArgs(o) + parser.parse(Array("--name", "hello")) + o.name should be ("hello") + parser.parse(Array("--comment", "blah di blah blah")) + o.name should be ("hello") + o.comment should be ("blah di blah blah") + parser.parse(Array("--name", "ooga", "--comment", "stuff")) + o.name should be ("ooga") + o.comment should be ("stuff") + } + + test("parseMixed") { + val o = new MixedTypes(null, 0) + + val parser = new ObjectToArgs(o) + + parser.parse(Array("--name", "foo", "--count", "17")) + o.name should be ("foo") + o.count should be (17) + parser.parse(Array("--count", "-5")) + o.name should be ("foo") + o.count should be (-5) + } + + test("field parsing") { + val o = new MixedTypes(null, 0) with FieldParsing + + o.parse(Array("--count", "981", "--name", "wakkawakka")) + o.name should be ("wakkawakka") + o.count should be (981) + } + +} + + +case class StringHolder(val name: String, val comment: String) + +case class MixedTypes(val name: String, val count: Int) \ No newline at end of file diff --git a/src/test/scala/optional/ParserTest.scala b/src/test/scala/optional/ParserTest.scala new file mode 100644 index 0000000..6fcd42d --- /dev/null +++ b/src/test/scala/optional/ParserTest.scala @@ -0,0 +1,40 @@ +package optional + +import org.scalatest.FunSuite +import org.scalatest.matchers.ShouldMatchers + +/** + * + */ + +class ParserTest extends FunSuite with ShouldMatchers { + + test("SimpleParser") { + StringParser.parse("ooga") should be ("ooga") + IntParser.parse("5") should be (5) + DoubleParser.parse("5") should be (5.0) + DoubleParser.parse("1e-10") should be (1e-10) + } + + test("ListParser") { + //Note this doesn't work w/ primitive types now, b/c its based on java reflection + + //Is there is better way to get a handle on parameterized types???? + val field = classOf[ContainerA].getDeclaredField("boundaries") + val parsed = ParseHelper.parseInto("a,b,cdef,g", field.getGenericType) + parsed should be (Some(ValueHolder(List("a", "b", "cdef", "g"), field.getGenericType))) + } + + test("ParseHelper") { + ParseHelper.parseInto("ooga", classOf[String]) should be (Some(ValueHolder("ooga", classOf[String]))) + ParseHelper.parseInto("5.6", classOf[Double]) should be (Some(ValueHolder(5.6, classOf[Double]))) + ParseHelper.parseInto("5.6", classOf[String]) should be (Some(ValueHolder("5.6", classOf[String]))) + ParseHelper.parseInto("abc", classOf[RandomUnknownClass]) should be (None) + } + +} + +class RandomUnknownClass + + +class ContainerA(val title: String, val count: Int, val boundaries: List[String]) From a839de6fe8d83e4fca0bd12e6f1f5be4886b6118 Mon Sep 17 00:00:00 2001 From: Imran Rashid Date: Wed, 3 Oct 2012 07:43:49 -0700 Subject: [PATCH 06/50] get rid of old optional code --- README | 30 ++- mkCommand | 1 - src/main/scala/ArgumentParser.scala | 42 ---- src/main/scala/application.scala | 267 ------------------------ src/main/scala/examples/erroneous.scala | 7 - src/main/scala/examples/fancy.scala | 44 ---- src/main/scala/examples/hello.scala | 19 -- src/main/scala/examples/sgrep.scala | 26 --- src/main/scala/mkCommand.scala | 49 ----- src/main/scala/options.scala | 93 --------- 10 files changed, 14 insertions(+), 564 deletions(-) delete mode 100755 mkCommand delete mode 100644 src/main/scala/ArgumentParser.scala delete mode 100644 src/main/scala/application.scala delete mode 100644 src/main/scala/examples/erroneous.scala delete mode 100644 src/main/scala/examples/fancy.scala delete mode 100644 src/main/scala/examples/hello.scala delete mode 100644 src/main/scala/examples/sgrep.scala delete mode 100644 src/main/scala/mkCommand.scala delete mode 100644 src/main/scala/options.scala diff --git a/README b/README index 2cda52a..56eaabc 100644 --- a/README +++ b/README @@ -1,25 +1,20 @@ optional is a command line option parser and library. -YOU WRITE: +#### Example -object MyAwesomeCommandLineTool extends optional.Application { - // for instance... - def main(count: Option[Int], file: Option[java.io.File], arg1: String) { - [...] +case class Arguments(val name: String, val count: Int) + +``` +object MyApp { + def main(args: Array[String]) { + val myArgs = new Arguments(null,0) with FieldParsing + myArgs.parse(args) + ... } } +``` -THEN YOU DO: - - scala MyAwesomeCommandLineTool --count 5 quux - -AND YOUR MAIN METHOD WILL BE INVOKED SUCH THAT: - - count = Some(5) - file = None - arg1 = quux - -See the example programs for many more features. +See the test cases for more examples HOW IT WORKS: @@ -27,6 +22,9 @@ HOW IT WORKS: CREDITS: +This was inspired by the optional package from alexy, which in turn came from: + Idea and prototype implementation: DRMacIver. Fleshing out and awesomification: paulp. +This is a total rewrite, though. \ No newline at end of file diff --git a/mkCommand b/mkCommand deleted file mode 100755 index 5d9b4e2..0000000 --- a/mkCommand +++ /dev/null @@ -1 +0,0 @@ -/scala/inst/27/bin/scala -cp 'lib/*' optional.MakeCommand $* diff --git a/src/main/scala/ArgumentParser.scala b/src/main/scala/ArgumentParser.scala deleted file mode 100644 index b087994..0000000 --- a/src/main/scala/ArgumentParser.scala +++ /dev/null @@ -1,42 +0,0 @@ -package optional - -import java.lang.reflect.{Type,Field} -import scala.collection._ - -class ArgumentParser[T <: ArgAssignable](val argHolders: Seq[T]) { - - val nameToHolder = argHolders.map{h => h.getName -> h}.toMap - - def parse(args: Seq[String]) : Map[T, ValueHolder[_]] = { - parse(args.toArray) - } - - def parse(args: Array[String]) : Map[T, ValueHolder[_]] = { - var idx = 0 - val result = mutable.Map[T, ValueHolder[_]]() - while (idx < args.length) { - val arg : String = args(idx) - val argName = - if (arg.startsWith("--")) - arg.substring(2) - else - throw new RuntimeException("arg at position " + idx + " does not begin w/ --") - val holder = nameToHolder.get(argName) - if (holder.isEmpty) - throw new RuntimeException("no place to put argument with name " + argName) - result(holder.get) = ParseHelper.parseInto(args(idx + 1), holder.get.getType).get - idx += 2 - } - result - } -} - -trait ArgAssignable { - def getName: String - def getType: Type -} - -class FieldArgAssignable(val field: Field) extends ArgAssignable { - def getName = field.getName - def getType = field.getGenericType -} \ No newline at end of file diff --git a/src/main/scala/application.scala b/src/main/scala/application.scala deleted file mode 100644 index cb7a03f..0000000 --- a/src/main/scala/application.scala +++ /dev/null @@ -1,267 +0,0 @@ -package optional - -import com.thoughtworks.paranamer.BytecodeReadingParanamer -import java.io.File.separator -import java.{ lang => jl } -import java.lang.{ Class => JClass } -import jl.reflect.{ Array => _, _ } -import collection.mutable.HashSet - -case class DesignError(msg: String) extends Error(msg) -case class UsageError(msg: String) extends RuntimeException(msg) - -object Util -{ - val CString = classOf[String] - val CInteger = classOf[jl.Integer] - val CBool = classOf[scala.Boolean] - val CBoolean = classOf[jl.Boolean] - val CArrayString = classOf[Array[String]] - - val Argument = """^arg(\d+)$""".r - - def cond[T](x: T)(f: PartialFunction[T, Boolean]) = - (f isDefinedAt x) && f(x) - def condOpt[T,U](x: T)(f: PartialFunction[T, U]): Option[U] = - if (f isDefinedAt x) Some(f(x)) else None - - def stringForType(tpe: Type): String = - if (tpe == classOf[Int] || tpe == classOf[jl.Integer]) "Int" - else if (tpe == classOf[Long] || tpe == classOf[jl.Long]) "Long" - else if (tpe == classOf[Short] || tpe == classOf[jl.Short]) "Short" - else if (tpe == classOf[Byte] || tpe == classOf[jl.Byte]) "Byte" - else if (tpe == classOf[Float] || tpe == classOf[jl.Float]) "Float" - else if (tpe == classOf[Double] || tpe == classOf[jl.Double]) "Double" - else if (tpe == classOf[Char] || tpe == classOf[jl.Character]) "Char" - else if (tpe == CString) "String" - else tpe match { - case x: Class[_] => x.getName() - case x => x.toString() - } -} -import Util._ - -private object OptionType { - def unapply(x: Any) = condOpt(x) { - case x: ParameterizedType if x.getRawType == classOf[Option[_]] => x.getActualTypeArguments()(0) - } -} - -object MainArg -{ - def apply(name: String, tpe: Type): MainArg = tpe match { - case CBool | CBoolean => BoolArg(name) - case OptionType(t) => OptArg(name, t, tpe) - case _ => - name match { - case Argument(num) => PosArg(name, tpe, num.toInt) - case _ => ReqArg(name, tpe) - } - } - - def unapply(x: Any): Option[(String, Type, Type)] = x match { - case OptArg(name, tpe, originalType) => Some(name, tpe, originalType) - case BoolArg(name) => Some(name, CBoolean, CBoolean) - case ReqArg(name, tpe) => Some(name, tpe, tpe) - case PosArg(name, tpe, num) => Some(name, tpe, tpe) - } -} - -sealed abstract class MainArg { - def name: String - def tpe: Type - def originalType: Type - def isOptional: Boolean - def usage: String - - def pos: Int = -1 - def isPositional = pos > -1 - def isBoolean = false -} -case class OptArg(name: String, tpe: Type, originalType: Type) extends MainArg { - val isOptional = true - def usage = "[--%s %s]".format(name, stringForType(tpe)) -} -case class ReqArg(name: String, tpe: Type) extends MainArg { - val originalType = tpe - val isOptional = false - def usage = "<%s: %s>".format(name, stringForType(tpe)) -} -case class PosArg(name: String, tpe: Type, override val pos: Int) extends MainArg { - val originalType = tpe - val isOptional = false - def usage = "<%s>".format(stringForType(tpe)) -} -case class BoolArg(name: String) extends MainArg { - override def isBoolean = true - val tpe, originalType = CBoolean - val isOptional = true - def usage = "[--%s]".format(name) -} - -/** - * This trait automagically finds a main method on the object - * which mixes this in and based on method names and types figures - * out the options it should be called with and takes care of parameter parsing - */ -trait Application -{ - /** Public methods. - */ - def getRawArgs() = _opts.rawArgs - def getArgs() = _opts.args - - /** These methods can be overridden to modify application behavior. - */ - - /** Override this if you want to restrict the search space of conversion methods. */ - protected def isConversionMethod(m: Method) = true - - /** The autogenerated usage message will usually suffice. */ - protected def programName = "program" - protected def usageMessage = "Usage: %s %s".format(programName, mainArgs map (_.usage) mkString " ") - - /** If you need to set up more argument info */ - protected def register(xs: ArgInfo*) { xs foreach (argInfos += _) } - - /** If you mess with anything from here on down, you're on your own. - */ - - private def methods(f: Method => Boolean): List[Method] = getClass.getMethods.toList filter f - private def signature(m: Method) = m.toGenericString.replaceAll("""\S+\.main\(""", "main(") // )) - private def designError(msg: String) = throw DesignError(msg) - private def usageError(msg: String) = throw UsageError(msg) - - private def isRealMain(m: Method) = cond(m.getParameterTypes) { case Array(CArrayString) => true } - private def isEligibleMain(m: Method) = m.getName == "main" && !isRealMain(m) - private lazy val mainMethod = methods(isEligibleMain) match { - case Nil => designError("No eligible main method found") - case List(x) => x - case xs => - designError("You seem to have multiple main methods, signatures:\n%s" . - format(xs map signature mkString "\n") - ) - } - - private lazy val parameterTypes = mainMethod.getGenericParameterTypes.toList - private lazy val argumentNames = (new BytecodeReadingParanamer lookupParameterNames mainMethod map (_.replaceAll("\\$.+", ""))).toList - private lazy val mainArgs = List.map2(argumentNames, parameterTypes)(MainArg(_, _)) - private lazy val reqArgs = mainArgs filter (x => !x.isOptional) - private def posArgCount = mainArgs filter (_.isPositional) size - - def getAnyValBoxedClass(x: JClass[_]): JClass[_] = - if (x == classOf[Byte]) classOf[jl.Byte] - else if (x == classOf[Short]) classOf[jl.Short] - else if (x == classOf[Int]) classOf[jl.Integer] - else if (x == classOf[Long]) classOf[jl.Long] - else if (x == classOf[Float]) classOf[jl.Float] - else if (x == classOf[Double]) classOf[jl.Double] - else if (x == classOf[Char]) classOf[jl.Character] - else if (x == classOf[Boolean]) classOf[jl.Boolean] - else if (x == classOf[Unit]) classOf[Unit] - else throw new Exception("Not an AnyVal: " + x) - - private val primitives = List( - classOf[Byte], classOf[Short], classOf[Int], classOf[Long], - classOf[Float], classOf[Double] // , classOf[Char], classOf[Boolean] - ) - - private val valueOfMap = { - def m(clazz: JClass[_]) = getAnyValBoxedClass(clazz).getMethod("valueOf", CString) - val xs1: List[(JClass[_], Method)] = primitives zip (primitives map m) - val xs2: List[(JClass[_], Method)] = for (clazz <- (primitives map getAnyValBoxedClass)) yield (clazz, clazz.getMethod("valueOf", CString)) - - Map[JClass[_], Method](xs1 ::: xs2 : _*) - // Map[JClass[_], Method](primitives zip (primitives map m) : _*) - } - - def getConv(tpe: Type, value: String): Option[AnyRef] = { - def isConv(m: Method) = isConversionMethod(m) && !(m.getName contains "$") - - methods(isConv) find (_.getGenericReturnType == tpe) map (_.invoke(this, value)) - } - - def getNumber(clazz: Class[_], value: String): Option[AnyRef] = - try { (valueOfMap get clazz) map (_.invoke(null, value)) } - catch { case _: InvocationTargetException => None } - - /** - * Magic method to take a string and turn it into something of a given type. - */ - private def coerceTo(name: String, tpe: Type)(value: String): AnyRef = { - def fail = designError("Could not create type '%s' from String".format(tpe)) - def mismatch = usageError("option --%s expects arg of type '%s' but was given '%s'".format(name, stringForType(tpe), value)) - def surprise = usageError("Unexpected type: %s (%s)".format(tpe, tpe.getClass)) - - println("trying to coerce " + name + " to " + tpe) - tpe match { - case CString => value - case CArrayString => value split separator - case OptionType(t) => Some(coerceTo(name, t)(value)) - case clazz: Class[_] => - if (valueOfMap contains clazz) - getNumber(clazz, value) getOrElse mismatch - else - getConv(clazz, value) getOrElse { - try { clazz.getConstructor(CString).newInstance(value).asInstanceOf[AnyRef] } - catch { case x: NoSuchMethodException => fail } - } - - case x: ParameterizedType => getConv(x, value) getOrElse fail - case x => surprise - } - } - - private var _opts: Options = null - - private val argInfos = new HashSet[ArgInfo]() - - def callWithOptions(): Unit = { - println(parameterTypes) - - def missing(s: String) = usageError("Missing required option '%s'".format(s)) - - // verify minimum quantity of positional arguments - if (_opts.args.size < posArgCount) - usageError("too few arguments: expected %d, got %d".format(posArgCount, _opts.args.size)) - - // verify all required options are present - val missingArgs = reqArgs filter (x => !(_opts.options contains x.name) && !(x.name matches """^arg\d+$""")) - if (!missingArgs.isEmpty) { - val missingStr = missingArgs map ("--" + _.name) mkString " " - val s = if (missingArgs.size == 1) "" else "s" - - usageError("missing required option%s: %s".format(s, missingStr)) - } - - def determineValue(ma: MainArg): AnyRef = { - val MainArg(name, _, tpe) = ma - def isPresent = _opts.options contains name - - if (ma.isPositional) coerceTo(name, tpe)(_opts.args(ma.pos - 1)) - else if (isPresent) coerceTo(name, tpe)(_opts.options(name)) - else if (ma.isBoolean) jl.Boolean.FALSE - else if (ma.isOptional) None - else missing(name) - } - - mainMethod.invoke(this, (mainArgs map determineValue).toArray : _*) - } - - - def main(cmdline: Array[String]) { - try { - _opts = Options.parse(argInfos, cmdline: _*) - println("_opts = " + _opts) - callWithOptions() - } - catch { - case ex:UsageError => - println("Error: " + ex.getMessage) - println(usageMessage) - throw ex - } - } -} - -case class OneArg(val longName: String, val shortName: Char, val isSwitch: Boolean) diff --git a/src/main/scala/examples/erroneous.scala b/src/main/scala/examples/erroneous.scala deleted file mode 100644 index 04a8f87..0000000 --- a/src/main/scala/examples/erroneous.scala +++ /dev/null @@ -1,7 +0,0 @@ -package optional.examples - -object Erroneous extends optional.Application -{ - def main(times: Option[Int], greeting: Option[String], file: java.io.File) { } - def main(dingle: Option[Int]) {} -} diff --git a/src/main/scala/examples/fancy.scala b/src/main/scala/examples/fancy.scala deleted file mode 100644 index cdb74db..0000000 --- a/src/main/scala/examples/fancy.scala +++ /dev/null @@ -1,44 +0,0 @@ -package optional.examples - -import java.io.File -import java.lang.reflect - -// -// A fancier example showing some other features. -// - -// Any class can be used as a parameter type if it has a one -// argument String constructor, like this one does. -class Whatever(x: String) { - override def toString() = "Whatever(" + x + ")" -} - -object Fancy extends optional.Application -{ - // Function1[String,T] methods in the class will be used to convert arguments - // to the desired type, unless isConversionMethod excludes them. - override def isConversionMethod(m: reflect.Method) = m.getName != "excludedConversionMethod" - - // this one will be ignored - def excludedConversionMethod(s: String): List[Int] = List(1,2,3) - - // these will be used - def arbitraryName(s: String): Set[Char] = Set(s : _*) - def anotherArbitraryName(s: String): List[Float] = List(1.1f, 2.2f) - - def main( - file: Option[File], - what: Option[Whatever], - chars: Set[Char], - arg1: Double) - { - // getRawArgs() returns the command line in its original form. - println(" Raw arguments: " + getRawArgs()) - - // getArgs() returns all the positional arguments (those not associated with an --option.) - println("Unprocessed args: " + getArgs()) - - // the conversion defined above was used to create 'chars' from the string argument - println("Set of chars == " + (chars mkString " ")) - } -} diff --git a/src/main/scala/examples/hello.scala b/src/main/scala/examples/hello.scala deleted file mode 100644 index 8f2c746..0000000 --- a/src/main/scala/examples/hello.scala +++ /dev/null @@ -1,19 +0,0 @@ -package optional.examples - -// A simple hello world with two optional arguments and one required argument. -// Try running it like so: -// -// scala optional.examples.Hello --times 2 --file . -// -object Hello extends optional.Application -{ - def main(times: Option[Int], greeting: Option[String], file: java.io.File) { - val _greeting = greeting getOrElse "hello" - val _times = times getOrElse 1 - - println( - List.fill(_times)(_greeting).mkString(", ") + - " and %s does %sexist.".format(file.getAbsolutePath, (if (file.exists) "" else "not ")) - ) - } -} diff --git a/src/main/scala/examples/sgrep.scala b/src/main/scala/examples/sgrep.scala deleted file mode 100644 index 7e4b03e..0000000 --- a/src/main/scala/examples/sgrep.scala +++ /dev/null @@ -1,26 +0,0 @@ -package optional.examples - -import scala.util.matching.Regex -import java.io.File -import optional.ArgInfo - -object sgrep extends optional.Application -{ - def mkRegexp(s: String): Regex = s.r - - register( - ArgInfo('v', "invert-match", true, "Invert the sense of matching, to select non-matching lines."), - ArgInfo('i', "ignore-case", true, "Ignore case distinctions in both the PATTERN and the input files.") - ) - - def main(v: Boolean, i: Boolean, arg1: Regex, arg2: File) { - // reverse condition if -v is given - def cond(x: Option[_]) = if (v) x.isEmpty else x.isDefined - // case insensitive if -i is given - val regex = if (i) ("""(?i)""" + arg1.toString).r else arg1 - - for (line <- io.Source.fromFile(arg2).getLines() - ; if cond(regex findFirstIn line)) - print(line) - } -} diff --git a/src/main/scala/mkCommand.scala b/src/main/scala/mkCommand.scala deleted file mode 100644 index 7d0def4..0000000 --- a/src/main/scala/mkCommand.scala +++ /dev/null @@ -1,49 +0,0 @@ -package optional - -object MakeCommand -{ - val template = """ -_%s() -{ - local cur prev opts - COMPREPLY=() - cur="${COMP_WORDS[COMP_CWORD]}" - prev="${COMP_WORDS[COMP_CWORD-1]}" - opts="%s" - - if [[ ${cur} == -* ]] ; then - COMPREPLY=( $(compgen -W "${opts}" -- ${cur}) ) - return 0 - fi -} -complete -F _%s %s -alias %s='scala %s $*' - """ - def mkTemplate(name: String, className: String, opts: Seq[String]): String = - template.format(name, opts mkString " ", name, name, name, className) - - private def getArgNames(className: String) = { - val clazz = Class.forName(className + "$") - val singleton = clazz.getField("MODULE$").get() - val m = clazz.getMethod("argumentNames") - - (m invoke singleton).asInstanceOf[Array[String]] map ("--" + _) - } - - def _main(args: Array[String]): Unit = { - if (args == null || args.size != 2) - return println("Usage: mkCommand ") - - val Array(scriptName, className) = args - val opts = getArgNames(className) - - val txt = mkTemplate(scriptName, className, opts) - val tmpfile = java.io.File.createTempFile(scriptName, "", null) - val writer = new java.io.FileWriter(tmpfile) - writer write txt - writer.close() - - println("# run this command in bash") - println("source " + tmpfile.getAbsolutePath()) - } -} \ No newline at end of file diff --git a/src/main/scala/options.scala b/src/main/scala/options.scala deleted file mode 100644 index 794a50a..0000000 --- a/src/main/scala/options.scala +++ /dev/null @@ -1,93 +0,0 @@ -package optional - -import scala.collection._ -import mutable.HashSet - -case class Options( - options: Map[String, String], - args: List[String], - rawArgs: List[String] -) -case class ArgInfo(short: Char, long: String, isSwitch: Boolean, help: String) - -object Options -{ - private val ShortOption = """-(\w)""".r - private val ShortSquashedOption = """-([^-\s]\w+)""".r - private val LongOption = """--(\w+)""".r - private val OptionTerminator = "--" - private val True = "true"; - - /** - * Take a list of string arguments and parse them into options. - * Currently the dumbest option parser in the entire world, but - * oh well. - */ - def parse(argInfos: HashSet[ArgInfo], args: String*): Options = { - import mutable._; - val optionsStack = new ArrayStack[String]; - val options = new OpenHashMap[String, String]; - val arguments = new ArrayBuffer[String]; - - def addSwitch(c: Char) = - options(c.toString) = True - - def isSwitch(c: Char) = - argInfos exists { - case ArgInfo(`c`, _, true, _) => true - case _ => false - } - - def addOption(name: String) = { - if (optionsStack.isEmpty) options(name) = True; - else { - val next = optionsStack.pop; - next match { - case ShortOption(_) | ShortSquashedOption(_) | LongOption(_) | OptionTerminator => - optionsStack.push(next); - options(name) = True; - case x => options(name) = x; - } - } - } - - optionsStack ++= args.reverse; - while(!optionsStack.isEmpty){ - optionsStack.pop match { - case ShortSquashedOption(xs) => - xs foreach addSwitch - - case ShortOption(name) => - val c = name(0) - if (isSwitch(c)) addSwitch(c) - else addOption(name) - - case LongOption(name) => addOption(name); - case OptionTerminator => optionsStack.drain(arguments += _); - case x => arguments += x; - } - } - - Options(options, arguments.toList, args.toList) - } -} - -object AutoArgInfo { - /** - * guess a short form of the arguments from the long names. - * - * - */ - def guessFromNames(names: Iterable[String]) : Seq[ArgInfo] = { - val usedLetters = mutable.Set[Char]() - names.map { n => - val l = n.charAt(0) - if (usedLetters.contains(l)) { - ArgInfo(l,n,false, null) //TODO - } - else { - ArgInfo(l, n, false, null) - } - }.toSeq - } -} From 2c4078f7259dda0e341bef000b5115aef88e52a2 Mon Sep 17 00:00:00 2001 From: Imran Rashid Date: Wed, 3 Oct 2012 07:48:51 -0700 Subject: [PATCH 07/50] change readme to markdown --- README => README.md | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename README => README.md (100%) diff --git a/README b/README.md similarity index 100% rename from README rename to README.md From 4357de1bc7b1b0654ce561deb45d894eda77f738 Mon Sep 17 00:00:00 2001 From: Imran Rashid Date: Wed, 3 Oct 2012 07:48:59 -0700 Subject: [PATCH 08/50] update readme --- README.md | 31 ++++++++++++++----------------- 1 file changed, 14 insertions(+), 17 deletions(-) diff --git a/README.md b/README.md index 56eaabc..8417945 100644 --- a/README.md +++ b/README.md @@ -2,29 +2,26 @@ optional is a command line option parser and library. #### Example -case class Arguments(val name: String, val count: Int) - -``` -object MyApp { - def main(args: Array[String]) { - val myArgs = new Arguments(null,0) with FieldParsing - myArgs.parse(args) - ... - } -} -``` -See the test cases for more examples + case class Arguments(val name: String, val count: Int) + + object MyApp { + def main(args: Array[String]) { + val myArgs = new Arguments(null,0) with FieldParsing + myArgs.parse(args) + ... + } + } -HOW IT WORKS: +See the test cases for more examples - Reflection, man. -CREDITS: +#### Credits This was inspired by the optional package from alexy, which in turn came from: - Idea and prototype implementation: DRMacIver. - Fleshing out and awesomification: paulp. +>Idea and prototype implementation: DRMacIver. + +>Fleshing out and awesomification: paulp. This is a total rewrite, though. \ No newline at end of file From db7c0374730b8af31f552873767400b0dd7007ba Mon Sep 17 00:00:00 2001 From: Imran Rashid Date: Wed, 3 Oct 2012 22:03:16 -0700 Subject: [PATCH 09/50] oops, somehow deleted ArgumentParser, recreated it --- src/main/scala/ArgumentParser.scala | 39 ++++++ src/test/scala/optional/ApplicationTest.scala | 121 ------------------ 2 files changed, 39 insertions(+), 121 deletions(-) create mode 100644 src/main/scala/ArgumentParser.scala delete mode 100644 src/test/scala/optional/ApplicationTest.scala diff --git a/src/main/scala/ArgumentParser.scala b/src/main/scala/ArgumentParser.scala new file mode 100644 index 0000000..5a497f2 --- /dev/null +++ b/src/main/scala/ArgumentParser.scala @@ -0,0 +1,39 @@ +package optional + +import java.lang.reflect.{Type, Field} + +import scala.collection._ + +class ArgumentParser[T <: ArgAssignable] (val argHolders: Seq[T]) { + lazy val nameToHolder = argHolders.map{ a => a.getName -> a}.toMap + + + def parse(args: Array[String]) : Map[T, ValueHolder[_]] = { + val result = mutable.Map[T, ValueHolder[_]]() + var idx = 0 + while (idx < args.length) { + val arg = args(idx) + if (!arg.startsWith("--")) + throw new RuntimeException("expecting argument name beginning with \"--\", instead got " + arg) + val name = arg.substring(2) + val holderOption = nameToHolder.get(name) + if (holderOption.isEmpty) + throw new RuntimeException("unknown option " + name) + result(holderOption.get) = ParseHelper.parseInto(args(idx +1), holderOption.get.getType).get + idx += 2 + } + result + } + +} + +trait ArgAssignable { + def getName : String + def getType: Type +} + + +class FieldArgAssignable(val field: Field) extends ArgAssignable { + def getName = field.getName + def getType = field.getGenericType +} \ No newline at end of file diff --git a/src/test/scala/optional/ApplicationTest.scala b/src/test/scala/optional/ApplicationTest.scala deleted file mode 100644 index 2169bd4..0000000 --- a/src/test/scala/optional/ApplicationTest.scala +++ /dev/null @@ -1,121 +0,0 @@ -package optional - -import org.scalatest.FunSuite -import org.scalatest.matchers.ShouldMatchers - -/** - * - */ - -class ApplicationTest extends FunSuite with ShouldMatchers { - -// test("readme parse args") { -// //straight from the Readme -// MyAwesomeCommandLineTool.main(Array("--count", "5", "quux")) -// MyAwesomeCommandLineTool.args._1 should be (Some(5)) -// MyAwesomeCommandLineTool.args._2 should be (None) -// MyAwesomeCommandLineTool.args._3 should be ("quux") -// } -// -// test("longNames"){ -// easy.main(Array("--aOne", "1", "--bTwo", "2", "--myDouble", "2.9", "--floatOptional", "2.3")) -// easy.args._1 should be (1) -// easy.args._2 should be (2) -// easy.args._3 should be (2.9) -// easy.args._4 should be (Some(2.3f)) -// } -// -// test("conflicting short names"){ -// -// conflictingNames.main(Array("-a", "7", "-b", "18", "--longerName", "some string", "--lousyConflictingName", "another string")) -// conflictingNames.args._1 should be (7) -// conflictingNames.args._2 should be (18) -// conflictingNames.args._3 should be ("some string") -// conflictingNames.args._4 should be ("another string") -// -// conflictingNames.main(Array("--a", "9", "--b", "10", "--longerName", "abcde", "--lousyConflictingName", "efgh")) -// conflictingNames.args._1 should be (9) -// conflictingNames.args._2 should be (10) -// conflictingNames.args._3 should be ("abcde") -// conflictingNames.args._4 should be ("efgh") -// -// -// evaluating { -// conflictingNames.main(Array("-a", "90", "-b", "100", "-l", "xyz", "--lousyConflictingName", "pqr")) -// } should produce [Exception] -// } - - - test("register single char args") { - registeredSingleChars.main(Array("-c", "5", "-d", "7.2")) - registeredSingleChars.args._1 should be (5) - registeredSingleChars.args._2 should be (7.2) - } - -// -// test("short param from long variables") { -// easy.main(Array("-a", "1", "-b", "2", "-m", "2.9", "-f", "2.3")) -// easy.args._1 should be (1) -// easy.args._2 should be (2) -// easy.args._3 should be (2.9) -// easy.args._4 should be (Some(2.3f)) -// } -// -// -// //won't work as long as we use java reflection! -// test("optional primitives") { -// primitiveOptions.main(Array("--count", "5", "--double", "7.6")) -// primitiveOptions.args._1 should be (Some(5)) -// primitiveOptions.args._2 should be (Some(7.6)) -// -// primitiveOptions.main(Array("--count", "1")) -// primitiveOptions.args._1 should be (Some(1)) -// primitiveOptions.args._2 should be (None) -// -// primitiveOptions.main(Array("--double", "13.9")) -// primitiveOptions.args._1 should be (None) -// primitiveOptions.args._2 should be (Some(13.9)) -// -// primitiveOptions.main(Array()) -// primitiveOptions.args._1 should be (None) -// primitiveOptions.args._2 should be (None) -// } -} - -object MyAwesomeCommandLineTool extends optional.Application { - //unfortunately, b/c this uses the java reflection api, it doesn't support Option for primitives - var args: (Option[java.lang.Integer], Option[java.io.File], String) = _ - def main(count: Option[java.lang.Integer], file: Option[java.io.File], arg1: String) { - args = (count, file, arg1) - } -} - -object easy extends optional.Application { - var args : (Int, Int, Double, Option[java.lang.Float]) = _ - def main(aOne: Int, bTwo: Int, myDouble: Double, floatOptional: Option[java.lang.Float]) { - args = (aOne, bTwo, myDouble, floatOptional) - } -} - -object conflictingNames extends optional.Application { - var args : (Int, Int, String, String) = _ - def main(a: Int, b : Int, longerName: String, lousyConflictingName: String) { - args = (a, b, longerName, lousyConflictingName) - } -} - -object primitiveOptions extends optional.Application { - var args: (Option[Int], Option[Double]) = _ - def main(count: Option[Int], double: Option[Double]) { - args = (count, double) - } -} - -object registeredSingleChars extends optional.Application { - var args: (Option[Int], Option[Double]) = _ - register(ArgInfo('c', "count", false, "the count"), ArgInfo('d', "double", false, "a double")) - def main(count: Option[Int], double: Option[Double]) { - args = (count, double) - } - -} \ No newline at end of file From 61b3db17281fc111da435a2b0a7e8553c775cb8c Mon Sep 17 00:00:00 2001 From: Imran Rashid Date: Wed, 3 Oct 2012 22:21:04 -0700 Subject: [PATCH 10/50] add BooleanParser --- src/main/scala/Parser.scala | 8 +++++++- src/test/scala/optional/ParserTest.scala | 2 ++ 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/src/main/scala/Parser.scala b/src/main/scala/Parser.scala index 96b7103..dce2389 100644 --- a/src/main/scala/Parser.scala +++ b/src/main/scala/Parser.scala @@ -40,6 +40,12 @@ object IntParser extends SimpleParser[Int] { def parse(s: String) = s.toInt } +object BooleanParser extends SimpleParser[Boolean] { + val knownTypes : Set[Class[_]] = Set(classOf[Boolean], classOf[java.lang.Boolean]) + def getKnownTypes() = knownTypes + def parse(s: String) = s.toBoolean +} + object DoubleParser extends SimpleParser[Double] { val knownTypes : Set[Class[_]] = Set(classOf[Double], classOf[java.lang.Double]) def getKnownTypes() = knownTypes @@ -74,7 +80,7 @@ object ListParser extends CompoundParser[List[_]] { object ParseHelper { - val parsers = Seq(StringParser, IntParser, DoubleParser, ListParser) + val parsers = Seq(StringParser, IntParser, DoubleParser, BooleanParser, ListParser) def findParser(tpe: Type) : Option[Parser[_]] = { for (p <- parsers) { diff --git a/src/test/scala/optional/ParserTest.scala b/src/test/scala/optional/ParserTest.scala index 6fcd42d..7c8a8cc 100644 --- a/src/test/scala/optional/ParserTest.scala +++ b/src/test/scala/optional/ParserTest.scala @@ -14,6 +14,8 @@ class ParserTest extends FunSuite with ShouldMatchers { IntParser.parse("5") should be (5) DoubleParser.parse("5") should be (5.0) DoubleParser.parse("1e-10") should be (1e-10) + BooleanParser.parse("false") should be (false) + BooleanParser.parse("true") should be (true) } test("ListParser") { From a5cb8f357bb284d4b295fab87a628f5cccccbda4 Mon Sep 17 00:00:00 2001 From: Imran Rashid Date: Wed, 3 Oct 2012 22:21:23 -0700 Subject: [PATCH 11/50] add custom parsing, tests for subclass parsing --- src/main/scala/ArgumentParser.scala | 7 +++- src/main/scala/ObjectToArgs.scala | 13 +++--- src/main/scala/Parser.scala | 10 +++-- .../scala/optional/ObjectToArgsTest.scala | 41 ++++++++++++++++++- 4 files changed, 59 insertions(+), 12 deletions(-) diff --git a/src/main/scala/ArgumentParser.scala b/src/main/scala/ArgumentParser.scala index 5a497f2..d83b10b 100644 --- a/src/main/scala/ArgumentParser.scala +++ b/src/main/scala/ArgumentParser.scala @@ -8,7 +8,9 @@ class ArgumentParser[T <: ArgAssignable] (val argHolders: Seq[T]) { lazy val nameToHolder = argHolders.map{ a => a.getName -> a}.toMap - def parse(args: Array[String]) : Map[T, ValueHolder[_]] = { + def parse(args: Array[String], + preParsers: Iterator[Parser[_]] = Iterator(), + postParsers: Iterator[Parser[_]] = Iterator()) : Map[T, ValueHolder[_]] = { val result = mutable.Map[T, ValueHolder[_]]() var idx = 0 while (idx < args.length) { @@ -19,7 +21,8 @@ class ArgumentParser[T <: ArgAssignable] (val argHolders: Seq[T]) { val holderOption = nameToHolder.get(name) if (holderOption.isEmpty) throw new RuntimeException("unknown option " + name) - result(holderOption.get) = ParseHelper.parseInto(args(idx +1), holderOption.get.getType).get + result(holderOption.get) = + ParseHelper.parseInto(args(idx +1), holderOption.get.getType, preParsers, postParsers).get idx += 2 } result diff --git a/src/main/scala/ObjectToArgs.scala b/src/main/scala/ObjectToArgs.scala index 326dd05..8232dc4 100644 --- a/src/main/scala/ObjectToArgs.scala +++ b/src/main/scala/ObjectToArgs.scala @@ -9,9 +9,10 @@ class ObjectToArgs(val obj: Object) { ReflectionUtils.getAllDeclaredFields(obj.getClass).map{f => new FieldArgAssignable(f)} ) - def parse(args: Array[String]) { - println(argParser.nameToHolder) - val parsed = argParser.parse(args) + def parse(args: Array[String], + preParsers: Iterator[Parser[_]] = Iterator(), + postParsers: Iterator[Parser[_]] = Iterator()) { + val parsed = argParser.parse(args, preParsers, postParsers) parsed.foreach{ kv => val field = kv._1.field @@ -23,7 +24,9 @@ class ObjectToArgs(val obj: Object) { trait FieldParsing { lazy val parser = new ObjectToArgs(this) - def parse(args: Array[String]) { - parser.parse(args) + def parse(args: Array[String], + preParsers: Iterator[Parser[_]] = Iterator(), + postParsers: Iterator[Parser[_]] = Iterator()) { + parser.parse(args, preParsers, postParsers) } } \ No newline at end of file diff --git a/src/main/scala/Parser.scala b/src/main/scala/Parser.scala index dce2389..4143220 100644 --- a/src/main/scala/Parser.scala +++ b/src/main/scala/Parser.scala @@ -82,17 +82,19 @@ object ListParser extends CompoundParser[List[_]] { object ParseHelper { val parsers = Seq(StringParser, IntParser, DoubleParser, BooleanParser, ListParser) - def findParser(tpe: Type) : Option[Parser[_]] = { - for (p <- parsers) { + def findParser(tpe: Type, preParsers: Iterator[Parser[_]] = Iterator(), postParsers: Iterator[Parser[_]] = Iterator()) : Option[Parser[_]] = { + for (p <- (preParsers ++ parsers.iterator ++ postParsers)) { if (p.canParse(tpe)) return Some(p) } None } - def parseInto[T](s: String, tpe: Type) : Option[ValueHolder[T]] = { + def parseInto[T](s: String, tpe: Type, + preParsers: Iterator[Parser[_]] = Iterator(), + postParsers: Iterator[Parser[_]] = Iterator()) : Option[ValueHolder[T]] = { //could change this to be a map, at least for the simple types - findParser(tpe).map{parser => ValueHolder[T](parser.parse(s, tpe).asInstanceOf[T], tpe)} + findParser(tpe, preParsers, postParsers).map{parser => ValueHolder[T](parser.parse(s, tpe).asInstanceOf[T], tpe)} } } diff --git a/src/test/scala/optional/ObjectToArgsTest.scala b/src/test/scala/optional/ObjectToArgsTest.scala index 492bca9..71ff761 100644 --- a/src/test/scala/optional/ObjectToArgsTest.scala +++ b/src/test/scala/optional/ObjectToArgsTest.scala @@ -43,9 +43,48 @@ class ObjectToArgsTest extends FunSuite with ShouldMatchers { o.count should be (981) } + test("subclass parsing") { + val o = new Child(false, null, 0) with FieldParsing + + o.parse(Array("--flag", "true", "--name", "bugaloo")) + o.name should be ("bugaloo") + o.flag should be (true) + } + + test("custom parsers") { + val o = new SpecialTypes(null, null) with FieldParsing + + o.parse(Array("--name", "blah")) + o.name should be ("blah") + + evaluating {o.parse(Array("--funky", "xyz"))} should produce [Exception] + + o.parse(Array("--funky", "xyz", "--name", "hi"), preParsers = Iterator(MyFunkyTypeParser)) + o.name should be ("hi") + o.funky should be (MyFunkyType("xyzoogabooga")) + + } + + //TODO tests that there are sensible errors on bad arguments + + } case class StringHolder(val name: String, val comment: String) -case class MixedTypes(val name: String, val count: Int) \ No newline at end of file +case class MixedTypes(val name: String, val count: Int) + +//is there an easier way to do this in scala? +class Child(val flag: Boolean, name: String, count: Int) extends MixedTypes(name, count) + +case class MyFunkyType(val stuff: String) + +object MyFunkyTypeParser extends Parser[MyFunkyType] { + def canParse(tpe: java.lang.reflect.Type) = + classOf[MyFunkyType].isAssignableFrom(tpe.asInstanceOf[Class[_]]) + def parse(s: String, tpe: java.lang.reflect.Type) = + MyFunkyType(s + "oogabooga") +} + +case class SpecialTypes(val name: String, val funky: MyFunkyType) \ No newline at end of file From 44863c5e635368406ba979e9596b90e2ce9292f4 Mon Sep 17 00:00:00 2001 From: Imran Rashid Date: Wed, 3 Oct 2012 22:21:39 -0700 Subject: [PATCH 12/50] change to apache2.0 license --- LICENSE | 32 ++++++++++---------------------- 1 file changed, 10 insertions(+), 22 deletions(-) diff --git a/LICENSE b/LICENSE index 9fb7d44..3012e4f 100644 --- a/LICENSE +++ b/LICENSE @@ -1,25 +1,13 @@ -Copyright (c) 2009, David R. MacIver and Paul Phillips (hereafter, DRMAPP) -All rights reserved. +Copyright 2012 Imran Rashid -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions are met: - * Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. - * Redistributions in binary form must reproduce the above copyright - notice, this list of conditions and the following disclaimer in the - documentation and/or other materials provided with the distribution. - * Neither the name of this software nor the - names of its contributors may be used to endorse or promote products - derived from this software without specific prior written permission. + Licensed 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 -THIS SOFTWARE IS PROVIDED BY DRMAPP ''AS IS'' AND ANY -EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED -WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -DISCLAIMED. IN NO EVENT SHALL DRMAPP BE LIABLE FOR ANY -DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES -(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; -LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND -ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS -SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + 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. \ No newline at end of file From ef0a6e6b48ccb1b9a4a4e6940480f7a1c6f9dc41 Mon Sep 17 00:00:00 2001 From: Imran Rashid Date: Sun, 21 Oct 2012 22:12:23 -0700 Subject: [PATCH 13/50] add very primitive help messages --- src/main/scala/ArgumentParser.scala | 49 +++++++++++++------ src/main/scala/ObjectToArgs.scala | 4 ++ .../scala/optional/ObjectToArgsTest.scala | 22 +++++++-- 3 files changed, 58 insertions(+), 17 deletions(-) diff --git a/src/main/scala/ArgumentParser.scala b/src/main/scala/ArgumentParser.scala index d83b10b..42f87c8 100644 --- a/src/main/scala/ArgumentParser.scala +++ b/src/main/scala/ArgumentParser.scala @@ -11,21 +11,38 @@ class ArgumentParser[T <: ArgAssignable] (val argHolders: Seq[T]) { def parse(args: Array[String], preParsers: Iterator[Parser[_]] = Iterator(), postParsers: Iterator[Parser[_]] = Iterator()) : Map[T, ValueHolder[_]] = { - val result = mutable.Map[T, ValueHolder[_]]() - var idx = 0 - while (idx < args.length) { - val arg = args(idx) - if (!arg.startsWith("--")) - throw new RuntimeException("expecting argument name beginning with \"--\", instead got " + arg) - val name = arg.substring(2) - val holderOption = nameToHolder.get(name) - if (holderOption.isEmpty) - throw new RuntimeException("unknown option " + name) - result(holderOption.get) = - ParseHelper.parseInto(args(idx +1), holderOption.get.getType, preParsers, postParsers).get - idx += 2 + try { + val result = mutable.Map[T, ValueHolder[_]]() + var idx = 0 + while (idx < args.length) { + val arg = args(idx) + if (arg == "--help") { + throw new ArgException(helpMessage) + } + if (!arg.startsWith("--")) + throw new ArgException("expecting argument name beginning with \"--\", instead got " + arg + "\n" + helpMessage) + val name = arg.substring(2) + val holderOption = nameToHolder.get(name) + if (holderOption.isEmpty) + throw new ArgException("unknown option " + name + "\n" + helpMessage) + result(holderOption.get) = + ParseHelper.parseInto(args(idx +1), holderOption.get.getType, preParsers, postParsers).get + idx += 2 + } + result + } catch { + case exc => throw new ArgException(helpMessage, exc) } - result + } + + def helpMessage = { + val msg = StringBuilder.newBuilder + msg.append("usage: \n") + nameToHolder.foreach{ kv => + msg.append("--" + kv._1 + "\t" + kv._2.getType + "\n\n") + //TODO add some way to include a usage message + } + msg.toString } } @@ -39,4 +56,8 @@ trait ArgAssignable { class FieldArgAssignable(val field: Field) extends ArgAssignable { def getName = field.getName def getType = field.getGenericType +} + +class ArgException(val msg: String, val cause: Throwable) extends IllegalArgumentException(msg, cause) { + def this(msg:String) = this(msg, null) } \ No newline at end of file diff --git a/src/main/scala/ObjectToArgs.scala b/src/main/scala/ObjectToArgs.scala index 8232dc4..ae24c3c 100644 --- a/src/main/scala/ObjectToArgs.scala +++ b/src/main/scala/ObjectToArgs.scala @@ -20,6 +20,8 @@ class ObjectToArgs(val obj: Object) { field.set(obj, kv._2.value) } } + + def helpMessage = argParser.helpMessage } trait FieldParsing { @@ -29,4 +31,6 @@ trait FieldParsing { postParsers: Iterator[Parser[_]] = Iterator()) { parser.parse(args, preParsers, postParsers) } + + def helpMessage = parser.helpMessage } \ No newline at end of file diff --git a/src/test/scala/optional/ObjectToArgsTest.scala b/src/test/scala/optional/ObjectToArgsTest.scala index 71ff761..7594fcb 100644 --- a/src/test/scala/optional/ObjectToArgsTest.scala +++ b/src/test/scala/optional/ObjectToArgsTest.scala @@ -65,9 +65,25 @@ class ObjectToArgsTest extends FunSuite with ShouldMatchers { } - //TODO tests that there are sensible errors on bad arguments - - + test("help message") { + val o = new StringHolder(null, null) + val parser = new ObjectToArgs(o) + val exc1 = evaluating {parser.parse(Array("--xyz", "hello"))} should produce [ArgException] + //the format is still ugly, but at least there is some info there + "\\-\\-name\\s.*String".r.findFirstIn(exc1.getMessage()) should be ('defined) + "\\-\\-comment\\s.*String".r.findFirstIn(exc1.getMessage()) should be ('defined) + + val o2 = new MixedTypes(null, 0) + val p2 = new ObjectToArgs(o2) + val exc2 = evaluating {p2.parse(Array("--foo", "bar"))} should produce [ArgException] + "\\-\\-name\\s.*String".r findFirstIn(exc2.getMessage) should be ('defined) + "\\-\\-count\\s.*[Ii]nt".r findFirstIn(exc2.getMessage) should be ('defined) //java or scala types, I'll take either for now + + val exc3 = evaluating {p2.parse(Array("--count", "ooga"))} should produce [ArgException] + //this message really should be much better. (a) the number format exception should come first and (b) should indicate that it was while processing the "count" argument + "\\-\\-name\\s.*String".r findFirstIn(exc3.getMessage) should be ('defined) + "\\-\\-count\\s.*[Ii]nt".r findFirstIn(exc3.getMessage) should be ('defined) //java or scala types, I'll take either for now + } } From 800994b7b86d1864372d613546928b365dbda248 Mon Sep 17 00:00:00 2001 From: Imran Rashid Date: Thu, 15 Nov 2012 21:10:32 -0800 Subject: [PATCH 14/50] fixes to build file --- project/Build.scala | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/project/Build.scala b/project/Build.scala index 3c99d81..38d6750 100644 --- a/project/Build.scala +++ b/project/Build.scala @@ -13,16 +13,14 @@ object SparkBuild extends Build { transitiveClassifiers in Scope.GlobalScope := Seq("sources"), publishTo <<= baseDirectory { base => Some(Resolver.file("Local", base / "target" / "maven" asFile)(Patterns(true, Resolver.mavenStyleBasePattern))) }, libraryDependencies ++= Seq( - "org.eclipse.jetty" % "jetty-server" % "7.5.3.v20111011", - "org.scalatest" %% "scalatest" % "1.6.1" % "test", - "org.scalacheck" %% "scalacheck" % "1.9" % "test" + "org.scalatest" %% "scalatest" % "1.6.1" % "test" ) ) val slf4jVersion = "1.6.1" def coreSettings = sharedSettings ++ Seq( - name := "sblaj-core", + name := "optional", resolvers ++= Seq( "Typesafe Repository" at "http://repo.typesafe.com/typesafe/releases/", "JBoss Repository" at "http://repository.jboss.org/nexus/content/repositories/releases/" From 51af96fd81013df6728ff99aac569544428b79d4 Mon Sep 17 00:00:00 2001 From: Imran Rashid Date: Thu, 15 Nov 2012 21:11:10 -0800 Subject: [PATCH 15/50] better error msg on unsupported types --- src/main/scala/ArgumentParser.scala | 7 +++++-- src/test/scala/optional/ArgumentParserTest.scala | 3 +-- src/test/scala/optional/ObjectToArgsTest.scala | 14 +++++++++++++- 3 files changed, 19 insertions(+), 5 deletions(-) diff --git a/src/main/scala/ArgumentParser.scala b/src/main/scala/ArgumentParser.scala index 42f87c8..52de9c9 100644 --- a/src/main/scala/ArgumentParser.scala +++ b/src/main/scala/ArgumentParser.scala @@ -25,8 +25,11 @@ class ArgumentParser[T <: ArgAssignable] (val argHolders: Seq[T]) { val holderOption = nameToHolder.get(name) if (holderOption.isEmpty) throw new ArgException("unknown option " + name + "\n" + helpMessage) - result(holderOption.get) = - ParseHelper.parseInto(args(idx +1), holderOption.get.getType, preParsers, postParsers).get + val parsed = ParseHelper.parseInto(args(idx +1), holderOption.get.getType, preParsers, postParsers) + parsed match { + case Some(x) => result(holderOption.get) = x + case None => throw new ArgException("don't know how to parse type: " + holderOption.get.getType) + } idx += 2 } result diff --git a/src/test/scala/optional/ArgumentParserTest.scala b/src/test/scala/optional/ArgumentParserTest.scala index bf1acde..f80c13a 100644 --- a/src/test/scala/optional/ArgumentParserTest.scala +++ b/src/test/scala/optional/ArgumentParserTest.scala @@ -30,11 +30,10 @@ class ArgumentParserTest extends FunSuite with ShouldMatchers { } } - def getSimpleNameToArgMap(parsedArgs : Map[_ <: ArgAssignable, ValueHolder[_]]) = { parsedArgs.map{kv => kv._1.getName -> kv._2.value}.toMap[String, Any] } } -case class SimpleClass(val name: String, val count: Int, val dummy: Double, val count2: Int) \ No newline at end of file +case class SimpleClass(val name: String, val count: Int, val dummy: Double, val count2: Int) diff --git a/src/test/scala/optional/ObjectToArgsTest.scala b/src/test/scala/optional/ObjectToArgsTest.scala index 7594fcb..e0f3940 100644 --- a/src/test/scala/optional/ObjectToArgsTest.scala +++ b/src/test/scala/optional/ObjectToArgsTest.scala @@ -84,6 +84,18 @@ class ObjectToArgsTest extends FunSuite with ShouldMatchers { "\\-\\-name\\s.*String".r findFirstIn(exc3.getMessage) should be ('defined) "\\-\\-count\\s.*[Ii]nt".r findFirstIn(exc3.getMessage) should be ('defined) //java or scala types, I'll take either for now } + + test("error msg on unknown types") { + val o = new SpecialTypes("", null) with FieldParsing + + o.parse(Array("--name", "ooga")) + o.name should be ("ooga") + o.funky should be (null) + + val exc = evaluating {o.parse(Array("--funky", "xyz"))} should produce [ArgException] + exc.cause.getMessage should include ("type") + exc.cause.getMessage should include ("MyFunkyType") + } } @@ -103,4 +115,4 @@ object MyFunkyTypeParser extends Parser[MyFunkyType] { MyFunkyType(s + "oogabooga") } -case class SpecialTypes(val name: String, val funky: MyFunkyType) \ No newline at end of file +case class SpecialTypes(val name: String, val funky: MyFunkyType) From bee169457581473e3ab9689c46ff71a8ee60cd5a Mon Sep 17 00:00:00 2001 From: Imran Rashid Date: Thu, 15 Nov 2012 21:15:05 -0800 Subject: [PATCH 16/50] add support for longs & floats --- src/main/scala/Parser.scala | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/src/main/scala/Parser.scala b/src/main/scala/Parser.scala index 4143220..a323ce9 100644 --- a/src/main/scala/Parser.scala +++ b/src/main/scala/Parser.scala @@ -40,18 +40,31 @@ object IntParser extends SimpleParser[Int] { def parse(s: String) = s.toInt } +object LongParser extends SimpleParser[Long] { + val knownTypes: Set[Class[_]] = Set(classOf(Long), classOf[java.lang.Long]) + def getKnownTypes() = knownTypes + def parse(s: String) = s.toLong +} + object BooleanParser extends SimpleParser[Boolean] { val knownTypes : Set[Class[_]] = Set(classOf[Boolean], classOf[java.lang.Boolean]) def getKnownTypes() = knownTypes def parse(s: String) = s.toBoolean } +object FloatParser extends SimpleParser[Float] { + val knownTypes : Set[Class[_]] = Set(classOf[Float], classOf[java.lang.Float]) + def getKnownTypes() = knownTypes + def parse(s: String) = s.toFloat +} + object DoubleParser extends SimpleParser[Double] { val knownTypes : Set[Class[_]] = Set(classOf[Double], classOf[java.lang.Double]) def getKnownTypes() = knownTypes def parse(s: String) = s.toDouble } + object ListParser extends CompoundParser[List[_]] { def canParse(tpe: Type) = { @@ -80,7 +93,7 @@ object ListParser extends CompoundParser[List[_]] { object ParseHelper { - val parsers = Seq(StringParser, IntParser, DoubleParser, BooleanParser, ListParser) + val parsers = Seq(StringParser, IntParser, LongParser, FloatParser, DoubleParser, BooleanParser, ListParser) def findParser(tpe: Type, preParsers: Iterator[Parser[_]] = Iterator(), postParsers: Iterator[Parser[_]] = Iterator()) : Option[Parser[_]] = { for (p <- (preParsers ++ parsers.iterator ++ postParsers)) { From 3bf23f38c990f7c0dcda61c28c531a92c3817182 Mon Sep 17 00:00:00 2001 From: Imran Rashid Date: Thu, 15 Nov 2012 21:17:28 -0800 Subject: [PATCH 17/50] fix compile error --- src/main/scala/Parser.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/scala/Parser.scala b/src/main/scala/Parser.scala index a323ce9..478a66b 100644 --- a/src/main/scala/Parser.scala +++ b/src/main/scala/Parser.scala @@ -41,7 +41,7 @@ object IntParser extends SimpleParser[Int] { } object LongParser extends SimpleParser[Long] { - val knownTypes: Set[Class[_]] = Set(classOf(Long), classOf[java.lang.Long]) + val knownTypes: Set[Class[_]] = Set(classOf[Long], classOf[java.lang.Long]) def getKnownTypes() = knownTypes def parse(s: String) = s.toLong } From d1df18f6ef0d41f2960952cd00e39d045266075d Mon Sep 17 00:00:00 2001 From: Imran Rashid Date: Mon, 3 Dec 2012 17:27:47 -0800 Subject: [PATCH 18/50] add SelectInput and MultiSelectInput. Required some major refactoring, since parsers need currentValue --- src/main/scala/ArgumentParser.scala | 8 +- src/main/scala/ObjectToArgs.scala | 2 +- src/main/scala/Parser.scala | 105 +++++++++++++++--- .../optional/types/MultiSelectInput.scala | 9 ++ .../scala/optional/types/SelectInput.scala | 7 ++ .../scala/optional/ArgumentParserTest.scala | 3 +- .../scala/optional/ObjectToArgsTest.scala | 40 ++++++- src/test/scala/optional/ParserTest.scala | 10 +- 8 files changed, 160 insertions(+), 24 deletions(-) create mode 100644 src/main/scala/optional/types/MultiSelectInput.scala create mode 100644 src/main/scala/optional/types/SelectInput.scala diff --git a/src/main/scala/ArgumentParser.scala b/src/main/scala/ArgumentParser.scala index 52de9c9..d172b11 100644 --- a/src/main/scala/ArgumentParser.scala +++ b/src/main/scala/ArgumentParser.scala @@ -25,7 +25,8 @@ class ArgumentParser[T <: ArgAssignable] (val argHolders: Seq[T]) { val holderOption = nameToHolder.get(name) if (holderOption.isEmpty) throw new ArgException("unknown option " + name + "\n" + helpMessage) - val parsed = ParseHelper.parseInto(args(idx +1), holderOption.get.getType, preParsers, postParsers) + val parsed = ParseHelper.parseInto(args(idx +1), holderOption.get.getType, holderOption.get.getCurrentValue, + preParsers, postParsers) parsed match { case Some(x) => result(holderOption.get) = x case None => throw new ArgException("don't know how to parse type: " + holderOption.get.getType) @@ -53,12 +54,15 @@ class ArgumentParser[T <: ArgAssignable] (val argHolders: Seq[T]) { trait ArgAssignable { def getName : String def getType: Type + def getCurrentValue: AnyRef } -class FieldArgAssignable(val field: Field) extends ArgAssignable { +class FieldArgAssignable(val field: Field, val obj: Object) extends ArgAssignable { + field.setAccessible(true) def getName = field.getName def getType = field.getGenericType + def getCurrentValue = field.get(obj) } class ArgException(val msg: String, val cause: Throwable) extends IllegalArgumentException(msg, cause) { diff --git a/src/main/scala/ObjectToArgs.scala b/src/main/scala/ObjectToArgs.scala index ae24c3c..3c99c52 100644 --- a/src/main/scala/ObjectToArgs.scala +++ b/src/main/scala/ObjectToArgs.scala @@ -6,7 +6,7 @@ package optional class ObjectToArgs(val obj: Object) { val argParser = new ArgumentParser[FieldArgAssignable]( - ReflectionUtils.getAllDeclaredFields(obj.getClass).map{f => new FieldArgAssignable(f)} + ReflectionUtils.getAllDeclaredFields(obj.getClass).map{f => new FieldArgAssignable(f, obj)} ) def parse(args: Array[String], diff --git a/src/main/scala/Parser.scala b/src/main/scala/Parser.scala index 478a66b..85ac63b 100644 --- a/src/main/scala/Parser.scala +++ b/src/main/scala/Parser.scala @@ -1,9 +1,10 @@ package optional +import types.{SelectInput,MultiSelectInput} import java.lang.reflect.{Type, ParameterizedType} trait Parser[T] { - def parse(s: String, tpe: Type): T + def parse(s: String, tpe: Type, currentValue: AnyRef): T /** * return true if this parser knows how to parse the given type @@ -21,7 +22,7 @@ trait SimpleParser[T] extends Parser[T] { else false } - def parse(s: String, tpe:Type) = parse(s) + def parse(s: String, tpe:Type, currentValue: AnyRef) = parse(s) def parse(s:String) :T } @@ -64,26 +65,21 @@ object DoubleParser extends SimpleParser[Double] { def parse(s: String) = s.toDouble } +//TODO CompoundParser are both a pain to write, and extremely unsafe. Design needs some work object ListParser extends CompoundParser[List[_]] { def canParse(tpe: Type) = { - val clz = if (tpe.isInstanceOf[Class[_]]) - tpe.asInstanceOf[Class[_]] - else if (tpe.isInstanceOf[ParameterizedType]) - tpe.asInstanceOf[ParameterizedType].getRawType.asInstanceOf[Class[_]] - else - classOf[Int] //just need something that won't match - classOf[List[_]].isAssignableFrom(clz) + ParseHelper.checkType(tpe, classOf[List[_]]) } - def parse(s: String, tpe: Type) = { + def parse(s: String, tpe: Type, currentValue: AnyRef) = { if (tpe.isInstanceOf[ParameterizedType]) { val ptpe = tpe.asInstanceOf[ParameterizedType] val subtype = ptpe.getActualTypeArguments()(0) val subParser = ParseHelper.findParser(subtype).get //TODO need to handle cases where its a list, but can't parse subtype val parts = s.split(",") - parts.map{sub => subParser.parse(sub, subtype)}.toList + parts.map{sub => subParser.parse(sub, subtype, currentValue)}.toList } else List() @@ -91,9 +87,76 @@ object ListParser extends CompoundParser[List[_]] { } +object SetParser extends CompoundParser[collection.Set[_]] { + def canParse(tpe: Type) = { + ParseHelper.checkType(tpe, classOf[collection.Set[_]]) + } + + def parse(s: String, tpe: Type, currentValue: AnyRef) = { + if (tpe.isInstanceOf[ParameterizedType]) { + val ptpe = tpe.asInstanceOf[ParameterizedType] + val subtype = ptpe.getActualTypeArguments()(0) + val subParser = ParseHelper.findParser(subtype).get + val parts = s.split(",") + parts.map{sub => subParser.parse(sub, subtype, currentValue)}.toSet + } + else + Set() + } +} + +object SelectInputParser extends CompoundParser[SelectInput[_]] { + def canParse(tpe: Type) = { + ParseHelper.checkType(tpe, classOf[SelectInput[_]]) + } + + def parse(s: String, tpe: Type, currentValue: AnyRef) = { + val currentVal = currentValue.asInstanceOf[SelectInput[Any]] //not really Any, but not sure how to make the compiler happy ... + if (tpe.isInstanceOf[ParameterizedType]) { + val ptpe = tpe.asInstanceOf[ParameterizedType] + val subtype = ptpe.getActualTypeArguments()(0) + val subParser = ParseHelper.findParser(subtype).get + val parsed = subParser.parse(s, subtype, currentVal.value) + if (currentVal.options(parsed)) + currentVal.value = Some(parsed) + else + throw new IllegalArgumentException(parsed + " is not the allowed values: " + currentVal.options) + //we don't return a new object, just modify the existing one + currentVal + } + else + throw new UnsupportedOperationException() + } +} + +object MultiSelectInputParser extends CompoundParser[MultiSelectInput[_]] { + + def canParse(tpe: Type) = ParseHelper.checkType(tpe, classOf[MultiSelectInput[_]]) + + def parse(s: String, tpe: Type, currentValue: AnyRef) = { + val currentVal = currentValue.asInstanceOf[MultiSelectInput[Any]] //not really Any, but not sure how to make the compiler happy ... + if (tpe.isInstanceOf[ParameterizedType]) { + val ptpe = tpe.asInstanceOf[ParameterizedType] + val subtype = ptpe.getActualTypeArguments()(0) + val subParser = ParseHelper.findParser(subtype).get + val parsed = s.split(",").map{sub => subParser.parse(sub, subtype, "dummy")}.toSet + val illegal = parsed.diff(currentVal.options) + if (illegal.isEmpty) + currentVal.value = parsed + else + throw new IllegalArgumentException(illegal.toString + " is not the allowed values: " + currentVal.options) + //we don't return a new object, just modify the existing one + currentVal + } + else + throw new UnsupportedOperationException() + + } +} object ParseHelper { - val parsers = Seq(StringParser, IntParser, LongParser, FloatParser, DoubleParser, BooleanParser, ListParser) + val parsers = Seq(StringParser, IntParser, LongParser, FloatParser, DoubleParser, BooleanParser, ListParser, + SetParser, SelectInputParser, MultiSelectInputParser) def findParser(tpe: Type, preParsers: Iterator[Parser[_]] = Iterator(), postParsers: Iterator[Parser[_]] = Iterator()) : Option[Parser[_]] = { for (p <- (preParsers ++ parsers.iterator ++ postParsers)) { @@ -103,12 +166,26 @@ object ParseHelper { None } - def parseInto[T](s: String, tpe: Type, + def parseInto[T](s: String, tpe: Type, currentValue: AnyRef, preParsers: Iterator[Parser[_]] = Iterator(), postParsers: Iterator[Parser[_]] = Iterator()) : Option[ValueHolder[T]] = { //could change this to be a map, at least for the simple types - findParser(tpe, preParsers, postParsers).map{parser => ValueHolder[T](parser.parse(s, tpe).asInstanceOf[T], tpe)} + findParser(tpe, preParsers, postParsers).map{parser => ValueHolder[T](parser.parse(s, tpe, currentValue).asInstanceOf[T], tpe)} } + + def checkType(tpe: Type, targetClassSet: Class[_]*) = { + def helper(tpe: Type, targetCls: Class[_]) = { + val clz = if (tpe.isInstanceOf[Class[_]]) + tpe.asInstanceOf[Class[_]] + else if (tpe.isInstanceOf[ParameterizedType]) + tpe.asInstanceOf[ParameterizedType].getRawType.asInstanceOf[Class[_]] + else + classOf[Int] //just need something that won't match + targetCls.isAssignableFrom(clz) + } + targetClassSet.exists(targetClass => helper(tpe, targetClass)) + } + } case class ValueHolder[T](value: T, tpe: Type) \ No newline at end of file diff --git a/src/main/scala/optional/types/MultiSelectInput.scala b/src/main/scala/optional/types/MultiSelectInput.scala new file mode 100644 index 0000000..ea8e35f --- /dev/null +++ b/src/main/scala/optional/types/MultiSelectInput.scala @@ -0,0 +1,9 @@ +package optional.types + +class MultiSelectInput[T](var value: Set[T], val options: Set[T]) + +object MultiSelectInput { + def apply[T](options:T*) = new MultiSelectInput[T](Set(), options.toSet) +} + + diff --git a/src/main/scala/optional/types/SelectInput.scala b/src/main/scala/optional/types/SelectInput.scala new file mode 100644 index 0000000..6d058c7 --- /dev/null +++ b/src/main/scala/optional/types/SelectInput.scala @@ -0,0 +1,7 @@ +package optional.types + +class SelectInput[T](var value: Option[T], val options: Set[T]) + +object SelectInput{ + def apply[T](options: T*) = new SelectInput[T](value = None, options = options.toSet) +} diff --git a/src/test/scala/optional/ArgumentParserTest.scala b/src/test/scala/optional/ArgumentParserTest.scala index f80c13a..b0e2879 100644 --- a/src/test/scala/optional/ArgumentParserTest.scala +++ b/src/test/scala/optional/ArgumentParserTest.scala @@ -7,7 +7,8 @@ import scala.collection._ class ArgumentParserTest extends FunSuite with ShouldMatchers { test("parse") { - val fieldArgs = classOf[SimpleClass].getDeclaredFields.map{f => new FieldArgAssignable(f)} + val c = SimpleClass("a", 0, 1.4, 2) + val fieldArgs = classOf[SimpleClass].getDeclaredFields.map{f => new FieldArgAssignable(f, c)} val argParser = new ArgumentParser(fieldArgs) { diff --git a/src/test/scala/optional/ObjectToArgsTest.scala b/src/test/scala/optional/ObjectToArgsTest.scala index e0f3940..6e58480 100644 --- a/src/test/scala/optional/ObjectToArgsTest.scala +++ b/src/test/scala/optional/ObjectToArgsTest.scala @@ -1,5 +1,6 @@ package optional +import types.{SelectInput,MultiSelectInput} import org.scalatest.FunSuite import org.scalatest.matchers.ShouldMatchers @@ -96,6 +97,43 @@ class ObjectToArgsTest extends FunSuite with ShouldMatchers { exc.cause.getMessage should include ("type") exc.cause.getMessage should include ("MyFunkyType") } + + test("set args") { + case class SetArgs(val set: Set[String]) extends FieldParsing + val s = new SetArgs(null) + s.parse(Array("--set", "a,b,c,def")) + s.set should be (Set("a", "b", "c", "def")) + } + + test("selectInput") { + case class SelectInputArgs(val select: SelectInput[String] = SelectInput("a", "b", "c")) extends FieldParsing + val s = new SelectInputArgs() + val id = System.identityHashCode(s.select) + s.parse(Array("--select", "b")) + s.select.value should be (Some("b")) + System.identityHashCode(s.select) should be (id) + s.select.options should be (Set("a", "b", "c")) + + evaluating {s.parse(Array("--select", "q"))} should produce [ArgException] + } + + test("multiSelectInput") { + case class MultiSelectInputArgs(val multiSelect: MultiSelectInput[String] = MultiSelectInput("a", "b", "c")) extends FieldParsing + val s = new MultiSelectInputArgs() + val id = System.identityHashCode(s.multiSelect) + s.parse(Array("--multiSelect", "b")) + s.multiSelect.value should be (Set("b")) + System.identityHashCode(s.multiSelect) should be (id) + s.multiSelect.options should be (Set("a", "b", "c")) + + s.parse(Array("--multiSelect", "b,c")) + s.multiSelect.value should be (Set("b", "c")) + + evaluating {s.parse(Array("--multiSelect", "q"))} should produce [ArgException] + evaluating {s.parse(Array("--multiSelect", "b,q"))} should produce [ArgException] + evaluating {s.parse(Array("--multiSelect", "q,b"))} should produce [ArgException] + + } } @@ -111,7 +149,7 @@ case class MyFunkyType(val stuff: String) object MyFunkyTypeParser extends Parser[MyFunkyType] { def canParse(tpe: java.lang.reflect.Type) = classOf[MyFunkyType].isAssignableFrom(tpe.asInstanceOf[Class[_]]) - def parse(s: String, tpe: java.lang.reflect.Type) = + def parse(s: String, tpe: java.lang.reflect.Type, currentValue: AnyRef) = MyFunkyType(s + "oogabooga") } diff --git a/src/test/scala/optional/ParserTest.scala b/src/test/scala/optional/ParserTest.scala index 7c8a8cc..6517b43 100644 --- a/src/test/scala/optional/ParserTest.scala +++ b/src/test/scala/optional/ParserTest.scala @@ -23,15 +23,15 @@ class ParserTest extends FunSuite with ShouldMatchers { //Is there is better way to get a handle on parameterized types???? val field = classOf[ContainerA].getDeclaredField("boundaries") - val parsed = ParseHelper.parseInto("a,b,cdef,g", field.getGenericType) + val parsed = ParseHelper.parseInto("a,b,cdef,g", field.getGenericType, "dummy") parsed should be (Some(ValueHolder(List("a", "b", "cdef", "g"), field.getGenericType))) } test("ParseHelper") { - ParseHelper.parseInto("ooga", classOf[String]) should be (Some(ValueHolder("ooga", classOf[String]))) - ParseHelper.parseInto("5.6", classOf[Double]) should be (Some(ValueHolder(5.6, classOf[Double]))) - ParseHelper.parseInto("5.6", classOf[String]) should be (Some(ValueHolder("5.6", classOf[String]))) - ParseHelper.parseInto("abc", classOf[RandomUnknownClass]) should be (None) + ParseHelper.parseInto("ooga", classOf[String], "dummy") should be (Some(ValueHolder("ooga", classOf[String]))) + ParseHelper.parseInto("5.6", classOf[Double], "dummy") should be (Some(ValueHolder(5.6, classOf[Double]))) + ParseHelper.parseInto("5.6", classOf[String], "dummy") should be (Some(ValueHolder("5.6", classOf[String]))) + ParseHelper.parseInto("abc", classOf[RandomUnknownClass], "dummy") should be (None) } } From 63833061c26d9bae814bcc24237281624e0ba4d3 Mon Sep 17 00:00:00 2001 From: Imran Rashid Date: Mon, 3 Dec 2012 17:29:57 -0800 Subject: [PATCH 19/50] move code to reflet package organization --- src/main/scala/{ => optional}/ArgumentParser.scala | 0 src/main/scala/{ => optional}/ObjectToArgs.scala | 0 src/main/scala/{ => optional}/Parser.scala | 0 src/main/scala/{ => optional}/ReflectionUtils.scala | 0 4 files changed, 0 insertions(+), 0 deletions(-) rename src/main/scala/{ => optional}/ArgumentParser.scala (100%) rename src/main/scala/{ => optional}/ObjectToArgs.scala (100%) rename src/main/scala/{ => optional}/Parser.scala (100%) rename src/main/scala/{ => optional}/ReflectionUtils.scala (100%) diff --git a/src/main/scala/ArgumentParser.scala b/src/main/scala/optional/ArgumentParser.scala similarity index 100% rename from src/main/scala/ArgumentParser.scala rename to src/main/scala/optional/ArgumentParser.scala diff --git a/src/main/scala/ObjectToArgs.scala b/src/main/scala/optional/ObjectToArgs.scala similarity index 100% rename from src/main/scala/ObjectToArgs.scala rename to src/main/scala/optional/ObjectToArgs.scala diff --git a/src/main/scala/Parser.scala b/src/main/scala/optional/Parser.scala similarity index 100% rename from src/main/scala/Parser.scala rename to src/main/scala/optional/Parser.scala diff --git a/src/main/scala/ReflectionUtils.scala b/src/main/scala/optional/ReflectionUtils.scala similarity index 100% rename from src/main/scala/ReflectionUtils.scala rename to src/main/scala/optional/ReflectionUtils.scala From 8a0eceaa7f718d5dda8b41fb6ebd4552ab64a405 Mon Sep 17 00:00:00 2001 From: Imran Rashid Date: Mon, 3 Dec 2012 17:35:03 -0800 Subject: [PATCH 20/50] remove unneeded jars --- lib/optional.jar | 1 - lib/paranamer-1.3.jar | Bin 27580 -> 0 bytes 2 files changed, 1 deletion(-) delete mode 120000 lib/optional.jar delete mode 100644 lib/paranamer-1.3.jar diff --git a/lib/optional.jar b/lib/optional.jar deleted file mode 120000 index bcd8c30..0000000 --- a/lib/optional.jar +++ /dev/null @@ -1 +0,0 @@ -../target/optional-0.1.jar \ No newline at end of file diff --git a/lib/paranamer-1.3.jar b/lib/paranamer-1.3.jar deleted file mode 100644 index 9cb7c61c53a3a58e4ee67be08493139244e8c058..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 27580 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 Date: Mon, 3 Dec 2012 18:10:30 -0800 Subject: [PATCH 21/50] add ArgApp, but doesnt really work as cleanly as I wanted --- src/main/scala/optional/ArgApp.scala | 35 ++++++++++++++++++++++++ src/test/scala/optional/ArgAppTest.scala | 26 ++++++++++++++++++ 2 files changed, 61 insertions(+) create mode 100644 src/main/scala/optional/ArgApp.scala create mode 100644 src/test/scala/optional/ArgAppTest.scala diff --git a/src/main/scala/optional/ArgApp.scala b/src/main/scala/optional/ArgApp.scala new file mode 100644 index 0000000..49292cd --- /dev/null +++ b/src/main/scala/optional/ArgApp.scala @@ -0,0 +1,35 @@ +package optional + +trait ArgApp[T <: FieldParsing] { + +// unfortunately, can't get this to work. can't make manifest available :( +// def main(rawArgs: Array[String]) { +// mainHelper(rawArgs) +// } + + def mainHelper(rawArgs: Array[String])(implicit m: Manifest[T]) { + val args = m.erasure.newInstance().asInstanceOf[T] + args.parse(rawArgs) + main(args) + } + + def main(args: T) : Unit +} + + +//below is just for testing, but want it in compiled classes ... + +class MyArgs extends FieldParsing { + val a: String = "" + val b: Int = 0 +} + +object MyApp extends ArgApp[MyArgs] { + def main(args: Array[String]) { + mainHelper(args) + } + def main(args: MyArgs) { + println(args.a) + println(args.b) + } +} diff --git a/src/test/scala/optional/ArgAppTest.scala b/src/test/scala/optional/ArgAppTest.scala new file mode 100644 index 0000000..1b1c457 --- /dev/null +++ b/src/test/scala/optional/ArgAppTest.scala @@ -0,0 +1,26 @@ +package optional + +import org.scalatest.FunSuite +import org.scalatest.matchers.ShouldMatchers + +class ArgAppTest extends FunSuite { + + test("main") { + val m = new MyApp() + m.main(Array("--a", "hello", "--b", "17")) + } + +} + +class MyArgs extends FieldParsing { + val a: String = "" + val b: Int = 0 +} + +class MyApp extends ArgApp[MyArgs] with ShouldMatchers { + def main(args: MyArgs) { + args.a should be ("hello") + args.b should be (17) + } +} + From 661f66582a2009414b87027554e38bb5fab4a25c Mon Sep 17 00:00:00 2001 From: Imran Rashid Date: Tue, 4 Dec 2012 07:37:04 -0800 Subject: [PATCH 22/50] working version of ArgApp --- src/main/scala/optional/ArgApp.scala | 36 ++++++++++++++++++++-------- 1 file changed, 26 insertions(+), 10 deletions(-) diff --git a/src/main/scala/optional/ArgApp.scala b/src/main/scala/optional/ArgApp.scala index 49292cd..009e6bf 100644 --- a/src/main/scala/optional/ArgApp.scala +++ b/src/main/scala/optional/ArgApp.scala @@ -1,14 +1,34 @@ package optional +import java.lang.reflect.{ParameterizedType, Type} + trait ArgApp[T <: FieldParsing] { -// unfortunately, can't get this to work. can't make manifest available :( -// def main(rawArgs: Array[String]) { -// mainHelper(rawArgs) -// } + def main(rawArgs: Array[String]) { + mainHelper(rawArgs) + } + + private def getArgumentClass() = { + val argApp = this.getClass.getGenericInterfaces.find{tpe => + val ptpe = tpe.asInstanceOf[ParameterizedType] + ParseHelper.checkType(ptpe, classOf[ArgApp[_]]) + } + getRawClass(argApp.get.asInstanceOf[ParameterizedType].getActualTypeArguments.apply(0)) + } + + private def getRawClass(tpe: Type) = { + tpe match { + case x:Class[_] => x + case p:ParameterizedType => p.getRawType.asInstanceOf[Class[_]] + } + } - def mainHelper(rawArgs: Array[String])(implicit m: Manifest[T]) { - val args = m.erasure.newInstance().asInstanceOf[T] + private def mainHelper(rawArgs: Array[String]) { + val argClass = getArgumentClass() + val ctors = argClass.getDeclaredConstructors() + val ctor = ctors.find(ctor => ctor.getGenericParameterTypes.length == 0).get + ctor.setAccessible(true) + val args = ctor.newInstance().asInstanceOf[T] args.parse(rawArgs) main(args) } @@ -16,7 +36,6 @@ trait ArgApp[T <: FieldParsing] { def main(args: T) : Unit } - //below is just for testing, but want it in compiled classes ... class MyArgs extends FieldParsing { @@ -25,9 +44,6 @@ class MyArgs extends FieldParsing { } object MyApp extends ArgApp[MyArgs] { - def main(args: Array[String]) { - mainHelper(args) - } def main(args: MyArgs) { println(args.a) println(args.b) From 84326fd66aa1e4349857f69bbebc2ce819d5f8bc Mon Sep 17 00:00:00 2001 From: Imran Rashid Date: Sun, 9 Dec 2012 17:41:01 -0800 Subject: [PATCH 23/50] expose argHolder of ArgApp --- src/main/scala/optional/ArgApp.scala | 25 ++++++++++++++++++------- 1 file changed, 18 insertions(+), 7 deletions(-) diff --git a/src/main/scala/optional/ArgApp.scala b/src/main/scala/optional/ArgApp.scala index 009e6bf..4bba820 100644 --- a/src/main/scala/optional/ArgApp.scala +++ b/src/main/scala/optional/ArgApp.scala @@ -8,6 +8,22 @@ trait ArgApp[T <: FieldParsing] { mainHelper(rawArgs) } + private lazy val argHolder = { + val argClass = getArgumentClass() + val ctors = argClass.getDeclaredConstructors() + val ctor = ctors.find(ctor => ctor.getGenericParameterTypes.length == 0).get + ctor.setAccessible(true) + ctor.newInstance().asInstanceOf[T] + } + + /** + * get the instance of T that holds the parsed args. + * + * not needed for the user that just wants to run their code -- this is accessible just for other libs + * built on top. + */ + def getArgHolder : T = argHolder + private def getArgumentClass() = { val argApp = this.getClass.getGenericInterfaces.find{tpe => val ptpe = tpe.asInstanceOf[ParameterizedType] @@ -24,13 +40,8 @@ trait ArgApp[T <: FieldParsing] { } private def mainHelper(rawArgs: Array[String]) { - val argClass = getArgumentClass() - val ctors = argClass.getDeclaredConstructors() - val ctor = ctors.find(ctor => ctor.getGenericParameterTypes.length == 0).get - ctor.setAccessible(true) - val args = ctor.newInstance().asInstanceOf[T] - args.parse(rawArgs) - main(args) + argHolder.parse(rawArgs) + main(argHolder) } def main(args: T) : Unit From b9532c5d2a92dab6dc548bebc69b3a5b04cb52ef Mon Sep 17 00:00:00 2001 From: Imran Rashid Date: Sun, 9 Dec 2012 18:02:51 -0800 Subject: [PATCH 24/50] fix ArgApp to deal w/ multiple traits --- src/main/scala/optional/ArgApp.scala | 7 +++++-- src/test/scala/optional/ArgAppTest.scala | 4 +++- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/src/main/scala/optional/ArgApp.scala b/src/main/scala/optional/ArgApp.scala index 4bba820..74cf9bd 100644 --- a/src/main/scala/optional/ArgApp.scala +++ b/src/main/scala/optional/ArgApp.scala @@ -26,8 +26,11 @@ trait ArgApp[T <: FieldParsing] { private def getArgumentClass() = { val argApp = this.getClass.getGenericInterfaces.find{tpe => - val ptpe = tpe.asInstanceOf[ParameterizedType] - ParseHelper.checkType(ptpe, classOf[ArgApp[_]]) + tpe match { + case ptpe: ParameterizedType => + ParseHelper.checkType(ptpe, classOf[ArgApp[_]]) + case _ => false + } } getRawClass(argApp.get.asInstanceOf[ParameterizedType].getActualTypeArguments.apply(0)) } diff --git a/src/test/scala/optional/ArgAppTest.scala b/src/test/scala/optional/ArgAppTest.scala index 1b1c457..8121f8f 100644 --- a/src/test/scala/optional/ArgAppTest.scala +++ b/src/test/scala/optional/ArgAppTest.scala @@ -17,10 +17,12 @@ class MyArgs extends FieldParsing { val b: Int = 0 } -class MyApp extends ArgApp[MyArgs] with ShouldMatchers { +class MyApp extends Dummy with ArgApp[MyArgs] with ShouldMatchers { def main(args: MyArgs) { args.a should be ("hello") args.b should be (17) } } +trait Dummy + From 9dfe3cc88d65471d4d73644f65bda011e59e4703 Mon Sep 17 00:00:00 2001 From: Imran Rashid Date: Sun, 9 Dec 2012 18:17:54 -0800 Subject: [PATCH 25/50] dont turn scala helper fields & library helper fields into arguments --- src/main/scala/optional/ObjectToArgs.scala | 4 ++- .../scala/optional/ObjectToArgsTest.scala | 29 +++++++++++++++++++ 2 files changed, 32 insertions(+), 1 deletion(-) diff --git a/src/main/scala/optional/ObjectToArgs.scala b/src/main/scala/optional/ObjectToArgs.scala index 3c99c52..e79ca2f 100644 --- a/src/main/scala/optional/ObjectToArgs.scala +++ b/src/main/scala/optional/ObjectToArgs.scala @@ -6,9 +6,11 @@ package optional class ObjectToArgs(val obj: Object) { val argParser = new ArgumentParser[FieldArgAssignable]( - ReflectionUtils.getAllDeclaredFields(obj.getClass).map{f => new FieldArgAssignable(f, obj)} + ReflectionUtils.getAllDeclaredFields(obj.getClass). + filter{f => f.getName != "parser" && f.getName != "bitmap$0"}.map{f => new FieldArgAssignable(f, obj)} ) + def parse(args: Array[String], preParsers: Iterator[Parser[_]] = Iterator(), postParsers: Iterator[Parser[_]] = Iterator()) { diff --git a/src/test/scala/optional/ObjectToArgsTest.scala b/src/test/scala/optional/ObjectToArgsTest.scala index 6e58480..85bd993 100644 --- a/src/test/scala/optional/ObjectToArgsTest.scala +++ b/src/test/scala/optional/ObjectToArgsTest.scala @@ -134,6 +134,25 @@ class ObjectToArgsTest extends FunSuite with ShouldMatchers { evaluating {s.parse(Array("--multiSelect", "q,b"))} should produce [ArgException] } + + test("exclude scala helper fields") { + + { + val m = new MixedTypes(null, 0) + val o = new ObjectToArgs(m) + val names = o.argParser.nameToHolder.keySet + names should be (Set("name", "count")) + } + + + { + val s = new SomeApp() + val names = s.getArgHolder.parser.argParser.nameToHolder.keySet + println(names) + names should be (Set("x", "y")) + } + + } } @@ -154,3 +173,13 @@ object MyFunkyTypeParser extends Parser[MyFunkyType] { } case class SpecialTypes(val name: String, val funky: MyFunkyType) + + +class SomeApp extends ArgApp[SomeArgs] { + def main(args: SomeArgs) {} +} + +class SomeArgs extends FieldParsing { + var x: Int = 0 + var y: String = "hello" +} From 3733464402baf4bea3e7a81f24fddaefb9a0cb4c Mon Sep 17 00:00:00 2001 From: Imran Rashid Date: Fri, 14 Dec 2012 16:01:46 -0800 Subject: [PATCH 26/50] ignore fields with unknown types; get rid of pre- and post-parser stuff --- src/main/scala/optional/ArgumentParser.scala | 7 +++++ src/main/scala/optional/ObjectToArgs.scala | 14 ++++----- .../scala/optional/ObjectToArgsTest.scala | 30 ++++--------------- 3 files changed, 17 insertions(+), 34 deletions(-) diff --git a/src/main/scala/optional/ArgumentParser.scala b/src/main/scala/optional/ArgumentParser.scala index d172b11..ad3a70a 100644 --- a/src/main/scala/optional/ArgumentParser.scala +++ b/src/main/scala/optional/ArgumentParser.scala @@ -51,6 +51,13 @@ class ArgumentParser[T <: ArgAssignable] (val argHolders: Seq[T]) { } +object ArgumentParser { + def apply[T <: ArgAssignable](argHolders: Traversable[T]) = { + //ignore things we dont' know how to parse + new ArgumentParser(argHolders.toSeq.filter{t => ParseHelper.findParser(t.getType).isDefined}) + } +} + trait ArgAssignable { def getName : String def getType: Type diff --git a/src/main/scala/optional/ObjectToArgs.scala b/src/main/scala/optional/ObjectToArgs.scala index e79ca2f..abe7254 100644 --- a/src/main/scala/optional/ObjectToArgs.scala +++ b/src/main/scala/optional/ObjectToArgs.scala @@ -5,16 +5,14 @@ package optional */ class ObjectToArgs(val obj: Object) { - val argParser = new ArgumentParser[FieldArgAssignable]( + val argParser = ArgumentParser[FieldArgAssignable]( ReflectionUtils.getAllDeclaredFields(obj.getClass). filter{f => f.getName != "parser" && f.getName != "bitmap$0"}.map{f => new FieldArgAssignable(f, obj)} ) - def parse(args: Array[String], - preParsers: Iterator[Parser[_]] = Iterator(), - postParsers: Iterator[Parser[_]] = Iterator()) { - val parsed = argParser.parse(args, preParsers, postParsers) + def parse(args: Array[String]) { + val parsed = argParser.parse(args) parsed.foreach{ kv => val field = kv._1.field @@ -28,10 +26,8 @@ class ObjectToArgs(val obj: Object) { trait FieldParsing { lazy val parser = new ObjectToArgs(this) - def parse(args: Array[String], - preParsers: Iterator[Parser[_]] = Iterator(), - postParsers: Iterator[Parser[_]] = Iterator()) { - parser.parse(args, preParsers, postParsers) + def parse(args: Array[String]) { + parser.parse(args) } def helpMessage = parser.helpMessage diff --git a/src/test/scala/optional/ObjectToArgsTest.scala b/src/test/scala/optional/ObjectToArgsTest.scala index 85bd993..f802095 100644 --- a/src/test/scala/optional/ObjectToArgsTest.scala +++ b/src/test/scala/optional/ObjectToArgsTest.scala @@ -52,20 +52,6 @@ class ObjectToArgsTest extends FunSuite with ShouldMatchers { o.flag should be (true) } - test("custom parsers") { - val o = new SpecialTypes(null, null) with FieldParsing - - o.parse(Array("--name", "blah")) - o.name should be ("blah") - - evaluating {o.parse(Array("--funky", "xyz"))} should produce [Exception] - - o.parse(Array("--funky", "xyz", "--name", "hi"), preParsers = Iterator(MyFunkyTypeParser)) - o.name should be ("hi") - o.funky should be (MyFunkyType("xyzoogabooga")) - - } - test("help message") { val o = new StringHolder(null, null) val parser = new ObjectToArgs(o) @@ -94,8 +80,9 @@ class ObjectToArgsTest extends FunSuite with ShouldMatchers { o.funky should be (null) val exc = evaluating {o.parse(Array("--funky", "xyz"))} should produce [ArgException] - exc.cause.getMessage should include ("type") - exc.cause.getMessage should include ("MyFunkyType") + //maybe sometime I should change the removal of unknown types to keep them around for error msgs ... +// exc.cause.getMessage should include ("type") +// exc.cause.getMessage should include ("MyFunkyType") } test("set args") { @@ -163,17 +150,10 @@ case class MixedTypes(val name: String, val count: Int) //is there an easier way to do this in scala? class Child(val flag: Boolean, name: String, count: Int) extends MixedTypes(name, count) -case class MyFunkyType(val stuff: String) - -object MyFunkyTypeParser extends Parser[MyFunkyType] { - def canParse(tpe: java.lang.reflect.Type) = - classOf[MyFunkyType].isAssignableFrom(tpe.asInstanceOf[Class[_]]) - def parse(s: String, tpe: java.lang.reflect.Type, currentValue: AnyRef) = - MyFunkyType(s + "oogabooga") -} - case class SpecialTypes(val name: String, val funky: MyFunkyType) +case class MyFunkyType(val x: String) + class SomeApp extends ArgApp[SomeArgs] { def main(args: SomeArgs) {} From 501575d91acfbb29c0b28cd2e635486465772d7e Mon Sep 17 00:00:00 2001 From: Imran Rashid Date: Mon, 17 Dec 2012 16:40:19 -0800 Subject: [PATCH 27/50] more useful error msg: say which field we were trying to parse during error --- src/main/scala/optional/ArgumentParser.scala | 15 ++++++++++----- src/test/scala/optional/ObjectToArgsTest.scala | 10 +++++++++- 2 files changed, 19 insertions(+), 6 deletions(-) diff --git a/src/main/scala/optional/ArgumentParser.scala b/src/main/scala/optional/ArgumentParser.scala index ad3a70a..2533bdf 100644 --- a/src/main/scala/optional/ArgumentParser.scala +++ b/src/main/scala/optional/ArgumentParser.scala @@ -25,16 +25,21 @@ class ArgumentParser[T <: ArgAssignable] (val argHolders: Seq[T]) { val holderOption = nameToHolder.get(name) if (holderOption.isEmpty) throw new ArgException("unknown option " + name + "\n" + helpMessage) - val parsed = ParseHelper.parseInto(args(idx +1), holderOption.get.getType, holderOption.get.getCurrentValue, - preParsers, postParsers) - parsed match { - case Some(x) => result(holderOption.get) = x - case None => throw new ArgException("don't know how to parse type: " + holderOption.get.getType) + try { + val parsed = ParseHelper.parseInto(args(idx +1), holderOption.get.getType, holderOption.get.getCurrentValue, + preParsers, postParsers) + parsed match { + case Some(x) => result(holderOption.get) = x + case None => throw new ArgException("don't know how to parse type: " + holderOption.get.getType) + } + } catch { + case exc => throw new ArgException("Error parsing \"" + args(idx + 1) + "\" into field \"" + name + "\" (type = " + holderOption.get.getType + ")\n" + helpMessage, exc) } idx += 2 } result } catch { + case ae: ArgException => throw ae case exc => throw new ArgException(helpMessage, exc) } } diff --git a/src/test/scala/optional/ObjectToArgsTest.scala b/src/test/scala/optional/ObjectToArgsTest.scala index f802095..dae2e24 100644 --- a/src/test/scala/optional/ObjectToArgsTest.scala +++ b/src/test/scala/optional/ObjectToArgsTest.scala @@ -85,6 +85,15 @@ class ObjectToArgsTest extends FunSuite with ShouldMatchers { // exc.cause.getMessage should include ("MyFunkyType") } + + test("good error msg") { + val o = new MixedTypes("", 0) with FieldParsing + + val exc1 = evaluating {o.parse(Array("--count", "hi"))} should produce [ArgException] + //don't actually need the message to look *exactly* like this, but extremely useful for it to at least say what it was trying to parse + exc1.getMessage should startWith ("""Error parsing "hi" into field "count" (type = int)""") + } + test("set args") { case class SetArgs(val set: Set[String]) extends FieldParsing val s = new SetArgs(null) @@ -135,7 +144,6 @@ class ObjectToArgsTest extends FunSuite with ShouldMatchers { { val s = new SomeApp() val names = s.getArgHolder.parser.argParser.nameToHolder.keySet - println(names) names should be (Set("x", "y")) } From c684b7391b8b14b5e3cd8b881f627a9af84773a7 Mon Sep 17 00:00:00 2001 From: Imran Rashid Date: Wed, 19 Dec 2012 19:02:24 -0800 Subject: [PATCH 28/50] add support for Files and Regex --- src/main/scala/optional/Parser.scala | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/src/main/scala/optional/Parser.scala b/src/main/scala/optional/Parser.scala index 85ac63b..474255d 100644 --- a/src/main/scala/optional/Parser.scala +++ b/src/main/scala/optional/Parser.scala @@ -2,6 +2,8 @@ package optional import types.{SelectInput,MultiSelectInput} import java.lang.reflect.{Type, ParameterizedType} +import util.matching.Regex +import java.io.File trait Parser[T] { def parse(s: String, tpe: Type, currentValue: AnyRef): T @@ -65,6 +67,18 @@ object DoubleParser extends SimpleParser[Double] { def parse(s: String) = s.toDouble } +object RegexParser extends SimpleParser[Regex] { + val knownTypes : Set[Class[_]] = Set(classOf[Regex]) + def getKnownTypes = knownTypes + def parse(s: String) = s.r +} + +object FileParser extends SimpleParser[File] { + val knownTypes : Set[Class[_]] = Set(classOf[File]) + def getKnownTypes = knownTypes + def parse(s: String) = new File(s) //TODO maybe this should deal w/ things like "~" +} + //TODO CompoundParser are both a pain to write, and extremely unsafe. Design needs some work object ListParser extends CompoundParser[List[_]] { From e3a573f560095ff2e838a1b3737fc7e53245fb53 Mon Sep 17 00:00:00 2001 From: Imran Rashid Date: Thu, 20 Dec 2012 07:19:51 -0800 Subject: [PATCH 29/50] add suport for ~ to FileParser, w/ some tests --- src/main/scala/optional/Parser.scala | 5 ++++- src/test/scala/optional/ParserTest.scala | 4 ++++ 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/src/main/scala/optional/Parser.scala b/src/main/scala/optional/Parser.scala index 474255d..21e8dd5 100644 --- a/src/main/scala/optional/Parser.scala +++ b/src/main/scala/optional/Parser.scala @@ -76,7 +76,10 @@ object RegexParser extends SimpleParser[Regex] { object FileParser extends SimpleParser[File] { val knownTypes : Set[Class[_]] = Set(classOf[File]) def getKnownTypes = knownTypes - def parse(s: String) = new File(s) //TODO maybe this should deal w/ things like "~" + def parse(s: String) = { + val fullPath = if (s.startsWith("~")) s.replaceFirst("~", System.getProperty("user.home")) else s + new File(fullPath) + } } //TODO CompoundParser are both a pain to write, and extremely unsafe. Design needs some work diff --git a/src/test/scala/optional/ParserTest.scala b/src/test/scala/optional/ParserTest.scala index 6517b43..9f826ad 100644 --- a/src/test/scala/optional/ParserTest.scala +++ b/src/test/scala/optional/ParserTest.scala @@ -16,6 +16,10 @@ class ParserTest extends FunSuite with ShouldMatchers { DoubleParser.parse("1e-10") should be (1e-10) BooleanParser.parse("false") should be (false) BooleanParser.parse("true") should be (true) + val homeDir = System.getProperty("user.home") + FileParser.parse("~/foo") should be (new java.io.File(homeDir, "foo")) + val cwd = System.getProperty("user.dir") + FileParser.parse("ooga").getAbsolutePath should be (new java.io.File(cwd, "ooga").getAbsolutePath) } test("ListParser") { From c444bf28a1d9e4407a77630f5dedff2a615714d4 Mon Sep 17 00:00:00 2001 From: Imran Rashid Date: Wed, 19 Dec 2012 19:11:49 -0800 Subject: [PATCH 30/50] some renaming and adding to "ArgApp" --- src/main/scala/optional/ArgApp.scala | 34 +++++++++++++++++++++------- 1 file changed, 26 insertions(+), 8 deletions(-) diff --git a/src/main/scala/optional/ArgApp.scala b/src/main/scala/optional/ArgApp.scala index 74cf9bd..22979e9 100644 --- a/src/main/scala/optional/ArgApp.scala +++ b/src/main/scala/optional/ArgApp.scala @@ -2,13 +2,9 @@ package optional import java.lang.reflect.{ParameterizedType, Type} -trait ArgApp[T <: FieldParsing] { +trait Argable[T <: FieldParsing] { - def main(rawArgs: Array[String]) { - mainHelper(rawArgs) - } - - private lazy val argHolder = { + protected lazy val argHolder = { val argClass = getArgumentClass() val ctors = argClass.getDeclaredConstructors() val ctor = ctors.find(ctor => ctor.getGenericParameterTypes.length == 0).get @@ -28,7 +24,7 @@ trait ArgApp[T <: FieldParsing] { val argApp = this.getClass.getGenericInterfaces.find{tpe => tpe match { case ptpe: ParameterizedType => - ParseHelper.checkType(ptpe, classOf[ArgApp[_]]) + ParseHelper.checkType(ptpe, classOf[Argable[_]]) case _ => false } } @@ -42,6 +38,13 @@ trait ArgApp[T <: FieldParsing] { } } +} + +trait ArgMain[T <: FieldParsing] extends Argable[T] { + def main(rawArgs: Array[String]) { + mainHelper(rawArgs) + } + private def mainHelper(rawArgs: Array[String]) { argHolder.parse(rawArgs) main(argHolder) @@ -50,6 +53,16 @@ trait ArgApp[T <: FieldParsing] { def main(args: T) : Unit } +trait ArgFunction[T <: FieldParsing, U] extends Function[T,U] + +trait ArgApp[T <: FieldParsing] extends Argable[T] with App { + override + def main(args: Array[String]) { + argHolder.parse(args) + super.main(args) + } +} + //below is just for testing, but want it in compiled classes ... class MyArgs extends FieldParsing { @@ -57,9 +70,14 @@ class MyArgs extends FieldParsing { val b: Int = 0 } -object MyApp extends ArgApp[MyArgs] { +object MyMain extends ArgMain[MyArgs] { def main(args: MyArgs) { println(args.a) println(args.b) } } + +object MyApp extends ArgApp[MyArgs] { + println(argHolder.a) + println(argHolder.b) +} From d9f4ddcba8170780de9c35e6c1c2f4db97ea03c2 Mon Sep 17 00:00:00 2001 From: Imran Rashid Date: Fri, 28 Dec 2012 11:26:33 -0800 Subject: [PATCH 31/50] eliminate more preParser & postParser stuff --- src/main/scala/optional/ArgumentParser.scala | 7 ++----- src/main/scala/optional/Parser.scala | 10 ++++------ 2 files changed, 6 insertions(+), 11 deletions(-) diff --git a/src/main/scala/optional/ArgumentParser.scala b/src/main/scala/optional/ArgumentParser.scala index 2533bdf..f7c14ca 100644 --- a/src/main/scala/optional/ArgumentParser.scala +++ b/src/main/scala/optional/ArgumentParser.scala @@ -8,9 +8,7 @@ class ArgumentParser[T <: ArgAssignable] (val argHolders: Seq[T]) { lazy val nameToHolder = argHolders.map{ a => a.getName -> a}.toMap - def parse(args: Array[String], - preParsers: Iterator[Parser[_]] = Iterator(), - postParsers: Iterator[Parser[_]] = Iterator()) : Map[T, ValueHolder[_]] = { + def parse(args: Array[String]) : Map[T, ValueHolder[_]] = { try { val result = mutable.Map[T, ValueHolder[_]]() var idx = 0 @@ -26,8 +24,7 @@ class ArgumentParser[T <: ArgAssignable] (val argHolders: Seq[T]) { if (holderOption.isEmpty) throw new ArgException("unknown option " + name + "\n" + helpMessage) try { - val parsed = ParseHelper.parseInto(args(idx +1), holderOption.get.getType, holderOption.get.getCurrentValue, - preParsers, postParsers) + val parsed = ParseHelper.parseInto(args(idx +1), holderOption.get.getType, holderOption.get.getCurrentValue) parsed match { case Some(x) => result(holderOption.get) = x case None => throw new ArgException("don't know how to parse type: " + holderOption.get.getType) diff --git a/src/main/scala/optional/Parser.scala b/src/main/scala/optional/Parser.scala index 21e8dd5..8bcf7f9 100644 --- a/src/main/scala/optional/Parser.scala +++ b/src/main/scala/optional/Parser.scala @@ -175,19 +175,17 @@ object ParseHelper { val parsers = Seq(StringParser, IntParser, LongParser, FloatParser, DoubleParser, BooleanParser, ListParser, SetParser, SelectInputParser, MultiSelectInputParser) - def findParser(tpe: Type, preParsers: Iterator[Parser[_]] = Iterator(), postParsers: Iterator[Parser[_]] = Iterator()) : Option[Parser[_]] = { - for (p <- (preParsers ++ parsers.iterator ++ postParsers)) { + def findParser(tpe: Type) : Option[Parser[_]] = { + for (p <- parsers.iterator) { if (p.canParse(tpe)) return Some(p) } None } - def parseInto[T](s: String, tpe: Type, currentValue: AnyRef, - preParsers: Iterator[Parser[_]] = Iterator(), - postParsers: Iterator[Parser[_]] = Iterator()) : Option[ValueHolder[T]] = { + def parseInto[T](s: String, tpe: Type, currentValue: AnyRef) : Option[ValueHolder[T]] = { //could change this to be a map, at least for the simple types - findParser(tpe, preParsers, postParsers).map{parser => ValueHolder[T](parser.parse(s, tpe, currentValue).asInstanceOf[T], tpe)} + findParser(tpe).map{parser => ValueHolder[T](parser.parse(s, tpe, currentValue).asInstanceOf[T], tpe)} } def checkType(tpe: Type, targetClassSet: Class[_]*) = { From 214ddda08b5cc00c321cace6c643f9898ba3d1df Mon Sep 17 00:00:00 2001 From: Imran Rashid Date: Fri, 28 Dec 2012 12:00:26 -0800 Subject: [PATCH 32/50] add support for supplying an argument description and name via annotations --- src/main/java/optional/Arg.java | 13 +++++ src/main/scala/optional/ArgumentParser.scala | 17 ++++++- .../scala/optional/ObjectToArgsTest.scala | 47 +++++++++++++++++++ 3 files changed, 76 insertions(+), 1 deletion(-) create mode 100644 src/main/java/optional/Arg.java diff --git a/src/main/java/optional/Arg.java b/src/main/java/optional/Arg.java new file mode 100644 index 0000000..5b6bd89 --- /dev/null +++ b/src/main/java/optional/Arg.java @@ -0,0 +1,13 @@ +package optional; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.FIELD) +public @interface Arg { + String name() default ""; + String description() default ""; +} diff --git a/src/main/scala/optional/ArgumentParser.scala b/src/main/scala/optional/ArgumentParser.scala index f7c14ca..218b73f 100644 --- a/src/main/scala/optional/ArgumentParser.scala +++ b/src/main/scala/optional/ArgumentParser.scala @@ -62,6 +62,7 @@ object ArgumentParser { trait ArgAssignable { def getName : String + def getDescription: String def getType: Type def getCurrentValue: AnyRef } @@ -69,7 +70,21 @@ trait ArgAssignable { class FieldArgAssignable(val field: Field, val obj: Object) extends ArgAssignable { field.setAccessible(true) - def getName = field.getName + val annotationOpt = Option(field.getAnnotation(classOf[Arg])) + def getName = { + val n = annotationOpt.map{_.name()}.getOrElse(field.getName) + if (n == "") + field.getName + else + n + } + def getDescription = { + val d = annotationOpt.map{_.description()}.getOrElse(field.getName) + if (d == "") + getName + else + d + } def getType = field.getGenericType def getCurrentValue = field.get(obj) } diff --git a/src/test/scala/optional/ObjectToArgsTest.scala b/src/test/scala/optional/ObjectToArgsTest.scala index dae2e24..7d5a7a1 100644 --- a/src/test/scala/optional/ObjectToArgsTest.scala +++ b/src/test/scala/optional/ObjectToArgsTest.scala @@ -148,6 +148,41 @@ class ObjectToArgsTest extends FunSuite with ShouldMatchers { } } + + + + test("annotations") { + val c = new ClassWithSomeAnnotations() + val o = new ObjectToArgs(c) + o.argParser.nameToHolder.values.foreach { f => + f.getName match { + case "foo" => + f.getDescription should be ("foo") + case "ooga" => + f.getDescription should be ("this is an integer argument") + case "" => + assert(false, "use variable name if no name given in annotation") + case "x" => assert(false, "use name from annotation instead of variable name") + case "y" => + f.getDescription should be ("another integer argument") + case "z" => + assert(false, "use name from annotation instead of variable name") + case "wakka" => + f.getDescription should be ("wakka") + } + } + + o.parse(Array("--foo", "hi", "--ooga", "17", "--y", "181", "--wakka", "1.81")) + c.foo should be ("hi") + c.x should be (17) + c.y should be (181) + c.z should be (1.81) + + evaluating {o.parse(Array("--x", "17"))} should produce [ArgException] + evaluating {o.parse(Array("--z", "1"))} should produce [ArgException] + } + + } @@ -171,3 +206,15 @@ class SomeArgs extends FieldParsing { var x: Int = 0 var y: String = "hello" } + + + +class ClassWithSomeAnnotations { + var foo: String = _ + @Arg(name="ooga", description="this is an integer argument") + var x: Int = _ + @Arg(description="another integer argument") + var y: Int = _ + @Arg(name="wakka") + var z: Double = _ +} \ No newline at end of file From 2949eefb327a5b9c2e4c563314d5319d598b0128 Mon Sep 17 00:00:00 2001 From: Imran Rashid Date: Fri, 28 Dec 2012 12:13:12 -0800 Subject: [PATCH 33/50] include description in usage msg --- src/main/scala/optional/ArgumentParser.scala | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/main/scala/optional/ArgumentParser.scala b/src/main/scala/optional/ArgumentParser.scala index 218b73f..60de93d 100644 --- a/src/main/scala/optional/ArgumentParser.scala +++ b/src/main/scala/optional/ArgumentParser.scala @@ -45,8 +45,7 @@ class ArgumentParser[T <: ArgAssignable] (val argHolders: Seq[T]) { val msg = StringBuilder.newBuilder msg.append("usage: \n") nameToHolder.foreach{ kv => - msg.append("--" + kv._1 + "\t" + kv._2.getType + "\n\n") - //TODO add some way to include a usage message + msg.append("--" + kv._1 + "\t" + kv._2.getType + "\t" + kv._2.getDescription + "\n\n") } msg.toString } From feef754ca72e0a99b0c27b6c5bcbc4d4808679b6 Mon Sep 17 00:00:00 2001 From: Imran Rashid Date: Fri, 28 Dec 2012 14:45:25 -0800 Subject: [PATCH 34/50] update README --- README.md | 114 ++++++++++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 107 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index 8417945..3d47e1b 100644 --- a/README.md +++ b/README.md @@ -1,22 +1,122 @@ -optional is a command line option parser and library. +optional is a command line option parser and library. It tries +to differentiate itself from other libraries by making it dead +simple to define arguments, removing boilerplate and repitition. It +is a very small, lightweight scala library. -#### Example +## Usage +Define a basic container object which extends FieldParsing. Every field of the object +becomes a command line argument with the same name. You can use the parse method to parse +arguments. - case class Arguments(val name: String, val count: Int) + class Arguments with FieldParsing { + var name: String = _ + var count: Int = _ + } object MyApp { def main(args: Array[String]) { - val myArgs = new Arguments(null,0) with FieldParsing + val myArgs = new Arguments() myArgs.parse(args) ... } } -See the test cases for more examples +### ArgApp and ArgMain + +You don't even have to call parse() yourself. The arguments are automatically parsed for you +if you extend ArgApp (for the scala "App" style way of creating a main method) or if you extend +ArgMain (for the main-method version, my personal perference). + +With ArgApp: + + class Arguments with FieldParsing { + var name: String = _ + var count: Int = _ + } + + object MyApp extends ArgApp[Arguments]{ + //this.argHolder is an Arguments object w/ the args already parsed + println(this.argHolder.name) + } + +or with ArgMain: + + class Arguments with FieldParsing { + var name: String = _ + var count: Int = _ + } + + object MyApp extends ArgMain[Arguments]{ + def main(args: Arguments) { + //the cmd line arguments get parsed, and then passed into this function + println(args.name) + } + } + +you could then run these programs with + + java -cp /// MyApp --name foobar --count 17 + +### Mixing In Multiple Traits + +You can use traits to create "sets" of arguments that tend to go together. Because you can mix in multiple traits into +one argument object, this lets you put together the arguments that want, without duplicating argument definitions. + +For example, lets say that you have some set of arguments for a database connection, another set of arguments for a +screen resolution, and another set of arguments for the username. You can define traits for each of these groups: + + trait DBConnectionArgs extends FieldParsing { + var dbHost : String = _ + var dbPort : Int = 4000 + def getDbConnection = { ... } + } + + trait ScreenResolutionArgs extends FieldParsing { + var width: Int = 800 + var height: Int = 600 + } + + trait UsernameArgs extends FieldParsing { + var username: String = _ + } + +Then one application that needs a database connection and a user name could be written as: + + class AppNumberOneArgs extends DBConnectionArgs with UsernameArgs + object AppNumberOne extends ArgMain[AppNumberOneArgs]{ + def main(args: AppNumberOneArgs) = { + val db = args.getDbConnection() + ... + } + } + +And another application that needs a database connection and screen resolution: + + class AppNumeroDosArgs extends DBConnectionArgs with ScreenResolutionArgs + object AppNumeroDos extends ArgMain[AppNumeroDosArgs]{ + def main(args: AppNumeroDosArgs) = { + val db = args.getDbConnection() + ... + } + } + +Note that you are sharing the argument names and types, AND the definition of helper methods like getDbConnection() + +## Status / TODO / Roadmap + +I use this library heavily, so I think it is safe for others to use as well. Of course, that just means I'm used to its +quirks :) There are still a lot of things I'd like to add (and would love contributions from anyone!) +* Support for primitives as a type parameter (eg., List[Int] doesn't work now) +* Add support for user-defined parsers. Not sure about the syntax here +* Automatically support types with an apply(String) method. (hint: http://stackoverflow.com/questions/9172775/get-companion-object-of-class-by-given-generic-type-scala) +* Add support for user-defined validation. again, not sure about the syntax. +* Nested objects, args named via "." +* turn scaladoc into description. may require compiler plugin +* export to properties and typesafe config object -#### Credits +## Credits This was inspired by the optional package from alexy, which in turn came from: @@ -24,4 +124,4 @@ This was inspired by the optional package from alexy, which in turn came from: >Fleshing out and awesomification: paulp. -This is a total rewrite, though. \ No newline at end of file +This is a total rewrite, though. From 378daeca0b425ed33e1729dc74f14572cb128dd4 Mon Sep 17 00:00:00 2001 From: Imran Rashid Date: Fri, 28 Dec 2012 14:47:51 -0800 Subject: [PATCH 35/50] minor update to README --- README.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/README.md b/README.md index 3d47e1b..58ed801 100644 --- a/README.md +++ b/README.md @@ -22,6 +22,11 @@ arguments. } } +Now MyApp has two arguments, "name" and "count". You can run it like: + + java -cp /// MyApp --name foobar --count 17 + + ### ArgApp and ArgMain You don't even have to call parse() yourself. The arguments are automatically parsed for you From 16b0d2bb94cd02dd8eb9ed1675b21859b31e4a7f Mon Sep 17 00:00:00 2001 From: Imran Rashid Date: Fri, 28 Dec 2012 15:03:54 -0800 Subject: [PATCH 36/50] ArgFunction is Argable also --- src/main/scala/optional/ArgApp.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/scala/optional/ArgApp.scala b/src/main/scala/optional/ArgApp.scala index 22979e9..1a2293d 100644 --- a/src/main/scala/optional/ArgApp.scala +++ b/src/main/scala/optional/ArgApp.scala @@ -53,7 +53,7 @@ trait ArgMain[T <: FieldParsing] extends Argable[T] { def main(args: T) : Unit } -trait ArgFunction[T <: FieldParsing, U] extends Function[T,U] +trait ArgFunction[T <: FieldParsing, U] extends Function[T,U] with Argable[T] trait ArgApp[T <: FieldParsing] extends Argable[T] with App { override From c3a511e05720fb931949403751fc22599601e612 Mon Sep 17 00:00:00 2001 From: Imran Rashid Date: Fri, 28 Dec 2012 17:48:24 -0800 Subject: [PATCH 37/50] include FileParser & RegexParser in default Parser list --- src/main/scala/optional/Parser.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/scala/optional/Parser.scala b/src/main/scala/optional/Parser.scala index 8bcf7f9..9474a28 100644 --- a/src/main/scala/optional/Parser.scala +++ b/src/main/scala/optional/Parser.scala @@ -173,7 +173,7 @@ object MultiSelectInputParser extends CompoundParser[MultiSelectInput[_]] { object ParseHelper { val parsers = Seq(StringParser, IntParser, LongParser, FloatParser, DoubleParser, BooleanParser, ListParser, - SetParser, SelectInputParser, MultiSelectInputParser) + SetParser, SelectInputParser, MultiSelectInputParser, FileParser, RegexParser) def findParser(tpe: Type) : Option[Parser[_]] = { for (p <- parsers.iterator) { From e5378f1ab90e16dc81a380afef4f9b051499c247 Mon Sep 17 00:00:00 2001 From: Imran Rashid Date: Sun, 27 Jan 2013 12:51:13 -0800 Subject: [PATCH 38/50] introduce Args type, to allow more ways to specify arguments, and remove ObjectToArgs --- src/main/scala/optional/Args.scala | 19 +++++++ src/main/scala/optional/ArgumentParser.scala | 9 +++ src/main/scala/optional/ObjectToArgs.scala | 38 ++++--------- ...ctToArgsTest.scala => FieldArgsTest.scala} | 57 +++++++------------ 4 files changed, 59 insertions(+), 64 deletions(-) create mode 100644 src/main/scala/optional/Args.scala rename src/test/scala/optional/{ObjectToArgsTest.scala => FieldArgsTest.scala} (78%) diff --git a/src/main/scala/optional/Args.scala b/src/main/scala/optional/Args.scala new file mode 100644 index 0000000..dbab818 --- /dev/null +++ b/src/main/scala/optional/Args.scala @@ -0,0 +1,19 @@ +package optional.optional + +import optional.{ArgumentParser, ArgAssignable} + +trait Args { + def getArgs : Traversable[ArgAssignable] + + val parser = ArgumentParser(getArgs) + + def parse(args: Array[String]) { + val parsedArgs = parser.parse(args) + parsedArgs.foreach{ case (argAssignable, valueHolder) => + argAssignable.setValue(valueHolder.value) + } + } + + def helpMessage = parser.helpMessage + +} diff --git a/src/main/scala/optional/ArgumentParser.scala b/src/main/scala/optional/ArgumentParser.scala index 60de93d..89f54ec 100644 --- a/src/main/scala/optional/ArgumentParser.scala +++ b/src/main/scala/optional/ArgumentParser.scala @@ -59,11 +59,15 @@ object ArgumentParser { } } +/** + * Container for one argument, that has name, type, and can be assigned a value + */ trait ArgAssignable { def getName : String def getDescription: String def getType: Type def getCurrentValue: AnyRef + def setValue(value: Any) : Unit } @@ -86,6 +90,11 @@ class FieldArgAssignable(val field: Field, val obj: Object) extends ArgAssignabl } def getType = field.getGenericType def getCurrentValue = field.get(obj) + + def setValue(value: Any) = { + field.set(obj, value) + } + } class ArgException(val msg: String, val cause: Throwable) extends IllegalArgumentException(msg, cause) { diff --git a/src/main/scala/optional/ObjectToArgs.scala b/src/main/scala/optional/ObjectToArgs.scala index abe7254..53cae46 100644 --- a/src/main/scala/optional/ObjectToArgs.scala +++ b/src/main/scala/optional/ObjectToArgs.scala @@ -1,34 +1,16 @@ package optional -/** - * - */ - -class ObjectToArgs(val obj: Object) { - val argParser = ArgumentParser[FieldArgAssignable]( - ReflectionUtils.getAllDeclaredFields(obj.getClass). - filter{f => f.getName != "parser" && f.getName != "bitmap$0"}.map{f => new FieldArgAssignable(f, obj)} - ) +import optional.Args - - def parse(args: Array[String]) { - val parsed = argParser.parse(args) - parsed.foreach{ - kv => - val field = kv._1.field - field.setAccessible(true) - field.set(obj, kv._2.value) - } +trait FieldArgs extends Args { + override + def getArgs = { + ReflectionUtils.getAllDeclaredFields(this.getClass). + filter{f => f.getName != "parser" && f.getName != "bitmap$0"}.map{f => new FieldArgAssignable(f, this)} } - - def helpMessage = argParser.helpMessage } -trait FieldParsing { - lazy val parser = new ObjectToArgs(this) - def parse(args: Array[String]) { - parser.parse(args) - } - - def helpMessage = parser.helpMessage -} \ No newline at end of file +/** + * @deprecated legacy naming + */ +trait FieldParsing extends FieldArgs \ No newline at end of file diff --git a/src/test/scala/optional/ObjectToArgsTest.scala b/src/test/scala/optional/FieldArgsTest.scala similarity index 78% rename from src/test/scala/optional/ObjectToArgsTest.scala rename to src/test/scala/optional/FieldArgsTest.scala index 7d5a7a1..a1245c5 100644 --- a/src/test/scala/optional/ObjectToArgsTest.scala +++ b/src/test/scala/optional/FieldArgsTest.scala @@ -8,42 +8,31 @@ import org.scalatest.matchers.ShouldMatchers * */ -class ObjectToArgsTest extends FunSuite with ShouldMatchers { +class FieldArgsTest extends FunSuite with ShouldMatchers { test("parseStrings") { - val o = new StringHolder(null, null) - val parser = new ObjectToArgs(o) - parser.parse(Array("--name", "hello")) + val o = new StringHolder(null, null) with FieldArgs + o.parse(Array("--name", "hello")) o.name should be ("hello") - parser.parse(Array("--comment", "blah di blah blah")) + o.parse(Array("--comment", "blah di blah blah")) o.name should be ("hello") o.comment should be ("blah di blah blah") - parser.parse(Array("--name", "ooga", "--comment", "stuff")) + o.parse(Array("--name", "ooga", "--comment", "stuff")) o.name should be ("ooga") o.comment should be ("stuff") } test("parseMixed") { - val o = new MixedTypes(null, 0) + val o = new MixedTypes(null, 0) with FieldArgs - val parser = new ObjectToArgs(o) - - parser.parse(Array("--name", "foo", "--count", "17")) + o.parse(Array("--name", "foo", "--count", "17")) o.name should be ("foo") o.count should be (17) - parser.parse(Array("--count", "-5")) + o.parse(Array("--count", "-5")) o.name should be ("foo") o.count should be (-5) } - test("field parsing") { - val o = new MixedTypes(null, 0) with FieldParsing - - o.parse(Array("--count", "981", "--name", "wakkawakka")) - o.name should be ("wakkawakka") - o.count should be (981) - } - test("subclass parsing") { val o = new Child(false, null, 0) with FieldParsing @@ -53,20 +42,18 @@ class ObjectToArgsTest extends FunSuite with ShouldMatchers { } test("help message") { - val o = new StringHolder(null, null) - val parser = new ObjectToArgs(o) - val exc1 = evaluating {parser.parse(Array("--xyz", "hello"))} should produce [ArgException] + val o = new StringHolder(null, null) with FieldArgs + val exc1 = evaluating {o.parse(Array("--xyz", "hello"))} should produce [ArgException] //the format is still ugly, but at least there is some info there "\\-\\-name\\s.*String".r.findFirstIn(exc1.getMessage()) should be ('defined) "\\-\\-comment\\s.*String".r.findFirstIn(exc1.getMessage()) should be ('defined) - val o2 = new MixedTypes(null, 0) - val p2 = new ObjectToArgs(o2) - val exc2 = evaluating {p2.parse(Array("--foo", "bar"))} should produce [ArgException] + val o2 = new MixedTypes(null, 0) with FieldArgs + val exc2 = evaluating {o2.parse(Array("--foo", "bar"))} should produce [ArgException] "\\-\\-name\\s.*String".r findFirstIn(exc2.getMessage) should be ('defined) "\\-\\-count\\s.*[Ii]nt".r findFirstIn(exc2.getMessage) should be ('defined) //java or scala types, I'll take either for now - val exc3 = evaluating {p2.parse(Array("--count", "ooga"))} should produce [ArgException] + val exc3 = evaluating {o2.parse(Array("--count", "ooga"))} should produce [ArgException] //this message really should be much better. (a) the number format exception should come first and (b) should indicate that it was while processing the "count" argument "\\-\\-name\\s.*String".r findFirstIn(exc3.getMessage) should be ('defined) "\\-\\-count\\s.*[Ii]nt".r findFirstIn(exc3.getMessage) should be ('defined) //java or scala types, I'll take either for now @@ -134,16 +121,15 @@ class ObjectToArgsTest extends FunSuite with ShouldMatchers { test("exclude scala helper fields") { { - val m = new MixedTypes(null, 0) - val o = new ObjectToArgs(m) - val names = o.argParser.nameToHolder.keySet + val m = new MixedTypes(null, 0) with FieldArgs + val names = m.parser.nameToHolder.keySet names should be (Set("name", "count")) } { val s = new SomeApp() - val names = s.getArgHolder.parser.argParser.nameToHolder.keySet + val names = s.getArgHolder.parser.nameToHolder.keySet names should be (Set("x", "y")) } @@ -152,9 +138,8 @@ class ObjectToArgsTest extends FunSuite with ShouldMatchers { test("annotations") { - val c = new ClassWithSomeAnnotations() - val o = new ObjectToArgs(c) - o.argParser.nameToHolder.values.foreach { f => + val c = new ClassWithSomeAnnotations() with FieldArgs + c.parser.nameToHolder.values.foreach { f => f.getName match { case "foo" => f.getDescription should be ("foo") @@ -172,14 +157,14 @@ class ObjectToArgsTest extends FunSuite with ShouldMatchers { } } - o.parse(Array("--foo", "hi", "--ooga", "17", "--y", "181", "--wakka", "1.81")) + c.parse(Array("--foo", "hi", "--ooga", "17", "--y", "181", "--wakka", "1.81")) c.foo should be ("hi") c.x should be (17) c.y should be (181) c.z should be (1.81) - evaluating {o.parse(Array("--x", "17"))} should produce [ArgException] - evaluating {o.parse(Array("--z", "1"))} should produce [ArgException] + evaluating {c.parse(Array("--x", "17"))} should produce [ArgException] + evaluating {c.parse(Array("--z", "1"))} should produce [ArgException] } From 8da1af9bce1f35267ce0e925543dd2e6558a50e7 Mon Sep 17 00:00:00 2001 From: Imran Rashid Date: Sun, 27 Jan 2013 12:54:10 -0800 Subject: [PATCH 39/50] minor cleanup -- change filename, fix package of Args --- src/main/scala/optional/Args.scala | 4 +--- .../scala/optional/{ObjectToArgs.scala => FieldArgs.scala} | 2 -- 2 files changed, 1 insertion(+), 5 deletions(-) rename src/main/scala/optional/{ObjectToArgs.scala => FieldArgs.scala} (93%) diff --git a/src/main/scala/optional/Args.scala b/src/main/scala/optional/Args.scala index dbab818..a2f03dc 100644 --- a/src/main/scala/optional/Args.scala +++ b/src/main/scala/optional/Args.scala @@ -1,6 +1,4 @@ -package optional.optional - -import optional.{ArgumentParser, ArgAssignable} +package optional trait Args { def getArgs : Traversable[ArgAssignable] diff --git a/src/main/scala/optional/ObjectToArgs.scala b/src/main/scala/optional/FieldArgs.scala similarity index 93% rename from src/main/scala/optional/ObjectToArgs.scala rename to src/main/scala/optional/FieldArgs.scala index 53cae46..f58131b 100644 --- a/src/main/scala/optional/ObjectToArgs.scala +++ b/src/main/scala/optional/FieldArgs.scala @@ -1,7 +1,5 @@ package optional -import optional.Args - trait FieldArgs extends Args { override def getArgs = { From 9be0abf6d271f14286bffcfd228041feba8958a2 Mon Sep 17 00:00:00 2001 From: Imran Rashid Date: Sun, 27 Jan 2013 12:56:37 -0800 Subject: [PATCH 40/50] mark method as tailrec, minor reformatting --- src/main/scala/optional/ReflectionUtils.scala | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/main/scala/optional/ReflectionUtils.scala b/src/main/scala/optional/ReflectionUtils.scala index d6af141..4b6946c 100644 --- a/src/main/scala/optional/ReflectionUtils.scala +++ b/src/main/scala/optional/ReflectionUtils.scala @@ -1,6 +1,7 @@ package optional import scala.collection._ +import annotation.tailrec import java.lang.reflect.Field /** @@ -8,13 +9,14 @@ import java.lang.reflect.Field */ object ReflectionUtils { - def getAllDeclaredFields(cls: Class[_]) : mutable.Buffer[Field]= { + def getAllDeclaredFields(cls: Class[_]) : mutable.Buffer[Field] = { val fields = mutable.Buffer[Field]() getAllDeclaredFields(cls, fields) fields } - def getAllDeclaredFields(cls: Class[_], holder: mutable.Buffer[Field]) : Unit = { + @tailrec + def getAllDeclaredFields(cls: Class[_], holder: mutable.Buffer[Field]) { holder ++= cls.getDeclaredFields val superCls = cls.getSuperclass if (superCls != null) From 1b7cbb009909ce5d64c94d82c28b8625c002a3bb Mon Sep 17 00:00:00 2001 From: ryanlecompte Date: Sun, 27 Jan 2013 16:01:42 -0800 Subject: [PATCH 41/50] various cleanup, make argument parser more functional --- project/plugins.sbt | 1 + src/main/scala/optional/ArgApp.scala | 33 +++--- src/main/scala/optional/Args.scala | 5 +- src/main/scala/optional/ArgumentParser.scala | 102 +++++++++--------- src/main/scala/optional/FieldArgs.scala | 9 +- src/main/scala/optional/Parser.scala | 102 +++++++----------- src/main/scala/optional/ReflectionUtils.scala | 21 ++-- .../optional/types/MultiSelectInput.scala | 2 +- 8 files changed, 117 insertions(+), 158 deletions(-) create mode 100644 project/plugins.sbt diff --git a/project/plugins.sbt b/project/plugins.sbt new file mode 100644 index 0000000..06308a7 --- /dev/null +++ b/project/plugins.sbt @@ -0,0 +1 @@ +addSbtPlugin("com.github.mpeltonen" % "sbt-idea" % "1.2.0") diff --git a/src/main/scala/optional/ArgApp.scala b/src/main/scala/optional/ArgApp.scala index 1a2293d..7266dbd 100644 --- a/src/main/scala/optional/ArgApp.scala +++ b/src/main/scala/optional/ArgApp.scala @@ -5,11 +5,14 @@ import java.lang.reflect.{ParameterizedType, Type} trait Argable[T <: FieldParsing] { protected lazy val argHolder = { - val argClass = getArgumentClass() - val ctors = argClass.getDeclaredConstructors() - val ctor = ctors.find(ctor => ctor.getGenericParameterTypes.length == 0).get - ctor.setAccessible(true) - ctor.newInstance().asInstanceOf[T] + val argClass = getArgumentClass + val ctors = argClass.getDeclaredConstructors + ctors.find(_.getGenericParameterTypes.length == 0) match { + case Some(ctor) => + ctor.setAccessible(true) + ctor.newInstance().asInstanceOf[T] + case None => throw new AssertionError("No zero-arg constructor found") + } } /** @@ -18,13 +21,12 @@ trait Argable[T <: FieldParsing] { * not needed for the user that just wants to run their code -- this is accessible just for other libs * built on top. */ - def getArgHolder : T = argHolder + def getArgHolder: T = argHolder - private def getArgumentClass() = { - val argApp = this.getClass.getGenericInterfaces.find{tpe => + private def getArgumentClass = { + val argApp = getClass.getGenericInterfaces.find { tpe => tpe match { - case ptpe: ParameterizedType => - ParseHelper.checkType(ptpe, classOf[Argable[_]]) + case ptpe: ParameterizedType => ParseHelper.checkType(ptpe, classOf[Argable[_]]) case _ => false } } @@ -33,8 +35,8 @@ trait Argable[T <: FieldParsing] { private def getRawClass(tpe: Type) = { tpe match { - case x:Class[_] => x - case p:ParameterizedType => p.getRawType.asInstanceOf[Class[_]] + case x: Class[_] => x + case p: ParameterizedType => p.getRawType.asInstanceOf[Class[_]] } } @@ -50,14 +52,13 @@ trait ArgMain[T <: FieldParsing] extends Argable[T] { main(argHolder) } - def main(args: T) : Unit + def main(args: T) } -trait ArgFunction[T <: FieldParsing, U] extends Function[T,U] with Argable[T] +trait ArgFunction[T <: FieldParsing, U] extends Function[T, U] with Argable[T] trait ArgApp[T <: FieldParsing] extends Argable[T] with App { - override - def main(args: Array[String]) { + override def main(args: Array[String]) { argHolder.parse(args) super.main(args) } diff --git a/src/main/scala/optional/Args.scala b/src/main/scala/optional/Args.scala index a2f03dc..9fee955 100644 --- a/src/main/scala/optional/Args.scala +++ b/src/main/scala/optional/Args.scala @@ -1,17 +1,16 @@ package optional trait Args { - def getArgs : Traversable[ArgAssignable] + def getArgs: Traversable[ArgAssignable] val parser = ArgumentParser(getArgs) def parse(args: Array[String]) { val parsedArgs = parser.parse(args) - parsedArgs.foreach{ case (argAssignable, valueHolder) => + parsedArgs.foreach { case (argAssignable, valueHolder) => argAssignable.setValue(valueHolder.value) } } def helpMessage = parser.helpMessage - } diff --git a/src/main/scala/optional/ArgumentParser.scala b/src/main/scala/optional/ArgumentParser.scala index 89f54ec..d3cd1b2 100644 --- a/src/main/scala/optional/ArgumentParser.scala +++ b/src/main/scala/optional/ArgumentParser.scala @@ -1,102 +1,96 @@ package optional +import scala.annotation.tailrec import java.lang.reflect.{Type, Field} -import scala.collection._ - class ArgumentParser[T <: ArgAssignable] (val argHolders: Seq[T]) { - lazy val nameToHolder = argHolders.map{ a => a.getName -> a}.toMap - + lazy val nameToHolder = argHolders.map(a => a.getName -> a).toMap.withDefault { arg => + throw new ArgException("unknown option %s\n%s".format(arg, helpMessage)) + } - def parse(args: Array[String]) : Map[T, ValueHolder[_]] = { - try { - val result = mutable.Map[T, ValueHolder[_]]() - var idx = 0 - while (idx < args.length) { - val arg = args(idx) - if (arg == "--help") { - throw new ArgException(helpMessage) - } - if (!arg.startsWith("--")) - throw new ArgException("expecting argument name beginning with \"--\", instead got " + arg + "\n" + helpMessage) - val name = arg.substring(2) - val holderOption = nameToHolder.get(name) - if (holderOption.isEmpty) - throw new ArgException("unknown option " + name + "\n" + helpMessage) - try { - val parsed = ParseHelper.parseInto(args(idx +1), holderOption.get.getType, holderOption.get.getCurrentValue) - parsed match { - case Some(x) => result(holderOption.get) = x - case None => throw new ArgException("don't know how to parse type: " + holderOption.get.getType) + def parse(args: Array[String]): Map[T, ValueHolder[_]] = { + @tailrec + def parse(args: List[String], acc: Map[T, ValueHolder[_]] = Map.empty): Map[T, ValueHolder[_]] = { + args match { + case Nil => acc + case "--help" :: _ => throw new ArgException(helpMessage) + case x :: _ if (!x.startsWith("--")) => + throw new ArgException("expecting argument name beginning with \"--\", instead got %s".format(x)) + case name :: value :: tail => + val suffix = name.drop(2) + val holder = nameToHolder(suffix) + val result = try { + ParseHelper.parseInto(value, holder.getType, holder.getCurrentValue) getOrElse { + throw new ArgException("don't know how to parse type: " + holder.getType) + } + } catch { + case ae: ArgException => throw ae + case e: Throwable => throw new ArgException("Error parsing \"%s\" into field \"%s\" (type = %s)\n%s".format(value, suffix, holder.getType, helpMessage)) } - } catch { - case exc => throw new ArgException("Error parsing \"" + args(idx + 1) + "\" into field \"" + name + "\" (type = " + holderOption.get.getType + ")\n" + helpMessage, exc) - } - idx += 2 + + // parse remaining options + parse(tail, acc + (holder -> result)) } - result + } + + try { + parse(args.toList) } catch { case ae: ArgException => throw ae - case exc => throw new ArgException(helpMessage, exc) + case e: Throwable => throw new ArgException(helpMessage, e) } } - def helpMessage = { - val msg = StringBuilder.newBuilder + def helpMessage: String = { + val msg = new StringBuilder msg.append("usage: \n") - nameToHolder.foreach{ kv => - msg.append("--" + kv._1 + "\t" + kv._2.getType + "\t" + kv._2.getDescription + "\n\n") + nameToHolder.foreach { case (k, v) => + msg.append("--%s\t%s\t%s\n\n".format(k, v.getType, v.getDescription)) } msg.toString } - } object ArgumentParser { def apply[T <: ArgAssignable](argHolders: Traversable[T]) = { - //ignore things we dont' know how to parse - new ArgumentParser(argHolders.toSeq.filter{t => ParseHelper.findParser(t.getType).isDefined}) + // ignore things we don't know how to parse + new ArgumentParser(argHolders.toSeq.filter(t => ParseHelper.findParser(t.getType).isDefined)) } } /** - * Container for one argument, that has name, type, and can be assigned a value + * Container for one argument, that has name, type, and can be assigned a value. */ trait ArgAssignable { - def getName : String + def getName: String def getDescription: String def getType: Type def getCurrentValue: AnyRef - def setValue(value: Any) : Unit + def setValue(value: Any) } - class FieldArgAssignable(val field: Field, val obj: Object) extends ArgAssignable { field.setAccessible(true) val annotationOpt = Option(field.getAnnotation(classOf[Arg])) + def getName = { - val n = annotationOpt.map{_.name()}.getOrElse(field.getName) - if (n == "") - field.getName - else - n + val n = annotationOpt.map(_.name).getOrElse(field.getName) + if (n == "") field.getName else n } + def getDescription = { - val d = annotationOpt.map{_.description()}.getOrElse(field.getName) - if (d == "") - getName - else - d + val d = annotationOpt.map(_.description).getOrElse(field.getName) + if (d == "") getName else d } + def getType = field.getGenericType def getCurrentValue = field.get(obj) def setValue(value: Any) = { field.set(obj, value) } - } -class ArgException(val msg: String, val cause: Throwable) extends IllegalArgumentException(msg, cause) { - def this(msg:String) = this(msg, null) +case class ArgException(msg: String, cause: Throwable) extends IllegalArgumentException(msg, cause) { + def this(msg: String) = this(msg, null) } \ No newline at end of file diff --git a/src/main/scala/optional/FieldArgs.scala b/src/main/scala/optional/FieldArgs.scala index f58131b..fc0a80f 100644 --- a/src/main/scala/optional/FieldArgs.scala +++ b/src/main/scala/optional/FieldArgs.scala @@ -1,14 +1,9 @@ package optional trait FieldArgs extends Args { - override - def getArgs = { - ReflectionUtils.getAllDeclaredFields(this.getClass). - filter{f => f.getName != "parser" && f.getName != "bitmap$0"}.map{f => new FieldArgAssignable(f, this)} + override def getArgs = ReflectionUtils.getAllDeclaredFields(getClass) collect { + case f if (f.getName != "parser" && f.getName != "bitmap$0") => new FieldArgAssignable(f, this) } } -/** - * @deprecated legacy naming - */ trait FieldParsing extends FieldArgs \ No newline at end of file diff --git a/src/main/scala/optional/Parser.scala b/src/main/scala/optional/Parser.scala index 9474a28..8fd5e0f 100644 --- a/src/main/scala/optional/Parser.scala +++ b/src/main/scala/optional/Parser.scala @@ -17,65 +17,54 @@ trait Parser[T] { } trait SimpleParser[T] extends Parser[T] { - def getKnownTypes() : Set[Class[_]] + val knownTypes: Set[Class[_]] def canParse(tpe: Type) = { - if (tpe.isInstanceOf[Class[_]]) - getKnownTypes()(tpe.asInstanceOf[Class[_]]) - else - false + if (tpe.isInstanceOf[Class[_]]) knownTypes(tpe.asInstanceOf[Class[_]]) + else false } def parse(s: String, tpe:Type, currentValue: AnyRef) = parse(s) - def parse(s:String) :T + def parse(s: String): T } trait CompoundParser[T] extends Parser[T] - object StringParser extends SimpleParser[String] { - val knownTypes : Set[Class[_]] = Set(classOf[String]) - def getKnownTypes() = knownTypes - def parse(s:String) = s + val knownTypes: Set[Class[_]] = Set(classOf[String]) + def parse(s: String) = s } object IntParser extends SimpleParser[Int] { - val knownTypes : Set[Class[_]] = Set(classOf[Int], classOf[java.lang.Integer]) - def getKnownTypes() = knownTypes + val knownTypes: Set[Class[_]] = Set(classOf[Int], classOf[java.lang.Integer]) def parse(s: String) = s.toInt } object LongParser extends SimpleParser[Long] { val knownTypes: Set[Class[_]] = Set(classOf[Long], classOf[java.lang.Long]) - def getKnownTypes() = knownTypes def parse(s: String) = s.toLong } object BooleanParser extends SimpleParser[Boolean] { - val knownTypes : Set[Class[_]] = Set(classOf[Boolean], classOf[java.lang.Boolean]) - def getKnownTypes() = knownTypes + val knownTypes: Set[Class[_]] = Set(classOf[Boolean], classOf[java.lang.Boolean]) def parse(s: String) = s.toBoolean } object FloatParser extends SimpleParser[Float] { - val knownTypes : Set[Class[_]] = Set(classOf[Float], classOf[java.lang.Float]) - def getKnownTypes() = knownTypes + val knownTypes: Set[Class[_]] = Set(classOf[Float], classOf[java.lang.Float]) def parse(s: String) = s.toFloat } object DoubleParser extends SimpleParser[Double] { - val knownTypes : Set[Class[_]] = Set(classOf[Double], classOf[java.lang.Double]) - def getKnownTypes() = knownTypes + val knownTypes: Set[Class[_]] = Set(classOf[Double], classOf[java.lang.Double]) def parse(s: String) = s.toDouble } object RegexParser extends SimpleParser[Regex] { - val knownTypes : Set[Class[_]] = Set(classOf[Regex]) - def getKnownTypes = knownTypes + val knownTypes: Set[Class[_]] = Set(classOf[Regex]) def parse(s: String) = s.r } object FileParser extends SimpleParser[File] { - val knownTypes : Set[Class[_]] = Set(classOf[File]) - def getKnownTypes = knownTypes + val knownTypes: Set[Class[_]] = Set(classOf[File]) def parse(s: String) = { val fullPath = if (s.startsWith("~")) s.replaceFirst("~", System.getProperty("user.home")) else s new File(fullPath) @@ -96,12 +85,9 @@ object ListParser extends CompoundParser[List[_]] { val subtype = ptpe.getActualTypeArguments()(0) val subParser = ParseHelper.findParser(subtype).get //TODO need to handle cases where its a list, but can't parse subtype val parts = s.split(",") - parts.map{sub => subParser.parse(sub, subtype, currentValue)}.toList - } - else - List() + parts.map(subParser.parse(_, subtype, currentValue)).toList + } else List.empty } - } object SetParser extends CompoundParser[collection.Set[_]] { @@ -115,10 +101,8 @@ object SetParser extends CompoundParser[collection.Set[_]] { val subtype = ptpe.getActualTypeArguments()(0) val subParser = ParseHelper.findParser(subtype).get val parts = s.split(",") - parts.map{sub => subParser.parse(sub, subtype, currentValue)}.toSet - } - else - Set() + parts.map(subParser.parse(_, subtype, currentValue)).toSet + } else Set.empty } } @@ -134,20 +118,15 @@ object SelectInputParser extends CompoundParser[SelectInput[_]] { val subtype = ptpe.getActualTypeArguments()(0) val subParser = ParseHelper.findParser(subtype).get val parsed = subParser.parse(s, subtype, currentVal.value) - if (currentVal.options(parsed)) - currentVal.value = Some(parsed) - else - throw new IllegalArgumentException(parsed + " is not the allowed values: " + currentVal.options) + if (currentVal.options(parsed)) currentVal.value = Some(parsed) + else throw new IllegalArgumentException(parsed + " is not the allowed values: " + currentVal.options) //we don't return a new object, just modify the existing one currentVal - } - else - throw new UnsupportedOperationException() + } else throw new UnsupportedOperationException() } } object MultiSelectInputParser extends CompoundParser[MultiSelectInput[_]] { - def canParse(tpe: Type) = ParseHelper.checkType(tpe, classOf[MultiSelectInput[_]]) def parse(s: String, tpe: Type, currentValue: AnyRef) = { @@ -156,39 +135,39 @@ object MultiSelectInputParser extends CompoundParser[MultiSelectInput[_]] { val ptpe = tpe.asInstanceOf[ParameterizedType] val subtype = ptpe.getActualTypeArguments()(0) val subParser = ParseHelper.findParser(subtype).get - val parsed = s.split(",").map{sub => subParser.parse(sub, subtype, "dummy")}.toSet + val parsed = s.split(",").map(subParser.parse(_, subtype, "dummy")).toSet val illegal = parsed.diff(currentVal.options) - if (illegal.isEmpty) - currentVal.value = parsed - else - throw new IllegalArgumentException(illegal.toString + " is not the allowed values: " + currentVal.options) + if (illegal.isEmpty) currentVal.value = parsed + else throw new IllegalArgumentException(illegal.toString + " is not the allowed values: " + currentVal.options) //we don't return a new object, just modify the existing one currentVal - } - else - throw new UnsupportedOperationException() - + } else throw new UnsupportedOperationException() } } object ParseHelper { - val parsers = Seq(StringParser, IntParser, LongParser, FloatParser, DoubleParser, BooleanParser, ListParser, - SetParser, SelectInputParser, MultiSelectInputParser, FileParser, RegexParser) - - def findParser(tpe: Type) : Option[Parser[_]] = { - for (p <- parsers.iterator) { - if (p.canParse(tpe)) - return Some(p) - } - None - } + val parsers = Seq( + StringParser, + IntParser, + LongParser, + FloatParser, + DoubleParser, + BooleanParser, + ListParser, + SetParser, + SelectInputParser, + MultiSelectInputParser, + FileParser, + RegexParser) + + def findParser(tpe: Type): Option[Parser[_]] = parsers.find(_.canParse(tpe)) def parseInto[T](s: String, tpe: Type, currentValue: AnyRef) : Option[ValueHolder[T]] = { //could change this to be a map, at least for the simple types - findParser(tpe).map{parser => ValueHolder[T](parser.parse(s, tpe, currentValue).asInstanceOf[T], tpe)} + findParser(tpe).map(parser => ValueHolder[T](parser.parse(s, tpe, currentValue).asInstanceOf[T], tpe)) } - def checkType(tpe: Type, targetClassSet: Class[_]*) = { + def checkType(tpe: Type, targetClassSet: Class[_]*) = { def helper(tpe: Type, targetCls: Class[_]) = { val clz = if (tpe.isInstanceOf[Class[_]]) tpe.asInstanceOf[Class[_]] @@ -200,7 +179,6 @@ object ParseHelper { } targetClassSet.exists(targetClass => helper(tpe, targetClass)) } - } case class ValueHolder[T](value: T, tpe: Type) \ No newline at end of file diff --git a/src/main/scala/optional/ReflectionUtils.scala b/src/main/scala/optional/ReflectionUtils.scala index 4b6946c..fe2fc16 100644 --- a/src/main/scala/optional/ReflectionUtils.scala +++ b/src/main/scala/optional/ReflectionUtils.scala @@ -1,25 +1,16 @@ package optional -import scala.collection._ import annotation.tailrec import java.lang.reflect.Field -/** - * - */ - object ReflectionUtils { - def getAllDeclaredFields(cls: Class[_]) : mutable.Buffer[Field] = { - val fields = mutable.Buffer[Field]() - getAllDeclaredFields(cls, fields) - fields - } @tailrec - def getAllDeclaredFields(cls: Class[_], holder: mutable.Buffer[Field]) { - holder ++= cls.getDeclaredFields - val superCls = cls.getSuperclass - if (superCls != null) - getAllDeclaredFields(superCls, holder) + def getAllDeclaredFields(cls: Class[_], acc: Seq[Field] = Seq.empty): Seq[Field] = { + val fields = acc ++ cls.getDeclaredFields + Option(cls.getSuperclass) match { + case Some(clazz) => getAllDeclaredFields(clazz, fields) + case _ => fields + } } } diff --git a/src/main/scala/optional/types/MultiSelectInput.scala b/src/main/scala/optional/types/MultiSelectInput.scala index ea8e35f..5930b10 100644 --- a/src/main/scala/optional/types/MultiSelectInput.scala +++ b/src/main/scala/optional/types/MultiSelectInput.scala @@ -3,7 +3,7 @@ package optional.types class MultiSelectInput[T](var value: Set[T], val options: Set[T]) object MultiSelectInput { - def apply[T](options:T*) = new MultiSelectInput[T](Set(), options.toSet) + def apply[T](options: T*) = new MultiSelectInput[T](Set(), options.toSet) } From 44609c711215133292c65ff50ec11c9c0ab74ddc Mon Sep 17 00:00:00 2001 From: ryanlecompte Date: Sun, 27 Jan 2013 16:29:23 -0800 Subject: [PATCH 42/50] add missing catch-all --- src/main/scala/optional/ArgumentParser.scala | 1 + 1 file changed, 1 insertion(+) diff --git a/src/main/scala/optional/ArgumentParser.scala b/src/main/scala/optional/ArgumentParser.scala index d3cd1b2..83797f7 100644 --- a/src/main/scala/optional/ArgumentParser.scala +++ b/src/main/scala/optional/ArgumentParser.scala @@ -30,6 +30,7 @@ class ArgumentParser[T <: ArgAssignable] (val argHolders: Seq[T]) { // parse remaining options parse(tail, acc + (holder -> result)) + case _ => throw new ArgException(helpMessage) } } From 92f92abeb09b1be34c4160ad7cb3aefb7f33568e Mon Sep 17 00:00:00 2001 From: ryanlecompte Date: Sun, 27 Jan 2013 16:31:09 -0800 Subject: [PATCH 43/50] minor cleanup --- src/main/scala/optional/ArgumentParser.scala | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/scala/optional/ArgumentParser.scala b/src/main/scala/optional/ArgumentParser.scala index 83797f7..dbfc0f9 100644 --- a/src/main/scala/optional/ArgumentParser.scala +++ b/src/main/scala/optional/ArgumentParser.scala @@ -14,8 +14,8 @@ class ArgumentParser[T <: ArgAssignable] (val argHolders: Seq[T]) { args match { case Nil => acc case "--help" :: _ => throw new ArgException(helpMessage) - case x :: _ if (!x.startsWith("--")) => - throw new ArgException("expecting argument name beginning with \"--\", instead got %s".format(x)) + case arg :: _ if (!arg.startsWith("--")) => + throw new ArgException("expecting argument name beginning with \"--\", instead got %s".format(arg)) case name :: value :: tail => val suffix = name.drop(2) val holder = nameToHolder(suffix) From 1415e30829bd16c4a7ec8ca6c58edadeafc1c6fc Mon Sep 17 00:00:00 2001 From: ryanlecompte Date: Sun, 27 Jan 2013 18:23:05 -0800 Subject: [PATCH 44/50] add deprecation warning back --- src/main/scala/optional/FieldArgs.scala | 1 + 1 file changed, 1 insertion(+) diff --git a/src/main/scala/optional/FieldArgs.scala b/src/main/scala/optional/FieldArgs.scala index fc0a80f..9db0745 100644 --- a/src/main/scala/optional/FieldArgs.scala +++ b/src/main/scala/optional/FieldArgs.scala @@ -6,4 +6,5 @@ trait FieldArgs extends Args { } } +@deprecated("legacy naming") trait FieldParsing extends FieldArgs \ No newline at end of file From 5ba634c8143e65284d311cfe2decbba9f8f889c3 Mon Sep 17 00:00:00 2001 From: Matt Gedigian Date: Mon, 11 Feb 2013 20:38:41 -0800 Subject: [PATCH 45/50] Preserve order of SelectInput options. --- src/main/scala/optional/types/SelectInput.scala | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/scala/optional/types/SelectInput.scala b/src/main/scala/optional/types/SelectInput.scala index 6d058c7..171ccbe 100644 --- a/src/main/scala/optional/types/SelectInput.scala +++ b/src/main/scala/optional/types/SelectInput.scala @@ -1,7 +1,7 @@ package optional.types -class SelectInput[T](var value: Option[T], val options: Set[T]) +class SelectInput[T](var value: Option[T], val options: mutable.LinkedHashSet[T]) object SelectInput{ - def apply[T](options: T*) = new SelectInput[T](value = None, options = options.toSet) + def apply[T](options: T*) = new OurSelectInput[T](value = None, options = (mutable.LinkedHashSet[T]() ++ options)) } From 0392ce4ebea5829e5218c068719e141428d83c09 Mon Sep 17 00:00:00 2001 From: ryanlecompte Date: Tue, 12 Feb 2013 13:45:34 -0800 Subject: [PATCH 46/50] add travis yml file --- .travis.yml | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 .travis.yml diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 0000000..2f4e1ae --- /dev/null +++ b/.travis.yml @@ -0,0 +1,4 @@ +language: scala +scala: + - 2.9.2 +script: "SBT_OPTS=-XX:MaxPermSize=256m sbt ++$TRAVIS_SCALA_VERSION assembly" From 507247f06e221d75a0f9f5a811a284d1f9b94ec0 Mon Sep 17 00:00:00 2001 From: ryanlecompte Date: Tue, 12 Feb 2013 13:46:14 -0800 Subject: [PATCH 47/50] use test target --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 2f4e1ae..2394521 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,4 +1,4 @@ language: scala scala: - 2.9.2 -script: "SBT_OPTS=-XX:MaxPermSize=256m sbt ++$TRAVIS_SCALA_VERSION assembly" +script: "SBT_OPTS=-XX:MaxPermSize=256m sbt ++$TRAVIS_SCALA_VERSION test" From 26167d369dac70748eb8db96c4833ff5a7227549 Mon Sep 17 00:00:00 2001 From: ryanlecompte Date: Tue, 12 Feb 2013 13:47:43 -0800 Subject: [PATCH 48/50] add build status to readme --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index 58ed801..6418e14 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,5 @@ +[![Build Status](https://secure.travis-ci.org/squito/optional.png?branch=master)](http://travis-ci.org/squito/optional) + optional is a command line option parser and library. It tries to differentiate itself from other libraries by making it dead simple to define arguments, removing boilerplate and repitition. It From 79c0686e426133ec9905a91cd3e9c02596d211b2 Mon Sep 17 00:00:00 2001 From: Matt Gedigian Date: Wed, 13 Feb 2013 13:17:03 -0800 Subject: [PATCH 49/50] SelectInput and ArgumentParser.parse retain order (using LinkedHashMap and LinkedHashSet). --- src/main/scala/optional/ArgumentParser.scala | 10 ++++++---- src/main/scala/optional/types/SelectInput.scala | 7 +++++-- src/test/scala/optional/FieldArgsTest.scala | 17 +++++++++++++++++ 3 files changed, 28 insertions(+), 6 deletions(-) diff --git a/src/main/scala/optional/ArgumentParser.scala b/src/main/scala/optional/ArgumentParser.scala index dbfc0f9..8e5e937 100644 --- a/src/main/scala/optional/ArgumentParser.scala +++ b/src/main/scala/optional/ArgumentParser.scala @@ -2,15 +2,16 @@ package optional import scala.annotation.tailrec import java.lang.reflect.{Type, Field} +import collection.mutable.LinkedHashMap class ArgumentParser[T <: ArgAssignable] (val argHolders: Seq[T]) { - lazy val nameToHolder = argHolders.map(a => a.getName -> a).toMap.withDefault { arg => + lazy val nameToHolder = (LinkedHashMap.empty ++ argHolders.map(a => a.getName -> a)).withDefault { arg => throw new ArgException("unknown option %s\n%s".format(arg, helpMessage)) } - def parse(args: Array[String]): Map[T, ValueHolder[_]] = { + def parse(args: Array[String]): LinkedHashMap[T, ValueHolder[_]] = { @tailrec - def parse(args: List[String], acc: Map[T, ValueHolder[_]] = Map.empty): Map[T, ValueHolder[_]] = { + def parse(args: List[String], acc: LinkedHashMap[T, ValueHolder[_]] = LinkedHashMap.empty): LinkedHashMap[T, ValueHolder[_]] = { args match { case Nil => acc case "--help" :: _ => throw new ArgException(helpMessage) @@ -29,7 +30,8 @@ class ArgumentParser[T <: ArgAssignable] (val argHolders: Seq[T]) { } // parse remaining options - parse(tail, acc + (holder -> result)) + acc += (holder -> result) + parse(tail, acc) case _ => throw new ArgException(helpMessage) } } diff --git a/src/main/scala/optional/types/SelectInput.scala b/src/main/scala/optional/types/SelectInput.scala index 171ccbe..f4293b1 100644 --- a/src/main/scala/optional/types/SelectInput.scala +++ b/src/main/scala/optional/types/SelectInput.scala @@ -1,7 +1,10 @@ package optional.types -class SelectInput[T](var value: Option[T], val options: mutable.LinkedHashSet[T]) +import collection.mutable.LinkedHashSet +import collection.Set + +class SelectInput[T](var value: Option[T], val options: Set[T]) object SelectInput{ - def apply[T](options: T*) = new OurSelectInput[T](value = None, options = (mutable.LinkedHashSet[T]() ++ options)) + def apply[T](options: T*) = new SelectInput[T](value = None, options = (LinkedHashSet.empty ++ options)) } diff --git a/src/test/scala/optional/FieldArgsTest.scala b/src/test/scala/optional/FieldArgsTest.scala index a1245c5..23b4ff1 100644 --- a/src/test/scala/optional/FieldArgsTest.scala +++ b/src/test/scala/optional/FieldArgsTest.scala @@ -100,6 +100,23 @@ class FieldArgsTest extends FunSuite with ShouldMatchers { evaluating {s.parse(Array("--select", "q"))} should produce [ArgException] } + test("selectInput order") { + import util.Random._ + val max = 1000 + val orderedChoices = shuffle(1.to(max).map(_.toString)) + case class SelectInputArgs(val select: SelectInput[String] = SelectInput(orderedChoices:_*)) extends FieldParsing + val s = new SelectInputArgs() + val id = System.identityHashCode(s.select) + + val index = nextInt(max).toString + s.parse(Array("--select", index)) + s.select.value should be (Some(index)) + System.identityHashCode(s.select) should be (id) + s.select.options.toList should be (orderedChoices) + + evaluating {s.parse(Array("--select", "q"))} should produce [ArgException] + } + test("multiSelectInput") { case class MultiSelectInputArgs(val multiSelect: MultiSelectInput[String] = MultiSelectInput("a", "b", "c")) extends FieldParsing val s = new MultiSelectInputArgs() From 1496f3137199ffa5ef0aa37d34cbfe2753c4e470 Mon Sep 17 00:00:00 2001 From: Imran Rashid Date: Mon, 25 Feb 2013 17:22:48 -0800 Subject: [PATCH 50/50] remove everything, point to Sumac repo --- LICENSE | 13 - README.md | 135 +---------- pom.xml | 127 ---------- project/Build.scala | 35 --- project/build.properties | 5 - project/plugins.sbt | 1 - src/main/java/optional/Arg.java | 13 - src/main/scala/optional/ArgApp.scala | 84 ------- src/main/scala/optional/Args.scala | 16 -- src/main/scala/optional/ArgumentParser.scala | 99 -------- src/main/scala/optional/FieldArgs.scala | 10 - src/main/scala/optional/Parser.scala | 184 --------------- src/main/scala/optional/ReflectionUtils.scala | 16 -- .../optional/types/MultiSelectInput.scala | 9 - .../scala/optional/types/SelectInput.scala | 10 - src/test/scala/optional/ArgAppTest.scala | 28 --- .../scala/optional/ArgumentParserTest.scala | 40 ---- src/test/scala/optional/FieldArgsTest.scala | 222 ------------------ src/test/scala/optional/ParserTest.scala | 46 ---- 19 files changed, 1 insertion(+), 1092 deletions(-) delete mode 100644 LICENSE delete mode 100644 pom.xml delete mode 100644 project/Build.scala delete mode 100644 project/build.properties delete mode 100644 project/plugins.sbt delete mode 100644 src/main/java/optional/Arg.java delete mode 100644 src/main/scala/optional/ArgApp.scala delete mode 100644 src/main/scala/optional/Args.scala delete mode 100644 src/main/scala/optional/ArgumentParser.scala delete mode 100644 src/main/scala/optional/FieldArgs.scala delete mode 100644 src/main/scala/optional/Parser.scala delete mode 100644 src/main/scala/optional/ReflectionUtils.scala delete mode 100644 src/main/scala/optional/types/MultiSelectInput.scala delete mode 100644 src/main/scala/optional/types/SelectInput.scala delete mode 100644 src/test/scala/optional/ArgAppTest.scala delete mode 100644 src/test/scala/optional/ArgumentParserTest.scala delete mode 100644 src/test/scala/optional/FieldArgsTest.scala delete mode 100644 src/test/scala/optional/ParserTest.scala diff --git a/LICENSE b/LICENSE deleted file mode 100644 index 3012e4f..0000000 --- a/LICENSE +++ /dev/null @@ -1,13 +0,0 @@ -Copyright 2012 Imran Rashid - - Licensed 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. \ No newline at end of file diff --git a/README.md b/README.md index 6418e14..2e92dfb 100644 --- a/README.md +++ b/README.md @@ -1,134 +1 @@ -[![Build Status](https://secure.travis-ci.org/squito/optional.png?branch=master)](http://travis-ci.org/squito/optional) - -optional is a command line option parser and library. It tries -to differentiate itself from other libraries by making it dead -simple to define arguments, removing boilerplate and repitition. It -is a very small, lightweight scala library. - -## Usage - -Define a basic container object which extends FieldParsing. Every field of the object -becomes a command line argument with the same name. You can use the parse method to parse -arguments. - - class Arguments with FieldParsing { - var name: String = _ - var count: Int = _ - } - - object MyApp { - def main(args: Array[String]) { - val myArgs = new Arguments() - myArgs.parse(args) - ... - } - } - -Now MyApp has two arguments, "name" and "count". You can run it like: - - java -cp /// MyApp --name foobar --count 17 - - -### ArgApp and ArgMain - -You don't even have to call parse() yourself. The arguments are automatically parsed for you -if you extend ArgApp (for the scala "App" style way of creating a main method) or if you extend -ArgMain (for the main-method version, my personal perference). - -With ArgApp: - - class Arguments with FieldParsing { - var name: String = _ - var count: Int = _ - } - - object MyApp extends ArgApp[Arguments]{ - //this.argHolder is an Arguments object w/ the args already parsed - println(this.argHolder.name) - } - -or with ArgMain: - - class Arguments with FieldParsing { - var name: String = _ - var count: Int = _ - } - - object MyApp extends ArgMain[Arguments]{ - def main(args: Arguments) { - //the cmd line arguments get parsed, and then passed into this function - println(args.name) - } - } - -you could then run these programs with - - java -cp /// MyApp --name foobar --count 17 - -### Mixing In Multiple Traits - -You can use traits to create "sets" of arguments that tend to go together. Because you can mix in multiple traits into -one argument object, this lets you put together the arguments that want, without duplicating argument definitions. - -For example, lets say that you have some set of arguments for a database connection, another set of arguments for a -screen resolution, and another set of arguments for the username. You can define traits for each of these groups: - - trait DBConnectionArgs extends FieldParsing { - var dbHost : String = _ - var dbPort : Int = 4000 - def getDbConnection = { ... } - } - - trait ScreenResolutionArgs extends FieldParsing { - var width: Int = 800 - var height: Int = 600 - } - - trait UsernameArgs extends FieldParsing { - var username: String = _ - } - -Then one application that needs a database connection and a user name could be written as: - - class AppNumberOneArgs extends DBConnectionArgs with UsernameArgs - object AppNumberOne extends ArgMain[AppNumberOneArgs]{ - def main(args: AppNumberOneArgs) = { - val db = args.getDbConnection() - ... - } - } - -And another application that needs a database connection and screen resolution: - - class AppNumeroDosArgs extends DBConnectionArgs with ScreenResolutionArgs - object AppNumeroDos extends ArgMain[AppNumeroDosArgs]{ - def main(args: AppNumeroDosArgs) = { - val db = args.getDbConnection() - ... - } - } - -Note that you are sharing the argument names and types, AND the definition of helper methods like getDbConnection() - -## Status / TODO / Roadmap - -I use this library heavily, so I think it is safe for others to use as well. Of course, that just means I'm used to its -quirks :) There are still a lot of things I'd like to add (and would love contributions from anyone!) - -* Support for primitives as a type parameter (eg., List[Int] doesn't work now) -* Add support for user-defined parsers. Not sure about the syntax here -* Automatically support types with an apply(String) method. (hint: http://stackoverflow.com/questions/9172775/get-companion-object-of-class-by-given-generic-type-scala) -* Add support for user-defined validation. again, not sure about the syntax. -* Nested objects, args named via "." -* turn scaladoc into description. may require compiler plugin -* export to properties and typesafe config object - -## Credits - -This was inspired by the optional package from alexy, which in turn came from: - ->Idea and prototype implementation: DRMacIver. - ->Fleshing out and awesomification: paulp. - -This is a total rewrite, though. +Moved to https://github.com/quantifind/Sumac. This repo is dead diff --git a/pom.xml b/pom.xml deleted file mode 100644 index 8862ac9..0000000 --- a/pom.xml +++ /dev/null @@ -1,127 +0,0 @@ - - 4.0.0 - optional - optional_${scala.version} - optional - 1.0 - git://github.com/paulp/optional.git - - DR MacIver and Paul Phillips - - Java Command-Line Arguments - - 1.6 - UTF-8 - - - - Scala-2.7.5 - - 2.7.5 - - - - Scala-2.8.0-SNAPSHOT - - true - - - 2.8.0-SNAPSHOT - - - - - - scala-tools.org - Scala-Tools Maven2 Release Repository - http://scala-tools.org/repo-releases - - - snapshots.scala-tools.org - Scala-Tools Maven2 Snapshot Repository - http://scala-tools.org/repo-snapshots - - true - - fail - - - true - - fail - - - - - - scala-tools.org - Scala-Tools Maven2 Repository - http://scala-tools.org/repo-releases - - - - - org.scala-lang - scala-library - ${scala.version} - - - org.scala-lang - scala-compiler - ${scala.version} - - - com.thoughtworks.paranamer - paranamer - 2.0 - - - - src/main/scala - target-${scala.version} - - - resources - - - - - org.scala-tools - maven-scala-plugin - - - -deprecation - -unchecked - -optimise - - - -Xmx128m - -Xss2m - -Dfile.encoding=UTF-8 - - - **/draft/*.* - - - - - - compile - testCompile - - - - - - - - - - org.scala-tools - maven-scala-plugin - - - - diff --git a/project/Build.scala b/project/Build.scala deleted file mode 100644 index 38d6750..0000000 --- a/project/Build.scala +++ /dev/null @@ -1,35 +0,0 @@ -import sbt._ -import Keys._ - -object SparkBuild extends Build { - lazy val core = Project("core", file("."), settings = coreSettings) - - def sharedSettings = Defaults.defaultSettings ++ Seq( - version := "0.1", - scalaVersion := "2.9.1", - scalacOptions := Seq(/*"-deprecation",*/ "-unchecked", "-optimize"), // -deprecation is too noisy due to usage of old Hadoop API, enable it once that's no longer an issue - unmanagedJars in Compile <<= baseDirectory map { base => (base / "lib" ** "*.jar").classpath }, - retrieveManaged := true, - transitiveClassifiers in Scope.GlobalScope := Seq("sources"), - publishTo <<= baseDirectory { base => Some(Resolver.file("Local", base / "target" / "maven" asFile)(Patterns(true, Resolver.mavenStyleBasePattern))) }, - libraryDependencies ++= Seq( - "org.scalatest" %% "scalatest" % "1.6.1" % "test" - ) - ) - - val slf4jVersion = "1.6.1" - - def coreSettings = sharedSettings ++ Seq( - name := "optional", - resolvers ++= Seq( - "Typesafe Repository" at "http://repo.typesafe.com/typesafe/releases/", - "JBoss Repository" at "http://repository.jboss.org/nexus/content/repositories/releases/" - ), - libraryDependencies ++= Seq( - "log4j" % "log4j" % "1.2.16", - "org.slf4j" % "slf4j-api" % slf4jVersion, - "org.slf4j" % "slf4j-log4j12" % slf4jVersion - ) - ) - -} diff --git a/project/build.properties b/project/build.properties deleted file mode 100644 index 27a69f9..0000000 --- a/project/build.properties +++ /dev/null @@ -1,5 +0,0 @@ -#Project properties -#Wed Aug 05 14:26:51 PDT 2009 -project.organization=empty -project.name=optional -project.version=0.1 diff --git a/project/plugins.sbt b/project/plugins.sbt deleted file mode 100644 index 06308a7..0000000 --- a/project/plugins.sbt +++ /dev/null @@ -1 +0,0 @@ -addSbtPlugin("com.github.mpeltonen" % "sbt-idea" % "1.2.0") diff --git a/src/main/java/optional/Arg.java b/src/main/java/optional/Arg.java deleted file mode 100644 index 5b6bd89..0000000 --- a/src/main/java/optional/Arg.java +++ /dev/null @@ -1,13 +0,0 @@ -package optional; - -import java.lang.annotation.ElementType; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.lang.annotation.Target; - -@Retention(RetentionPolicy.RUNTIME) -@Target(ElementType.FIELD) -public @interface Arg { - String name() default ""; - String description() default ""; -} diff --git a/src/main/scala/optional/ArgApp.scala b/src/main/scala/optional/ArgApp.scala deleted file mode 100644 index 7266dbd..0000000 --- a/src/main/scala/optional/ArgApp.scala +++ /dev/null @@ -1,84 +0,0 @@ -package optional - -import java.lang.reflect.{ParameterizedType, Type} - -trait Argable[T <: FieldParsing] { - - protected lazy val argHolder = { - val argClass = getArgumentClass - val ctors = argClass.getDeclaredConstructors - ctors.find(_.getGenericParameterTypes.length == 0) match { - case Some(ctor) => - ctor.setAccessible(true) - ctor.newInstance().asInstanceOf[T] - case None => throw new AssertionError("No zero-arg constructor found") - } - } - - /** - * get the instance of T that holds the parsed args. - * - * not needed for the user that just wants to run their code -- this is accessible just for other libs - * built on top. - */ - def getArgHolder: T = argHolder - - private def getArgumentClass = { - val argApp = getClass.getGenericInterfaces.find { tpe => - tpe match { - case ptpe: ParameterizedType => ParseHelper.checkType(ptpe, classOf[Argable[_]]) - case _ => false - } - } - getRawClass(argApp.get.asInstanceOf[ParameterizedType].getActualTypeArguments.apply(0)) - } - - private def getRawClass(tpe: Type) = { - tpe match { - case x: Class[_] => x - case p: ParameterizedType => p.getRawType.asInstanceOf[Class[_]] - } - } - -} - -trait ArgMain[T <: FieldParsing] extends Argable[T] { - def main(rawArgs: Array[String]) { - mainHelper(rawArgs) - } - - private def mainHelper(rawArgs: Array[String]) { - argHolder.parse(rawArgs) - main(argHolder) - } - - def main(args: T) -} - -trait ArgFunction[T <: FieldParsing, U] extends Function[T, U] with Argable[T] - -trait ArgApp[T <: FieldParsing] extends Argable[T] with App { - override def main(args: Array[String]) { - argHolder.parse(args) - super.main(args) - } -} - -//below is just for testing, but want it in compiled classes ... - -class MyArgs extends FieldParsing { - val a: String = "" - val b: Int = 0 -} - -object MyMain extends ArgMain[MyArgs] { - def main(args: MyArgs) { - println(args.a) - println(args.b) - } -} - -object MyApp extends ArgApp[MyArgs] { - println(argHolder.a) - println(argHolder.b) -} diff --git a/src/main/scala/optional/Args.scala b/src/main/scala/optional/Args.scala deleted file mode 100644 index 9fee955..0000000 --- a/src/main/scala/optional/Args.scala +++ /dev/null @@ -1,16 +0,0 @@ -package optional - -trait Args { - def getArgs: Traversable[ArgAssignable] - - val parser = ArgumentParser(getArgs) - - def parse(args: Array[String]) { - val parsedArgs = parser.parse(args) - parsedArgs.foreach { case (argAssignable, valueHolder) => - argAssignable.setValue(valueHolder.value) - } - } - - def helpMessage = parser.helpMessage -} diff --git a/src/main/scala/optional/ArgumentParser.scala b/src/main/scala/optional/ArgumentParser.scala deleted file mode 100644 index 8e5e937..0000000 --- a/src/main/scala/optional/ArgumentParser.scala +++ /dev/null @@ -1,99 +0,0 @@ -package optional - -import scala.annotation.tailrec -import java.lang.reflect.{Type, Field} -import collection.mutable.LinkedHashMap - -class ArgumentParser[T <: ArgAssignable] (val argHolders: Seq[T]) { - lazy val nameToHolder = (LinkedHashMap.empty ++ argHolders.map(a => a.getName -> a)).withDefault { arg => - throw new ArgException("unknown option %s\n%s".format(arg, helpMessage)) - } - - def parse(args: Array[String]): LinkedHashMap[T, ValueHolder[_]] = { - @tailrec - def parse(args: List[String], acc: LinkedHashMap[T, ValueHolder[_]] = LinkedHashMap.empty): LinkedHashMap[T, ValueHolder[_]] = { - args match { - case Nil => acc - case "--help" :: _ => throw new ArgException(helpMessage) - case arg :: _ if (!arg.startsWith("--")) => - throw new ArgException("expecting argument name beginning with \"--\", instead got %s".format(arg)) - case name :: value :: tail => - val suffix = name.drop(2) - val holder = nameToHolder(suffix) - val result = try { - ParseHelper.parseInto(value, holder.getType, holder.getCurrentValue) getOrElse { - throw new ArgException("don't know how to parse type: " + holder.getType) - } - } catch { - case ae: ArgException => throw ae - case e: Throwable => throw new ArgException("Error parsing \"%s\" into field \"%s\" (type = %s)\n%s".format(value, suffix, holder.getType, helpMessage)) - } - - // parse remaining options - acc += (holder -> result) - parse(tail, acc) - case _ => throw new ArgException(helpMessage) - } - } - - try { - parse(args.toList) - } catch { - case ae: ArgException => throw ae - case e: Throwable => throw new ArgException(helpMessage, e) - } - } - - def helpMessage: String = { - val msg = new StringBuilder - msg.append("usage: \n") - nameToHolder.foreach { case (k, v) => - msg.append("--%s\t%s\t%s\n\n".format(k, v.getType, v.getDescription)) - } - msg.toString - } -} - -object ArgumentParser { - def apply[T <: ArgAssignable](argHolders: Traversable[T]) = { - // ignore things we don't know how to parse - new ArgumentParser(argHolders.toSeq.filter(t => ParseHelper.findParser(t.getType).isDefined)) - } -} - -/** - * Container for one argument, that has name, type, and can be assigned a value. - */ -trait ArgAssignable { - def getName: String - def getDescription: String - def getType: Type - def getCurrentValue: AnyRef - def setValue(value: Any) -} - -class FieldArgAssignable(val field: Field, val obj: Object) extends ArgAssignable { - field.setAccessible(true) - val annotationOpt = Option(field.getAnnotation(classOf[Arg])) - - def getName = { - val n = annotationOpt.map(_.name).getOrElse(field.getName) - if (n == "") field.getName else n - } - - def getDescription = { - val d = annotationOpt.map(_.description).getOrElse(field.getName) - if (d == "") getName else d - } - - def getType = field.getGenericType - def getCurrentValue = field.get(obj) - - def setValue(value: Any) = { - field.set(obj, value) - } -} - -case class ArgException(msg: String, cause: Throwable) extends IllegalArgumentException(msg, cause) { - def this(msg: String) = this(msg, null) -} \ No newline at end of file diff --git a/src/main/scala/optional/FieldArgs.scala b/src/main/scala/optional/FieldArgs.scala deleted file mode 100644 index 9db0745..0000000 --- a/src/main/scala/optional/FieldArgs.scala +++ /dev/null @@ -1,10 +0,0 @@ -package optional - -trait FieldArgs extends Args { - override def getArgs = ReflectionUtils.getAllDeclaredFields(getClass) collect { - case f if (f.getName != "parser" && f.getName != "bitmap$0") => new FieldArgAssignable(f, this) - } -} - -@deprecated("legacy naming") -trait FieldParsing extends FieldArgs \ No newline at end of file diff --git a/src/main/scala/optional/Parser.scala b/src/main/scala/optional/Parser.scala deleted file mode 100644 index 8fd5e0f..0000000 --- a/src/main/scala/optional/Parser.scala +++ /dev/null @@ -1,184 +0,0 @@ -package optional - -import types.{SelectInput,MultiSelectInput} -import java.lang.reflect.{Type, ParameterizedType} -import util.matching.Regex -import java.io.File - -trait Parser[T] { - def parse(s: String, tpe: Type, currentValue: AnyRef): T - - /** - * return true if this parser knows how to parse the given type - * @param tpe - * @return - */ - def canParse(tpe: Type): Boolean -} - -trait SimpleParser[T] extends Parser[T] { - val knownTypes: Set[Class[_]] - def canParse(tpe: Type) = { - if (tpe.isInstanceOf[Class[_]]) knownTypes(tpe.asInstanceOf[Class[_]]) - else false - } - def parse(s: String, tpe:Type, currentValue: AnyRef) = parse(s) - def parse(s: String): T -} - -trait CompoundParser[T] extends Parser[T] - -object StringParser extends SimpleParser[String] { - val knownTypes: Set[Class[_]] = Set(classOf[String]) - def parse(s: String) = s -} - -object IntParser extends SimpleParser[Int] { - val knownTypes: Set[Class[_]] = Set(classOf[Int], classOf[java.lang.Integer]) - def parse(s: String) = s.toInt -} - -object LongParser extends SimpleParser[Long] { - val knownTypes: Set[Class[_]] = Set(classOf[Long], classOf[java.lang.Long]) - def parse(s: String) = s.toLong -} - -object BooleanParser extends SimpleParser[Boolean] { - val knownTypes: Set[Class[_]] = Set(classOf[Boolean], classOf[java.lang.Boolean]) - def parse(s: String) = s.toBoolean -} - -object FloatParser extends SimpleParser[Float] { - val knownTypes: Set[Class[_]] = Set(classOf[Float], classOf[java.lang.Float]) - def parse(s: String) = s.toFloat -} - -object DoubleParser extends SimpleParser[Double] { - val knownTypes: Set[Class[_]] = Set(classOf[Double], classOf[java.lang.Double]) - def parse(s: String) = s.toDouble -} - -object RegexParser extends SimpleParser[Regex] { - val knownTypes: Set[Class[_]] = Set(classOf[Regex]) - def parse(s: String) = s.r -} - -object FileParser extends SimpleParser[File] { - val knownTypes: Set[Class[_]] = Set(classOf[File]) - def parse(s: String) = { - val fullPath = if (s.startsWith("~")) s.replaceFirst("~", System.getProperty("user.home")) else s - new File(fullPath) - } -} - -//TODO CompoundParser are both a pain to write, and extremely unsafe. Design needs some work - -object ListParser extends CompoundParser[List[_]] { - - def canParse(tpe: Type) = { - ParseHelper.checkType(tpe, classOf[List[_]]) - } - - def parse(s: String, tpe: Type, currentValue: AnyRef) = { - if (tpe.isInstanceOf[ParameterizedType]) { - val ptpe = tpe.asInstanceOf[ParameterizedType] - val subtype = ptpe.getActualTypeArguments()(0) - val subParser = ParseHelper.findParser(subtype).get //TODO need to handle cases where its a list, but can't parse subtype - val parts = s.split(",") - parts.map(subParser.parse(_, subtype, currentValue)).toList - } else List.empty - } -} - -object SetParser extends CompoundParser[collection.Set[_]] { - def canParse(tpe: Type) = { - ParseHelper.checkType(tpe, classOf[collection.Set[_]]) - } - - def parse(s: String, tpe: Type, currentValue: AnyRef) = { - if (tpe.isInstanceOf[ParameterizedType]) { - val ptpe = tpe.asInstanceOf[ParameterizedType] - val subtype = ptpe.getActualTypeArguments()(0) - val subParser = ParseHelper.findParser(subtype).get - val parts = s.split(",") - parts.map(subParser.parse(_, subtype, currentValue)).toSet - } else Set.empty - } -} - -object SelectInputParser extends CompoundParser[SelectInput[_]] { - def canParse(tpe: Type) = { - ParseHelper.checkType(tpe, classOf[SelectInput[_]]) - } - - def parse(s: String, tpe: Type, currentValue: AnyRef) = { - val currentVal = currentValue.asInstanceOf[SelectInput[Any]] //not really Any, but not sure how to make the compiler happy ... - if (tpe.isInstanceOf[ParameterizedType]) { - val ptpe = tpe.asInstanceOf[ParameterizedType] - val subtype = ptpe.getActualTypeArguments()(0) - val subParser = ParseHelper.findParser(subtype).get - val parsed = subParser.parse(s, subtype, currentVal.value) - if (currentVal.options(parsed)) currentVal.value = Some(parsed) - else throw new IllegalArgumentException(parsed + " is not the allowed values: " + currentVal.options) - //we don't return a new object, just modify the existing one - currentVal - } else throw new UnsupportedOperationException() - } -} - -object MultiSelectInputParser extends CompoundParser[MultiSelectInput[_]] { - def canParse(tpe: Type) = ParseHelper.checkType(tpe, classOf[MultiSelectInput[_]]) - - def parse(s: String, tpe: Type, currentValue: AnyRef) = { - val currentVal = currentValue.asInstanceOf[MultiSelectInput[Any]] //not really Any, but not sure how to make the compiler happy ... - if (tpe.isInstanceOf[ParameterizedType]) { - val ptpe = tpe.asInstanceOf[ParameterizedType] - val subtype = ptpe.getActualTypeArguments()(0) - val subParser = ParseHelper.findParser(subtype).get - val parsed = s.split(",").map(subParser.parse(_, subtype, "dummy")).toSet - val illegal = parsed.diff(currentVal.options) - if (illegal.isEmpty) currentVal.value = parsed - else throw new IllegalArgumentException(illegal.toString + " is not the allowed values: " + currentVal.options) - //we don't return a new object, just modify the existing one - currentVal - } else throw new UnsupportedOperationException() - } -} - -object ParseHelper { - val parsers = Seq( - StringParser, - IntParser, - LongParser, - FloatParser, - DoubleParser, - BooleanParser, - ListParser, - SetParser, - SelectInputParser, - MultiSelectInputParser, - FileParser, - RegexParser) - - def findParser(tpe: Type): Option[Parser[_]] = parsers.find(_.canParse(tpe)) - - def parseInto[T](s: String, tpe: Type, currentValue: AnyRef) : Option[ValueHolder[T]] = { - //could change this to be a map, at least for the simple types - findParser(tpe).map(parser => ValueHolder[T](parser.parse(s, tpe, currentValue).asInstanceOf[T], tpe)) - } - - def checkType(tpe: Type, targetClassSet: Class[_]*) = { - def helper(tpe: Type, targetCls: Class[_]) = { - val clz = if (tpe.isInstanceOf[Class[_]]) - tpe.asInstanceOf[Class[_]] - else if (tpe.isInstanceOf[ParameterizedType]) - tpe.asInstanceOf[ParameterizedType].getRawType.asInstanceOf[Class[_]] - else - classOf[Int] //just need something that won't match - targetCls.isAssignableFrom(clz) - } - targetClassSet.exists(targetClass => helper(tpe, targetClass)) - } -} - -case class ValueHolder[T](value: T, tpe: Type) \ No newline at end of file diff --git a/src/main/scala/optional/ReflectionUtils.scala b/src/main/scala/optional/ReflectionUtils.scala deleted file mode 100644 index fe2fc16..0000000 --- a/src/main/scala/optional/ReflectionUtils.scala +++ /dev/null @@ -1,16 +0,0 @@ -package optional - -import annotation.tailrec -import java.lang.reflect.Field - -object ReflectionUtils { - - @tailrec - def getAllDeclaredFields(cls: Class[_], acc: Seq[Field] = Seq.empty): Seq[Field] = { - val fields = acc ++ cls.getDeclaredFields - Option(cls.getSuperclass) match { - case Some(clazz) => getAllDeclaredFields(clazz, fields) - case _ => fields - } - } -} diff --git a/src/main/scala/optional/types/MultiSelectInput.scala b/src/main/scala/optional/types/MultiSelectInput.scala deleted file mode 100644 index 5930b10..0000000 --- a/src/main/scala/optional/types/MultiSelectInput.scala +++ /dev/null @@ -1,9 +0,0 @@ -package optional.types - -class MultiSelectInput[T](var value: Set[T], val options: Set[T]) - -object MultiSelectInput { - def apply[T](options: T*) = new MultiSelectInput[T](Set(), options.toSet) -} - - diff --git a/src/main/scala/optional/types/SelectInput.scala b/src/main/scala/optional/types/SelectInput.scala deleted file mode 100644 index f4293b1..0000000 --- a/src/main/scala/optional/types/SelectInput.scala +++ /dev/null @@ -1,10 +0,0 @@ -package optional.types - -import collection.mutable.LinkedHashSet -import collection.Set - -class SelectInput[T](var value: Option[T], val options: Set[T]) - -object SelectInput{ - def apply[T](options: T*) = new SelectInput[T](value = None, options = (LinkedHashSet.empty ++ options)) -} diff --git a/src/test/scala/optional/ArgAppTest.scala b/src/test/scala/optional/ArgAppTest.scala deleted file mode 100644 index 8121f8f..0000000 --- a/src/test/scala/optional/ArgAppTest.scala +++ /dev/null @@ -1,28 +0,0 @@ -package optional - -import org.scalatest.FunSuite -import org.scalatest.matchers.ShouldMatchers - -class ArgAppTest extends FunSuite { - - test("main") { - val m = new MyApp() - m.main(Array("--a", "hello", "--b", "17")) - } - -} - -class MyArgs extends FieldParsing { - val a: String = "" - val b: Int = 0 -} - -class MyApp extends Dummy with ArgApp[MyArgs] with ShouldMatchers { - def main(args: MyArgs) { - args.a should be ("hello") - args.b should be (17) - } -} - -trait Dummy - diff --git a/src/test/scala/optional/ArgumentParserTest.scala b/src/test/scala/optional/ArgumentParserTest.scala deleted file mode 100644 index b0e2879..0000000 --- a/src/test/scala/optional/ArgumentParserTest.scala +++ /dev/null @@ -1,40 +0,0 @@ -package optional - -import org.scalatest.FunSuite -import org.scalatest.matchers.ShouldMatchers -import scala.collection._ - -class ArgumentParserTest extends FunSuite with ShouldMatchers { - - test("parse") { - val c = SimpleClass("a", 0, 1.4, 2) - val fieldArgs = classOf[SimpleClass].getDeclaredFields.map{f => new FieldArgAssignable(f, c)} - val argParser = new ArgumentParser(fieldArgs) - - { - val parsed = getSimpleNameToArgMap(argParser.parse(Array("--name", "foo"))) - parsed.size should be (1) - parsed should contain key ("name") - parsed("name") should be ("foo") - } - - - { - val parsed = getSimpleNameToArgMap(argParser.parse(Array("--count", "5", "--dummy", "7.4e3", "--name", "ooga"))) - parsed.size should be (3) - parsed should contain key ("count") - parsed("count") should be (5) - parsed should contain key ("dummy") - parsed("dummy") should be (7.4e3) - parsed should contain key ("name") - parsed("name") should be ("ooga") - } - } - - def getSimpleNameToArgMap(parsedArgs : Map[_ <: ArgAssignable, ValueHolder[_]]) = { - parsedArgs.map{kv => kv._1.getName -> kv._2.value}.toMap[String, Any] - } -} - - -case class SimpleClass(val name: String, val count: Int, val dummy: Double, val count2: Int) diff --git a/src/test/scala/optional/FieldArgsTest.scala b/src/test/scala/optional/FieldArgsTest.scala deleted file mode 100644 index 23b4ff1..0000000 --- a/src/test/scala/optional/FieldArgsTest.scala +++ /dev/null @@ -1,222 +0,0 @@ -package optional - -import types.{SelectInput,MultiSelectInput} -import org.scalatest.FunSuite -import org.scalatest.matchers.ShouldMatchers - -/** - * - */ - -class FieldArgsTest extends FunSuite with ShouldMatchers { - - test("parseStrings") { - val o = new StringHolder(null, null) with FieldArgs - o.parse(Array("--name", "hello")) - o.name should be ("hello") - o.parse(Array("--comment", "blah di blah blah")) - o.name should be ("hello") - o.comment should be ("blah di blah blah") - o.parse(Array("--name", "ooga", "--comment", "stuff")) - o.name should be ("ooga") - o.comment should be ("stuff") - } - - test("parseMixed") { - val o = new MixedTypes(null, 0) with FieldArgs - - o.parse(Array("--name", "foo", "--count", "17")) - o.name should be ("foo") - o.count should be (17) - o.parse(Array("--count", "-5")) - o.name should be ("foo") - o.count should be (-5) - } - - test("subclass parsing") { - val o = new Child(false, null, 0) with FieldParsing - - o.parse(Array("--flag", "true", "--name", "bugaloo")) - o.name should be ("bugaloo") - o.flag should be (true) - } - - test("help message") { - val o = new StringHolder(null, null) with FieldArgs - val exc1 = evaluating {o.parse(Array("--xyz", "hello"))} should produce [ArgException] - //the format is still ugly, but at least there is some info there - "\\-\\-name\\s.*String".r.findFirstIn(exc1.getMessage()) should be ('defined) - "\\-\\-comment\\s.*String".r.findFirstIn(exc1.getMessage()) should be ('defined) - - val o2 = new MixedTypes(null, 0) with FieldArgs - val exc2 = evaluating {o2.parse(Array("--foo", "bar"))} should produce [ArgException] - "\\-\\-name\\s.*String".r findFirstIn(exc2.getMessage) should be ('defined) - "\\-\\-count\\s.*[Ii]nt".r findFirstIn(exc2.getMessage) should be ('defined) //java or scala types, I'll take either for now - - val exc3 = evaluating {o2.parse(Array("--count", "ooga"))} should produce [ArgException] - //this message really should be much better. (a) the number format exception should come first and (b) should indicate that it was while processing the "count" argument - "\\-\\-name\\s.*String".r findFirstIn(exc3.getMessage) should be ('defined) - "\\-\\-count\\s.*[Ii]nt".r findFirstIn(exc3.getMessage) should be ('defined) //java or scala types, I'll take either for now - } - - test("error msg on unknown types") { - val o = new SpecialTypes("", null) with FieldParsing - - o.parse(Array("--name", "ooga")) - o.name should be ("ooga") - o.funky should be (null) - - val exc = evaluating {o.parse(Array("--funky", "xyz"))} should produce [ArgException] - //maybe sometime I should change the removal of unknown types to keep them around for error msgs ... -// exc.cause.getMessage should include ("type") -// exc.cause.getMessage should include ("MyFunkyType") - } - - - test("good error msg") { - val o = new MixedTypes("", 0) with FieldParsing - - val exc1 = evaluating {o.parse(Array("--count", "hi"))} should produce [ArgException] - //don't actually need the message to look *exactly* like this, but extremely useful for it to at least say what it was trying to parse - exc1.getMessage should startWith ("""Error parsing "hi" into field "count" (type = int)""") - } - - test("set args") { - case class SetArgs(val set: Set[String]) extends FieldParsing - val s = new SetArgs(null) - s.parse(Array("--set", "a,b,c,def")) - s.set should be (Set("a", "b", "c", "def")) - } - - test("selectInput") { - case class SelectInputArgs(val select: SelectInput[String] = SelectInput("a", "b", "c")) extends FieldParsing - val s = new SelectInputArgs() - val id = System.identityHashCode(s.select) - s.parse(Array("--select", "b")) - s.select.value should be (Some("b")) - System.identityHashCode(s.select) should be (id) - s.select.options should be (Set("a", "b", "c")) - - evaluating {s.parse(Array("--select", "q"))} should produce [ArgException] - } - - test("selectInput order") { - import util.Random._ - val max = 1000 - val orderedChoices = shuffle(1.to(max).map(_.toString)) - case class SelectInputArgs(val select: SelectInput[String] = SelectInput(orderedChoices:_*)) extends FieldParsing - val s = new SelectInputArgs() - val id = System.identityHashCode(s.select) - - val index = nextInt(max).toString - s.parse(Array("--select", index)) - s.select.value should be (Some(index)) - System.identityHashCode(s.select) should be (id) - s.select.options.toList should be (orderedChoices) - - evaluating {s.parse(Array("--select", "q"))} should produce [ArgException] - } - - test("multiSelectInput") { - case class MultiSelectInputArgs(val multiSelect: MultiSelectInput[String] = MultiSelectInput("a", "b", "c")) extends FieldParsing - val s = new MultiSelectInputArgs() - val id = System.identityHashCode(s.multiSelect) - s.parse(Array("--multiSelect", "b")) - s.multiSelect.value should be (Set("b")) - System.identityHashCode(s.multiSelect) should be (id) - s.multiSelect.options should be (Set("a", "b", "c")) - - s.parse(Array("--multiSelect", "b,c")) - s.multiSelect.value should be (Set("b", "c")) - - evaluating {s.parse(Array("--multiSelect", "q"))} should produce [ArgException] - evaluating {s.parse(Array("--multiSelect", "b,q"))} should produce [ArgException] - evaluating {s.parse(Array("--multiSelect", "q,b"))} should produce [ArgException] - - } - - test("exclude scala helper fields") { - - { - val m = new MixedTypes(null, 0) with FieldArgs - val names = m.parser.nameToHolder.keySet - names should be (Set("name", "count")) - } - - - { - val s = new SomeApp() - val names = s.getArgHolder.parser.nameToHolder.keySet - names should be (Set("x", "y")) - } - - } - - - - test("annotations") { - val c = new ClassWithSomeAnnotations() with FieldArgs - c.parser.nameToHolder.values.foreach { f => - f.getName match { - case "foo" => - f.getDescription should be ("foo") - case "ooga" => - f.getDescription should be ("this is an integer argument") - case "" => - assert(false, "use variable name if no name given in annotation") - case "x" => assert(false, "use name from annotation instead of variable name") - case "y" => - f.getDescription should be ("another integer argument") - case "z" => - assert(false, "use name from annotation instead of variable name") - case "wakka" => - f.getDescription should be ("wakka") - } - } - - c.parse(Array("--foo", "hi", "--ooga", "17", "--y", "181", "--wakka", "1.81")) - c.foo should be ("hi") - c.x should be (17) - c.y should be (181) - c.z should be (1.81) - - evaluating {c.parse(Array("--x", "17"))} should produce [ArgException] - evaluating {c.parse(Array("--z", "1"))} should produce [ArgException] - } - - -} - - -case class StringHolder(val name: String, val comment: String) - -case class MixedTypes(val name: String, val count: Int) - -//is there an easier way to do this in scala? -class Child(val flag: Boolean, name: String, count: Int) extends MixedTypes(name, count) - -case class SpecialTypes(val name: String, val funky: MyFunkyType) - -case class MyFunkyType(val x: String) - - -class SomeApp extends ArgApp[SomeArgs] { - def main(args: SomeArgs) {} -} - -class SomeArgs extends FieldParsing { - var x: Int = 0 - var y: String = "hello" -} - - - -class ClassWithSomeAnnotations { - var foo: String = _ - @Arg(name="ooga", description="this is an integer argument") - var x: Int = _ - @Arg(description="another integer argument") - var y: Int = _ - @Arg(name="wakka") - var z: Double = _ -} \ No newline at end of file diff --git a/src/test/scala/optional/ParserTest.scala b/src/test/scala/optional/ParserTest.scala deleted file mode 100644 index 9f826ad..0000000 --- a/src/test/scala/optional/ParserTest.scala +++ /dev/null @@ -1,46 +0,0 @@ -package optional - -import org.scalatest.FunSuite -import org.scalatest.matchers.ShouldMatchers - -/** - * - */ - -class ParserTest extends FunSuite with ShouldMatchers { - - test("SimpleParser") { - StringParser.parse("ooga") should be ("ooga") - IntParser.parse("5") should be (5) - DoubleParser.parse("5") should be (5.0) - DoubleParser.parse("1e-10") should be (1e-10) - BooleanParser.parse("false") should be (false) - BooleanParser.parse("true") should be (true) - val homeDir = System.getProperty("user.home") - FileParser.parse("~/foo") should be (new java.io.File(homeDir, "foo")) - val cwd = System.getProperty("user.dir") - FileParser.parse("ooga").getAbsolutePath should be (new java.io.File(cwd, "ooga").getAbsolutePath) - } - - test("ListParser") { - //Note this doesn't work w/ primitive types now, b/c its based on java reflection - - //Is there is better way to get a handle on parameterized types???? - val field = classOf[ContainerA].getDeclaredField("boundaries") - val parsed = ParseHelper.parseInto("a,b,cdef,g", field.getGenericType, "dummy") - parsed should be (Some(ValueHolder(List("a", "b", "cdef", "g"), field.getGenericType))) - } - - test("ParseHelper") { - ParseHelper.parseInto("ooga", classOf[String], "dummy") should be (Some(ValueHolder("ooga", classOf[String]))) - ParseHelper.parseInto("5.6", classOf[Double], "dummy") should be (Some(ValueHolder(5.6, classOf[Double]))) - ParseHelper.parseInto("5.6", classOf[String], "dummy") should be (Some(ValueHolder("5.6", classOf[String]))) - ParseHelper.parseInto("abc", classOf[RandomUnknownClass], "dummy") should be (None) - } - -} - -class RandomUnknownClass - - -class ContainerA(val title: String, val count: Int, val boundaries: List[String])