michael-hoke commented on code in PR #253:
URL: https://github.com/apache/daffodil-vscode/pull/253#discussion_r963974390
##########
server/core/src/main/scala/org.apache.daffodil.debugger.dap/Parse.scala:
##########
@@ -176,71 +180,333 @@ object Parse {
case class File(path: Path) extends InfosetOutput
}
+ 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] =
+ // Determine, based on the presence of the tdmlConfig object in the
launch config, whether
+ // this is a "normal" DFDL operation or if we should attempt to
parse the values from
+ // the tdmlConfig object.
+ Option(arguments.getAsJsonObject("tdmlConfig")) match {
+ case None => parseManual(arguments)
+ case Some(tdmlConfig) => parseTDML(arguments, tdmlConfig)
+ }
+
+ // Parse a launch config that has been found to not have a tdmlConfig
object
+ 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,
- Option(arguments.getAsJsonPrimitive("stopOnEntry"))
- .map(_.getAsBoolean())
- .getOrElse(true)
- .asRight[String]
- .toEitherNel,
- Option(arguments.getAsJsonObject("infosetOutput")) match {
+ parseProgram(arguments),
+ parseData(arguments),
+ parseStopOnEntry(arguments),
+ parseInfosetOutput(arguments)
+ ).parMapN(LaunchArgs.Manual.apply)
+ }
+
+ // Parse a tdmlConfig object from the launch config
+ //
+ // tdmlConfig: {
+ // action: '',
+ // name: '',
+ // description: '',
+ // path: ''
+ // }
+ //
+ // The action field is parsed first.
+ // If it is a valid action ('generate' | 'append' | 'execute'), create a
LaunchArgs object of the appropriate type
+ // If it is 'none' or missing, create a LaunchArgs.Manual object. This
will ignore any other fields in the tdmlConfig object.
+ //
+ // arguments: Launch config
+ // tdmlConfig: tdmlConfig object from the launch config
+ def parseTDML(arguments: JsonObject, tdmlConfig: JsonObject):
EitherNel[String, LaunchArgs] =
+ Option(tdmlConfig.getAsJsonPrimitive("action")) match {
+ case None =>
+ (
+ parseProgram(arguments),
+ parseData(arguments),
+ parseStopOnEntry(arguments),
+ parseInfosetOutput(arguments)
+ ).parMapN(LaunchArgs.Manual.apply)
+ case Some(action) =>
+ action.getAsString() match {
+ case "generate" =>
+ (
+ parseProgram(arguments),
+ parseData(arguments),
+ parseStopOnEntry(arguments),
+ parseInfosetOutput(arguments, true),
+ parseTDMLName(tdmlConfig),
+ parseTDMLDescription(tdmlConfig),
+ parseTDMLPath(tdmlConfig)
+ ).parMapN(LaunchArgs.TDMLConfig.Generate.apply)
+ case "append" =>
+ (
+ parseProgram(arguments),
+ parseData(arguments),
+ parseStopOnEntry(arguments),
+ parseInfosetOutput(arguments, true),
+ parseTDMLName(tdmlConfig),
+ parseTDMLDescription(tdmlConfig),
+ parseTDMLPath(tdmlConfig)
+ ).parMapN(LaunchArgs.TDMLConfig.Append.apply)
+ case "execute" =>
+ (
+ parseStopOnEntry(arguments),
+ parseInfosetOutput(arguments),
+ parseTDMLName(tdmlConfig),
+ parseTDMLDescription(tdmlConfig),
+ parseTDMLPath(tdmlConfig)
+ ).parMapN(LaunchArgs.TDMLConfig.Execute.apply)
+ case "none" =>
+ (
+ parseProgram(arguments),
+ parseData(arguments),
+ parseStopOnEntry(arguments),
+ parseInfosetOutput(arguments)
+ ).parMapN(LaunchArgs.Manual.apply)
+ case invalidType =>
+ Left(
+ s"invalid 'tdmlConfig.action': '$invalidType', must be 'none',
'generate', 'append', or 'execute'"
+ ).toEitherNel
+ }
+ }
+
+ // Parse the program field from the launch config
+ // Returns an error if the program field is missing or is an invalid path
+ // A case where the user expects a new directory to be created is not a
valid path
+ // eg. /path/to/<existing>/<non-existing>/file.tdml
+ //
+ // arguments: Launch config
+ 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
+
+ // Parse the data field from the launch config
+ // Returns an error if the data field is missing or is an invalid path
+ // A case where the user expects a new directory to be created is not a
valid path
+ // eg. /path/to/<existing>/<non-existing>/file.tdml
+ //
+ // arguments: Launch config
+ 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
+
+ // Parse the stopOnEntry field from the launch config
+ // Defaults to true
+ //
+ // arguments: Launch config
+ def parseStopOnEntry(arguments: JsonObject) =
+ Option(arguments.getAsJsonPrimitive("stopOnEntry"))
+ .map(_.getAsBoolean())
+ .getOrElse(true)
+ .asRight[String]
+ .toEitherNel
+
+ // Parse the infosetOutput object from the launch config
+ //
+ // infosetOutput: {
+ // type: '',
+ // path: ''
+ // }
+ //
+ // Type must be 'none' | 'console' | 'file'
+ // If type is 'file', there must be a 'path' field that contains a valid
path
+ // for the resulting infoset to be written to
+ // A case where the user expects a new directory to be created is not a
valid path
+ // eg. /path/to/<existing>/<non-existing>/file.tdml
+ //
+ // arguments: Launch config
+ // requireFile: Whether or not the type field must be set to file. This is
a requirement
+ // for the TDML generate operation. Returns an error if this
boolean
+ // is set to True, and the type field is set to a value other
than file
+ def parseInfosetOutput(arguments: JsonObject, requireFile: Boolean =
false) =
+ 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(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
- }
+ case Some(typ) =>
+ typ.getAsString() match {
+ case "none" =>
+ if (requireFile)
+ Left("'type' field in 'infosetOutput' must be set to
'file'").toEitherNel
+ else
+ Right(LaunchArgs.InfosetOutput.None).toEitherNel
+ case "console" =>
+ if (requireFile)
+ Left("'type' field in 'infosetOutput' must be set to
'file'").toEitherNel
+ else
+ 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
+ // If an empty string is passed in, it will be set
to the workspace directory by default
+ // This is inside the Java code, so we have to make
sure that the TDML file we
+ // are working with is not a directory
+ !file.isDirectory() && (file.canWrite ||
(!file.exists && file.getParentFile.canWrite))
+ }
+ )
+ .toEitherNel
+ case invalidType =>
+ Left(
+ s"invalid 'infosetOutput.type': '$invalidType', must be
'none', 'console', or 'file'"
+ ).toEitherNel
}
}
- ).parMapN(LaunchArgs.apply)
- }
+ }
+
+ // The following functions granularly parse the tdmlConfig object from the
launch config
+ //
+ // tdmlConfig: {
+ // action: '',
+ // name: '',
+ // description: '',
+ // path: ''
+ // }
+ //
+ // The action field is parsed elsewhere. If these functions are hit, a
valid action
+ // other than 'none' was found.
+
+ // Parse the name field from the tdmlConfig object from the launch config
+ // Returns an error if the field is missing or is an empty string
+ //
+ // tdmlConfig: tdmlConfig object from the launch config
+ def parseTDMLName(tdmlConfig: JsonObject) =
+ Option(tdmlConfig.getAsJsonPrimitive("name"))
+ .toRight("missing 'tdmlConfig.name' field from launch request")
+ .map(_.getAsString())
+ .flatMap(name => Either.cond(name.length() > 0, name, "'name' field
from 'tdmlConfig' object cannot be empty"))
+ .toEitherNel
+
+ // Parse the description field from the tdmlConfig object from the launch
config
+ // Returns an error if the field is missing or is an empty string
+ //
+ // tdmlConfig: tdmlConfig object from the launch config
+ def parseTDMLDescription(tdmlConfig: JsonObject) =
+ Option(tdmlConfig.getAsJsonPrimitive("description"))
+ .toRight("missing 'tdmlConfig.description' field from launch request")
+ .map(_.getAsString())
+ .flatMap(description =>
+ Either
+ .cond(description.length() > 0, description, "'description' field
from 'tdmlConfig' object cannot be empty")
+ )
+ .toEitherNel
+
+ // Parse the path field from the tdmlConfig object from the launch config
+ // Returns an error if the field is missing or is an invalid path
+ // A case where the user expects a new directory to be created is not a
valid path
+ // eg. /path/to/<existing>/<non-existing>/file.tdml
+ //
+ // tdmlConfig: tdmlConfig object from the launch config
+ def parseTDMLPath(tdmlConfig: JsonObject) =
+ Option(tdmlConfig.getAsJsonPrimitive("path"))
+ .toRight("missing 'tdmlConfig.path' field from launch request")
+ .flatMap(path =>
+ Either
+
.catchNonFatal(Paths.get(path.getAsString).toFile().getAbsolutePath())
+ .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}") { f =>
+ val file = Paths.get(f).toFile()
+ // If an empty string is passed in, it will be set to the
workspace directory by default
+ // This is inside the Java code, so we have to make sure that
the TDML file we
+ // are working with is not a directory
+ !file.isDirectory() && (file.canWrite || (!file.exists &&
file.getParentFile.canWrite))
+ }
+ )
+ .toEitherNel
Review Comment:
The intent of this feature was just to implement very basic TDML test cases
into the extension. I agree that the code should not be duplicated. I'm hoping
that someone will be willing to come along to refactor this to reference the
TDML code in the main daffodil repo (either directly, or refactor it into a
separate library), but that seemed out of scope for this implementation.
The main use case for this feature as it exists now is for a user of the
extension to use the 'Generate TDML' command to create a TDML file that can be
added to a DFDL schema and an input file, and sent to the user list for
debugging purposes. Then, someone should be able to take those files and
execute the test case from the TDML file to get the same execution as the
original poster.
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: [email protected]
For queries about this service, please contact Infrastructure at:
[email protected]