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:32 UTC

[daffodil-vscode] 28/28: Rejigger TDML config, parse cases and handle TDML actions.

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 cbd5e6ca21ae94fd15314d4053c04873542eb09b
Author: Adam Rosien <ad...@rosien.net>
AuthorDate: Fri Aug 5 10:37:35 2022 -0700

    Rejigger TDML config, parse cases and handle TDML actions.
---
 .../org.apache.daffodil.debugger.dap/Parse.scala   | 364 ++++++++++++---------
 .../org.apache.daffodil.tdml/TDMLWrapper.scala     |   8 +-
 2 files changed, 217 insertions(+), 155 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 4620b91..68a8107 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,11 @@ 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.TDMLWrapper
+import org.apache.daffodil.debugger.dap.Parse.Debugee.LaunchArgs.Manual
+import org.apache.daffodil.debugger.dap.Parse.Debugee.LaunchArgs.TDMLConfig.Generate
+import org.apache.daffodil.debugger.dap.Parse.Debugee.LaunchArgs.TDMLConfig.Append
+import org.apache.daffodil.debugger.dap.Parse.Debugee.LaunchArgs.TDMLConfig.Execute
 
 trait Parse {
 
@@ -156,28 +159,21 @@ object Parse {
 
   object Debugee {
 
-    case class LaunchArgs(
-        defaultSchemaPath: Path,
-        defaultDataPath: Path,
-        stopOnEntry: Boolean,
-        infosetOutput: LaunchArgs.InfosetOutput,
-        tdmlConfig: Option[LaunchArgs.TDMLConfig]
-    ) extends Arguments {
-      lazy val (schemaPath: Path, dataPath: Path) =
-        tdmlConfig
-          .map {
-            case LaunchArgs.TDMLConfig(action, name, description, path) =>
-              if (action == "execute")
-                TDMLWrapper.execute(defaultSchemaPath, defaultDataPath, name, description, path)
-          }
-          .getOrElse(defaultSchemaPath -> defaultDataPath)
-
-      def data: IO[InputStream] =
-        IO.blocking(FileUtils.readFileToByteArray(dataPath.toFile))
-          .map(new ByteArrayInputStream(_))
-    }
+    sealed trait LaunchArgs
 
     object LaunchArgs {
+      case class Manual(
+          schemaPath: Path,
+          dataPath: Path,
+          stopOnEntry: Boolean,
+          infosetOutput: LaunchArgs.InfosetOutput
+      ) extends Arguments
+          with LaunchArgs {
+        def data: IO[InputStream] =
+          IO.blocking(FileUtils.readFileToByteArray(dataPath.toFile))
+            .map(new ByteArrayInputStream(_))
+      }
+
       sealed trait InfosetOutput
 
       object InfosetOutput {
@@ -186,106 +182,188 @@ object Parse {
         case class File(path: Path) extends InfosetOutput
       }
 
-      case class TDMLConfig(action: String, name: String, description: String, path: String)
+      sealed trait TDMLConfig extends LaunchArgs
+      object TDMLConfig {
+        case class Generate(
+            schemaPath: Path,
+            dataPath: Path,
+            stopOnEntry: Boolean,
+            infosetOutput: LaunchArgs.InfosetOutput,
+            name: String,
+            description: String,
+            path: String
+        ) extends TDMLConfig
+
+        case class Append(
+            schemaPath: Path,
+            dataPath: Path,
+            stopOnEntry: Boolean,
+            infosetOutput: LaunchArgs.InfosetOutput,
+            name: String,
+            description: String,
+            path: String
+        ) extends TDMLConfig
+
+        case class Execute(
+            stopOnEntry: Boolean,
+            infosetOutput: LaunchArgs.InfosetOutput,
+            name: String,
+            description: String,
+            path: String
+        ) extends TDMLConfig
+      }
 
       def parse(arguments: JsonObject): EitherNel[String, LaunchArgs] =
+        Option(arguments.getAsJsonObject("tdmlConfig")) match {
+          case None             => parseManual(arguments)
+          case Some(tdmlConfig) => parseTDML(arguments, tdmlConfig)
+        }
+
+      def parseManual(arguments: JsonObject): EitherNel[String, LaunchArgs] =
         (
-          Option(arguments.getAsJsonPrimitive("program"))
-            .toRight("missing 'program' field from launch request")
-            .flatMap(path =>
-              Either
-                .catchNonFatal(Paths.get(path.getAsString))
-                .leftMap(t => s"'program' field from launch request is not a valid path: $t")
-                .ensureOr(path => s"program file at $path doesn't exist")(_.toFile().exists())
-            )
-            .toEitherNel,
-          Option(arguments.getAsJsonPrimitive("data"))
-            .toRight("missing 'data' field from launch request")
-            .flatMap(path =>
-              Either
-                .catchNonFatal(Paths.get(path.getAsString))
-                .leftMap(t => s"'data' field from launch request is not a valid path: $t")
-                .ensureOr(path => s"data file at $path doesn't exist")(_.toFile().exists())
-            )
-            .toEitherNel,
+          parseProgram(arguments),
+          parseData(arguments),
           Option(arguments.getAsJsonPrimitive("stopOnEntry"))
             .map(_.getAsBoolean())
             .getOrElse(true)
             .asRight[String]
             .toEitherNel,
-          Option(arguments.getAsJsonObject("infosetOutput")) match {
-            case None => Right(LaunchArgs.InfosetOutput.Console).toEitherNel
-            case Some(infosetOutput) =>
-              Option(infosetOutput.getAsJsonPrimitive("type")) match {
-                case None => Right(LaunchArgs.InfosetOutput.Console).toEitherNel
-                case Some(typ) =>
-                  typ.getAsString() match {
-                    case "none"    => Right(LaunchArgs.InfosetOutput.None).toEitherNel
-                    case "console" => Right(LaunchArgs.InfosetOutput.Console).toEitherNel
-                    case "file" =>
-                      Option(infosetOutput.getAsJsonPrimitive("path"))
-                        .toRight("missing 'infosetOutput.path' field from launch request")
-                        .flatMap(path =>
-                          Either
-                            .catchNonFatal(LaunchArgs.InfosetOutput.File(Paths.get(path.getAsString)))
-                            .leftMap(t => s"'infosetOutput.path' field from launch request is not a valid path: $t")
-                            .ensureOr(file => s"can't write to infoset output file at ${file.path}") { f =>
-                              val file = f.path.toFile
-                              file.canWrite || (!file.exists && file.getParentFile.canWrite)
-                            }
-                        )
-                        .toEitherNel
-                    case invalidType =>
-                      Left(s"invalid 'infosetOutput.type': '$invalidType', must be 'none', 'console', or 'file'").toEitherNel
-                  }
+          parseInfosetOutput(arguments)
+        ).parMapN(LaunchArgs.Manual.apply)
+    }
+
+    def parseTDML(arguments: JsonObject, tdmlConfig: JsonObject): EitherNel[String, LaunchArgs] =
+      Option(tdmlConfig.getAsJsonPrimitive("action")) match {
+        case None         => Left("'action' is required").toEitherNel
+        case Some(action) =>
+          // TODO: use mapN instead of providing default empty values
+          action.getAsString() match {
+            case "generate" | "append" =>
+              (parseProgram(arguments), parseData(arguments), parseInfosetOutput(arguments)).parMapN {
+                (schemaPath, dataPath, infosetOutput) =>
+                  // TODO: handle append case
+                  LaunchArgs.TDMLConfig.Generate(
+                    schemaPath,
+                    dataPath,
+                    stopOnEntry = false,
+                    infosetOutput,
+                    Option(tdmlConfig.getAsJsonPrimitive("name"))
+                      .map(_.getAsString())
+                      .getOrElse("Default Test Case"),
+                    Option(tdmlConfig.getAsJsonPrimitive("description"))
+                      .map(_.getAsString())
+                      .getOrElse("Generated by DFDL VSCode Extension"),
+                    Option(tdmlConfig.getAsJsonPrimitive("path"))
+                      .map(_.getAsString())
+                      .getOrElse("")
+                  )
               }
-          },
-          Option(arguments.getAsJsonObject("tdmlConfig")) match {
-            case None => Right(Option.empty[LaunchArgs.TDMLConfig]).toEitherNel
-            case Some(tdmlConfig) =>
-              Option(tdmlConfig.getAsJsonPrimitive("action")) match {
-                case None => Right(Option.empty[LaunchArgs.TDMLConfig]).toEitherNel
-                case Some(action) =>
-                  action.getAsString() match {
-                    case "none" => Right(Option.empty[LaunchArgs.TDMLConfig]).toEitherNel
-                    case "generate" | "append" | "execute" =>
-                      Right(
-                        // TODO: use mapN instead of providing default empty values
-                        Some(
-                          LaunchArgs.TDMLConfig(
-                            Option(tdmlConfig.getAsJsonPrimitive("action"))
-                              .map(_.getAsString())
-                              .getOrElse("None"),
-                            Option(tdmlConfig.getAsJsonPrimitive("name"))
-                              .map(_.getAsString())
-                              .getOrElse("Default Test Case"),
-                            Option(tdmlConfig.getAsJsonPrimitive("description"))
-                              .map(_.getAsString())
-                              .getOrElse("Generated by DFDL VSCode Extension"),
-                            Option(tdmlConfig.getAsJsonPrimitive("path"))
-                              .map(_.getAsString())
-                              .getOrElse("")
-                          )
-                        )
-                      ).toEitherNel
-                    case invalidType =>
-                      Left(
-                        s"invalid 'tdmlConfig.action': '$invalidType', must be 'none', 'generate', 'append', or 'execute'"
-                      ).toEitherNel
-                  }
+            case "execute" =>
+              parseInfosetOutput(arguments).map { infosetOutput =>
+                LaunchArgs.TDMLConfig.Execute(
+                  stopOnEntry = false,
+                  infosetOutput,
+                  Option(tdmlConfig.getAsJsonPrimitive("name"))
+                    .map(_.getAsString())
+                    .getOrElse("Default Test Case"),
+                  Option(tdmlConfig.getAsJsonPrimitive("description"))
+                    .map(_.getAsString())
+                    .getOrElse("Generated by DFDL VSCode Extension"),
+                  Option(tdmlConfig.getAsJsonPrimitive("path"))
+                    .map(_.getAsString())
+                    .getOrElse("")
+                )
               }
+            case invalidType =>
+              Left(
+                s"invalid 'tdmlConfig.action': '$invalidType', must be 'none', 'generate', 'append', or 'execute'"
+              ).toEitherNel
           }
-        ).parMapN(LaunchArgs.apply)
-    }
+      }
+
+    def parseProgram(arguments: JsonObject) =
+      Option(arguments.getAsJsonPrimitive("program"))
+        .toRight("missing 'program' field from launch request")
+        .flatMap(path =>
+          Either
+            .catchNonFatal(Paths.get(path.getAsString))
+            .leftMap(t => s"'program' field from launch request is not a valid path: $t")
+            .ensureOr(path => s"program file at $path doesn't exist")(_.toFile().exists())
+        )
+        .toEitherNel
+
+    def parseData(arguments: JsonObject) =
+      Option(arguments.getAsJsonPrimitive("data"))
+        .toRight("missing 'data' field from launch request")
+        .flatMap(path =>
+          Either
+            .catchNonFatal(Paths.get(path.getAsString))
+            .leftMap(t => s"'data' field from launch request is not a valid path: $t")
+            .ensureOr(path => s"data file at $path doesn't exist")(_.toFile().exists())
+        )
+        .toEitherNel
+
+    def parseInfosetOutput(arguments: JsonObject) =
+      Option(arguments.getAsJsonObject("infosetOutput")) match {
+        case None => Right(LaunchArgs.InfosetOutput.Console).toEitherNel
+        case Some(infosetOutput) =>
+          Option(infosetOutput.getAsJsonPrimitive("type")) match {
+            case None => Right(LaunchArgs.InfosetOutput.Console).toEitherNel
+            case Some(typ) =>
+              typ.getAsString() match {
+                case "none"    => Right(LaunchArgs.InfosetOutput.None).toEitherNel
+                case "console" => Right(LaunchArgs.InfosetOutput.Console).toEitherNel
+                case "file" =>
+                  Option(infosetOutput.getAsJsonPrimitive("path"))
+                    .toRight("missing 'infosetOutput.path' field from launch request")
+                    .flatMap(path =>
+                      Either
+                        .catchNonFatal(LaunchArgs.InfosetOutput.File(Paths.get(path.getAsString)))
+                        .leftMap(t => s"'infosetOutput.path' field from launch request is not a valid path: $t")
+                        .ensureOr(file => s"can't write to infoset output file at ${file.path}") { f =>
+                          val file = f.path.toFile
+                          file.canWrite || (!file.exists && file.getParentFile.canWrite)
+                        }
+                    )
+                    .toEitherNel
+                case invalidType =>
+                  Left(s"invalid 'infosetOutput.type': '$invalidType', must be 'none', 'console', or 'file'").toEitherNel
+              }
+          }
+      }
   }
 
   val infosetSource = DAPodil.Source(Paths.get("infoset"), Some(DAPodil.Source.Ref(1)))
   val dataDumpSource = DAPodil.Source(Paths.get("data"), Some(DAPodil.Source.Ref(2)))
 
   def debugee(request: Request): EitherNel[String, Resource[IO, DAPodil.Debugee]] =
-    Debugee.LaunchArgs.parse(request.arguments).map(debugee)
+    Debugee.LaunchArgs.parse(request.arguments).map {
+      case args: Debugee.LaunchArgs.Manual => debugee(args)
+      case Debugee.LaunchArgs.TDMLConfig
+            .Generate(schemaPath, dataPath, stopOnEntry, infosetOutput, name, description, tdmlPath) =>
+        // action=generate: create a LaunchArgs.Manual, run the debugee with it, and then generate the TDML file
+        debugee(Debugee.LaunchArgs.Manual(schemaPath, dataPath, stopOnEntry, infosetOutput)).onFinalize(
+          infosetOutput match {
+            case Debugee.LaunchArgs.InfosetOutput.None =>
+              IO.unit // TODO: perhaps guard against this during config parse
+            case Debugee.LaunchArgs.InfosetOutput.Console =>
+              IO.unit // TODO: perhaps guard against this during config parse
+            case Debugee.LaunchArgs.InfosetOutput.File(path) =>
+              IO(TDMLWrapper.generate(path, dataPath, schemaPath, name, description, tdmlPath))
+          }
+        )
+      case _: Debugee.LaunchArgs.TDMLConfig.Append =>
+        // TODO: action=append: create a LaunchArgs.Manual, run the debugee with it, and then append to the existing TDML file
+        ???
+      case Debugee.LaunchArgs.TDMLConfig.Execute(stopOnEntry, infosetOutput, name, description, tdmlPath) =>
+        // action=execute: from the created TDML file, create a LaunchArgs.Manual from the named test, run the debugee with it
+        Resource.eval(IO(TDMLWrapper.execute(name, description, tdmlPath))).flatMap {
+          case (schemaPath, dataPath) =>
+            debugee(Debugee.LaunchArgs.Manual(schemaPath, dataPath, stopOnEntry, infosetOutput))
+        }
+    }
 
-  def debugee(args: Debugee.LaunchArgs): Resource[IO, DAPodil.Debugee] =
+  def debugee(args: Debugee.LaunchArgs.Manual): Resource[IO, DAPodil.Debugee] =
     for {
       data <- Resource.eval(Queue.bounded[IO, Option[DAPodil.Data]](10))
       state <- Resource.eval(Queue.bounded[IO, Option[DAPodil.Debugee.State]](10))
@@ -366,28 +444,6 @@ object Parse {
               case ec                                => Logger[IO].debug(s"deliverParseData: $ec")
             } ++ Stream.eval(
               dapEvents.offer(None) // ensure dapEvents is terminated when the parse is terminated
-            ) ++ Stream.eval(
-              args.tdmlConfig match {
-                case Some(Debugee.LaunchArgs.TDMLConfig(action, name, description, tdmlPath)) =>
-                  if (action == "generate")
-                    args.infosetOutput match {
-                      case Debugee.LaunchArgs.InfosetOutput.File(path) =>
-                        IO(TDMLWrapper.generate(path, args.dataPath, args.schemaPath, name, description, tdmlPath))
-                      case _ =>
-                        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
-                    IO(new PrintStream(NullOutputStream.NULL_OUTPUT_STREAM))
-                case _ =>
-                  IO(new PrintStream(NullOutputStream.NULL_OUTPUT_STREAM))
-              }
             ),
             infosetChanges
           ).parJoinUnbounded
@@ -654,13 +710,28 @@ object Parse {
   case class ConfigEvent(launchArgs: ConfigEvent.LaunchArgs, buildInfo: ConfigEvent.BuildInfo)
       extends Events.DebugEvent("daffodil.config")
   object ConfigEvent {
-    case class LaunchArgs(
-        schemaPath: String,
-        dataPath: String,
-        stopOnEntry: Boolean,
-        infosetOutput: InfosetOutput,
-        tdmlConfig: Option[TDMLConfig]
-    )
+    sealed trait LaunchArgs
+    object LaunchArgs {
+      case class Manual(
+          schemaPath: String,
+          dataPath: String,
+          stopOnEntry: Boolean,
+          infosetOutput: InfosetOutput
+      ) extends LaunchArgs
+      case class TDMLConfig(action: String, name: String, description: String, path: String) extends LaunchArgs
+
+      object TDMLConfig {
+        def apply(that: Debugee.LaunchArgs.TDMLConfig): TDMLConfig =
+          that match {
+            case Generate(_, _, _, _, name, description, path) =>
+              TDMLConfig("generate", name, description, path)
+            case Append(_, _, _, _, name, description, path) =>
+              TDMLConfig("append", name, description, path)
+            case Execute(_, _, name, description, path) =>
+              TDMLConfig("execute", name, description, path)
+          }
+      }
+    }
 
     sealed trait InfosetOutput {
       val `type`: String =
@@ -674,35 +745,30 @@ object Parse {
       case object None extends InfosetOutput
       case object Console extends InfosetOutput
       case class File(path: String) extends InfosetOutput
-      
+
       def apply(that: Debugee.LaunchArgs.InfosetOutput): InfosetOutput =
         that match {
           case Debugee.LaunchArgs.InfosetOutput.None       => None
           case Debugee.LaunchArgs.InfosetOutput.Console    => Console
           case Debugee.LaunchArgs.InfosetOutput.File(path) => File(path.toString)
         }
-      }
-    case class TDMLConfig(action: String, name: String, description: String, path: String)
-
-    object TDMLConfig {
-      def apply(that: Debugee.LaunchArgs.TDMLConfig): TDMLConfig =
-        that match {
-          case Debugee.LaunchArgs.TDMLConfig(action, name, description, path) =>
-            TDMLConfig(action, name, description, path)
-        }
     }
 
     case class BuildInfo(version: String, daffodilVersion: String, scalaVersion: String)
 
     def apply(launchArgs: Debugee.LaunchArgs): ConfigEvent =
       ConfigEvent(
-        LaunchArgs(
-          launchArgs.schemaPath.toString,
-          launchArgs.dataPath.toString(),
-          launchArgs.stopOnEntry,
-          InfosetOutput(launchArgs.infosetOutput),
-          launchArgs.tdmlConfig.map(ConfigEvent.TDMLConfig.apply)
-        ),
+        launchArgs match {
+          case Manual(schemaPath, dataPath, stopOnEntry, infosetOutput) =>
+            ConfigEvent.LaunchArgs.Manual(
+              schemaPath.toString,
+              dataPath.toString(),
+              stopOnEntry,
+              InfosetOutput(infosetOutput)
+            )
+          case tdmlConfig: Debugee.LaunchArgs.TDMLConfig =>
+            ConfigEvent.LaunchArgs.TDMLConfig(tdmlConfig)
+        },
         BuildInfo(
           DAPBuildInfo.version,
           DAPBuildInfo.daffodilVersion,
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
index 33df6dc..aa188f7 100644
--- a/server/core/src/main/scala/org.apache.daffodil.tdml/TDMLWrapper.scala
+++ b/server/core/src/main/scala/org.apache.daffodil.tdml/TDMLWrapper.scala
@@ -65,12 +65,8 @@ object TDMLWrapper {
   // 
   // 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) = {
+  def execute(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)
+    (Paths.get(newSchemaPath), Paths.get(newDataPath))
   }
 }