summaryrefslogtreecommitdiff
path: root/support/arara/source/src/main/kotlin/org/islandoftex/arara/utils/InterpreterUtils.kt
blob: 00313a7714871ac212f7f8e4d1b78c2f65858bbc (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
// SPDX-License-Identifier: BSD-3-Clause
package org.islandoftex.arara.utils

import java.io.ByteArrayOutputStream
import java.io.File
import java.io.IOException
import java.io.OutputStream
import java.util.concurrent.TimeUnit
import java.util.concurrent.TimeoutException
import kotlin.time.Duration
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.model.AraraException
import org.islandoftex.arara.ruleset.Command
import org.islandoftex.arara.ruleset.Conditional
import org.slf4j.LoggerFactory
import org.zeroturnaround.exec.InvalidExitValueException
import org.zeroturnaround.exec.ProcessExecutor
import org.zeroturnaround.exec.listener.ShutdownHookProcessDestroyer

/**
 * Implements interpreter utilitary methods.
 *
 * @author Island of TeX
 * @version 5.0
 * @since 4.0
 */
object InterpreterUtils {
    // the application messages obtained from the
    // language controller
    private val messages = LanguageController

    // get the logger context from a factory
    private val logger = LoggerFactory.getLogger(InterpreterUtils::class.java)

    /**
     * Checks if the current conditional has a prior evaluation.
     *
     * @param conditional The current conditional object.
     * @return A boolean value indicating if the current conditional has a prior
     * evaluation.
     */
    fun runPriorEvaluation(conditional: Conditional): Boolean {
        return if (Arara.config[AraraSpec.Execution.dryrun]) {
            false
        } else {
            when (conditional.type) {
                Conditional.ConditionalType.IF,
                Conditional.ConditionalType.WHILE,
                Conditional.ConditionalType.UNLESS -> true
                else -> false
            }
        }
    }

    @ExperimentalTime
    private fun getProcessExecutorForCommand(
        command: Command,
        buffer: OutputStream
    ):
            ProcessExecutor {
        val timeOutValue = Arara.config[AraraSpec.Execution.timeoutValue]
        var executor = ProcessExecutor().command((command).elements)
                .directory(command.workingDirectory.absoluteFile)
                .addDestroyer(ShutdownHookProcessDestroyer())
        if (Arara.config[AraraSpec.Execution.timeout]) {
            if (timeOutValue == Duration.ZERO) {
                throw AraraException(messages.getMessage(Messages
                        .ERROR_RUN_TIMEOUT_INVALID_RANGE))
            }
            executor = executor.timeout(timeOutValue.toLongNanoseconds(),
                    TimeUnit.NANOSECONDS)
        }
        val tee = if (Arara.config[AraraSpec.Execution.verbose]) {
            executor = executor.redirectInput(System.`in`)
            TeeOutputStream(System.out, buffer)
        } else {
            TeeOutputStream(buffer)
        }
        executor = executor.redirectOutput(tee).redirectError(tee)
        return executor
    }

    /**
     * Runs the command in the underlying operating system.
     *
     * @param command An object representing the command.
     * @return An integer value representing the exit code.
     * @throws AraraException Something wrong happened, to be caught in the
     * higher levels.
     */
    @ExperimentalTime
    @Throws(AraraException::class)
    fun run(command: Command): Int {
        val buffer = ByteArrayOutputStream()
        val executor = getProcessExecutorForCommand(command, buffer)
        return executor.runCatching {
            val exit = execute().exitValue
            logger.info(DisplayUtils.displayOutputSeparator(
                    messages.getMessage(Messages.LOG_INFO_BEGIN_BUFFER)))
            logger.info(buffer.toString())
            logger.info(DisplayUtils.displayOutputSeparator(
                    messages.getMessage(Messages.LOG_INFO_END_BUFFER)))
            exit
        }.getOrElse {
            throw AraraException(messages.getMessage(
                    when (it) {
                        is IOException -> Messages.ERROR_RUN_IO_EXCEPTION
                        is InterruptedException ->
                            Messages.ERROR_RUN_INTERRUPTED_EXCEPTION
                        is InvalidExitValueException ->
                            Messages.ERROR_RUN_INVALID_EXIT_VALUE_EXCEPTION
                        is TimeoutException ->
                            Messages.ERROR_RUN_TIMEOUT_EXCEPTION
                        else -> Messages.ERROR_RUN_GENERIC_EXCEPTION
                    }), it)
        }
    }

    /**
     * Builds the rule path based on the rule name and returns the corresponding
     * file location.
     *
     * @param name The rule name.
     * @return The rule file.
     * @throws AraraException Something wrong happened, to be caught in the
     * higher levels.
     */
    @Throws(AraraException::class)
    fun buildRulePath(name: String): File? {
        Arara.config[AraraSpec.Execution.rulePaths].forEach { path ->
            val location = File(construct(path, name))
            if (location.exists())
                return location
        }
        return null
    }

    /**
     * Constructs the path given the current path and the rule name.
     *
     * @param path The current path.
     * @param name The rule name.
     * @return The constructed path.
     * @throws AraraException Something wrong happened, to be caught in the
     * higher levels.
     */
    @Throws(AraraException::class)
    fun construct(path: String, name: String): String {
        val fileName = "$name.yaml"
        val location = File(path)
        return if (location.isAbsolute) {
            location.resolve(fileName).toString()
        } else {
            Arara.config[AraraSpec.Execution.workingDirectory]
                    // first resolve the path (rule path) against the working
                    // directory, then the rule name we want to resolve
                    .resolve(path).resolve(fileName).toAbsolutePath().toString()
        }
    }
}