diff options
Diffstat (limited to 'support/arara/source/src/main/kotlin/org/islandoftex/arara/model')
6 files changed, 826 insertions, 0 deletions
diff --git a/support/arara/source/src/main/kotlin/org/islandoftex/arara/model/AraraException.kt b/support/arara/source/src/main/kotlin/org/islandoftex/arara/model/AraraException.kt new file mode 100644 index 0000000000..6bf2d1e617 --- /dev/null +++ b/support/arara/source/src/main/kotlin/org/islandoftex/arara/model/AraraException.kt @@ -0,0 +1,50 @@ +// SPDX-License-Identifier: BSD-3-Clause +package org.islandoftex.arara.model + +/** + * Implements the specific exception model for arara. + * + * @author Island of TeX + * @version 5.0 + * @since 4.0 + */ +class AraraException : Exception { + /** + * The underlying exception, used to hold more details + * on what really happened + */ + val exception: Exception? + + /** + * Constructor. Takes the exception message. + * @param message The exception message. + */ + constructor(message: String) : super(message) { + this.exception = null + } + + /** + * Constructor. Takes the exception message and the underlying exception. + * @param message The exception message. + * @param exception The underlying exception object. + */ + constructor(message: String, exception: Exception) : super(message) { + this.exception = exception + } + + /** + * Constructor. Takes the exception message and the underlying exception. + * @param message The exception message. + * @param throwable The underlying exception as generic throwable. + */ + constructor(message: String, throwable: Throwable) : super(message) { + this.exception = RuntimeException(throwable) + } + + /** + * Checks if there is an underlying exception defined in the current object. + * @return A boolean value indicating if the current object has an + * underlying exception. + */ + fun hasException(): Boolean = exception?.message != null +} diff --git a/support/arara/source/src/main/kotlin/org/islandoftex/arara/model/Evaluator.kt b/support/arara/source/src/main/kotlin/org/islandoftex/arara/model/Evaluator.kt new file mode 100644 index 0000000000..78b98e709a --- /dev/null +++ b/support/arara/source/src/main/kotlin/org/islandoftex/arara/model/Evaluator.kt @@ -0,0 +1,122 @@ +// SPDX-License-Identifier: BSD-3-Clause +package org.islandoftex.arara.model + +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.Conditional +import org.islandoftex.arara.utils.Methods +import org.mvel2.templates.TemplateRuntime + +/** + * Implements the evaluator model, on which a conditional can be analyzed and + * processed. + * + * @author Island of TeX + * @version 5.0 + * @since 4.0 + */ +class Evaluator { + // this attribute holds the maximum number of + // loops arara will accept; it's like + // reaching infinity + private val loops: Int = Arara.config[AraraSpec.Execution.maxLoops] + + // the counter for the current execution, it + // helps us keep track of the number of times + // this evaluation has happened, and also to + // prevent potential infinite loops + private var counter: Int = 0 + + // a flag that indicates the + // evaluation to halt regardless + // of the the result + private var halt: Boolean = false + + /** + * Check if a condition is of type if or unless and whether halt + * is set. + * @param type The type to check. + * @param haltCheck The value [halt] should have. + * @return `(type == if || type == unless) && haltCheck` + */ + private fun isIfUnlessAndHalt( + type: Conditional.ConditionalType, + haltCheck: Boolean = true + ): Boolean = + (type == Conditional.ConditionalType.IF || + type == Conditional.ConditionalType.UNLESS) && + halt == haltCheck + + /** + * Only run the evaluation of the conditional including a check whether + * the result needs to be inverted. + * @param conditional The conditional. + * @return The result of the evaluation. + */ + @Throws(AraraException::class, RuntimeException::class) + private fun evaluateCondition(conditional: Conditional): Boolean { + val result = TemplateRuntime.eval("@{ " + conditional.condition + " }", + Methods.getConditionalMethods()) + return if (result is Boolean) { + if (conditional.type == Conditional.ConditionalType.UNLESS || + conditional.type == Conditional.ConditionalType.UNTIL) + !result + else + result + } else { + throw AraraException(messages.getMessage( + Messages.ERROR_EVALUATE_NOT_BOOLEAN_VALUE)) + } + } + + /** + * Evaluate the provided conditional. + * + * @param conditional The conditional object. + * @return A boolean value indicating if the conditional holds. + * @throws AraraException Something wrong happened, to be caught in the + * higher levels. + */ + @Throws(AraraException::class) + @Suppress("TooGenericExceptionCaught") + fun evaluate(conditional: Conditional): Boolean { + // when in dry-run mode or not evaluating a + // conditional, arara always ignores conditional + // evaluations + if (conditional.type == Conditional.ConditionalType.NONE || + Arara.config[AraraSpec.Execution.dryrun] || + isIfUnlessAndHalt(conditional.type, true)) + return false + else if (isIfUnlessAndHalt(conditional.type, false)) { + halt = true + } + + // check counters and see if the execution + // has reached our concept of infinity, + // thus breaking the cycles + counter++ + return when { + conditional.type === Conditional.ConditionalType.WHILE + && counter > loops -> false + conditional.type === Conditional.ConditionalType.UNTIL + && counter >= loops -> false + else -> { + try { + evaluateCondition(conditional) + } catch (exception: RuntimeException) { + throw AraraException(messages.getMessage(Messages + .ERROR_EVALUATE_COMPILATION_FAILED), + exception) + } + } + } + } + + companion object { + // the application messages obtained from the + // language controller + private val messages = LanguageController + } +} diff --git a/support/arara/source/src/main/kotlin/org/islandoftex/arara/model/Extractor.kt b/support/arara/source/src/main/kotlin/org/islandoftex/arara/model/Extractor.kt new file mode 100644 index 0000000000..ed4d811d22 --- /dev/null +++ b/support/arara/source/src/main/kotlin/org/islandoftex/arara/model/Extractor.kt @@ -0,0 +1,50 @@ +// SPDX-License-Identifier: BSD-3-Clause +package org.islandoftex.arara.model + +import java.io.File +import java.io.IOException +import java.nio.charset.Charset +import org.islandoftex.arara.localization.LanguageController +import org.islandoftex.arara.localization.Messages +import org.islandoftex.arara.ruleset.Directive +import org.islandoftex.arara.ruleset.DirectiveUtils +import org.islandoftex.arara.utils.CommonUtils + +/** + * Extractor for directives from the provided main file. + * + * @author Island of TeX + * @version 5.0 + * @since 4.0 + */ +object Extractor { + // the application messages obtained from the + // language controller + private val messages = LanguageController + + /** + * Extracts a list of directives from the provided main file, obtained from + * the configuration controller. + * @param file The file to extract the directives from. + * @param charset The charset of the file. + * @return A list of directives. + * @throws AraraException Something wrong happened, to be caught in the + * higher levels. + */ + @Throws(AraraException::class) + fun extract(file: File, charset: Charset = Charsets.UTF_8): + List<Directive> { + try { + val content = CommonUtils.preambleContent.toMutableList() + content.addAll(file.readLines(charset)) + return DirectiveUtils.extractDirectives(content) + } catch (ioexception: IOException) { + throw AraraException( + messages.getMessage( + Messages.ERROR_EXTRACTOR_IO_ERROR + ), + ioexception + ) + } + } +} diff --git a/support/arara/source/src/main/kotlin/org/islandoftex/arara/model/FileType.kt b/support/arara/source/src/main/kotlin/org/islandoftex/arara/model/FileType.kt new file mode 100644 index 0000000000..e6ed27d51a --- /dev/null +++ b/support/arara/source/src/main/kotlin/org/islandoftex/arara/model/FileType.kt @@ -0,0 +1,110 @@ +// SPDX-License-Identifier: BSD-3-Clause +package org.islandoftex.arara.model + +import java.util.regex.PatternSyntaxException +import kotlinx.serialization.Serializable +import org.islandoftex.arara.configuration.ConfigurationUtils +import org.islandoftex.arara.localization.LanguageController +import org.islandoftex.arara.localization.Messages +import org.islandoftex.arara.utils.CommonUtils + +/** + * Implements the file type model. + * + * @author Island of TeX + * @version 5.0 + * @since 4.0 + */ +@Serializable +class FileType { + // string representing the + // file extension + var extension: String = INVALID_EXTENSION + get() = CommonUtils.removeKeywordNotNull(field) + private set + // string representing the + // file pattern to be used + // as directive lookup + var pattern: String = INVALID_PATTERN + @Throws(AraraException::class) + get() { + CommonUtils.removeKeywordNotNull(field) + if (field == INVALID_PATTERN) { + field = ConfigurationUtils.defaultFileTypePatterns[extension] + ?: throw AraraException( + LanguageController.getMessage( + Messages.ERROR_FILETYPE_UNKNOWN_EXTENSION, + extension, + CommonUtils.fileTypesList + ) + ) + } + return field + } + private set + + constructor(extension: String, pattern: String) { + this.extension = extension + this.pattern = pattern + + try { + pattern.toPattern() + } catch (e: PatternSyntaxException) { + if (!ConfigurationUtils.defaultFileTypePatterns.containsKey(extension)) + throw AraraException( + LanguageController.getMessage( + Messages.ERROR_FILETYPE_UNKNOWN_EXTENSION, + extension, + CommonUtils.fileTypesList + ) + ) + } + } + + companion object { + /** + * This constant identifies an invalid extension. As unices do not + * allow a forward and Windows does not allow a backward slash, this + * should suffice. + */ + const val INVALID_EXTENSION = "/\\" + /** + * This constant identifies an invalid pattern. This is a opening + * character class which is invalid. + */ + const val INVALID_PATTERN = "[" + } + + /** + * Provides a textual representation of the current file type object. + * @return A string containing a textual representation of the current file + * type object. + */ + override fun toString(): String { + return ".$extension" + } + + /** + * Implements the file type equals method, checking if one file type is + * equal to another. Note that only the file extension is considered. + * @param other The object to be analyzed. + * @return A boolean value indicating if those two objects are equal. + */ + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (javaClass != other?.javaClass) return false + + other as FileType + if (extension != other.extension) return false + return true + } + + /** + * Implements the file type hash code. Note that only the file extension is + * considered. + * @return An integer representing the file type hash code. + */ + override fun hashCode(): Int { + return extension.hashCode() + } +} 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) + } +} diff --git a/support/arara/source/src/main/kotlin/org/islandoftex/arara/model/Session.kt b/support/arara/source/src/main/kotlin/org/islandoftex/arara/model/Session.kt new file mode 100644 index 0000000000..c2093429e1 --- /dev/null +++ b/support/arara/source/src/main/kotlin/org/islandoftex/arara/model/Session.kt @@ -0,0 +1,119 @@ +// SPDX-License-Identifier: BSD-3-Clause +package org.islandoftex.arara.model + +import org.islandoftex.arara.localization.LanguageController +import org.islandoftex.arara.localization.Messages + +/** + * Implements the session. + * + * This class wraps a map that holds the execution session, that is, a dirty + * maneuver to exchange pretty much any data between commands and even rules. + * + * @author Island of TeX + * @version 5.0 + * @since 4.0 + */ +object Session { + // the application messages obtained from the + // language controller + private val messages = LanguageController + + // the session map which holds the execution session; + // the idea here is to provide wrappers to the map + // methods, so it could be easily manipulated + private val map = mutableMapOf<String, Any>() + + /** + * Gets the object indexed by the provided key from the session. This method + * holds the map method of the very same name. + * + * @param key The provided key. + * @return The object indexed by the provided key. + * @throws AraraException Something wrong happened, to be caught in the + * higher levels. + */ + @Throws(AraraException::class) + operator fun get(key: String): Any { + return if (contains(key)) { + map.getValue(key) + } else { + throw AraraException( + messages.getMessage( + Messages.ERROR_SESSION_OBTAIN_UNKNOWN_KEY, + key + ) + ) + } + } + + /** + * Inserts (or overwrites) the object indexed by the provided key into the + * session. This method holds the map method of the very same name. + * + * @param key The provided key. + * @param value The value to be inserted. + */ + fun put(key: String, value: Any) { + map[key] = value + } + + /** + * Removes the entry indexed by the provided key from the session. This method + * holds the map method of the same name. + * + * @param key The provided key. + * @throws AraraException Something wrong happened, to be caught in the + * higher levels. + */ + @Throws(AraraException::class) + fun remove(key: String) { + if (contains(key)) { + map.remove(key) + } else { + throw AraraException( + messages.getMessage( + Messages.ERROR_SESSION_REMOVE_UNKNOWN_KEY, + key + ) + ) + } + } + + /** + * Checks if the provided key exists in the session. + * + * @param key The provided key. + * @return A boolean value indicating if the provided key exists in the + * session. + */ + operator fun contains(key: String): Boolean = map.containsKey(key) + + /** + * Clears the session (map). This method, as usual, holds the map method of + * the same name. + */ + fun clear() = map.clear() + + /** + * Update the environment variables stored in the session. + * + * @param additionFilter Which environment variables to include. You can + * filter their names (the string parameter) but not their values. By + * default all values will be added. + * @param removalFilter Which environment variables to remove beforehand. + * By default all values will be removed. + */ + fun updateEnvironmentVariables( + additionFilter: (String) -> Boolean = { true }, + removalFilter: (String) -> Boolean = { true } + ) { + // remove all current environment variables to clean up the session + map.filterKeys { it.startsWith("environment:") } + .filterKeys(removalFilter) + .forEach { remove(it.key) } + // add all relevant new environment variables + System.getenv().filterKeys(additionFilter) + .forEach { map["environment:${it.key}"] = it.value } + } +} |