summaryrefslogtreecommitdiff
path: root/support/arara/source/src/main/kotlin/org/islandoftex/arara/configuration
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/configuration
parent19d25b8009801aa98ea2f46b45c37c257f990491 (diff)
CTAN sync 202003050300
Diffstat (limited to 'support/arara/source/src/main/kotlin/org/islandoftex/arara/configuration')
-rw-r--r--support/arara/source/src/main/kotlin/org/islandoftex/arara/configuration/AraraSpec.kt90
-rw-r--r--support/arara/source/src/main/kotlin/org/islandoftex/arara/configuration/Configuration.kt101
-rw-r--r--support/arara/source/src/main/kotlin/org/islandoftex/arara/configuration/ConfigurationUtils.kt157
-rw-r--r--support/arara/source/src/main/kotlin/org/islandoftex/arara/configuration/LocalConfiguration.kt85
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)
+}