diff --git a/pom.xml b/pom.xml index 0d422d4..f62c5ba 100644 --- a/pom.xml +++ b/pom.xml @@ -4,7 +4,7 @@ org.docopt docopt - 0.1-SNAPSHOT + 0.1c jar docopt parser for jvm @@ -13,7 +13,7 @@ UTF-8 UTF-8 - 2.10.0 + 2.11.2 @@ -46,12 +46,12 @@ org.scala-lang scala-library - 2.10.0 + 2.11.2 org.scalatest - scalatest_2.10 - 2.0.M5b + scalatest_2.11 + 2.2.1-M3 test @@ -134,7 +134,7 @@ org.scalatest scalatest-maven-plugin - 1.0-M2 + 1.0 ${project.build.directory}/surefire-reports . diff --git a/src/main/scala/org/docopt/Docopt.scala b/src/main/scala/org/docopt/Docopt.scala index c60f4ba..2016ead 100644 --- a/src/main/scala/org/docopt/Docopt.scala +++ b/src/main/scala/org/docopt/Docopt.scala @@ -22,7 +22,7 @@ object Docopt { help: Boolean = true, version: String = "", optionsFirst: Boolean = false): Map[String, Any] = { - val collected = PatternParser.docopt(usage, argv.mkString(" "), help, version, optionsFirst) + val collected = PatternParser.docopt(usage, argv.filter(_ != ""), help, version, optionsFirst) val tupled:Seq[(String, Any)] = collected.map(pattern => pattern match { case o@Option(l,s,a,value:Value) => (o.name ,extractValue(value)) case Argument(name,value:Value) => (name, extractValue(value)) diff --git a/src/main/scala/org/docopt/PatternMatcher.scala b/src/main/scala/org/docopt/PatternMatcher.scala index 7877620..163d962 100644 --- a/src/main/scala/org/docopt/PatternMatcher.scala +++ b/src/main/scala/org/docopt/PatternMatcher.scala @@ -49,10 +49,8 @@ object PatternMatcher { private def collectSameName(matched: ChildPattern, originalValue: Value, collected: SeqPat): SeqPat = { - // http://stackoverflow.com/questions/11394034/why-scalas-pattern-maching-does-not-work-in-for-loops-for-type-matching - // http://www.scala-lang.org/node/2187 - val sameName = (for (a@(_a:ChildPattern) <- collected - if a.name == matched.name) yield a).toList + val (psameName, nonSameName) = collected.partition { case a:ChildPattern => (a.name == matched.name) } + val sameName = psameName.asInstanceOf[Seq[Pattern with ChildPattern]] def childPatternUpdateValue(child: ChildPattern, newValue: Value) = child match { case Argument(n, _) => Argument(n, newValue) @@ -72,7 +70,7 @@ object PatternMatcher { collected ++ List(childPatternUpdateValue(matched, IntValue(1))) case head :: tail => head.value match { case IntValue(i) => - childPatternUpdateValue(head, IntValue(1 + i)) :: tail + nonSameName ++ (childPatternUpdateValue(head, IntValue(1 + i)) :: tail) } } // we must update the list or start a new one. @@ -82,9 +80,9 @@ object PatternMatcher { collected ++ List(childPatternUpdateValue(matched, matched.value match {case StringValue(v_) => ManyStringValue(List(v_)) case x => x})) case head :: tail => matched.value match { case ManyStringValue(s_) => - childPatternUpdateValue(head, ManyStringValue(s ++ s_)) :: tail + nonSameName ++ (childPatternUpdateValue(head, ManyStringValue(s ++ s_)) :: tail) case StringValue(s_) => - childPatternUpdateValue(head, ManyStringValue(s ++ List(s_))) :: tail + nonSameName ++ (childPatternUpdateValue(head, ManyStringValue(s ++ List(s_))) :: tail) } } case _ => collected ++ List(childPatternUpdateValue(matched, matched.value)) diff --git a/src/main/scala/org/docopt/PatternParser.scala b/src/main/scala/org/docopt/PatternParser.scala index 3c74064..4fe75ca 100644 --- a/src/main/scala/org/docopt/PatternParser.scala +++ b/src/main/scala/org/docopt/PatternParser.scala @@ -171,8 +171,9 @@ object PatternParser { private def extractLongOptionValue(longOption: String) = if (longOption.exists(_ == '=')) { + val Splitter = """^(.*?)=(.*)$""".r try { - val Array(long, value) = longOption.split("=") + val Splitter(long, value) = longOption //val Array(long, value) = longOption.split("=") (long, Some(value)) } catch { case _:Throwable => throw new UnparsableOptionException(longOption) @@ -217,8 +218,9 @@ object PatternParser { case head :: tail => head :: clarifyLongOptionAmbiguities(tail, options) } - def parseArgv(argv: String, options: SeqOpt, optionFirst:Boolean = false) = - parseArgvRecursive(clarifyLongOptionAmbiguities(tokenStream(argv), options), options, optionFirst) + //def parseArgv(argv: String, options: SeqOpt, optionFirst:Boolean = false) : (SeqOpt, SeqPat) = parseArgv(argv.split("""\s+"""), options, optionFirst) + def parseArgv(argv: Array[String], options: SeqOpt, optionFirst:Boolean = false) : (SeqOpt, SeqPat) = + parseArgvRecursive(clarifyLongOptionAmbiguities(argv.toList, options), options, optionFirst) private def parseArgvRecursive(tokens: Tokens, options: SeqOpt, optionFirst: Boolean, ret: List[Pattern] = Nil): (SeqOpt, SeqPat) = tokens match { @@ -241,7 +243,7 @@ object PatternParser { private def tokenStream(source: String, split: Boolean = true): Tokens = - source.split("\\s+").filter(_ != "").toList + source.split("""\s+""").filter(_ != "").toList // keep only the Usage: part, remove everything after def printableUsage(doc: String): String = { @@ -259,7 +261,7 @@ object PatternParser { "( " + words.tail.map(x => if (x == programName) ") | (" else x).mkString(" ") + " )" } - def docopt(doc: String, argv: String = "", help: Boolean = true, version: String = "", optionsFirst: Boolean = false): SeqPat = { + def docopt(doc: String, argv: Array[String] = Array[String](), help: Boolean = true, version: String = "", optionsFirst: Boolean = false): SeqPat = { val usage = formalUsage(printableUsage(doc)) val options = parseOptionDescriptions(doc) val (options_, pattern) = parsePattern(usage, options) diff --git a/src/test/scala/org/docopt/PatternParserFunSpec.scala b/src/test/scala/org/docopt/PatternParserFunSpec.scala index b9518f5..c9007a1 100644 --- a/src/test/scala/org/docopt/PatternParserFunSpec.scala +++ b/src/test/scala/org/docopt/PatternParserFunSpec.scala @@ -239,30 +239,30 @@ class PatternParserFunSpec extends FunSpec { Option("-v", "--verbose"), Option("-f", "--file", 1)) it("should parse correctly: %s".format("")) { - assert (PP.parseArgv("", options) == (options, Nil)) + assert (PP.parseArgv(Array[String](), options) == (options, Nil)) } it("should parse correctly: %s".format("-h")) { - assert (PP.parseArgv("-h", options) == + assert (PP.parseArgv(Array("-h"), options) == (options, List(Option("-h","",0,BooleanValue(value = true))))) } it("should parse correctly: %s".format("-h --verbose")) { - assert (PP.parseArgv("-h --verbose", options) == + assert (PP.parseArgv("-h --verbose".split("""\s+"""), options) == (options, List(Option("-h","",0,BooleanValue(value = true)), Option("-v","--verbose",0,BooleanValue(value = true))))) } it("should parse correctly: %s".format("-h --file f.txt")) { - assert (PP.parseArgv("-h --file f.txt", options) == + assert (PP.parseArgv("-h --file f.txt".split("""\s+"""), options) == (options, List(Option("-h","",0,BooleanValue(value = true)), Option("-f","--file",1,StringValue("f.txt"))))) } it("should parse correctly: %s".format("-h --file f.txt arg")) { - assert (PP.parseArgv("-h --file f.txt arg", options) == + assert (PP.parseArgv("-h --file f.txt arg".split("""\s+"""), options) == (options, List(Option("-h","",0,BooleanValue(value = true)), Option("-f","--file",1,StringValue("f.txt")), @@ -270,7 +270,7 @@ class PatternParserFunSpec extends FunSpec { } it("should parse correctly: %s".format("-h --file f.txt arg arg2")) { - assert (PP.parseArgv("-h --file f.txt arg arg2", options) == + assert (PP.parseArgv("-h --file f.txt arg arg2".split("""\s+"""), options) == (options, List(Option("-h","",0,BooleanValue(value = true)), Option("-f","--file",1,StringValue("f.txt")), @@ -279,7 +279,7 @@ class PatternParserFunSpec extends FunSpec { } it("should parse correctly: %s".format("-h arg -- -v")) { - assert (PP.parseArgv("-h arg -- -v", options) == + assert (PP.parseArgv("-h arg -- -v".split("""\s+"""), options) == (options, List(Option("-h","",0,BooleanValue(value = true)), Argument("", StringValue("arg")), @@ -290,7 +290,7 @@ class PatternParserFunSpec extends FunSpec { describe("long options error handling") { it("it should intercept a non existant option") { intercept[UnconsumedTokensException] { - PP.docopt("Usage: prog", "--non-existant", help = false, version = "", optionsFirst = false) + PP.docopt("Usage: prog", Array("--non-existant"), help = false, version = "", optionsFirst = false) } } @@ -300,7 +300,7 @@ class PatternParserFunSpec extends FunSpec { --version --verbose""" intercept[RuntimeException] { - PP.docopt(usage, "--ver", help = false, "", optionsFirst = false) + PP.docopt(usage, Array("--ver"), help = false, "", optionsFirst = false) } } @@ -308,19 +308,19 @@ class PatternParserFunSpec extends FunSpec { // since the option is defined to have an argument, the implicit ')' is // consumed by the parseOption intercept[MissingEnclosureException] { - PP.docopt("Usage: prog --conflict\n\n--conflict ARG", "", help = false, "", optionsFirst = false) + PP.docopt("Usage: prog --conflict\n\n--conflict ARG", Array(""), help = false, "", optionsFirst = false) } } it("it should intercept a reversed conflicting definition") { intercept[UnexpectedArgumentException] { - PP.docopt("Usage: prog --long=ARG\n\n --long", "", help = false, "", optionsFirst = false) + PP.docopt("Usage: prog --long=ARG\n\n --long", Array(""), help = false, "", optionsFirst = false) } } it("it should intercept a missing argument") { intercept[MissingArgumentException] { - PP.docopt("Usage: prog --long ARG\n\n --long ARG", "--long", help = false, "", optionsFirst = false) + PP.docopt("Usage: prog --long ARG\n\n --long ARG", Array("--long"), help = false, "", optionsFirst = false) } } @@ -331,7 +331,7 @@ Usage: prog --derp Options: --derp""" - PP.docopt(doc, "--derp=ARG", help = false, "", optionsFirst = false) + PP.docopt(doc, Array("--derp=ARG"), help = false, "", optionsFirst = false) } } } @@ -339,25 +339,25 @@ Options: describe("short options error handling") { it("it should detect conflicting definitions") { intercept[UnparsableOptionException] { - PP.docopt("Usage: prog -x\n\n-x this\n-x that", "", help = false, "", optionsFirst = false) + PP.docopt("Usage: prog -x\n\n-x this\n-x that", Array(""), help = false, "", optionsFirst = false) } } it("it should detect undefined options") { intercept[UnconsumedTokensException] { - PP.docopt("Usage: prog", "-x", help = false, "", optionsFirst = false) + PP.docopt("Usage: prog", Array("-x"), help = false, "", optionsFirst = false) } } it("it should detect conflicting definitions with arguments") { intercept[MissingEnclosureException] { - PP.docopt("Usage: prog -x\n\n-x ARG", "", help = false, "", optionsFirst = false) + PP.docopt("Usage: prog -x\n\n-x ARG", Array(""), help = false, "", optionsFirst = false) } } it("it should detect missing arguments") { intercept[MissingArgumentException] { - PP.docopt("Usage: prog -x ARG\n\n-x ARG", "-x", help = false, "", optionsFirst = false) + PP.docopt("Usage: prog -x ARG\n\n-x ARG", Array("-x"), help = false, "", optionsFirst = false) } } } @@ -365,24 +365,24 @@ Options: describe("[]|{}|() matching") { it("it should detect missing ]") { intercept[MissingEnclosureException] { - PP.docopt("Usage: prog [a [b]", "", help = false, "", optionsFirst = false) + PP.docopt("Usage: prog [a [b]", Array(""), help = false, "", optionsFirst = false) } } it("it should detect extra )") { intercept[UnconsumedTokensException] { - PP.docopt("Usage: prog [a [b] ] c )", "", help = false, "", optionsFirst = false) + PP.docopt("Usage: prog [a [b] ] c )", Array(""), help = false, "", optionsFirst = false) } } } describe("double-dash support") { it("it should handle correctly '--'") { - PP.docopt("Usage: prog [-o] [--] \n\n-o", "-- -o", help = false, "", optionsFirst = false) + PP.docopt("Usage: prog [-o] [--] \n\n-o", "-- -o".split("""\s+"""), help = false, "", optionsFirst = false) } it("it should handle correctly '--' swapped") { - PP.docopt("Usage: prog [-o] [--] \n\n -o", "-o 1", help = false, "", optionsFirst = false) + PP.docopt("Usage: prog [-o] [--] \n\n -o", "-o 1".split("""\s+"""), help = false, "", optionsFirst = false) } } }