You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@daffodil.apache.org by ar...@apache.org on 2022/08/05 17:38:25 UTC
[daffodil-vscode] 21/28: Add TDML wrapper that will simplify the actual TDML interface Remove temporary/test code Add in first attempt at support for append/execute
This is an automated email from the ASF dual-hosted git repository.
arosien pushed a commit to branch daffodil-vscode-tdml
in repository https://gitbox.apache.org/repos/asf/daffodil-vscode.git
commit 14902500abeb7433bf19d0b236400156aff0f68b
Author: Michael Hoke <mi...@nteligen.com>
AuthorDate: Mon Jul 25 18:49:07 2022 -0400
Add TDML wrapper that will simplify the actual TDML interface
Remove temporary/test code
Add in first attempt at support for append/execute
---
.../org.apache.daffodil.debugger.dap/Parse.scala | 57 ++++++++--------
.../main/scala/org.apache.daffodil.tdml/TDML.scala | 72 +++++++-------------
.../org.apache.daffodil.tdml/TDMLWrapper.scala | 76 ++++++++++++++++++++++
3 files changed, 130 insertions(+), 75 deletions(-)
diff --git a/server/core/src/main/scala/org.apache.daffodil.debugger.dap/Parse.scala b/server/core/src/main/scala/org.apache.daffodil.debugger.dap/Parse.scala
index b55def1..1a390ed 100644
--- a/server/core/src/main/scala/org.apache.daffodil.debugger.dap/Parse.scala
+++ b/server/core/src/main/scala/org.apache.daffodil.debugger.dap/Parse.scala
@@ -46,8 +46,8 @@ import org.apache.daffodil.util.Misc
import org.typelevel.log4cats.Logger
import org.typelevel.log4cats.slf4j.Slf4jLogger
import scala.util.Try
-// import org.apache.commons.io.output.NullOutputStream
-import org.apache.daffodil.tdml.TDML
+import org.apache.commons.io.output.NullOutputStream
+import org.apache.daffodil.tdml.TDMLWrapper
trait Parse {
@@ -282,6 +282,22 @@ object Parse {
infoset <- Resource.eval(Queue.bounded[IO, Option[String]](10)) // TODO: it's a bit incongruous to have a separate channel for infoset changes, vs. streaming Parse.Event values
control <- Resource.eval(Control.stopped())
+ pathTuple = args.tdmlConfig match {
+ case Debugee.LaunchArgs.TDMLConfig.Config(action, name, description, tdmlPath) =>
+ if (action == "execute") {
+ TDMLWrapper.execute(args.schemaPath, args.dataPath, name, description, tdmlPath)
+ }
+ else
+ (args.schemaPath, args.dataPath)
+ case _ =>
+ (args.schemaPath, args.dataPath)
+ }
+
+ // pathTuple will start with the original values of these paths and will only get updated if
+ // these paths need to be changed.
+ args.schemaPath = pathTuple._1
+ args.dataPath = pathTuple._2
+
latestData <- Stream.fromQueueNoneTerminated(data).holdResource(DAPodil.Data.empty)
latestInfoset <- Resource.eval(SignallingRef[IO, String](""))
@@ -335,6 +351,7 @@ object Parse {
breakpoints,
control
)
+
startup = dapEvents.offer(Some(ConfigEvent(args))) *>
(if (args.stopOnEntry)
control.step() *> state.offer(
@@ -359,35 +376,21 @@ object Parse {
if (action == "generate")
args.infosetOutput match {
case Debugee.LaunchArgs.InfosetOutput.File(path) =>
- // Logger[IO].debug("Getting ready to generate")
- IO(TDML.generate(path.toString(), args.dataPath.toString(), args.schemaPath.toString(), name, description, tdmlPath))
+ IO(TDMLWrapper.generate(path, args.dataPath, args.schemaPath, name, description, tdmlPath))
case _ =>
- Logger[IO].debug("Non-file InfosetOutput")
- // IO(new PrintStream(NullOutputStream.NULL_OUTPUT_STREAM))
+ IO(new PrintStream(NullOutputStream.NULL_OUTPUT_STREAM))
+ }
+ else if (action == "append")
+ args.infosetOutput match {
+ case Debugee.LaunchArgs.InfosetOutput.File(path) =>
+ IO(TDMLWrapper.append(path, args.dataPath, args.schemaPath, name, description, tdmlPath))
+ case _ =>
+ IO(new PrintStream(NullOutputStream.NULL_OUTPUT_STREAM))
}
else
- Logger[IO].debug("TDMLConfig is not generate")
- // IO(new PrintStream(NullOutputStream.NULL_OUTPUT_STREAM))
- case _ =>
- Logger[IO].debug("Not sure why this is here")
- // IO(new PrintStream(NullOutputStream.NULL_OUTPUT_STREAM))
- /* args.infosetOutput match {
- case Debugee.LaunchArgs.InfosetOutput.File(path) =>
- args.tdmlConfig match {
- // case Debugee.LaunchArgs.TDMLConfig.Config(action, name, description, tdmlPath) =>
- if (action == "generate")
- Logger[IO].debug("Makes it into the generate")
- // TDML.generate(path.toString(), args.dataPath.toString(), args.schemaPath.toString(), name, description, tdmlPath)
- else
- Logger[IO].debug("TDMLConfig is None")
- // IO(new PrintStream(NullOutputStream.NULL_OUTPUT_STREAM))
- case _ =>
- Logger[IO].debug("Not sure why this is here")
- // IO(new PrintStream(NullOutputStream.NULL_OUTPUT_STREAM))
- }
+ IO(new PrintStream(NullOutputStream.NULL_OUTPUT_STREAM))
case _ =>
- Logger[IO].debug("Non-file InfosetOutput")
- // IO(new PrintStream(NullOutputStream.NULL_OUTPUT_STREAM)) */
+ IO(new PrintStream(NullOutputStream.NULL_OUTPUT_STREAM))
}
),
infosetChanges
diff --git a/server/core/src/main/scala/org.apache.daffodil.tdml/TDML.scala b/server/core/src/main/scala/org.apache.daffodil.tdml/TDML.scala
index e388666..4bc88e1 100644
--- a/server/core/src/main/scala/org.apache.daffodil.tdml/TDML.scala
+++ b/server/core/src/main/scala/org.apache.daffodil.tdml/TDML.scala
@@ -1,17 +1,15 @@
package org.apache.daffodil.tdml
-import javax.xml.bind.JAXBContext
-import java.io.FileOutputStream
-import cats.effect.IO
import java.io.File
+import java.io.FileOutputStream
+import java.nio.file.Paths
+import javax.xml.bind.JAXBContext
+import javax.xml.bind.JAXBElement
import javax.xml.bind.Marshaller
+import javax.xml.namespace.QName
+import javax.xml.bind.annotation.XmlType
-// import org.typelevel.log4cats.Logger
-// import org.typelevel.log4cats.slf4j.Slf4jLogger
-
-// TODO: Put TDML path in class definition?
object TDML {
- // implicit val logger: Logger[IO] = Slf4jLogger.getLogger
// Create a ParserTestCaseType object that can be put into a TestSuite
// These types are generated when JAXB is executed on the TDML schema
//
@@ -47,9 +45,17 @@ object TDML {
docPart.setType(DocumentPartTypeEnum.FILE)
docPart.setValue(dataPath)
+ // These lines are necessary because there is no @XmlRootElement annotation on the DocumentPartType class in JAXB
+ // Ideally, we would want to have JAXB add the annotation - probably with the bindings.xjb file. The only way I found
+ // that did that required an external plugin just to add the annotation (https://github.com/highsource/jaxb2-annotate-plugin).
+ // We are getting the namespace from the JAXB class so that we don't have to hard-code it here
+ // Unfortunately, it seems like hard-coding the class name isn't an easy thing to avoid. There is a name in the XmlType
+ // annotation, but it is documentPartType instead of documentPart. We would need to remove the Type from this anyway.
+ val tdmlNamespacePrefix = classOf[DocumentPartType].getAnnotation(classOf[XmlType]).namespace()
+ val docPartElement = new JAXBElement[DocumentPartType](new QName(tdmlNamespacePrefix, "documentPart"), classOf[DocumentPartType], docPart)
+
val doc = factory.createDocumentType()
- // The following line causes the output of the marshalling to be empty
- doc.getContent().add(docPart)
+ doc.getContent().add(docPartElement)
val testCase = factory.createParserTestCaseType()
testCase.setName(tdmlName)
@@ -74,10 +80,8 @@ object TDML {
// tdmlDescription: Description for the DFDL operation
// tdmlPath: Path to the TDML file
//
- // There is a suiteName attribute in the root element of the document. This is set to $tdmlName
- // TODO: I think the return type here should just be Unit
+ // There is a suiteName attribute in the root element of the document. This is set to tdmlName
def generate(infosetPath: String, dataPath: String, schemaPath: String, tdmlName: String, tdmlDescription: String, tdmlPath: String): Unit = {
- // Logger[IO].debug("Generating")
val factory = new ObjectFactory()
val testSuite = factory.createTestSuite()
@@ -85,12 +89,6 @@ object TDML {
testSuite.setDefaultRoundTrip(RoundTripType.ONE_PASS)
testSuite.getTutorialOrParserTestCaseOrDefineSchema().add(createTestCase(infosetPath, dataPath, schemaPath, tdmlName, tdmlDescription))
- // Logger[IO].debug("Getting ready to send XML to file")
- // val marshaller = JAXBContext.newInstance(classOf[TestSuite]).createMarshaller()
- // marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true)
- // marshaller.marshal(testSuite, new FileOutputStream(tdmlPath))
- // JAXBContext.newInstance(classOf[TestSuite]).createMarshaller().marshal(testSuite, fos)
- // val fos = new FileOutputStream(tdmlPath)
val marshaller = JAXBContext.newInstance(classOf[TestSuite]).createMarshaller()
marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true)
marshaller.marshal(testSuite, new java.io.File(tdmlPath))
@@ -105,15 +103,13 @@ object TDML {
// tdmlName: Name of the DFDL operation
// tdmlDescription: Description for the DFDL operation
// tdmlPath: Path to the TDML file
- //
- // TODO: I think the return type here should just be Unit
- def append(infosetPath: String, dataPath: String, schemaPath: String, tdmlName: String, tdmlDescription: String, tdmlPath: String): IO[Unit] = {
+ def append(infosetPath: String, dataPath: String, schemaPath: String, tdmlName: String, tdmlDescription: String, tdmlPath: String): Unit = {
val testSuite = JAXBContext.newInstance(classOf[TestSuite]).createUnmarshaller().unmarshal(new File(tdmlPath)).asInstanceOf[TestSuite]
testSuite.getTutorialOrParserTestCaseOrDefineSchema().add(createTestCase(infosetPath, dataPath, schemaPath, tdmlName, tdmlDescription))
- IO(JAXBContext.newInstance(classOf[TestSuite]).createMarshaller().marshal(testSuite, new FileOutputStream(tdmlPath)))
+ JAXBContext.newInstance(classOf[TestSuite]).createMarshaller().marshal(testSuite, new FileOutputStream(tdmlPath))
}
// Find the parameters needed to execute a DFDL parse based on the given TDML Parameters
@@ -128,41 +124,21 @@ object TDML {
val testCaseList = JAXBContext.newInstance(classOf[TestSuite]).createUnmarshaller().unmarshal(new File(tdmlPath)).asInstanceOf[TestSuite].getTutorialOrParserTestCaseOrDefineSchema()
testCaseList.forEach { tc =>
- // var foundDoc = ""
- // var foundInfoset = ""
-
- // TODO: Do I really have to cast to instances every time? I've already checked that they are...
tc match {
case ptc: ParserTestCaseType =>
if (ptc.getName() == tdmlName && ptc.getDescription() == tdmlDescription) {
ptc.getTutorialOrDocumentOrInfoset().forEach { dis =>
dis match {
case doc: DocumentType =>
- return (ptc.getModel(), doc.getContent().indexOf(0).asInstanceOf[DocumentPartType].getValue())
+ // The right part of the tuple only takes the first DocumentPart inside the Document.
+ // In the case that there are more than one, any extras will be ignored.
+ val schemaPath = Paths.get(ptc.getModel()).toFile().getCanonicalPath()
+ val dataPath = Paths.get(doc.getContent().get(0).asInstanceOf[JAXBElement[DocumentPartType]].getValue().getValue()).toFile().getCanonicalPath()
+ return (schemaPath, dataPath)
}
}
}
}
- /*if (tc.isInstanceOf[ParserTestCaseType]) {
- // Match name and description of potential test case
- if (tc.asInstanceOf[ParserTestCaseType].getName() == tdmlName && tc.asInstanceOf[ParserTestCaseType].getDescription() == tdmlDescription) {
- tc.asInstanceOf[ParserTestCaseType].getTutorialOrDocumentOrInfoset().forEach { dis =>
- if (dis.isInstanceOf[DocumentType]) {
- // foundDoc = dis.asInstanceOf[DocumentType].getContent().indexOf(0).asInstanceOf[DocumentPartType].getValue()
- // if (!foundInfoset.isEmpty()) {
- // return (tc.asInstanceOf[ParserTestCaseType].getModel(), foundInfoset, foundDoc)
- // }
- return (tc.asInstanceOf[ParserTestCaseType].getModel(), dis.asInstanceOf[DocumentType].getContent().indexOf(0).asInstanceOf[DocumentPartType].getValue())
- }
- // else if (dis.isInstanceOf[InfosetType]) {
- // foundInfoset = dis.asInstanceOf[InfosetType].getDfdlInfoset().getContent().indexOf(0).asInstanceOf[String]
- // if (!foundDoc.isEmpty()) {
- // return (tc.asInstanceOf[ParserTestCaseType].getModel(), foundInfoset, foundDoc)
- // }
- // }
- }
- }
- }*/
}
// If there is no test case in the TDML file meeting the name/description criteria, return empty
diff --git a/server/core/src/main/scala/org.apache.daffodil.tdml/TDMLWrapper.scala b/server/core/src/main/scala/org.apache.daffodil.tdml/TDMLWrapper.scala
new file mode 100644
index 0000000..33df6dc
--- /dev/null
+++ b/server/core/src/main/scala/org.apache.daffodil.tdml/TDMLWrapper.scala
@@ -0,0 +1,76 @@
+package org.apache.daffodil.tdml
+
+import java.io.File
+import java.nio.file.Path
+import java.nio.file.Paths
+
+object TDMLWrapper {
+ // Convert an absolute path into a path relative to the current working directory
+ //
+ // path: Absolute path to convert into a relative path
+ //
+ // Returns the relative path. Note that this path is given as a string.
+ private def convertToRelativePath(path: Path): String = {
+ // Don't forget the getParent because toAbsolutePath returns something like the following:
+ // /absolute/path/to/directory/.
+ // The getParent gets rid of the dot at the end.
+ var workingDir = Paths.get(".").toAbsolutePath().getParent()
+ var prefix = ""
+
+ // This is used to back up the path tree in order to find the first common ancestor of both paths
+ // If a user wants to use a file not in or under the current working directory, this will be required to
+ // produce the expected output.
+ // A possible use case of this is where a user has a data folder and a schema folder that are siblings.
+ while (!path.startsWith(workingDir) && Paths.get(workingDir.toString()).getParent() != null)
+ {
+ workingDir = Paths.get(workingDir.toString()).getParent()
+ // Need to add the dots to represent that we've gone back a step up the path
+ prefix += ".." + File.separator
+ }
+
+ return prefix + new File(workingDir.toString()).toURI().relativize(new File(path.toString()).toURI()).getPath().toString()
+ }
+
+ // Generate a new TDML file.
+ // Paths given to this function should be absolute as they will be converted to relative paths
+ //
+ // infosetPath: Path to the infoset
+ // dataPath: Path to the data file
+ // schemaPath: Path to the DFDL Schema
+ // tdmlName: Name of the DFDL operation
+ // tdmlDescription: Description for the DFDL operation
+ // tdmlPath: Path to the TDML file
+ def generate(infosetPath: Path, dataPath: Path, schemaPath: Path, tdmlName: String, tdmlDescription: String, tdmlPath: String): Unit = {
+ TDML.generate(convertToRelativePath(infosetPath), convertToRelativePath(dataPath), convertToRelativePath(schemaPath), tdmlName, tdmlDescription, tdmlPath)
+ }
+
+ // Append a new test case to an existing TDML file.
+ // Paths given to this function should be absolute as they will be converted to relative paths
+ //
+ // infosetPath: Path to the infoset
+ // dataPath: Path to the data file
+ // schemaPath: Path to the DFDL Schema
+ // tdmlName: Name of the DFDL operation
+ // tdmlDescription: Description for the DFDL operation
+ // tdmlPath: Path to the TDML file
+ def append(infosetPath: Path, dataPath: Path, schemaPath: Path, tdmlName: String, tdmlDescription: String, tdmlPath: String): Unit = {
+ TDML.append(convertToRelativePath(infosetPath), convertToRelativePath(dataPath), convertToRelativePath(schemaPath), tdmlName, tdmlDescription, tdmlPath)
+ }
+
+ // Find the parameters needed to execute a DFDL parse based on the given TDML Parameters
+ //
+ // tdmlName: Test case name to run
+ // tdmlDescription: Description of test case to run
+ // tdmlPath: File path of TDML file to extract test case from
+ //
+ // Returns a tuple containing the following (Path to DFDL Schema, Path to Data File)
+ // All paths returned could be either relative or absolute - it depends on what exists in the TDML file
+ def execute(schemaPath: Path, dataPath: Path, tdmlName: String, tdmlDescription: String, tdmlPath: String): (Path, Path) = {
+ val (newSchemaPath, newDataPath) = TDML.execute(tdmlName, tdmlDescription, tdmlPath)
+ if (newSchemaPath.length > 0 && newDataPath.length > 0) {
+ return (Paths.get(newSchemaPath), Paths.get(newDataPath))
+ }
+
+ return (schemaPath, dataPath)
+ }
+}