diff options
Diffstat (limited to 'support/arara/source/src/main/kotlin/org/islandoftex/arara/configuration')
4 files changed, 433 insertions, 0 deletions
diff --git a/support/arara/source/src/main/kotlin/org/islandoftex/arara/configuration/AraraSpec.kt b/support/arara/source/src/main/kotlin/org/islandoftex/arara/configuration/AraraSpec.kt new file mode 100644 index 0000000000..14100ec98b --- /dev/null +++ b/support/arara/source/src/main/kotlin/org/islandoftex/arara/configuration/AraraSpec.kt @@ -0,0 +1,90 @@ +// SPDX-License-Identifier: BSD-3-Clause +package org.islandoftex.arara.configuration + +import com.uchuhimo.konf.ConfigSpec +import java.io.File +import java.nio.file.Paths +import kotlin.time.ExperimentalTime +import kotlin.time.milliseconds +import org.islandoftex.arara.localization.Language + +/** + * Configuration hierarchy for arara + * + * @author Island of TeX + * @version 5.0 + * @since 5.0 + */ +@Suppress("MagicNumber") +object AraraSpec : ConfigSpec() { + object Application : ConfigSpec() { + val defaultLanguageCode by optional("en") + val version by optional(AraraSpec::class.java.`package`.implementationVersion + ?: "DEVELOPMENT BUILD") + val namePattern by optional("arara:\\s") + val width by optional(65) + } + + object Execution : ConfigSpec() { + val maxLoops by optional(10) + val timeout by optional(false) + @ExperimentalTime + val timeoutValue by optional(0.milliseconds) + val haltOnErrors by optional(true) + + val databaseName by optional("arara") + val logName by optional("arara") + + val verbose by optional(false) + val language by optional(Language(Application.defaultLanguageCode.default)) + val logging by optional(false) + val dryrun by optional(false) + val status by optional(0) + val fileTypes by optional(ConfigurationUtils.defaultFileTypes) + val rulePaths by optional(setOf( + ConfigurationUtils.applicationPath.resolve("rules") + .toString() + )) + val preambles by optional(mapOf<String, String>()) + val preamblesActive by optional(false) + val preamblesContent by optional("") + + val workingDirectory by optional(Paths.get("")) + val configurationName by optional("[none]") + val onlyHeader by optional(false) + + // TODO: these are runtime values, they should be properly + // initialized and tested (maybe move them into their own + // Spec or session) + val reference by optional(File("/tmp/")) + val file by optional(File("/tmp/")) + + object InfoSpec : ConfigSpec() { + val ruleId by optional<String?>(null) + val rulePath by optional<String?>(null) + } + + object DirectiveSpec : ConfigSpec() { + val lines by optional(listOf<Int>()) + } + + val filePattern by optional("") + } + + object Directive : ConfigSpec() { + val linebreakPattern by optional("^\\s*-->\\s(.*)$") + + private const val directivestart = """^\s*(\w+)\s*(:\s*(\{.*\})\s*)?""" + private const val pattern = """(\s+(if|while|until|unless)\s+(\S.*))?$""" + val directivePattern by optional(directivestart + pattern) + } + + object UserInteraction : ConfigSpec() { + val lookAndFeel by optional("none") + val displayTime by optional(false) + val displayLine by optional(true) + val displayResult by optional(false) + val displayRolling by optional(false) + val displayException by optional(false) + } +} diff --git a/support/arara/source/src/main/kotlin/org/islandoftex/arara/configuration/Configuration.kt b/support/arara/source/src/main/kotlin/org/islandoftex/arara/configuration/Configuration.kt new file mode 100644 index 0000000000..06abeeb6db --- /dev/null +++ b/support/arara/source/src/main/kotlin/org/islandoftex/arara/configuration/Configuration.kt @@ -0,0 +1,101 @@ +// SPDX-License-Identifier: BSD-3-Clause +package org.islandoftex.arara.configuration + +import kotlin.time.ExperimentalTime +import org.islandoftex.arara.Arara +import org.islandoftex.arara.filehandling.FileHandlingUtils +import org.islandoftex.arara.localization.Language +import org.islandoftex.arara.localization.LanguageController +import org.islandoftex.arara.localization.Messages +import org.islandoftex.arara.model.AraraException + +/** + * Implements the configuration model, which holds the default settings and can + * load the configuration file. The idea here is to provide a map that holds + * all configuration settings used by model and utilitary classes throughout + * the execution. This controller is implemented as a singleton. + * + * @author Island of TeX + * @version 5.0 + * @since 4.0 + */ +object Configuration { + // the application messages obtained from the + // language controller + private val messages = LanguageController + + /** + * Loads the application configuration. + * + * @throws AraraException Something wrong happened, to be caught in the + * higher levels. + */ + @ExperimentalTime + @Throws(AraraException::class) + fun load() { + // initialize both file type and language models, + // since we can track errors from there instead + // of relying on a check on this level + + // get the configuration file, if any + val file = ConfigurationUtils.configFile + if (file != null) { + // set the configuration file name for + // logging purposes + Arara.config[AraraSpec.Execution.configurationName] = + FileHandlingUtils.getCanonicalPath(file) + + // then validate it and update the + // configuration accordingly + val resource = ConfigurationUtils.loadLocalConfiguration(file) + update(resource) + } + + // just to be sure, update the + // current locale in order to + // display localized messages + val locale = Arara.config[AraraSpec.Execution.language].locale + LanguageController.setLocale(locale) + } + + /** + * Update the configuration based on the provided map. + * + * @param resource Map containing the new configuration settings. + * @throws AraraException Something wrong happened, to be caught in the + * higher levels. + */ + @Throws(AraraException::class) + private fun update(resource: LocalConfiguration) { + if (resource.paths.isNotEmpty()) + Arara.config[AraraSpec.Execution.rulePaths] = + ConfigurationUtils.normalizePaths(resource.paths) + + if (resource.filetypes.isNotEmpty()) { + Arara.config[AraraSpec.Execution.fileTypes] = ConfigurationUtils.normalizeFileTypes(resource.filetypes) + } + + Arara.config[AraraSpec.Execution.verbose] = resource.isVerbose + Arara.config[AraraSpec.Execution.logging] = resource.isLogging + Arara.config[AraraSpec.Execution.onlyHeader] = resource.isHeader + Arara.config[AraraSpec.Execution.language] = + Language(resource.language) + Arara.config[AraraSpec.UserInteraction.lookAndFeel] = resource.laf + + Arara.config[AraraSpec.Execution.databaseName] = + ConfigurationUtils.cleanFileName(resource.dbname) + Arara.config[AraraSpec.Execution.logName] = + ConfigurationUtils.cleanFileName(resource.logname) + + val loops = resource.loops + if (loops <= 0) { + throw AraraException(messages.getMessage(Messages + .ERROR_CONFIGURATION_LOOPS_INVALID_RANGE)) + } else { + Arara.config[AraraSpec.Execution.maxLoops] = loops + } + + if (resource.preambles.isNotEmpty()) + Arara.config[AraraSpec.Execution.preambles] = resource.preambles + } +} diff --git a/support/arara/source/src/main/kotlin/org/islandoftex/arara/configuration/ConfigurationUtils.kt b/support/arara/source/src/main/kotlin/org/islandoftex/arara/configuration/ConfigurationUtils.kt new file mode 100644 index 0000000000..31a92c3009 --- /dev/null +++ b/support/arara/source/src/main/kotlin/org/islandoftex/arara/configuration/ConfigurationUtils.kt @@ -0,0 +1,157 @@ +// SPDX-License-Identifier: BSD-3-Clause +package org.islandoftex.arara.configuration + +import com.charleskorn.kaml.Yaml +import java.io.File +import java.io.UnsupportedEncodingException +import java.net.URLDecoder +import java.nio.file.Path +import java.nio.file.Paths +import org.islandoftex.arara.Arara +import org.islandoftex.arara.localization.LanguageController +import org.islandoftex.arara.localization.Messages +import org.islandoftex.arara.model.AraraException +import org.islandoftex.arara.model.FileType +import org.islandoftex.arara.utils.CommonUtils + +/** + * Implements configuration utilitary methods. + * + * @author Island of TeX + * @version 5.0 + * @since 4.0 + */ +object ConfigurationUtils { + // the application messages obtained from the + // language controller + private val messages = LanguageController + + /** + * This map contains all file types that arara accepts + * and their corresponding search patterns (for comments). + */ + val defaultFileTypePatterns = mapOf( + "tex" to "^\\s*%\\s+", + "dtx" to "^\\s*%\\s+", + "ltx" to "^\\s*%\\s+", + "drv" to "^\\s*%\\s+", + "ins" to "^\\s*%\\s+" + ) + + /** + * Set of default file types provided by arara. + * Initialization may throw AraraException if file types are wrong + */ + val defaultFileTypes: Set<FileType> by lazy { + defaultFileTypePatterns + .map { (extension, pattern) -> FileType(extension, pattern) } + .toSet() + } + + /** + * The configuration file in use. + * + * Look for configuration files in the user's working directory first + * if no configuration files are found in the user's working directory, + * try to look up in a global directory, that is, the user home. + */ + val configFile: File? + get() { + val names = listOf(".araraconfig.yaml", + "araraconfig.yaml", ".arararc.yaml", "arararc.yaml") + Arara.config[AraraSpec.Execution.workingDirectory] + .let { workingDir -> + val first = names + .map { workingDir.resolve(it).toFile() } + .firstOrNull { it.exists() } + if (first != null) + return first + } + CommonUtils.getSystemPropertyOrNull("user.home")?.let { userHome -> + return names.map { File(userHome).resolve(it) } + .firstOrNull { it.exists() } + } + return null + } + + /** + * The canonical absolute application path. + * + * @throws AraraException Something wrong happened, to be caught in the + * higher levels. + */ + val applicationPath: Path + @Throws(AraraException::class) + get() { + try { + var path = Arara::class.java.protectionDomain.codeSource + .location.path + path = URLDecoder.decode(path, "UTF-8") + return Paths.get(path).parent.toAbsolutePath() + } catch (exception: UnsupportedEncodingException) { + throw AraraException( + messages.getMessage( + Messages.ERROR_GETAPPLICATIONPATH_ENCODING_EXCEPTION + ), + exception + ) + } + } + + /** + * Validates the configuration file. + * + * @param file The configuration file. + * @return The configuration file as a resource. + * @throws AraraException Something wrong happened, to be caught in the + * higher levels. + */ + @Throws(AraraException::class) + fun loadLocalConfiguration(file: File): LocalConfiguration { + return file.runCatching { + val text = readText() + if (!text.startsWith("!config")) + throw Exception("Configuration should start with !config") + Yaml.default.parse(LocalConfiguration.serializer(), + text) + }.getOrElse { + throw AraraException(messages.getMessage( + Messages.ERROR_CONFIGURATION_GENERIC_ERROR), it) + } + } + + /** + * Normalize a list of rule paths, removing all duplicates. + * + * @param paths The list of rule paths. + * @return A list of normalized paths, without duplicates. + * @throws AraraException Something wrong happened, to be caught in the + * higher levels. + */ + @Throws(AraraException::class) + fun normalizePaths(paths: Iterable<String>): Set<String> = + paths.union(AraraSpec.Execution.rulePaths.default) + + /** + * Normalize a list of file types, removing all duplicates. + * + * @param types The list of file types. + * @return A list of normalized file types, without duplicates. + * @throws AraraException Something wrong happened, to be caught in the + * higher levels. + */ + @Throws(AraraException::class) + fun normalizeFileTypes(types: Iterable<FileType>): Set<FileType> = + types.union(defaultFileTypes) + + /** + * Cleans the file name to avoid invalid entries. + * + * @param name The file name. + * @return A cleaned file name. + */ + fun cleanFileName(name: String): String { + val result = File(name).name.trim() + return if (result.isEmpty()) "arara" else result.trim() + } +} diff --git a/support/arara/source/src/main/kotlin/org/islandoftex/arara/configuration/LocalConfiguration.kt b/support/arara/source/src/main/kotlin/org/islandoftex/arara/configuration/LocalConfiguration.kt new file mode 100644 index 0000000000..959f2a9ab8 --- /dev/null +++ b/support/arara/source/src/main/kotlin/org/islandoftex/arara/configuration/LocalConfiguration.kt @@ -0,0 +1,85 @@ +// SPDX-License-Identifier: BSD-3-Clause +package org.islandoftex.arara.configuration + +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable +import org.islandoftex.arara.Arara +import org.islandoftex.arara.model.FileType +import org.islandoftex.arara.utils.CommonUtils +import org.mvel2.templates.TemplateRuntime + +/** + * A local configuration which resembles configuration files in the working + * directory. + * + * @author Island of TeX + * @version 5.0 + * @since 4.0 + */ +@Serializable +class LocalConfiguration { + // rule paths + var paths: List<String> = listOf() + get() { + val user = mapOf( + "home" to (CommonUtils.getSystemPropertyOrNull("user.home") + ?: ""), + "name" to (CommonUtils.getSystemPropertyOrNull("user.name") + ?: "")) + val application = mapOf( + "workingDirectory" to Arara.config[AraraSpec.Execution.workingDirectory].toAbsolutePath().toString() + ) + + return field.map { input -> + var path = CommonUtils.removeKeywordNotNull(input) + try { + path = TemplateRuntime.eval(path, mapOf( + "user" to user, "application" to application + )) as String + } catch (_: RuntimeException) { + // do nothing, gracefully fallback to + // the default, unparsed path + } + path + } + } + + // file types + var filetypes: List<FileType> = listOf() + + // the application language + // default to English + var language: String = Arara.config[AraraSpec.Application.defaultLanguageCode] + get() = CommonUtils.removeKeywordNotNull(field) + + // maximum number of loops + var loops: Int = Arara.config[AraraSpec.Execution.maxLoops] + + // verbose flag + @SerialName("verbose") + var isVerbose: Boolean = Arara.config[AraraSpec.Execution.verbose] + + // logging flag + @SerialName("logging") + var isLogging: Boolean = Arara.config[AraraSpec.Execution.logging] + + // header flag + @SerialName("header") + var isHeader: Boolean = Arara.config[AraraSpec.Execution.onlyHeader] + + // database name + var dbname: String = Arara.config[AraraSpec.Execution.databaseName] + get() = CommonUtils.removeKeywordNotNull(field) + + // log name + var logname: String = Arara.config[AraraSpec.Execution.logName] + get() = CommonUtils.removeKeywordNotNull(field) + + // map of preambles + var preambles: Map<String, String> = Arara.config[AraraSpec.Execution.preambles] + + // look and feel + // default to none + var laf: String = Arara.config[AraraSpec.UserInteraction.lookAndFeel] + get() = CommonUtils.removeKeywordNotNull(field) +} |