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 <[email protected]> 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) + } +}
