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-* + diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 0000000..2394521 --- /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 test" diff --git a/LICENSE b/LICENSE deleted file mode 100644 index 9fb7d44..0000000 --- a/LICENSE +++ /dev/null @@ -1,25 +0,0 @@ -Copyright (c) 2009, David R. MacIver and Paul Phillips (hereafter, DRMAPP) -All rights reserved. - -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. - -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. - diff --git a/README b/README deleted file mode 100644 index 2cda52a..0000000 --- a/README +++ /dev/null @@ -1,32 +0,0 @@ -optional is a command line option parser and library. - -YOU WRITE: - -object MyAwesomeCommandLineTool extends optional.Application { - // for instance... - def main(count: Option[Int], file: Option[java.io.File], arg1: String) { - [...] - } -} - -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. - -HOW IT WORKS: - - Reflection, man. - -CREDITS: - - Idea and prototype implementation: DRMacIver. - Fleshing out and awesomification: paulp. - diff --git a/README.md b/README.md new file mode 100644 index 0000000..2e92dfb --- /dev/null +++ b/README.md @@ -0,0 +1 @@ +Moved to https://github.com/quantifind/Sumac. This repo is dead 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 9cb7c61..0000000 Binary files a/lib/paranamer-1.3.jar and /dev/null differ 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/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.properties b/project/build.properties deleted file mode 100644 index 17f8f34..0000000 --- a/project/build.properties +++ /dev/null @@ -1,7 +0,0 @@ -#Project properties -#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 diff --git a/src/main/scala/application.scala b/src/main/scala/application.scala deleted file mode 100644 index bf07352..0000000 --- a/src/main/scala/application.scala +++ /dev/null @@ -1,262 +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)) - - 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 - 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)) - - // verify all required options are present - val missingArgs = reqArgs filter (x => !(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 = options contains name - - if (ma.isPositional) coerceTo(name, tpe)(args(ma.pos - 1)) - else if (isPresent) coerceTo(name, tpe)(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: _*) - callWithOptions() - } - catch { - case UsageError(msg) => - println("Error: " + msg) - println(usageMessage) - } - } -} 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 ef59e8d..0000000 --- a/src/main/scala/options.scala +++ /dev/null @@ -1,73 +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) - } -}