From e7cb17e146615a90c34a2a16f8daf194b16932a6 Mon Sep 17 00:00:00 2001 From: Wade Shen Date: Fri, 19 Jul 2013 21:32:15 -0400 Subject: [PATCH 1/5] - fix "--optA A --optB B ..." behavior. If multiple files are on the command line, docopt would throw away settings for --optA and --optB. --- src/main/scala/org/docopt/PatternMatcher.scala | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) 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)) From 1b3653ec75829debcc62a6ad83e2580c1fc9f69a Mon Sep 17 00:00:00 2001 From: Wade Shen Date: Wed, 20 Nov 2013 09:32:54 -0500 Subject: [PATCH 2/5] - fix for long options: --arg="xxx=yyy" and --arg xxx=yyy --- src/main/scala/org/docopt/PatternParser.scala | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/main/scala/org/docopt/PatternParser.scala b/src/main/scala/org/docopt/PatternParser.scala index 3c74064..538ba8b 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) From fcd47269c83457aa6b97b954588032a8a5598fea Mon Sep 17 00:00:00 2001 From: Wade Shen Date: Sat, 7 Dec 2013 17:56:00 -0500 Subject: [PATCH 3/5] - for some reason, docopt insists on regluing together the command line then resplitting. To temporarily fix support for command lines like: `--test="a b c"`, we rejoin with "\t" and tokenizer via \t when parsing the command line. --- src/main/scala/org/docopt/Docopt.scala | 2 +- src/main/scala/org/docopt/PatternParser.scala | 6 +++--- .../scala/org/docopt/PatternParserFunSpec.scala | 14 +++++++------- 3 files changed, 11 insertions(+), 11 deletions(-) diff --git a/src/main/scala/org/docopt/Docopt.scala b/src/main/scala/org/docopt/Docopt.scala index c60f4ba..7d09c47 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.mkString("\t"), 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/PatternParser.scala b/src/main/scala/org/docopt/PatternParser.scala index 538ba8b..9590f70 100644 --- a/src/main/scala/org/docopt/PatternParser.scala +++ b/src/main/scala/org/docopt/PatternParser.scala @@ -219,7 +219,7 @@ object PatternParser { } def parseArgv(argv: String, options: SeqOpt, optionFirst:Boolean = false) = - parseArgvRecursive(clarifyLongOptionAmbiguities(tokenStream(argv), options), options, optionFirst) + parseArgvRecursive(clarifyLongOptionAmbiguities(tokenStream(argv, tokenizer = """\t+"""), options), options, optionFirst) private def parseArgvRecursive(tokens: Tokens, options: SeqOpt, optionFirst: Boolean, ret: List[Pattern] = Nil): (SeqOpt, SeqPat) = tokens match { @@ -241,8 +241,8 @@ object PatternParser { option <- parseOption(optionMatch.group(1))) yield option).toList - private def tokenStream(source: String, split: Boolean = true): Tokens = - source.split("\\s+").filter(_ != "").toList + private def tokenStream(source: String, split: Boolean = true, tokenizer : String = """\s+"""): Tokens = + source.split(tokenizer).filter(_ != "").toList // keep only the Usage: part, remove everything after def printableUsage(doc: String): String = { diff --git a/src/test/scala/org/docopt/PatternParserFunSpec.scala b/src/test/scala/org/docopt/PatternParserFunSpec.scala index b9518f5..5bba32e 100644 --- a/src/test/scala/org/docopt/PatternParserFunSpec.scala +++ b/src/test/scala/org/docopt/PatternParserFunSpec.scala @@ -248,21 +248,21 @@ class PatternParserFunSpec extends FunSpec { } it("should parse correctly: %s".format("-h --verbose")) { - assert (PP.parseArgv("-h --verbose", options) == + assert (PP.parseArgv("-h\t--verbose", 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\t--file\tf.txt", 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\t--file\tf.txt\targ", 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\t--file\tf.txt\targ\targ2", 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\targ\t--\t-v", options) == (options, List(Option("-h","",0,BooleanValue(value = true)), Argument("", StringValue("arg")), @@ -378,11 +378,11 @@ Options: 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", "--\t-o", 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\t1", help = false, "", optionsFirst = false) } } } From ae2fadc0f17cf92eb4a04dd857ebb88b5320cd2b Mon Sep 17 00:00:00 2001 From: Wade Shen Date: Sat, 7 Dec 2013 18:33:02 -0500 Subject: [PATCH 4/5] - fix issue with --long="test a b c" Docopt was rejoining argv and then resplitting based on """\s+""". Don't do this anymore... --- pom.xml | 2 +- src/main/scala/org/docopt/Docopt.scala | 2 +- src/main/scala/org/docopt/PatternParser.scala | 11 ++--- .../org/docopt/PatternParserFunSpec.scala | 42 +++++++++---------- 4 files changed, 29 insertions(+), 28 deletions(-) diff --git a/pom.xml b/pom.xml index 0d422d4..ec9d10b 100644 --- a/pom.xml +++ b/pom.xml @@ -4,7 +4,7 @@ org.docopt docopt - 0.1-SNAPSHOT + 0.1b jar docopt parser for jvm diff --git a/src/main/scala/org/docopt/Docopt.scala b/src/main/scala/org/docopt/Docopt.scala index 7d09c47..6cff23b 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("\t"), help, version, optionsFirst) + val collected = PatternParser.docopt(usage, argv, 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/PatternParser.scala b/src/main/scala/org/docopt/PatternParser.scala index 9590f70..4fe75ca 100644 --- a/src/main/scala/org/docopt/PatternParser.scala +++ b/src/main/scala/org/docopt/PatternParser.scala @@ -218,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, tokenizer = """\t+"""), 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,8 +242,8 @@ object PatternParser { option <- parseOption(optionMatch.group(1))) yield option).toList - private def tokenStream(source: String, split: Boolean = true, tokenizer : String = """\s+"""): Tokens = - source.split(tokenizer).filter(_ != "").toList + private def tokenStream(source: String, split: Boolean = true): Tokens = + source.split("""\s+""").filter(_ != "").toList // keep only the Usage: part, remove everything after def printableUsage(doc: String): String = { @@ -260,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 5bba32e..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\t--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\t--file\tf.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\t--file\tf.txt\targ", 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\t--file\tf.txt\targ\targ2", 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\targ\t--\t-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", "--\t-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\t1", help = false, "", optionsFirst = false) + PP.docopt("Usage: prog [-o] [--] \n\n -o", "-o 1".split("""\s+"""), help = false, "", optionsFirst = false) } } } From 67fccea87564df1b943a7e47b8326b1f95e84a09 Mon Sep 17 00:00:00 2001 From: Wade Shen Date: Tue, 29 Jul 2014 17:17:01 -0400 Subject: [PATCH 5/5] - updates --- pom.xml | 12 ++++++------ src/main/scala/org/docopt/Docopt.scala | 2 +- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/pom.xml b/pom.xml index ec9d10b..f62c5ba 100644 --- a/pom.xml +++ b/pom.xml @@ -4,7 +4,7 @@ org.docopt docopt - 0.1b + 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 6cff23b..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, 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))