Skip to content
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)
}
}
}