summaryrefslogtreecommitdiff
path: root/support/arara/source/src/main/kotlin/org/islandoftex/arara/CLI.kt
blob: 9de0f1bd3f8ab662b55efbe6c765de432ac095e3 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
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)
    }
}