diff options
Diffstat (limited to 'support/arara/source/src/main/kotlin/org/islandoftex/arara/CLI.kt')
-rw-r--r-- | support/arara/source/src/main/kotlin/org/islandoftex/arara/CLI.kt | 171 |
1 files changed, 171 insertions, 0 deletions
diff --git a/support/arara/source/src/main/kotlin/org/islandoftex/arara/CLI.kt b/support/arara/source/src/main/kotlin/org/islandoftex/arara/CLI.kt new file mode 100644 index 0000000000..9de0f1bd3f --- /dev/null +++ b/support/arara/source/src/main/kotlin/org/islandoftex/arara/CLI.kt @@ -0,0 +1,171 @@ +// SPDX-License-Identifier: BSD-3-Clause +package org.islandoftex.arara + +import com.github.ajalt.clikt.core.CliktCommand +import com.github.ajalt.clikt.parameters.arguments.argument +import com.github.ajalt.clikt.parameters.arguments.multiple +import com.github.ajalt.clikt.parameters.options.default +import com.github.ajalt.clikt.parameters.options.flag +import com.github.ajalt.clikt.parameters.options.option +import com.github.ajalt.clikt.parameters.types.int +import com.github.ajalt.clikt.parameters.types.path +import com.github.ajalt.clikt.parameters.types.restrictTo +import kotlin.system.exitProcess +import kotlin.time.ClockMark +import kotlin.time.ExperimentalTime +import kotlin.time.MonoClock +import kotlin.time.milliseconds +import org.islandoftex.arara.configuration.AraraSpec +import org.islandoftex.arara.filehandling.FileSearchingUtils +import org.islandoftex.arara.localization.Language +import org.islandoftex.arara.localization.LanguageController +import org.islandoftex.arara.localization.Messages +import org.islandoftex.arara.model.AraraException +import org.islandoftex.arara.model.Session +import org.islandoftex.arara.utils.CommonUtils +import org.islandoftex.arara.utils.DisplayUtils +import org.islandoftex.arara.utils.LoggingUtils + +/** + * arara's command line interface + * + * @author Island of TeX + * @version 5.0 + * @since 5.0 + */ +class CLI : CliktCommand(name = "arara", printHelpOnEmptyArgs = true) { + private val log by option("-l", "--log", + help = "Generate a log output") + .flag(default = AraraSpec.Execution.logging.default) + private val verbose by option("-v", "--verbose", + help = "Print the command output") + .flag("-s", "--silent", + default = AraraSpec.Execution.verbose.default) + private val dryrun by option("-n", "--dry-run", + help = "Go through all the motions of running a command, but " + + "with no actual calls") + .flag(default = AraraSpec.Execution.dryrun.default) + private val onlyheader by option("-H", "--header", + help = "Extract directives only in the file header") + .flag(default = AraraSpec.Execution.onlyHeader.default) + private val timeout by option("-t", "--timeout", + help = "Set the execution timeout (in milliseconds)") + .int().restrictTo(min = 1) + private val language by option("-L", "--language", + help = "Set the application language") + .default(AraraSpec.Application.defaultLanguageCode.default) + private val maxLoops by option("-m", "--max-loops", + help = "Set the maximum number of loops (> 0)") + .int().restrictTo(min = 1) + .default(AraraSpec.Execution.maxLoops.default) + private val preamble by option("-p", "--preamble", + help = "Set the file preamble based on the configuration file") + private val workingDirectory by option("-d", "--working-directory", + help = "Set the working directory for all tools") + .path(exists = true, fileOkay = false, readable = true) + .default(AraraSpec.Execution.workingDirectory.default) + + private val reference by argument("file", + help = "The file(s) to evaluate and process") + .multiple(required = true) + + /** + * Update the default configuration with the values parsed from the + * command line. + */ + @ExperimentalTime + private fun updateConfigurationFromCommandLine() { + Arara.config[AraraSpec.Execution.language] = Language(language) + LanguageController.setLocale(Arara.config[AraraSpec.Execution.language] + .locale) + + Arara.config[AraraSpec.Execution.logging] = log + Arara.config[AraraSpec.Execution.verbose] = verbose + Arara.config[AraraSpec.Execution.dryrun] = dryrun + Arara.config[AraraSpec.Execution.onlyHeader] = onlyheader + Arara.config[AraraSpec.Execution.maxLoops] = maxLoops + Arara.config[AraraSpec.Execution.workingDirectory] = workingDirectory + preamble?.let { + val preambles = Arara.config[AraraSpec.Execution.preambles] + if (preambles.containsKey(it)) { + Arara.config[AraraSpec.Execution.preamblesActive] = true + Arara.config[AraraSpec.Execution.preamblesContent] = + // will never throw (see check above) + preambles.getValue(it) + } else { + throw AraraException( + LanguageController.getMessage( + Messages.ERROR_PARSER_INVALID_PREAMBLE, it) + ) + } + } + timeout?.let { + Arara.config[AraraSpec.Execution.timeout] = true + Arara.config[AraraSpec.Execution.timeoutValue] = it.milliseconds + } + + Arara.config[AraraSpec.UserInteraction.displayTime] = true + } + + /** + * The actual main method of arara (when run in command-line mode) + */ + @ExperimentalTime + override fun run() { + // the first component to be initialized is the + // logging controller; note init() actually disables + // the logging, so early exceptions won't generate + // a lot of noise in the terminal + LoggingUtils.init() + + // arara features a stopwatch, so we can see how much time has passed + // since everything started; internally, this class makes use of + // nano time, so we might get an interesting precision here + // (although timing is not a serious business in here, it's + // just a cool addition) + val executionStart: ClockMark = MonoClock.markNow() + + // arara stores the environment variables accessible at the start + // of the execution in the session object for the user + Session.updateEnvironmentVariables() + + // logging has to be initialized only once and for all because + // context resets lead to missing output + LoggingUtils.enableLogging(log) + + try { + // TODO: this will have to change for parallelization + reference.forEach { + // TODO: do we have to reset some more file-specific config? + // especially the working directory will have to be set and + // changed + Arara.config = Arara.baseconfig.withLayer(it) + // next, update the configuration + updateConfigurationFromCommandLine() + FileSearchingUtils.discoverFile(it) + Arara.run() + // add an empty line between file executions + println() + } + + // this is the last command from arara; once the execution time is + // available, print it; note that this notification is suppressed + // when the command line parsing returns false as result (it makes + // no sense to print the execution time for a help message, I guess) + DisplayUtils.printTime(executionStart.elapsedNow().inSeconds) + } catch (ex: AraraException) { + DisplayUtils.printException(ex) + Arara.config[AraraSpec.Execution.status] = 2 + } + + // gets the application exit status; the rule here is: + // 0 : everything went just fine (note that the dry-run mode always + // makes arara exit with 0, unless it is an error in the directive + // builder itself). + // 1 : one of the tasks failed, so the execution ended abruptly. This + // means the error relies on the command line call, not with arara. + // 2 : arara just handled an exception, meaning that something bad + // just happened and might require user intervention. + exitProcess(CommonUtils.exitStatus) + } +} |