Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix for oneOrMany <file>... problem #2

Open
wants to merge 9 commits into
base: develop
Choose a base branch
from
12 changes: 6 additions & 6 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

<groupId>org.docopt</groupId>
<artifactId>docopt</artifactId>
<version>0.1-SNAPSHOT</version>
<version>0.1c</version>
<packaging>jar</packaging>

<name>docopt parser for jvm</name>
Expand All @@ -13,7 +13,7 @@
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<scala.version>2.10.0</scala.version>
<scala.version>2.11.2</scala.version>
</properties>

<repositories>
Expand Down Expand Up @@ -46,12 +46,12 @@
<dependency>
<groupId>org.scala-lang</groupId>
<artifactId>scala-library</artifactId>
<version>2.10.0</version>
<version>2.11.2</version>
</dependency>
<dependency>
<groupId>org.scalatest</groupId>
<artifactId>scalatest_2.10</artifactId>
<version>2.0.M5b</version>
<artifactId>scalatest_2.11</artifactId>
<version>2.2.1-M3</version>
<scope>test</scope>
</dependency>
<dependency>
Expand Down Expand Up @@ -134,7 +134,7 @@
<plugin>
<groupId>org.scalatest</groupId>
<artifactId>scalatest-maven-plugin</artifactId>
<version>1.0-M2</version>
<version>1.0</version>
<configuration>
<reportsDirectory>${project.build.directory}/surefire-reports</reportsDirectory>
<junitxml>.</junitxml>
Expand Down
2 changes: 1 addition & 1 deletion src/main/scala/org/docopt/Docopt.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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))
Expand Down
12 changes: 5 additions & 7 deletions src/main/scala/org/docopt/PatternMatcher.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand All @@ -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.
Expand All @@ -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))
Expand Down
12 changes: 7 additions & 5 deletions src/main/scala/org/docopt/PatternParser.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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 {
Expand All @@ -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 = {
Expand All @@ -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)
Expand Down
42 changes: 21 additions & 21 deletions src/test/scala/org/docopt/PatternParserFunSpec.scala
Original file line number Diff line number Diff line change
Expand Up @@ -239,38 +239,38 @@ 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")),
Argument("", StringValue("arg")))))
}

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")),
Expand All @@ -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")),
Expand All @@ -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)
}
}

Expand All @@ -300,27 +300,27 @@ 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)
}
}

it("it should intercept a conflicting definition") {
// 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)
}
}

Expand All @@ -331,58 +331,58 @@ Usage: prog --derp

Options:
--derp"""
PP.docopt(doc, "--derp=ARG", help = false, "", optionsFirst = false)
PP.docopt(doc, Array("--derp=ARG"), help = false, "", optionsFirst = false)
}
}
}

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)
}
}
}

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] [--] <arg>\n\n-o", "-- -o", help = false, "", optionsFirst = false)
PP.docopt("Usage: prog [-o] [--] <arg>\n\n-o", "-- -o".split("""\s+"""), help = false, "", optionsFirst = false)
}

it("it should handle correctly '--' swapped") {
PP.docopt("Usage: prog [-o] [--] <arg>\n\n -o", "-o 1", help = false, "", optionsFirst = false)
PP.docopt("Usage: prog [-o] [--] <arg>\n\n -o", "-o 1".split("""\s+"""), help = false, "", optionsFirst = false)
}
}
}