From a107e2b2e7d63375dc93eaa96134c0c124b0f250 Mon Sep 17 00:00:00 2001 From: Jakob Odersky Date: Sun, 29 Apr 2018 19:47:30 -0700 Subject: Reenable tests --- build.sbt | 4 +- project/plugins.sbt | 6 +- src/main/scala/api.scala | 30 +++++ src/main/scala/definitions.scala | 2 +- src/main/scala/package.scala | 38 ++++--- src/main/scala/parsing.scala | 8 +- src/test/scala/CmdTest.scala.disabled | 207 ---------------------------------- src/test/scala/ParserTest.scala | 140 +++++++++++++++++++++-- 8 files changed, 188 insertions(+), 247 deletions(-) create mode 100644 src/main/scala/api.scala delete mode 100644 src/test/scala/CmdTest.scala.disabled diff --git a/build.sbt b/build.sbt index fe7afad..b79e71e 100644 --- a/build.sbt +++ b/build.sbt @@ -7,8 +7,8 @@ lazy val commando = crossProject(JSPlatform, JVMPlatform, NativePlatform) .in(file(".")) .settings( scalacOptions ++= Seq( - "-deprecation", - "-feature" + "-deprecation", + "-feature" ), libraryDependencies ++= Seq( "com.lihaoyi" %%% "utest" % "0.6.3" % "test" diff --git a/project/plugins.sbt b/project/plugins.sbt index 8725466..e63c0d7 100644 --- a/project/plugins.sbt +++ b/project/plugins.sbt @@ -1,6 +1,6 @@ addSbtPlugin("com.geirsson" % "sbt-scalafmt" % "1.5.1") -addSbtPlugin("org.portable-scala" % "sbt-scalajs-crossproject" % "0.4.0") +addSbtPlugin("org.portable-scala" % "sbt-scalajs-crossproject" % "0.4.0") addSbtPlugin("org.portable-scala" % "sbt-scala-native-crossproject" % "0.4.0") -addSbtPlugin("org.scala-js" % "sbt-scalajs" % "0.6.22") -addSbtPlugin("org.scala-native" % "sbt-scala-native" % "0.3.7") +addSbtPlugin("org.scala-js" % "sbt-scalajs" % "0.6.22") +addSbtPlugin("org.scala-native" % "sbt-scala-native" % "0.3.7") diff --git a/src/main/scala/api.scala b/src/main/scala/api.scala new file mode 100644 index 0000000..44a26f1 --- /dev/null +++ b/src/main/scala/api.scala @@ -0,0 +1,30 @@ +package test + +import commando._ + +object Main { + + val command = cmd("docker")( + opt("debug", 'D') + ).sub( + cmd("run")( + opt("interactive", 'i'), + opt("tty", 't'), + pos("container") + ).run { arguments => + // run container with arguments + }, + cmd("ps")( + opt("all", 'a') + ).run { arguments => + if (arguments.contains("all")) { + // ... + } else { + // ... + } + } + ) + + def main(args: Array[String]): Unit = commando.parse(args, command) + +} diff --git a/src/main/scala/definitions.scala b/src/main/scala/definitions.scala index 15c8e2c..8de9e3a 100644 --- a/src/main/scala/definitions.scala +++ b/src/main/scala/definitions.scala @@ -63,4 +63,4 @@ case class Command( object Command { type Arguments = Map[String, Seq[String]] -} \ No newline at end of file +} diff --git a/src/main/scala/package.scala b/src/main/scala/package.scala index f813971..68936cd 100644 --- a/src/main/scala/package.scala +++ b/src/main/scala/package.scala @@ -2,10 +2,11 @@ package commando class CommandBuilder(name: String, params: Seq[Parameter]) { - private def optionals = params.collect{ - case opt: Optional => opt - }.toSet - private def positionals = params.collect{ + private def optionals = + params.collect { + case opt: Optional => opt + }.toSet + private def positionals = params.collect { case pos: Positional => pos } @@ -17,32 +18,33 @@ class CommandBuilder(name: String, params: Seq[Parameter]) { } - - object `package` { - val DefaultErrorHandler: (Command, String) => Unit = (command: Command, err: String) => { - System.err.println(s"${command.name}: $err") - System.exit(2) - } - - + val DefaultErrorHandler: (Command, String) => Unit = + (command: Command, err: String) => { + System.err.println(s"${command.name}: $err") + System.exit(2) + } def parse(arguments: Seq[String], - command: Command, onError: (Command, String) => Unit = DefaultErrorHandler): Unit = + command: Command, + onError: (Command, String) => Unit = DefaultErrorHandler): Unit = Parser.parse(arguments, command, onError) - def cmd(name: String)(params: Parameter*): CommandBuilder = new CommandBuilder(name, params) - def opt(name: String, short: Char = '\u0000', param: (String, Boolean) = ("", false)): Optional = + def cmd(name: String)(params: Parameter*): CommandBuilder = + new CommandBuilder(name, params) + def opt(name: String, + short: Char = '\u0000', + param: (String, Boolean) = ("", false)): Optional = Optional( name, if (short == '\u0000') None else Some(short), argumentAllowed = (param != ("", false)), argumentRequired = (param != ("", false)) && param._2, - parameterName = if(param._1 == "") "param" else param._1 + parameterName = if (param._1 == "") "param" else param._1 ) - def pos(name: String, required: Boolean = true): Positional = Positional(name, required) - + def pos(name: String, required: Boolean = true): Positional = + Positional(name, required) } diff --git a/src/main/scala/parsing.scala b/src/main/scala/parsing.scala index 95edc05..2526d39 100644 --- a/src/main/scala/parsing.scala +++ b/src/main/scala/parsing.scala @@ -101,9 +101,9 @@ object Parser { if (parts.size > 1) Some(parts(1)) else None val opt = (tok.kind: @unchecked) match { case LONG => - longs.getOrElse(name, fatal(s"option --$name unknown")) + longs.getOrElse(name, fatal(s"unknown option '--$name'")) case SHORT => - shorts.getOrElse(name, fatal(s"option -$name unknown")) + shorts.getOrElse(name, fatal(s"unknown option '-$name'")) } if (opt.argumentRequired) { @@ -144,7 +144,7 @@ object Parser { accept().value ) } else { - fatal(s"too many parameters: '${token.value}'") + fatal(s"too many arguments: '${token.value}'") } } @@ -180,7 +180,7 @@ object Parser { innerLine() } else if (token.kind == POSITIONAL) { if (subcommands.isEmpty) { - fatal(s"too many parameters: '${token.value}'") + fatal(s"too many arguments: '${token.value}'") } else { subcommands.get(token.value) match { case None => diff --git a/src/test/scala/CmdTest.scala.disabled b/src/test/scala/CmdTest.scala.disabled deleted file mode 100644 index d943a79..0000000 --- a/src/test/scala/CmdTest.scala.disabled +++ /dev/null @@ -1,207 +0,0 @@ -package commando - -import utest._ - -object CmdTests extends TestSuite { - - val cbx = commando.Command( - "cbx", - commando.Optional("server", Some('s'), Optional.ArgRequired("name")), - commando.Command( - "version", - commando.Optional("verbose", Some('v'), Optional.ArgAllowed("k=v"))), - commando.Command("login", - commando.Positional("server_url"), - commando.Positional("username", false), - commando.Positional("password", false)), - commando.Command("run", - commando.Optional("file", Some('f'), Optional.ArgRequired("file_name")), - commando.Optional("force", None), - commando.Positional("pipeline", false)), - commando.Command("level1", - commando.Command("level2-1", - commando.Positional("p2"), - commando.Command("level3", commando.Positional("p3"))), - commando.Command("level2-2")) - ) - - def parse(in: String): CommandLine = commando.parse(cbx, in.split(" ").tail) match { - case Left(ex) => throw ex - case Right(res) => res - } - - def shouldFail(in: String) = - assert(commando.parse(cbx, in.split(" ").tail).isLeft) - - val tests = Tests { - "printUsage" - { - cbx.usage - } - "simple" - { - assert( - parse("cbx version").subcommand.get == CommandLine("version", - Map.empty, - None)) - } - "emptyAllowedOption" - { - assert( - parse("cbx version -v").subcommand.get == CommandLine( - "version", - Map("verbose" -> ""), - None)) - assert( - parse("cbx version --verbose").subcommand.get == CommandLine( - "version", - Map("verbose" -> ""), - None)) - } - "setAllowedOption" - { - assert( - parse("cbx version -v x").subcommand.get == CommandLine( - "version", - Map("verbose" -> "x"), - None)) - assert( - parse("cbx version --verbose x").subcommand.get == CommandLine( - "version", - Map("verbose" -> "x"), - None)) - assert( - parse("cbx version --verbose=x").subcommand.get == CommandLine( - "version", - Map("verbose" -> "x"), - None)) - assert( - parse("cbx version --verbose=x=y").subcommand.get == CommandLine( - "version", - Map("verbose" -> "x=y"), - None)) - assert( - parse("cbx version --verbose=x=y,z=w").subcommand.get == CommandLine( - "version", - Map("verbose" -> "x=y,z=w"), - None)) - assert( - parse("cbx version --verbose x=y,z=w").subcommand.get == CommandLine( - "version", - Map("verbose" -> "x=y,z=w"), - None)) - shouldFail("cbx version --verbose x=y z=w") - } - "requiredArgOption" - { - assert(parse("cbx run").subcommand.get == CommandLine("run", Map(), None)) // make sure it works first - assert( - parse("cbx run -f x").subcommand.get == CommandLine("run", - Map("file" -> "x"), - None)) - assert( - parse("cbx run --file x").subcommand.get == CommandLine( - "run", - Map("file" -> "x"), - None)) - assert( - parse("cbx run --file=x").subcommand.get == CommandLine( - "run", - Map("file" -> "x"), - None)) - assert( - parse("cbx run --file=x=y,z=w").subcommand.get == CommandLine( - "run", - Map("file" -> "x=y,z=w"), - None)) - shouldFail("cbx run --file") - shouldFail("cbx run --file --") - } - "noArgOption" - { - shouldFail("cbx run --force=x") - assert( - parse("cbx run --force x").subcommand.get == CommandLine( - "run", - Map("force" -> "", "pipeline" -> "x"), - None)) - } - "globalOption" - { - assert(parse("cbx --server run run").arguments == Map("server" -> "run")) - assert( - parse("cbx --server run run").subcommand.get == CommandLine("run", - Map.empty, - None)) - assert(parse("cbx -s run run").arguments == Map("server" -> "run")) - assert( - parse("cbx -s run run").subcommand.get == CommandLine("run", - Map.empty, - None)) - assert(parse("cbx --server=run run").arguments == Map("server" -> "run")) - assert( - parse("cbx --server=run run").subcommand.get == CommandLine("run", - Map.empty, - None)) - shouldFail("cbx -x run") - shouldFail("cbx --x run") - } - "parameter" - { - assert( - parse("cbx login x").subcommand.get == CommandLine( - "login", - Map("server_url" -> "x"), - None)) - assert( - parse("cbx login x y").subcommand.get == CommandLine( - "login", - Map("server_url" -> "x", "username" -> "y"), - None)) - assert( - parse("cbx login x y z").subcommand.get == CommandLine( - "login", - Map("server_url" -> "x", "username" -> "y", "password" -> "z"), - None)) - shouldFail("cbx login - y z w") - assert( - parse("cbx login - y").subcommand.get == CommandLine( - "login", - Map("server_url" -> "-", "username" -> "y"), - None)) - } - "outOfOrderOptions" - { - assert( - parse("cbx run --force pipelinename -f x").subcommand.get == CommandLine( - "run", - Map("force" -> "", "pipeline" -> "pipelinename", "file" -> "x"), - None)) - assert( - parse("cbx run --force -- -f").subcommand.get == CommandLine( - "run", - Map("force" -> "", "pipeline" -> "-f"), - None)) - assert( - parse("cbx run --force -- --file").subcommand.get == CommandLine( - "run", - Map("force" -> "", "pipeline" -> "--file"), - None)) - assert( - parse("cbx run --force -- --").subcommand.get == CommandLine( - "run", - Map("force" -> "", "pipeline" -> "--"), - None)) - shouldFail("cbx run --force -- -f x") // too many parameters - } - "nested1" - { - val line = parse("cbx level1 level2-1 x=y level3 z").subcommand.get - val expected = CommandLine( - "level1", - Map.empty, - Some( - CommandLine("level2-1", - Map("p2" -> "x=y"), - Some(CommandLine("level3", Map("p3" -> "z"), None))))) - assert(line == expected) - } - "nested2" - { - val line = parse("cbx level1 level2-2 --").subcommand.get - val expected = CommandLine("level1", - Map.empty, - Some(CommandLine("level2-2", Map.empty, None))) - assert(line == expected) - } - } -} diff --git a/src/test/scala/ParserTest.scala b/src/test/scala/ParserTest.scala index 11694c4..579ba95 100644 --- a/src/test/scala/ParserTest.scala +++ b/src/test/scala/ParserTest.scala @@ -6,22 +6,138 @@ object ParserTest extends TestSuite { implicit class EliteCommando(line: String) { def parse(command: Command): Unit = { - val args = line.split(" ") - commando.parse(args, command)(err => throw new ParseException(err)) + val args = line.split(" ").tail + commando.parse(args, command, (c, err) => throw new ParseException(err)) + } + def fail(command: Command, message: String): Unit = { + val args = line.split(" ").tail + try { + commando.parse(args, command, (c, err) => throw new ParseException(err)) + sys.error("parsing succeeded but was expected to fail") + } catch { + case err: ParseException if err.getMessage.contains(message) => + case err: ParseException => + sys.error(s"parsing failed for the wrong reason: ${err.getMessage}") + } } } - val tests = Tests { - "foo" - { - val command = cmd("cbx")( - opt("server", 'S', param = "url" -> false), - pos("number") - ).run( - ctx => println("yoyo, my context was: " + ctx) + def cbx(asserts: Command.Arguments => Unit) = + cmd("cbx")( + opt("server", 's', "name" -> true) + ).sub( + cmd("version")( + opt("verbose", 'v', "k=v" -> false) + ).run(asserts), + cmd("login")( + pos("server_url"), + pos("username", false), + pos("password", false) + ).run(asserts), + cmd("run")( + opt("file", 'f', "file_name" -> true), + opt("force"), + pos("pipeline", false) + ).run(asserts), + cmd("level1")().sub( + cmd("level2-1")( + pos("p2") + ).sub( + cmd("level3")(pos("p3")).run(asserts) + ), + cmd("level2-2")().run(asserts) ) - "--server x 3 -S 5 --server=2 --server 2".parse(command) - println(command.usage) + ) + + val tests = Tests { + "print usage" - { + cbx(_ => ()).usage + } + "simple" - { + "cbx version" parse cbx { args => + args ==> Map.empty + } + } + "empty allowed optional" - { + "cbx version -v" parse cbx { args => + args ==> Map("verbose" -> Seq("")) + } + "cbx version --verbose" parse cbx { args => + args ==> Map("verbose" -> Seq("")) + } + } + "set allowed optional" - { + "cbx version -v x" parse cbx { args => + args ==> Map("verbose" -> Seq("x")) + } + "cbx version --verbose x" parse cbx { args => + args ==> Map("verbose" -> Seq("x")) + } + "cbx version --verbose=x" parse cbx { args => + args ==> Map("verbose" -> Seq("x")) + } + "cbx version --verbose=x=y" parse cbx { args => + args ==> Map("verbose" -> Seq("x=y")) + } + "cbx version --verbose=x=y,z=w" parse cbx { args => + args ==> Map("verbose" -> Seq("x=y,z=w")) + } + "cbx version --verbose x=y" parse cbx { args => + args ==> Map("verbose" -> Seq("x=y")) + } + "cbx version --verbose x=y z=w".fail(cbx(_ => ()), "too many arguments") + } + "required argument optional" - { + "cbx run" parse cbx { _ ==> Map.empty } // make sure it works first + "cbx run -f x" parse cbx { _ ==> Map("file" -> Seq("x")) } + "cbx run --file x" parse cbx { _ ==> Map("file" -> Seq("x")) } + "cbx run --file=x" parse cbx { _ ==> Map("file" -> Seq("x")) } + "cbx run --file=x=y,z=w" parse cbx { _ ==> Map("file" -> Seq("x=y,z=w")) } + "cbx run --file".fail(cbx(_ => ()), "requires") + "cbx run --file --".fail(cbx(_ => ()), "requires") + } + "no argument optional" - { + "cbx run --force=x".fail(cbx(_ => ()), "no argument allowed") + "cbx run --force x" parse cbx { _ ==> Map("force" -> Seq(""), "pipeline" -> Seq("x")) } + } + "global optional" - { + "cbx --server run run" parse cbx {_ ==> Map("server" -> Seq("run"))} + "cbx -s run run" parse cbx {_ ==> Map("server" -> Seq("run"))} + "cbx --server=run run" parse cbx {_ ==> Map("server" -> Seq("run"))} + "cbx -x run".fail(cbx(_ => ()), "unknown option") + "cbx --x run".fail(cbx(_ => ()), "unknown option") + } + "positional" - { + "cbx login x" parse cbx { _ ==> Map("server_url" -> Seq("x"))} + "cbx login x y" parse cbx { _ ==> Map("server_url" -> Seq("x"), "username" -> Seq("y"))} + "cbx login x y z" parse cbx { _ ==> Map("server_url" -> Seq("x"), "username" -> Seq("y"), "password" -> Seq("z"))} + "cbx login - x y z".fail(cbx(_ => ()), "too many") + "cbx login - y" parse cbx { _ ==> Map("server_url" -> Seq("-"), "username" -> Seq("y"))} + } + "out of order options" - { + "cbx run --force pipelinename -f x" parse cbx { + _ ==> Map("force" -> Seq(""), "pipeline" -> Seq("pipelinename"), "file" -> Seq("x")) + } + "cbx run --force -- -f" parse cbx { + _ ==> Map("force" -> Seq(""), "pipeline" -> Seq("-f")) + } + "cbx run --force -- --file" parse cbx { + _ ==> Map("force" -> Seq(""), "pipeline" -> Seq("--file")) + } + "cbx run --force -- --" parse cbx { + _ ==> Map("force" -> Seq(""), "pipeline" -> Seq("--")) + } + "cbx run --force -- -f x".fail(cbx(_ => ()), "too many") + } + "nested1" - { + "cbx level1 level2-1 x=y level3 z" parse cbx { + _ ==> Map("p2" -> Seq("x=y"), "p3" -> Seq("z")) + } + } + "nested2" - { + "cbx level1 level2-2 --" parse cbx { + _ ==> Map.empty + } } } - } -- cgit v1.2.3