You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@daffodil.apache.org by ji...@apache.org on 2021/03/18 12:23:28 UTC

[daffodil] branch master updated: Update scallop to 4.0.2

This is an automated email from the ASF dual-hosted git repository.

jinterrante pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/daffodil.git


The following commit(s) were added to refs/heads/master by this push:
     new 420f882  Update scallop to 4.0.2
420f882 is described below

commit 420f88222ff3f6fc519e08c0be3f67fb6005635b
Author: Scala Steward <me...@scala-steward.org>
AuthorDate: Mon Mar 8 14:18:26 2021 +0100

    Update scallop to 4.0.2
    
    - Also updates scallop configuration to use more modern validation
      mechanisms, which makes error messages more consistent
---
 .../apache/daffodil/parsing/TestCLIParsing.scala   |   4 +-
 .../daffodil/unparsing/TestCLIUnparsing.scala      |   2 +-
 .../src/main/scala/org/apache/daffodil/Main.scala  | 196 ++++++++-------------
 project/Dependencies.scala                         |   2 +-
 4 files changed, 80 insertions(+), 124 deletions(-)

diff --git a/daffodil-cli/src/it/scala/org/apache/daffodil/parsing/TestCLIParsing.scala b/daffodil-cli/src/it/scala/org/apache/daffodil/parsing/TestCLIParsing.scala
index e69ceaa..94cde1f 100644
--- a/daffodil-cli/src/it/scala/org/apache/daffodil/parsing/TestCLIParsing.scala
+++ b/daffodil-cli/src/it/scala/org/apache/daffodil/parsing/TestCLIParsing.scala
@@ -415,7 +415,7 @@ class TestCLIparsing {
     try {
       val cmd = String.format("echo 0,1,2,3| %s parse", Util.binPath)
       shell.sendLine(cmd)
-      shell.expect(contains("One of --schema or --parser must be defined"))
+      shell.expect(contains("There should be exactly one of the following options: schema, parser"))
       shell.send("exit\n")
       shell.expect(eof)
     } finally {
@@ -771,7 +771,7 @@ class TestCLIparsing {
 
     try {
       shell.sendLine(cmd)
-      shell.expect(contains("subcommand"))
+      shell.expect(contains("Subcommand required"))
       shell.sendLine("exit")
       shell.expect(eof)
     } finally {
diff --git a/daffodil-cli/src/it/scala/org/apache/daffodil/unparsing/TestCLIUnparsing.scala b/daffodil-cli/src/it/scala/org/apache/daffodil/unparsing/TestCLIUnparsing.scala
index ecfc9b5..9e0ce12 100644
--- a/daffodil-cli/src/it/scala/org/apache/daffodil/unparsing/TestCLIUnparsing.scala
+++ b/daffodil-cli/src/it/scala/org/apache/daffodil/unparsing/TestCLIUnparsing.scala
@@ -270,7 +270,7 @@ class TestCLIunparsing {
     try {
       val cmd = String.format("""echo '<tns:e1 xmlns:tns="http://example.com">Hello</tns:e1>' | %s unparse""", Util.binPath)
       shell.sendLine(cmd)
-      shell.expect(contains("One of --schema or --parser must be defined"))
+      shell.expect(contains("There should be exactly one of the following options: schema, parser"))
       shell.send("exit\n")
       shell.expect(eof)
     } finally {
diff --git a/daffodil-cli/src/main/scala/org/apache/daffodil/Main.scala b/daffodil-cli/src/main/scala/org/apache/daffodil/Main.scala
index 4737e67..a94672b 100644
--- a/daffodil-cli/src/main/scala/org/apache/daffodil/Main.scala
+++ b/daffodil-cli/src/main/scala/org/apache/daffodil/Main.scala
@@ -35,7 +35,6 @@ import scala.concurrent.Await
 import scala.concurrent.ExecutionContext
 import scala.concurrent.Future
 import scala.concurrent.duration.Duration
-import scala.language.reflectiveCalls
 import scala.xml.Node
 import scala.xml.SAXParseException
 
@@ -156,6 +155,18 @@ object TDMLLogWriter extends CLILogPrefix {
   }
 }
 
+object InfosetType extends Enumeration {
+  type Type = Value
+
+  val JDOM = Value("jdom")
+  val JSON = Value("json")
+  val SAX = Value("sax")
+  val SCALA_XML = Value("scala-xml")
+  val W3CDOM = Value("w3cdom")
+  val XML = Value("xml")
+  val NULL = Value("null")
+}
+
 class CLIConf(arguments: Array[String]) extends scallop.ScallopConf(arguments)
   with Logging {
 
@@ -212,14 +223,6 @@ class CLIConf(arguments: Array[String]) extends scallop.ScallopConf(arguments)
       override def argFormat(name: String): String = "[" + name + "]"
     }
 
-  def validateConf(c1: => Option[scallop.ScallopConfBase])(fn: (Option[scallop.ScallopConfBase]) => Either[String, Unit]): Unit = {
-    validations :+= new Function0[Either[String, Unit]] {
-      def apply = {
-        fn(c1)
-      }
-    }
-  }
-
   implicit def validateConverter = singleArgConverter[ValidationMode.Type]((s: String) => {
     import ValidatorPatterns._
     s match {
@@ -235,6 +238,14 @@ class CLIConf(arguments: Array[String]) extends scallop.ScallopConf(arguments)
     }
   })
 
+  implicit def infosetTypeConverter = singleArgConverter[InfosetType.Type]((s: String) => {
+    try {
+      InfosetType.withName(s.toLowerCase)
+    } catch {
+      case _: NoSuchElementException => throw new Exception("Unrecognized infoset type: %s.  Must be one of %s".format(s, InfosetType.values.mkString(", ")))
+    }
+  })
+
   def qnameConvert(s: String): RefQName = {
     val eQN = QName.refQNameFromExtendedSyntax(s)
     eQN.get
@@ -314,7 +325,7 @@ class CLIConf(arguments: Array[String]) extends scallop.ScallopConf(arguments)
   val version = opt[Boolean](descr = "Show version of this program")
 
   // Parse Subcommand Options
-  val parse = new scallop.Subcommand("parse") {
+  object parse extends scallop.Subcommand("parse") {
     banner("""|Usage: daffodil parse (-s <schema> [-r [{namespace}]<root>] [-p <path>] |
               |                       -P <parser>)
               |                      [--validate [mode]]
@@ -338,44 +349,29 @@ class CLIConf(arguments: Array[String]) extends scallop.ScallopConf(arguments)
     val validate: ScallopOption[ValidationMode.Type] = opt[ValidationMode.Type](short = 'V', default = Some(ValidationMode.Off), argName = "mode", descr = "the validation mode. 'on', 'limited', 'off', or a validator plugin name.")
     val vars = props[String]('D', keyName = "variable", valueName = "value", descr = "variables to be used when parsing. An optional namespace may be provided.")
     val tunables = props[String]('T', keyName = "tunable", valueName = "value", descr = "daffodil tunable to be used when parsing.")
-    val config = opt[String](short = 'c', argName = "file", descr = "path to file containing configuration items.")
-    val infosetType = opt[String](short = 'I', argName = "infoset_type", descr = "infoset type to output. Must be one of 'xml', 'scala-xml', 'json', 'jdom', 'w3cdom', 'sax', or 'null'.", default = Some("xml")).map { _.toLowerCase }
+    val config = opt[File](short = 'c', argName = "file", descr = "path to file containing configuration items.")
+    val infosetType = opt[InfosetType.Type](short = 'I', argName = "infoset_type", descr = "infoset type to output. Must be one of 'xml', 'scala-xml', 'json', 'jdom', 'w3cdom', 'sax', or 'null'.", default = Some(InfosetType.XML))
     val stream = toggle(noshort = true, default = Some(false), descrYes = "when left over data exists, parse again with remaining data, separating infosets by a NUL character", descrNo = "stop after the first parse, even if left over data exists")
     val infile = trailArg[String](required = false, descr = "input file to parse. If not specified, or a value of -, reads from stdin.")
 
-    validateOpt(debug, infile) {
-      case (Some(None), Some("-")) | (Some(None), None) => Left("Input must not be stdin during interactive debugging")
-      case _ => Right(Unit)
-    }
+    requireOne(schema, parser) // must have one of --schema or --parser
+    conflicts(parser, List(rootNS)) // if --parser is provided, cannot also provide --root
+    validateFileIsFile(config) // --config must be a file that exists
 
-    validateOpt(schema, parser, rootNS) {
-      case (None, None, _) => Left("One of --schema or --parser must be defined")
-      case (Some(_), Some(_), _) => Left("Only one of --parser and --schema may be defined")
-      case (None, Some(_), Some(_)) => Left("--root cannot be defined with --parser")
+    validateOpt(debug, infile) {
+      case (Some(_), Some("-")) | (Some(_), None) => Left("Input must not be stdin during interactive debugging")
       case _ => Right(Unit)
     }
 
     validateOpt(parser, validate) {
-      case (Some(_), Some(v)) if v == ValidationMode.Full => Left("The validation mode must be 'limited' or 'off' when using a saved parser.")
+      case (Some(_), Some(ValidationMode.Full)) => Left("The validation mode must be 'limited' or 'off' when using a saved parser.")
       case _ => Right(Unit)
     }
 
-    validateOpt(infosetType) {
-      case (Some("xml")) => Right(Unit)
-      case (Some("scala-xml")) => Right(Unit)
-      case (Some("json")) => Right(Unit)
-      case (Some("jdom")) => Right(Unit)
-      case (Some("w3cdom")) => Right(Unit)
-      case (Some("sax")) => Right(Unit)
-      case (Some("null")) => Right(Unit)
-      case (Some(t)) => Left("Unknown infoset type: " + t)
-      case _ => Assert.impossible() // not possible due to default value
-    }
-
   }
 
   // Performance Subcommand Options
-  val performance = new scallop.Subcommand("performance") {
+  object performance extends scallop.Subcommand("performance") {
     banner("""|Usage: daffodil performance (-s <schema> [-r [{namespace}]<root>] [-p <path>] |
               |                       -P <parser>)
               |                      [--unparse]
@@ -403,33 +399,22 @@ class CLIConf(arguments: Array[String]) extends scallop.ScallopConf(arguments)
     val validate: ScallopOption[ValidationMode.Type] = opt[ValidationMode.Type](short = 'V', default = Some(ValidationMode.Off), argName = "mode", descr = "the validation mode. 'on', 'limited', 'off', or a validator plugin name.")
     val vars = props[String]('D', keyName = "variable", valueName = "value", descr = "variables to be used when processing. An optional namespace may be provided.")
     val tunables = props[String]('T', keyName = "tunable", valueName = "value", descr = "daffodil tunable to be used when processing.")
-    val config = opt[String](short = 'c', argName = "file", descr = "path to file containing configuration items.")
-    val infosetType = opt[String](short = 'I', argName = "infoset_type", descr = "infoset type to parse/unparse. Must be one of 'xml', 'scala-xml', 'json', 'jdom', 'w3cdom', 'sax', or 'null'.", default = Some("xml")).map { _.toLowerCase }
+    val config = opt[File](short = 'c', argName = "file", descr = "path to file containing configuration items.")
+    val infosetType = opt[InfosetType.Type](short = 'I', argName = "infoset_type", descr = "infoset type to parse/unparse. Must be one of 'xml', 'scala-xml', 'json', 'jdom', 'w3cdom', 'sax', or 'null'.", default = Some(InfosetType.XML))
     val infile = trailArg[String](required = true, descr = "input file or directory containing files to process.")
 
-    validateOpt(schema, parser, rootNS) {
-      case (None, None, _) => Left("One of --schema or --parser must be defined")
-      case (Some(_), Some(_), _) => Left("Only one of --parser and --schema may be defined")
-      case (None, Some(_), Some(_)) => Left("--root cannot be defined with --parser")
-      case _ => Right(Unit)
-    }
+    requireOne(schema, parser) // must have one of --schema or --parser
+    conflicts(parser, List(rootNS)) // if --parser is provided, cannot also provide --root
+    validateFileIsFile(config) // --config must be a file that exists
 
     validateOpt(infosetType, unparse) {
-      case (Some("xml"), _) => Right(Unit)
-      case (Some("scala-xml"), _) => Right(Unit)
-      case (Some("json"), _) => Right(Unit)
-      case (Some("jdom"), _) => Right(Unit)
-      case (Some("w3cdom"), _) => Right(Unit)
-      case (Some("sax"), _) => Right(Unit)
-      case (Some("null"), Some(true)) => Left("infoset type null not valid with performance --unparse")
-      case (Some("null"), _) => Right(Unit)
-      case (Some(t), _) => Left("Unknown infoset type: " + t)
-      case _ => Assert.impossible() // not possible due to default value
+      case (Some(InfosetType.NULL), Some(true)) => Left("null infoset type not valid with performance --unparse")
+      case _ => Right(Unit)
     }
   }
 
   // Unparse Subcommand Options
-  val unparse = new scallop.Subcommand("unparse") {
+  object unparse extends scallop.Subcommand("unparse") {
     banner("""|Usage: daffodil unparse (-s <schema> [-r [{namespace}]<root>] [-p <path>] |
               |                         -P <parser>)
               |                        [--validate [mode]]
@@ -453,48 +438,28 @@ class CLIConf(arguments: Array[String]) extends scallop.ScallopConf(arguments)
     val validate: ScallopOption[ValidationMode.Type] = opt[ValidationMode.Type](short = 'V', default = Some(ValidationMode.Off), argName = "mode", descr = "the validation mode. 'on', 'limited', 'off', or a validator plugin name.")
     val vars = props[String]('D', keyName = "variable", valueName = "value", descr = "variables to be used when unparsing. An optional namespace may be provided.")
     val tunables = props[String]('T', keyName = "tunable", valueName = "value", descr = "daffodil tunable to be used when parsing.")
-    val config = opt[String](short = 'c', argName = "file", descr = "path to file containing configuration items.")
-    val infosetType = opt[String](short = 'I', argName = "infoset_type", descr = "infoset type to unparse. Must be one of 'xml', 'scala-xml', 'json', 'jdom', 'w3cdom' or 'sax'.", default = Some("xml")).map { _.toLowerCase }
+    val config = opt[File](short = 'c', argName = "file", descr = "path to file containing configuration items.")
+    val infosetType = opt[InfosetType.Type](short = 'I', argName = "infoset_type", descr = "infoset type to unparse. Must be one of 'xml', 'scala-xml', 'json', 'jdom', 'w3cdom' or 'sax'.", default = Some(InfosetType.XML))
     val stream = toggle(noshort = true, default = Some(false), descrYes = "split the input data on the NUL character, and unparse each chuck separately", descrNo = "treat the entire input data as one infoset")
     val infile = trailArg[String](required = false, descr = "input file to unparse. If not specified, or a value of -, reads from stdin.")
 
-    validateOpt(debug, infile) {
-      case (Some(None), Some("-")) | (Some(None), None) => Left("Input must not be stdin during interactive debugging")
-      case _ => Right(Unit)
-    }
-
-    validateOpt(schema, parser, rootNS) {
-      case (None, None, _) => Left("One of --schema or --parser must be defined")
-      case (Some(_), Some(_), _) => Left("Only one of --parser and --schema may be defined")
-      case (None, Some(_), Some(_)) => Left("--root cannot be defined with --parser")
-      case _ => Right(Unit)
-    }
+    requireOne(schema, parser) // must have one of --schema or --parser
+    conflicts(parser, List(rootNS)) // if --parser is provided, cannot also provide --root
+    validateFileIsFile(config) // --config must be a file that exists
 
-    validateOpt(config) {
-      case Some(path) => {
-        val fin = new File(path)
-        if (!fin.exists) Left("--config file does not exist.")
-        else if (!fin.canRead) Left("--config file could not be read.")
-        else Right(Unit)
-      }
+    validateOpt(debug, infile) {
+      case (Some(_), Some("-")) | (Some(_), None) => Left("Input must not be stdin during interactive debugging")
       case _ => Right(Unit)
     }
 
     validateOpt(infosetType) {
-      case (Some("xml")) => Right(Unit)
-      case (Some("scala-xml")) => Right(Unit)
-      case (Some("json")) => Right(Unit)
-      case (Some("jdom")) => Right(Unit)
-      case (Some("w3cdom")) => Right(Unit)
-      case (Some("sax")) => Right(Unit)
-      //case (Some("null")) => Right(Unit) // null is not valid for unparsing
-      case (Some(t)) => Left("Unknown infoset type: " + t)
-      case _ => Assert.impossible() // not possible due to default value
+      case (Some(InfosetType.NULL)) => Left("Invalid infoset type: null") // null is not valid for unparsing
+      case _ => Right(Unit)
     }
   }
 
   // Save Subcommand Options
-  val save = new scallop.Subcommand("save-parser") {
+  object save extends scallop.Subcommand("save-parser") {
     banner("""|Usage: daffodil save-parser -s <schema> [-r [{namespace}]<root>]
               |                            [-p <path>]
               |                            [-D[{namespace}]<variable>=<value>...]
@@ -513,16 +478,14 @@ class CLIConf(arguments: Array[String]) extends scallop.ScallopConf(arguments)
     val outfile = trailArg[String](required = false, descr = "output file to save parser. If not specified, or a value of -, writes to stdout.")
     val vars = props[String]('D', keyName = "variable", valueName = "value", descr = "variables to be used.")
     val tunables = props[String]('T', keyName = "tunable", valueName = "value", descr = "daffodil tunable to be used when parsing.")
-    val config = opt[String](short = 'c', argName = "file", descr = "path to file containing configuration items.")
+    val config = opt[File](short = 'c', argName = "file", descr = "path to file containing configuration items.")
 
-    validateOpt(schema) {
-      case (None) => Left("No schemas specified using the --schema option")
-      case _ => Right(Unit)
-    }
+    requireOne(schema) // --schema must be provided
+    validateFileIsFile(config) // --config must be a file that exists
   }
 
   // Test Subcommand Options
-  val test = new scallop.Subcommand("test") {
+  object test extends scallop.Subcommand("test") {
     banner("""|Usage: daffodil test <tdmlfile> [testname...]
               |
               |List or execute tests in a TDML file
@@ -545,15 +508,8 @@ class CLIConf(arguments: Array[String]) extends scallop.ScallopConf(arguments)
   addSubcommand(save)
   addSubcommand(test)
 
-  validateOpt(trace, debug) {
-    case (Some(true), Some(_)) => Left("Only one of --trace and --debug may be defined")
-    case _ => Right(Unit)
-  }
-
-  validateConf(subcommand) {
-    case None => Left("Missing subcommand")
-    case _ => Right(Unit)
-  }
+  mutuallyExclusive(trace, debug) // cannot provide both --trace and --debug
+  requireSubcommand()
 
   verify()
 }
@@ -586,9 +542,9 @@ object Main extends Logging {
    * @param pathName The path to the file.
    * @return The Node representation of the file.
    */
-  def loadConfigurationFile(pathName: String) = {
+  def loadConfigurationFile(file: File) = {
     val loader = new DaffodilXMLLoader()
-    val node = ConfigurationLoader.getConfiguration(loader, pathName)
+    val node = ConfigurationLoader.getConfiguration(loader, file.toURI)
     node
   }
 
@@ -733,16 +689,16 @@ object Main extends Logging {
   val blobDir = Paths.get(System.getProperty("user.dir"), "daffodil-blobs")
   val blobSuffix = ".bin"
 
-  def getInfosetOutputter(infosetType: String, os: java.io.OutputStream)
+  def getInfosetOutputter(infosetType: InfosetType.Type, os: java.io.OutputStream)
   : Either[InfosetOutputter, DaffodilParseOutputStreamContentHandler] = {
     val outputter = infosetType match {
-      case "xml" => Left(new XMLTextInfosetOutputter(os, pretty = true))
-      case "scala-xml" => Left(new ScalaXMLInfosetOutputter())
-      case "json" => Left(new JsonInfosetOutputter(os, pretty = true))
-      case "jdom" => Left(new JDOMInfosetOutputter())
-      case "w3cdom" => Left(new W3CDOMInfosetOutputter())
-      case "sax" => Right(new DaffodilParseOutputStreamContentHandler(os, pretty = true))
-      case "null" => Left(new NullInfosetOutputter())
+      case InfosetType.XML => Left(new XMLTextInfosetOutputter(os, pretty = true))
+      case InfosetType.SCALA_XML => Left(new ScalaXMLInfosetOutputter())
+      case InfosetType.JSON => Left(new JsonInfosetOutputter(os, pretty = true))
+      case InfosetType.JDOM => Left(new JDOMInfosetOutputter())
+      case InfosetType.W3CDOM => Left(new W3CDOMInfosetOutputter())
+      case InfosetType.SAX => Right(new DaffodilParseOutputStreamContentHandler(os, pretty = true))
+      case InfosetType.NULL => Left(new NullInfosetOutputter())
     }
     if (outputter.isLeft) {
       outputter.left.map(_.setBlobAttributes(blobDir, null, blobSuffix))
@@ -779,27 +735,27 @@ object Main extends Logging {
    * it into an object, this should be called outside of a performance loop,
    * with getInfosetInputter called inside the performance loop.
    */
-  def infosetDataToInputterData(infosetType: String, data: Either[Array[Byte],InputStream]): AnyRef = {
+  def infosetDataToInputterData(infosetType: InfosetType.Type, data: Either[Array[Byte],InputStream]): AnyRef = {
     infosetType match {
-      case "xml" | "json" | "sax" => data match {
+      case InfosetType.XML | InfosetType.JSON | InfosetType.SAX => data match {
         case Left(bytes) => bytes
         case Right(is) => is
       }
-      case "scala-xml" => {
+      case InfosetType.SCALA_XML => {
         val is = data match {
           case Left(bytes) => new ByteArrayInputStream(bytes)
           case Right(is) => is
         }
         scala.xml.XML.load(is)
       }
-      case "jdom" => {
+      case InfosetType.JDOM => {
         val is = data match {
           case Left(bytes) => new ByteArrayInputStream(bytes)
           case Right(is) => is
         }
         new org.jdom2.input.SAXBuilder().build(is)
       }
-      case "w3cdom" => {
+      case InfosetType.W3CDOM => {
         val byteArr = data match {
           case Left(bytes) => bytes
           case Right(is) => IOUtils.toByteArray(is)
@@ -817,36 +773,36 @@ object Main extends Logging {
   }
 
   def getInfosetInputter(
-    infosetType: String,
+    infosetType: InfosetType.Type,
     anyRef: AnyRef,
     processor: DFDL.DataProcessor,
     outChannel: DFDL.Output): Either[InfosetInputter, DFDL.DaffodilUnparseContentHandler] = {
     infosetType match {
-      case "xml" => {
+      case InfosetType.XML => {
         val is = anyRef match {
           case bytes: Array[Byte] => new ByteArrayInputStream(bytes)
           case is: InputStream => is
         }
         Left(new XMLTextInfosetInputter(is))
       }
-      case "json" => {
+      case InfosetType.JSON => {
         val is = anyRef match {
           case bytes: Array[Byte] => new ByteArrayInputStream(bytes)
           case is: InputStream => is
         }
         Left(new JsonInfosetInputter(is))
       }
-      case "scala-xml" => {
+      case InfosetType.SCALA_XML => {
         Left(new ScalaXMLInfosetInputter(anyRef.asInstanceOf[scala.xml.Node]))
       }
-      case "jdom" => {
+      case InfosetType.JDOM => {
         Left(new JDOMInfosetInputter(anyRef.asInstanceOf[org.jdom2.Document]))
       }
-      case "w3cdom" => {
+      case InfosetType.W3CDOM => {
         val tl = anyRef.asInstanceOf[ThreadLocal[org.w3c.dom.Document]]
         Left(new W3CDOMInfosetInputter(tl.get))
       }
-      case "sax" => {
+      case InfosetType.SAX => {
         val dp = processor
         Right(dp.newContentHandlerInstance(outChannel))
       }
diff --git a/project/Dependencies.scala b/project/Dependencies.scala
index 7f9ace9..d69688e 100644
--- a/project/Dependencies.scala
+++ b/project/Dependencies.scala
@@ -40,7 +40,7 @@ object Dependencies {
    
   lazy val cli = Seq( 
     "org.fusesource.jansi" % "jansi" % "1.17.1",
-    "org.rogach" %% "scallop" % "3.5.1",
+    "org.rogach" %% "scallop" % "4.0.2",
     "net.sf.expectit" % "expectit-core" % "0.9.0" % "it,test"
   )