summaryrefslogtreecommitdiff
path: root/support/arara/source/src/main/kotlin/org/islandoftex/arara/model/Interpreter.kt
diff options
context:
space:
mode:
authorNorbert Preining <norbert@preining.info>2020-03-05 03:00:59 +0000
committerNorbert Preining <norbert@preining.info>2020-03-05 03:00:59 +0000
commit898048513951b471a492afa23e46112d14bcb236 (patch)
tree8596afc705f55d2d07b324a756f7283ac0e2d21b /support/arara/source/src/main/kotlin/org/islandoftex/arara/model/Interpreter.kt
parent19d25b8009801aa98ea2f46b45c37c257f990491 (diff)
CTAN sync 202003050300
Diffstat (limited to 'support/arara/source/src/main/kotlin/org/islandoftex/arara/model/Interpreter.kt')
-rw-r--r--support/arara/source/src/main/kotlin/org/islandoftex/arara/model/Interpreter.kt375
1 files changed, 375 insertions, 0 deletions
diff --git a/support/arara/source/src/main/kotlin/org/islandoftex/arara/model/Interpreter.kt b/support/arara/source/src/main/kotlin/org/islandoftex/arara/model/Interpreter.kt
new file mode 100644
index 0000000000..49cf40f1a2
--- /dev/null
+++ b/support/arara/source/src/main/kotlin/org/islandoftex/arara/model/Interpreter.kt
@@ -0,0 +1,375 @@
+// SPDX-License-Identifier: BSD-3-Clause
+package org.islandoftex.arara.model
+
+import java.io.File
+import kotlin.time.ExperimentalTime
+import org.islandoftex.arara.Arara
+import org.islandoftex.arara.configuration.AraraSpec
+import org.islandoftex.arara.localization.LanguageController
+import org.islandoftex.arara.localization.Messages
+import org.islandoftex.arara.ruleset.Argument
+import org.islandoftex.arara.ruleset.Command
+import org.islandoftex.arara.ruleset.Conditional
+import org.islandoftex.arara.ruleset.Directive
+import org.islandoftex.arara.ruleset.Rule
+import org.islandoftex.arara.ruleset.RuleCommand
+import org.islandoftex.arara.ruleset.RuleUtils
+import org.islandoftex.arara.utils.CommonUtils
+import org.islandoftex.arara.utils.DisplayUtils
+import org.islandoftex.arara.utils.InterpreterUtils
+import org.islandoftex.arara.utils.Methods
+import org.mvel2.templates.TemplateRuntime
+import org.slf4j.LoggerFactory
+
+/**
+ * Interprets the list of directives.
+ *
+ * @author Island of TeX
+ * @version 5.0
+ * @since 4.0
+ */
+class Interpreter(
+ /**
+ * The list of directives to be interpreted and evaluated.
+ */
+ val directives: List<Directive>
+) {
+ /**
+ * Exception class to represent that the interpreter should stop for some
+ * reason
+ */
+ private class HaltExpectedException(msg: String) : Exception(msg)
+
+ /**
+ * Gets the rule according to the provided directive.
+ *
+ * @param directive The provided directive.
+ * @return The absolute canonical path of the rule, given the provided
+ * directive.
+ * @throws AraraException Something wrong happened, to be caught in the
+ * higher levels.
+ */
+ @Throws(AraraException::class)
+ private fun getRule(directive: Directive): File {
+ return InterpreterUtils.buildRulePath(directive.identifier)
+ ?: throw AraraException(
+ messages.getMessage(
+ Messages.ERROR_INTERPRETER_RULE_NOT_FOUND,
+ directive.identifier,
+ "(" + CommonUtils.allRulePaths
+ .joinToString("; ") + ")"
+ )
+ )
+ }
+
+ // TODO: in the following, extract the printing into the higher level
+ // function
+ /**
+ * "Run" a boolean return value
+ * @param value The boolean.
+ * @param conditional The conditional to print in dry-run mode.
+ * @param authors The authors of the rule.
+ * @return Returns [value]
+ */
+ private fun runBoolean(
+ value: Boolean,
+ conditional: Conditional,
+ authors: List<String>
+ ): Boolean {
+ logger.info(messages.getMessage(Messages.LOG_INFO_BOOLEAN_MODE,
+ value.toString()))
+
+ if (Arara.config[AraraSpec.Execution.dryrun]) {
+ DisplayUtils.printAuthors(authors)
+ DisplayUtils.wrapText(messages.getMessage(Messages
+ .INFO_INTERPRETER_DRYRUN_MODE_BOOLEAN_MODE,
+ value))
+ DisplayUtils.printConditional(conditional)
+ }
+
+ return value
+ }
+
+ /**
+ * Run a command
+ * @param command The command to run.
+ * @param conditional The conditional applied to the run (only for printing).
+ * @param authors The rule authors (only for printing).
+ * @param ruleCommandExitValue The exit value of the rule command.
+ * @return Success of the execution.
+ * @throws AraraException Execution failed.
+ */
+ @ExperimentalTime
+ @Throws(AraraException::class)
+ @Suppress("TooGenericExceptionCaught")
+ private fun runCommand(
+ command: Command,
+ conditional: Conditional,
+ authors: List<String>,
+ ruleCommandExitValue: String?
+ ): Boolean {
+ logger.info(messages.getMessage(Messages.LOG_INFO_SYSTEM_COMMAND,
+ command))
+ var success = true
+
+ if (!Arara.config[AraraSpec.Execution.dryrun]) {
+ val code = InterpreterUtils.run(command)
+ val check: Any = try {
+ val context = mapOf<String, Any>("value" to code)
+ TemplateRuntime.eval(
+ "@{ " + (ruleCommandExitValue ?: "value == 0") + " }",
+ context)
+ } catch (exception: RuntimeException) {
+ throw AraraException(CommonUtils.ruleErrorHeader +
+ messages.getMessage(Messages
+ .ERROR_INTERPRETER_EXIT_RUNTIME_ERROR),
+ exception)
+ }
+
+ success = if (check is Boolean) {
+ check
+ } else {
+ throw AraraException(
+ CommonUtils.ruleErrorHeader + messages.getMessage(
+ Messages.ERROR_INTERPRETER_WRONG_EXIT_CLOSURE_RETURN
+ )
+ )
+ }
+ } else {
+ DisplayUtils.printAuthors(authors)
+ DisplayUtils.wrapText(messages.getMessage(
+ Messages.INFO_INTERPRETER_DRYRUN_MODE_SYSTEM_COMMAND,
+ command))
+ DisplayUtils.printConditional(conditional)
+ }
+
+ return success
+ }
+
+ /**
+ * Converts the command evaluation result to a flat list.
+ * @param result The result
+ * @return A flat list.
+ */
+ private fun resultToList(result: Any) = if (result is List<*>) {
+ CommonUtils.flatten(result)
+ } else {
+ listOf(result)
+ }
+
+ /**
+ * Execute a command.
+ * @param command The command to evaluate.
+ * @param conditional Under which condition to execute.
+ * @param rule The rule (only passed for output purposes).
+ * @param parameters The parameters for evaluation
+ * @throws AraraException Running the command failed.
+ */
+ @ExperimentalTime
+ @Throws(AraraException::class)
+ @Suppress("TooGenericExceptionCaught", "ThrowsCount")
+ private fun executeCommand(
+ command: RuleCommand,
+ conditional: Conditional,
+ rule: Rule,
+ parameters: Map<String, Any>
+ ) {
+ val result: Any = try {
+ TemplateRuntime.eval(command.command!!, parameters)
+ } catch (exception: RuntimeException) {
+ throw AraraException(CommonUtils.ruleErrorHeader +
+ messages.getMessage(Messages
+ .ERROR_INTERPRETER_COMMAND_RUNTIME_ERROR),
+ exception)
+ }
+
+ // TODO: check nullability
+ resultToList(result).filter { it.toString().isNotBlank() }
+ .forEach { current ->
+ DisplayUtils.printEntry(rule.name, command.name
+ ?: messages.getMessage(Messages
+ .INFO_LABEL_UNNAMED_TASK))
+
+ val success = when (current) {
+ is Boolean -> runBoolean(current, conditional,
+ rule.authors)
+ is Command -> runCommand(current, conditional,
+ rule.authors, command.exit)
+ else -> TODO("error: this should not happen" +
+ "we are only supporting boolean + command")
+ }
+
+ DisplayUtils.printEntryResult(success)
+
+ if (Arara.config[AraraSpec.Execution.haltOnErrors] && !success)
+ // TODO: localize
+ throw HaltExpectedException("Command failed")
+
+ // TODO: document this key
+ val haltKey = "arara:${Arara.config[AraraSpec
+ .Execution.reference].name}:halt"
+ if (Session.contains(haltKey)) {
+ Arara.config[AraraSpec.Execution.status] =
+ Session[haltKey].toString().toInt()
+ // TODO: localize
+ throw HaltExpectedException("User requested halt")
+ }
+ }
+ }
+
+ /**
+ * Executes each directive, throwing an exception if something bad has
+ * happened.
+ *
+ * @throws AraraException Something wrong happened, to be caught in the
+ * higher levels.
+ */
+ @ExperimentalTime
+ @Throws(AraraException::class)
+ @Suppress("NestedBlockDepth")
+ fun execute() {
+ for (directive in directives) {
+ logger.info(messages.getMessage(Messages.LOG_INFO_INTERPRET_RULE,
+ directive.identifier))
+
+ Arara.config[AraraSpec.Execution.file] =
+ directive.parameters.getValue("reference") as File
+ val file = getRule(directive)
+
+ logger.info(messages.getMessage(Messages.LOG_INFO_RULE_LOCATION,
+ file.parent))
+
+ Arara.config[AraraSpec.Execution.InfoSpec.ruleId] =
+ directive.identifier
+ Arara.config[AraraSpec.Execution.InfoSpec.rulePath] =
+ file.parent
+ Arara.config[AraraSpec.Execution.DirectiveSpec.lines] =
+ directive.lineNumbers
+
+ // parse the rule identified by the directive
+ // (may throw an exception)
+ val rule = RuleUtils.parseRule(file, directive.identifier)
+ val parameters = parseArguments(rule, directive)
+ .plus(Methods.getRuleMethods())
+
+ val evaluator = Evaluator()
+
+ var available = true
+ if (InterpreterUtils.runPriorEvaluation(directive.conditional)) {
+ available = evaluator.evaluate(directive.conditional)
+ }
+
+ // if this directive is conditionally disabled, skip
+ if (!available) continue
+ // if not execute the commands associated with the directive
+ do {
+ rule.commands.forEach { command ->
+ try {
+ executeCommand(command, directive.conditional, rule, parameters)
+ } catch (_: HaltExpectedException) {
+ // if the user uses the halt rule to trigger
+ // a halt, this will be raised
+ return
+ }
+ }
+ } while (evaluator.evaluate(directive.conditional))
+ }
+ }
+
+ /**
+ * Parses the rule arguments against the provided directive.
+ *
+ * @param rule The rule object.
+ * @param directive The directive.
+ * @return A map containing all arguments resolved according to the
+ * directive parameters.
+ * @throws AraraException Something wrong happened, to be caught in the
+ * higher levels.
+ */
+ @Throws(AraraException::class)
+ private fun parseArguments(rule: Rule, directive: Directive):
+ Map<String, Any> {
+ val arguments = rule.arguments
+ val unknown = CommonUtils.getUnknownKeys(directive.parameters,
+ arguments).minus("reference")
+ if (unknown.isNotEmpty())
+ throw AraraException(CommonUtils.ruleErrorHeader +
+ messages.getMessage(
+ Messages.ERROR_INTERPRETER_UNKNOWN_KEYS,
+ "(" + unknown.joinToString(", ") + ")"))
+
+ val resolvedArguments = mutableMapOf<String, Any>()
+ resolvedArguments["reference"] = directive.parameters
+ .getValue("reference")
+
+ val context = mapOf(
+ "parameters" to directive.parameters,
+ "reference" to directive.parameters.getValue("reference")
+ ).plus(Methods.getRuleMethods())
+
+ arguments.forEach { argument ->
+ resolvedArguments[argument.identifier!!] = processArgument(argument,
+ directive.parameters.containsKey(argument.identifier!!),
+ context)
+ }
+
+ return resolvedArguments
+ }
+
+ /**
+ * Process a single argument and return the evaluated result.
+ * @param argument The argument to process.
+ * @param idInDirectiveParams Whether the argument's identifier is
+ * contained in the directive's parameters field.
+ * @param context The context for the evaluation.
+ * @return The result of the evaluation.
+ * @throws AraraException The argument could not be processed.
+ */
+ @Throws(AraraException::class)
+ @Suppress("TooGenericExceptionCaught", "ThrowsCount")
+ private fun processArgument(
+ argument: Argument,
+ idInDirectiveParams: Boolean,
+ context: Map<String, Any>
+ ): Any {
+ if (argument.isRequired && !idInDirectiveParams)
+ throw AraraException(CommonUtils.ruleErrorHeader +
+ messages.getMessage(
+ Messages.ERROR_INTERPRETER_ARGUMENT_IS_REQUIRED,
+ argument.identifier!!))
+
+ var ret = argument.default?.let {
+ try {
+ TemplateRuntime.eval(it, context)
+ } catch (exception: RuntimeException) {
+ throw AraraException(CommonUtils.ruleErrorHeader +
+ messages.getMessage(Messages
+ .ERROR_INTERPRETER_DEFAULT_VALUE_RUNTIME_ERROR),
+ exception)
+ }
+ } ?: ""
+
+ if (argument.flag != null && idInDirectiveParams) {
+ ret = try {
+ TemplateRuntime.eval(argument.flag!!, context)
+ } catch (exception: RuntimeException) {
+ throw AraraException(CommonUtils.ruleErrorHeader + messages
+ .getMessage(Messages
+ .ERROR_INTERPRETER_FLAG_RUNTIME_EXCEPTION),
+ exception)
+ }
+ }
+
+ return ret
+ }
+
+ companion object {
+ // the application messages obtained from the
+ // language controller
+ private val messages = LanguageController
+
+ // the class logger obtained from
+ // the logger factory
+ private val logger = LoggerFactory.getLogger(Interpreter::class.java)
+ }
+}