blob: a218f17c213e1175ec770218a3bf44bc62c49729 [file] [log] [blame]
package edu.illinois.ncsa.daffodil
/* Copyright (c) 2013 Tresys Technology, LLC. All rights reserved.
*
* Developed by: Tresys Technology, LLC
* http://www.tresys.com
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of
* this software and associated documentation files (the "Software"), to deal with
* the Software without restriction, including without limitation the rights to
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
* of the Software, and to permit persons to whom the Software is furnished to do
* so, subject to the following conditions:
*
* 1. Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimers.
*
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimers in the
* documentation and/or other materials provided with the distribution.
*
* 3. Neither the names of Tresys Technology, nor the names of its contributors
* may be used to endorse or promote products derived from this Software
* without specific prior written permission.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* CONTRIBUTORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS WITH THE
* SOFTWARE.
*/
import java.io.FileOutputStream
import java.io.BufferedWriter
import java.io.OutputStreamWriter
import java.io.FileInputStream
import java.io.File
import scala.xml.SAXParseException
import org.rogach.scallop
import edu.illinois.ncsa.daffodil.debugger.{ Debugger, InteractiveDebugger, TraceDebuggerRunner, CLIDebuggerRunner }
import edu.illinois.ncsa.daffodil.util.Misc
import edu.illinois.ncsa.daffodil.util.Timer
import edu.illinois.ncsa.daffodil.xml.DaffodilXMLLoader
import edu.illinois.ncsa.daffodil.exceptions.Assert
import edu.illinois.ncsa.daffodil.compiler.Compiler
import edu.illinois.ncsa.daffodil.api.WithDiagnostics
import edu.illinois.ncsa.daffodil.util.Glob
import edu.illinois.ncsa.daffodil.util.Logging
import edu.illinois.ncsa.daffodil.util.LogLevel
import edu.illinois.ncsa.daffodil.util.LogWriter
import edu.illinois.ncsa.daffodil.util.LoggingDefaults
import edu.illinois.ncsa.daffodil.exceptions.NotYetImplementedException
import java.io.File
import edu.illinois.ncsa.daffodil.tdml.DFDLTestSuite
import edu.illinois.ncsa.daffodil.api.ValidationMode
import scala.xml.Node
import edu.illinois.ncsa.daffodil.externalvars.Binding
import edu.illinois.ncsa.daffodil.externalvars.ExternalVariablesLoader
import edu.illinois.ncsa.daffodil.configuration.ConfigurationLoader
import edu.illinois.ncsa.daffodil.api.ValidationMode
class CommandLineXMLLoaderErrorHandler() extends org.xml.sax.ErrorHandler with Logging {
def warning(exception: SAXParseException) = {
log(LogLevel.Warning, "loading schema: " + exception.getMessage)
}
def error(exception: SAXParseException) = {
log(LogLevel.Error, "loading schema: " + exception.getMessage)
System.exit(1)
}
def fatalError(exception: SAXParseException) = {
log(LogLevel.Error, "loading schema: " + exception.getMessage)
System.exit(1)
}
}
trait CLILogPrefix extends LogWriter {
override def prefix(logID: String, glob: Glob): String = {
"[" + glob.lvl.toString.toLowerCase + "] "
}
override def suffix(logID: String, glob: Glob): String = {
""
}
}
object CLILogWriter extends CLILogPrefix {
def write(msg: String) {
Console.err.println(msg)
Console.flush
}
}
object TDMLLogWriter extends CLILogPrefix {
var logs: scala.collection.mutable.Queue[String] = scala.collection.mutable.Queue.empty
def write(msg: String) {
logs += msg
}
def reset() {
logs = scala.collection.mutable.Queue.empty
}
}
class CLIConf(arguments: Array[String]) extends scallop.ScallopConf(arguments)
with Logging {
// This essentially reimplements the listArgConverter in scallop to not
// allow a list of options to follow the option. For example, the
// following is illegal: --schema foo bar. Instead, it must be --schema
// foo --schema bar. It does this by copying listArgConverter, but
// setting the argType to SINGLE.
def singleListArgConverter[A](conv: String => A)(implicit m: Manifest[List[A]]) = new scallop.ValueConverter[List[A]] {
def parse(s: List[(String, List[String])]) = {
try {
// this happens when options are provided after a trailing arg
assert(s.forall(_._2.size == 1))
val l = s.map(_._2).flatten.map(i => conv(i))
if (l.isEmpty) Right(Some(Nil))
else Right(Some(l))
} catch {
case _: Throwable => Left("too many arguments for flag option")
}
}
val manifest = m
val argType = scallop.ArgType.SINGLE
}
/**
* This is used when the flag is optional and so is its
* argument.
*
* Let's use --validate [mode] as an example.
*
* This optionalValueConverter first determines if the
* --validate flag was given. If it wasn't, Right(None)
* is returned.
*
* If the flag was given we then need to determine
* if 'mode' was also given. If mode was not given, Right(Some(None))
* is returned meaning that the --validate flag was there but 'mode'
* was not. If the mode was given, Right(Some(Some(conv(mode)))) is
* returned. This means that the --validate flag was there as well as
* a value for 'mode'.
*
* conv(mode) simply performs the necessary conversion
* of the string to the type [A].
*
*/
def optionalValueConverter[A](conv: String => A)(implicit m: Manifest[Option[A]]) = new scallop.ValueConverter[Option[A]] {
// From the Scallop wiki:
//
// parse is a method, that takes a list of arguments to all option invocations:
// for example, "-a 1 2 -a 3 4 5" would produce List(List(1,2),List(3,4,5)).
// parse returns Left with error message, if there was an error while parsing
// if no option was found, it returns Right(None)
// and if option was found, it returns Right(...)
def parse(s: List[(String, List[String])]): Either[String, Option[Option[A]]] = {
s match {
case Nil => Right(None) // --validate flag was not present
case (_, Nil) :: Nil => Right(Some(None)) // --validate flag was present but 'mode' wasn't
case (_, v :: Nil) :: Nil => { // --validate [mode] was present, perform the conversion
try {
Right(Some(Some(conv(v))))
} catch {
case e: Exception => {
Left(e.getMessage())
}
}
}
case _ => Left("you should provide no more than one argument for this option") // Error because we expect there to be at most one --validate flag
}
}
val manifest = m
val argType = scallop.ArgType.LIST
override def argFormat(name: String): String = "[" + name + "]"
}
def validateConf(c1: => Option[scallop.ScallopConf])(fn: (Option[scallop.ScallopConf]) => Either[String, Unit]) {
validations :+= new Function0[Either[String, Unit]] {
def apply = {
fn(c1)
}
}
}
def validateConverter(s: String) = {
s.toLowerCase match {
case "on" => ValidationMode.Full
case "limited" => ValidationMode.Limited
case "off" => ValidationMode.Off
case "" => ValidationMode.Full // Is this even possible?
case _ => throw new Exception("Unrecognized ValidationMode %s. Must be 'on', 'limited' or 'off'.".format(s))
}
}
printedName = "daffodil"
helpWidth(76)
def error(msg: String) = errorMessageHandler(msg)
errorMessageHandler = { message =>
val msg =
if (message.indexOf("Wrong format for option 'schema'") >= 0) {
// the 'wrong format' error only occurs on --schema when options are
// provided after the trailing arg, so let's give a more helpful error
// message
"Options are not allow after a trailing argument"
} else {
message
}
log(LogLevel.Error, "%s", msg)
sys.exit(1)
}
banner("""|Usage: %s [GLOBAL_OPTS] <subcommand> [SUBCOMMAND_OPTS]
|
|Global Options:""".format(printedName).stripMargin)
footer("""|
|Run '%s <subcommand> --help' for subcommand specific options""".format(printedName).stripMargin)
version({
val versions = Misc.getDaffodilVersion
val strVers = "%s %s (build %s)".format(printedName, versions._1, versions._2)
strVers
})
shortSubcommandsHelp()
// Global Options
val debug = opt[Option[String]](argName = "file", descr = "enable debugging. Optionally, read initial debugger commands from [file] if provided.")(optionalValueConverter[String](a => a))
val trace = opt[Boolean](descr = "run the debugger with verbose trace output")
val verbose = tally(descr = "increment verbosity level, one level for each -v")
// Parse Subcommand Options
val parse = new scallop.Subcommand("parse") {
banner("""|Usage: daffodil parse (-s <schema>... [-r <root> [-n <namespace>]] [-p <path>] |
| -P <parser>)
| [--validate [mode]]
| [-D[{namespace}]<variable>=<value>...] [-o <output>]
| [-c <file>] [infile]
|
|Parse a file, using either a DFDL schema or a saved parser
|
|Parse Options:""".stripMargin)
descr("parse data to a DFDL infoset")
helpWidth(76)
val schemas = opt[List[String]]("schema", argName = "file", descr = "the annotated DFDL schema to use to create the parser. May be supplied multiple times for multi-schema support.")(singleListArgConverter[String](a => a))
val root = opt[String](argName = "node", descr = "the root element of the XML file to use. This needs to be one of the top-level elements of the DFDL schema defined with --schema. Requires --schema. If not supplied uses the first element of the first schema")
val namespace = opt[String](argName = "ns", descr = "the namespace of the root element. Requires --root.")
val path = opt[String](argName = "path", descr = "path to the node to create parser.")
val parser = opt[String](short = 'P', argName = "file", descr = "use a previously saved parser.")
val output = opt[String](argName = "file", descr = "write output to a given file. If not given or is -, output is written to stdout.")
val validate = opt[ValidationMode.Type](short = 'V', default = Some(ValidationMode.Off), argName = "mode", descr = "the validation mode. 'on', 'limited' or 'off'. Defaults to 'on' if mode is not supplied.")(optionalValueConverter[ValidationMode.Type](a => validateConverter(a)).map {
case None => ValidationMode.Full
case Some(mode) => mode
})
val vars = props[String]('D', keyName = "variable", valueName = "value", descr = "variables to be used when parsing. An option namespace may be provided.")
val config = opt[String](short = 'c', argName = "file", descr = "path to file containing configuration items.")
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)
}
validateOpt(schemas, parser, root, namespace) {
case (Some(Nil), None, _, _) => Left("One of --schema or --parser must be defined")
case (Some(_ :: _), Some(_), _, _) => Left("Only one of --parser and --schema may be defined")
case (Some(_ :: _), None, None, Some(_)) => Left("--root must be defined if --namespace is defined")
case (None, Some(_), Some(_), _) => Left("--root cannot be defined with --parser")
case (None, Some(_), _, Some(_)) => Left("--namespace cannot be defined with --parser")
case _ => Right(Unit)
}
}
// Unparse Subcommand Options
val unparse = new scallop.Subcommand("unparse") {
banner("""|Usage: daffodil unparse (-s <schema>... [-r <root> [-n <namespace>]] [-p <path>] |
| -P <parser>)
| [--validate [mode]]
| [-D[{namespace}]<variable>=<value>...] [-c <file>]
| [-o <output>] [infile]
|
|Unparse an infoset file, using either a DFDL schema or a saved paser
|
|Unparse Options:""".stripMargin)
descr("unparse a DFDL infoset")
helpWidth(76)
val schemas = opt[List[String]]("schema", argName = "file", descr = "the annotated DFDL schema to use to create the parser. May be supplied multiple times for multi-schema support.")(singleListArgConverter[String](a => a))
val root = opt[String](argName = "node", descr = "the root element of the XML file to use. This needs to be one of the top-level elements of the DFDL schema defined with --schema. Requires --schema. If not supplied uses the first element of the first schema")
val namespace = opt[String](argName = "ns", descr = "the namespace of the root element. Requires --root.")
val path = opt[String](argName = "path", descr = "path to the node to create parser.")
val parser = opt[String](short = 'P', argName = "file", descr = "use a previously saved parser.")
val output = opt[String](argName = "file", descr = "write output to file. If not given or is -, output is written to standard output.")
val validate = opt[ValidationMode.Type](short = 'V', default = Some(ValidationMode.Off), argName = "mode", descr = "the validation mode. 'on', 'limited' or 'off'. Defaults to 'on' if mode is not supplied.")(optionalValueConverter[ValidationMode.Type](a => validateConverter(a)).map {
case None => ValidationMode.Full
case Some(mode) => mode
})
val vars = props[String]('D', keyName = "variable", valueName = "value", descr = "variables to be used when unparsing. An optional namespace may be provided.")
val config = opt[String](short = 'c', argName = "file", descr = "path to file containing configuration items.")
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(schemas, parser, root, namespace) {
case (Some(Nil), None, _, _) => Left("One of --schema or --parser must be defined")
case (Some(_ :: _), Some(_), _, _) => Left("Only one of --parser and --schema may be defined")
case (Some(_ :: _), None, None, Some(_)) => Left("--root must be defined if --namespace is defined")
case (None, Some(_), Some(_), _) => Left("--root cannot be defined with --parser")
case (None, Some(_), _, Some(_)) => Left("--namespace cannot be defined with --parser")
case _ => Right(Unit)
}
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)
}
case _ => Right(Unit)
}
}
// Save Subcommand Options
val save = new scallop.Subcommand("save-parser") {
banner("""|Usage: daffodil save-parser -s <schema>... [-r <root> [-n <namespace>]]
| [-p <path>] [outfile]
| [--validate [mode]]
| [-D[{namespace}]<variable>=<value>...]
| [-c <file>]
|
|Create and save a parser using a DFDL schema
|
|Save Parser Options:""".stripMargin)
descr("save a daffodil parser for reuse")
helpWidth(76)
val schemas = opt[List[String]]("schema", argName = "file", required = true, descr = "the annotated DFDL schema to use to create the parser. May be supplied multiple times for multi-schema support.")(singleListArgConverter[String](a => a))
val root = opt[String](argName = "node", descr = "the root element of the XML file to use. This needs to be one of the top-level elements of the DFDL schema defined with --schema. Requires --schema. If not supplied uses the first element of the first schema.")
val namespace = opt[String](argName = "ns", descr = "the namespace of the root element. Requires --root.")
val path = opt[String](argName = "path", descr = "path to the node to create parser.")
val outfile = trailArg[String](required = false, descr = "output file to save parser. If not specified, or a value of -, writes to stdout.")
val validate = opt[ValidationMode.Type](short = 'V', default = Some(ValidationMode.Off), argName = "mode", descr = "the validation mode. 'on', 'limited' or 'off'. Defaults to 'on' if mode is not supplied.")(optionalValueConverter[ValidationMode.Type](a => validateConverter(a)).map {
case None => ValidationMode.Full
case Some(mode) => mode
})
val vars = props[String]('D', keyName = "variable", valueName = "value", descr = "variables to be used.")
val config = opt[String](short = 'c', argName = "file", descr = "path to file containing configuration items.")
validateOpt(schemas, root, namespace, outfile) {
case (Some(Nil), _, _, _) => Left("No schemas specified using the --schema option")
case (_, None, Some(_), _) => Left("--root must be defined if --namespace is defined")
case _ => Right(Unit)
}
}
// Test Subcommand Options
val test = new scallop.Subcommand("test") {
banner("""|Usage: daffodil test <tdmlfile> [testname...]
|
|List or execute tests in a TDML file
|
|Test Options:""".stripMargin)
descr("list or execute TDML tests")
helpWidth(76)
val list = opt[Boolean](descr = "show names and descriptions instead of running test cases.")
val regex = opt[Boolean](descr = "treat <names> as regular expressions.")
val tdmlfile = trailArg[String](required = true, descr = "test data markup language (TDML) file.")
val names = trailArg[List[String]](required = false, descr = "name of test case(s) in tdml file. If not given, all tests in tdmlfile are run.")
val info = tally(descr = "increment test result information output level, one level for each -i")
}
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)
}
}
object Main extends Logging {
val traceCommands = Seq("display info parser",
"display info data",
"display info infoset",
"display info diff",
"trace")
/* indents a multi-line string */
def indent(str: String, pad: Int): String = {
val lines = str.split("\n")
val prefix = " " * pad
val indented = lines.map(prefix + _)
indented.mkString("\n")
}
def createProcessorFromParser(parseFile: String, path: Option[String], mode: ValidationMode.Type) = {
val compiler = Compiler()
val processorFactory = Timer.getResult("reloading", compiler.reload(parseFile))
if (processorFactory.canProceed) {
val processor = processorFactory.onPath(path.getOrElse("/"))
processor.setValidationMode(mode)
Some(processor)
} else None
}
/**
* Loads and validates the configuration file.
*
* @param pathName The path to the file.
* @return The Node representation of the file.
*/
def loadConfigurationFile(pathName: String) = {
val node = ConfigurationLoader.getConfiguration(pathName)
node
}
/**
* Overrides bindings specified via the configuration file with those
* specified via the -D command.
*
* @param bindings A sequence of Bindings (external variables)
* @param bindingsToOverride The sequence of Bindings that could be overridden.
*/
def overrideBindings(bindings: Seq[Binding], bindingsToOverride: Seq[Binding]) = {
val inBoth = bindings.intersect(bindingsToOverride).distinct
val bindingsMinusBoth = bindings.diff(inBoth)
val bindingsToOverrideMinusBoth = bindingsToOverride.diff(inBoth)
val bindingsWithCorrectValues = bindings.filter(b => inBoth.exists(p => b.hashCode == p.hashCode))
val bindingsMinusUpdates = bindingsMinusBoth.union(bindingsToOverrideMinusBoth)
val bindingsWithUpdates = bindingsMinusUpdates.union(bindingsWithCorrectValues)
bindingsWithUpdates
}
/**
* Retrieves all external variables specified via the command line interface.
*
* @param vars The individual variables input via the command line using the -D command.
* @param configFileNode The Node representing the configuration file if there is one.
*/
def retrieveExternalVariables(vars: Map[String, String], configFileNode: Option[Node]): Seq[Binding] = {
val configFileVars: Seq[Binding] = configFileNode match {
case None => Seq.empty
case Some(configNode) => {
// We have a configuration file node, we now need to grab
// the externalVariableBindings node.
val extVarBindingNodeOpt = (configNode \ "externalVariableBindings").headOption
extVarBindingNodeOpt match {
case None => Seq.empty
case Some(extVarBindingsNode) => ExternalVariablesLoader.getVariables(extVarBindingsNode)
}
}
}
val individualVars = ExternalVariablesLoader.getVariables(vars)
val bindings = overrideBindings(individualVars, configFileVars)
bindings
}
def createProcessorFromSchemas(schemaFiles: List[File], root: Option[String],
namespace: Option[String], path: Option[String],
extVars: Seq[Binding],
mode: ValidationMode.Type) = {
val compiler = Compiler()
val ns = namespace.getOrElse(null)
compiler.setExternalDFDLVariables(extVars)
root match {
case Some(r) => {
compiler.setDistinguishedRootNode(r, ns)
}
case None => //ignore
}
// Wrap timing around the whole of compilation
//
// compilation extends from the call to compile
// to also include the call to pf.onPath. (which is the last phase
// of compilation, where it asks for the parser)
//
val pf = Timer.getResult("compiling", {
val processorFactory = compiler.compile(schemaFiles: _*)
if (processorFactory.canProceed) {
val processor = processorFactory.onPath(path.getOrElse("/"))
processor.setValidationMode(mode)
Some(processor) // note: processor could still be isError == true
// but we do definitely get a processor.
} else
None
})
pf
}
def run(arguments: Array[String]): Int = {
LoggingDefaults.setLogWriter(CLILogWriter)
val conf = new CLIConf(arguments)
val verboseLevel = conf.verbose() match {
case 0 => LogLevel.Warning
case 1 => LogLevel.Info
case 2 => LogLevel.Compile
case 3 => LogLevel.Debug
case _ => LogLevel.OOLAGDebug
}
LoggingDefaults.setLoggingLevel(verboseLevel)
if (conf.trace() || conf.debug.isDefined) {
val runner =
if (conf.trace()) {
new TraceDebuggerRunner
} else {
if (System.console == null) {
log(LogLevel.Warning, "Using --debug on a non-interactive console may result in display issues")
}
conf.debug() match {
case Some(f) => new CLIDebuggerRunner(new File(f))
case None => new CLIDebuggerRunner()
}
}
Debugger.setDebugging(true)
Debugger.setDebugger(new InteractiveDebugger(runner))
}
val ret = conf.subcommand match {
case Some(conf.parse) => {
val parseOpts = conf.parse
val validate = parseOpts.validate.get.get
val cfgFileNode = parseOpts.config.get match {
case None => None
case Some(pathToConfig) => Some(this.loadConfigurationFile(pathToConfig))
}
val extVarsBindings = retrieveExternalVariables(parseOpts.vars, cfgFileNode)
val processor = {
if (parseOpts.parser.isDefined) {
createProcessorFromParser(parseOpts.parser(), parseOpts.path.get, validate)
} else {
val files: List[File] = parseOpts.schemas().map(s => new File(s))
createProcessorFromSchemas(files, parseOpts.root.get, parseOpts.namespace.get, parseOpts.path.get, extVarsBindings, validate)
}
}
def displayDiagnostics(lvl: LogLevel.Type, pr: WithDiagnostics) {
pr.getDiagnostics.foreach { d =>
log(lvl, "%s", d.getMessage())
}
}
val rc = processor match {
case Some(processor) if (processor.canProceed) => {
val (input, optDataSize) = parseOpts.infile.get match {
case Some("-") | None => (System.in, None)
case Some(file) => {
val f = new File(parseOpts.infile())
(new FileInputStream(f), Some(f.length()))
}
}
val inChannel = java.nio.channels.Channels.newChannel(input);
processor.setValidationMode(validate)
val parseResult = Timer.getResult("parsing",
optDataSize match {
case None => processor.parse(inChannel)
case Some(szInBytes) => processor.parse(inChannel, szInBytes * 8)
})
val loc = parseResult.resultState.currentLocation
if (parseResult.isError) {
displayDiagnostics(LogLevel.Error, parseResult)
1
} else {
displayDiagnostics(LogLevel.Warning, parseResult) // displays any warnings (should be no errors)
val output = parseOpts.output.get match {
case Some("-") | None => System.out
case Some(file) => new FileOutputStream(file)
}
// check for left over data (if we know the size up front, like for a file)
val hasLeftOverData = optDataSize match {
case Some(sz) => {
if (!loc.isAtEnd) {
log(LogLevel.Error, "Left over data. %s bytes available. Location: %s", sz, loc)
true
} else false
}
case None => {
// we need to look at the internal state of the parser inStream to
// see how big it is. We do this after execution so as
// not to traverse the data twice.
val lengthInBytes = parseResult.resultState.lengthInBytes
val positionInBytes = loc.bytePos
if (positionInBytes != lengthInBytes) {
log(LogLevel.Error, "Left over data. %s bytes available. Location: %s", lengthInBytes, loc)
true
} else false
}
}
val writer: BufferedWriter = new BufferedWriter(new OutputStreamWriter(output));
val pp = new scala.xml.PrettyPrinter(80, 2)
Timer.getResult("writing", writer.write(pp.format(parseResult.result) + "\n"))
writer.flush()
if (hasLeftOverData) 1 else 0
}
}
case Some(processor) => 1
case None => 1
}
rc
}
case Some(conf.unparse) => {
val unparseOpts = conf.unparse
val validate = unparseOpts.validate.get match {
case None => ValidationMode.Off
case Some(vMode) => vMode
}
val cfgFileNode = unparseOpts.config.get match {
case None => None
case Some(pathToConfig) => Some(this.loadConfigurationFile(pathToConfig))
}
val extVarsBindings = retrieveExternalVariables(unparseOpts.vars, cfgFileNode)
val processor = {
if (unparseOpts.parser.isDefined) {
createProcessorFromParser(unparseOpts.parser(), unparseOpts.path.get, validate)
} else {
val files: List[File] = unparseOpts.schemas().map(s => new File(s))
createProcessorFromSchemas(files, unparseOpts.root.get, unparseOpts.namespace.get, unparseOpts.path.get, extVarsBindings, validate)
}
}
val output = unparseOpts.output.get match {
case Some("-") | None => System.out
case Some(file) => new FileOutputStream(file)
}
val outChannel = java.nio.channels.Channels.newChannel(output)
//
// We are not loading a schema here, we're loading the infoset to unparse.
//
val dataLoader = new DaffodilXMLLoader(new CommandLineXMLLoaderErrorHandler)
dataLoader.setValidation(true) //TODO: make this flag an option.
val document = unparseOpts.infile.get match {
case Some("-") | None => dataLoader.load(System.in)
case Some(file) => dataLoader.loadFile(file)
}
val rc = processor match {
case None => 1
case Some(processor) => {
val unparseResult = Timer.getResult("unparsing", processor.unparse(outChannel, document))
output.close()
if (unparseResult.isError) 1 else 0
}
}
rc
}
case Some(conf.save) => {
val saveOpts = conf.save
val files: List[File] = saveOpts.schemas().map(s => new File(s))
val validate = saveOpts.validate.get match {
case None => ValidationMode.Off
case Some(vMode) => vMode
}
val cfgFileNode = saveOpts.config.get match {
case None => None
case Some(pathToConfig) => Some(this.loadConfigurationFile(pathToConfig))
}
val extVarsBindings = retrieveExternalVariables(saveOpts.vars, cfgFileNode)
val processor = createProcessorFromSchemas(files, saveOpts.root.get, saveOpts.namespace.get, saveOpts.path.get, extVarsBindings, validate)
val output = saveOpts.outfile.get match {
case Some("-") | None => System.out
case Some(file) => new FileOutputStream(file)
}
val outChannel = java.nio.channels.Channels.newChannel(output)
val rc = processor match {
case Some(processor) => {
Timer.getResult("saving", processor.save(outChannel))
0
}
case None => 1
}
rc
}
case Some(conf.test) => {
val testOpts = conf.test
val tdmlFile = testOpts.tdmlfile()
val tdmlRunner = new DFDLTestSuite(new java.io.File(tdmlFile))
val tests = {
if (testOpts.names.isDefined) {
testOpts.names().flatMap(testName => {
if (testOpts.regex()) {
val regex = testName.r
val matches = tdmlRunner.testCases.filter(testCase => regex.pattern.matcher(testCase.name).matches)
matches.map(testCase => (testCase.name, Some(testCase)))
} else {
List((testName, tdmlRunner.testCases.find(_.name == testName)))
}
})
} else {
tdmlRunner.testCases.map(test => (test.name, Some(test)))
}
}.distinct.sortBy(_._1)
if (testOpts.list()) {
if (testOpts.info() > 0) {
// determine the max lengths of the various pieces of atest
val headers = List("Name", "Model", "Root", "Description")
val maxCols = tests.foldLeft(headers.map(_.length)) {
(maxVals, testPair) =>
{
testPair match {
case (name, None) => List(
maxVals(0).max(name.length),
maxVals(1),
maxVals(2),
maxVals(3))
case (name, Some(test)) => List(
maxVals(0).max(name.length),
maxVals(1).max(test.model.length),
maxVals(2).max(test.root.length),
maxVals(3).max(test.description.length))
}
}
}
val formatStr = maxCols.map(max => "%" + -max + "s").mkString(" ")
println(formatStr.format(headers: _*))
tests.foreach { testPair =>
testPair match {
case (name, Some(test)) => println(formatStr.format(name, test.model, test.root, test.description))
case (name, None) => println(formatStr.format(name, "[Not Found]", "", ""))
}
}
} else {
tests.foreach { testPair =>
testPair match {
case (name, Some(test)) => println(name)
case (name, None) => println("%s [Not Found]".format(name))
}
}
}
} else {
LoggingDefaults.setLogWriter(TDMLLogWriter)
var pass = 0
var fail = 0
var notfound = 0
tests.foreach { testPair =>
testPair match {
case (name, Some(test)) => {
try {
test.run()
println("[Pass] %s".format(name))
pass += 1
} catch {
case e: Throwable =>
println("[Fail] %s".format(name))
fail += 1
if (testOpts.info() > 0) {
println(" Failure Information:")
println(indent(e.getMessage, 4))
if (testOpts.info() > 1) {
println(" Logs:")
if (TDMLLogWriter.logs.size > 0) {
TDMLLogWriter.logs.foreach { l => println(indent(l, 4)) }
} else {
println(" None")
}
println(" Backtrace:")
e.getStackTrace.foreach { st => println(indent(st.toString, 4)) }
}
}
}
TDMLLogWriter.reset
}
case (name, None) => {
println("[Not Found] %s".format(name))
notfound += 1
}
}
}
println("")
println("Total: %d, Pass: %d, Fail: %d, Not Found: %s".format(pass + fail + notfound, pass, fail, notfound))
}
0
}
case _ => {
// This should never happen, this is caught by validation
Assert.impossible()
1
}
}
ret
}
def bugFound(e: Exception): Int = {
System.err.println("""|
|!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
|!! An unexpected exception occurred. This is a bug! !!
|!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
|
| Please report this bug and help us fix it:
|
| https://opensource.ncsa.illinois.edu/confluence/display/DFDL/How+to+Report+a+Bug
|
| Please include the following exception, the command you
| ran, and any input, schema, or tdml files used that led
| to this bug.
|
|""".stripMargin)
e.printStackTrace
1
}
def nyiFound(e: NotYetImplementedException): Int = {
System.err.println("""|
|!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
|!! Not Yet Implemented !!
|!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
|
| You are using a feature that is not yet implemented:
|
| %s
|
| You can create a bug and track the progress of this
| feature at:
|
| https://opensource.ncsa.illinois.edu/jira/browse/DFDL
|
|""".format(e.getMessage).stripMargin)
1
}
def main(arguments: Array[String]): Unit = {
val ret = try {
run(arguments)
} catch {
case e: java.io.FileNotFoundException => {
log(LogLevel.Error, "%s", e.getMessage)
1
}
case e: NotYetImplementedException => {
nyiFound(e)
}
case e: Exception => {
bugFound(e)
}
}
System.exit(ret)
}
}