diff options
Diffstat (limited to 'support/arara/source/src/main/kotlin/org/islandoftex/arara/utils')
9 files changed, 0 insertions, 1697 deletions
diff --git a/support/arara/source/src/main/kotlin/org/islandoftex/arara/utils/ClassLoadingUtils.kt b/support/arara/source/src/main/kotlin/org/islandoftex/arara/utils/ClassLoadingUtils.kt deleted file mode 100644 index 05a1031a8a..0000000000 --- a/support/arara/source/src/main/kotlin/org/islandoftex/arara/utils/ClassLoadingUtils.kt +++ /dev/null @@ -1,121 +0,0 @@ -// SPDX-License-Identifier: BSD-3-Clause -package org.islandoftex.arara.utils - -import java.io.File -import java.lang.reflect.InvocationTargetException -import java.net.MalformedURLException -import java.net.URLClassLoader - -/** - * Implements utilitary methods for classloading and object instantiation. - * - * @author Island of TeX - * @version 5.0 - * @since 4.0 - */ -object ClassLoadingUtils { - /** - * Indicator of success or failure of class loading. - */ - enum class ClassLoadingStatus { - SUCCESS, - FILE_NOT_FOUND, - MALFORMED_URL, - CLASS_NOT_FOUND, - ILLEGAL_ACCESS, - INSTANTIATION_EXCEPTION - } - - /** - * Loads a class from the provided file, potentially a Java archive. - * @param file File containing the Java bytecode (namely, a JAR). - * @param name The canonical name of the class. - * @return A pair representing the status and the class. - */ - fun loadClass(file: File, name: String): - Pair<ClassLoadingStatus, Class<*>> { - // status and class to be returned, - // it defaults to an object class - var value: Class<*> = Any::class.java - - // if file does not exist, nothing - // can be done, status is changed - val status = if (!file.exists()) { - ClassLoadingStatus.FILE_NOT_FOUND - } else { - // classloading involves defining - // a classloader and fetching the - // desired class from it, based on - // the provided file archive - try { - // creates a new classloader with - // the provided file (potentially - // a JAR file) - val classloader = URLClassLoader(arrayOf(file.toURI().toURL()), - ClassLoadingUtils::class.java.classLoader) - - // fetches the class from the - // instantiated classloader - value = Class.forName(name, true, classloader) - ClassLoadingStatus.SUCCESS - } catch (_: MalformedURLException) { - ClassLoadingStatus.MALFORMED_URL - } catch (_: ClassNotFoundException) { - ClassLoadingStatus.CLASS_NOT_FOUND - } - } - - // return a new pair based on the - // current status and class holder - return status to value - } - - /** - * Loads a class from the provided file, instantiating it. - * @param file File containing the Java bytecode (namely, a JAR). - * @param name The canonical name of the class. - * @return A pair representing the status and the class object. - */ - fun loadObject(file: File, name: String): Pair<ClassLoadingStatus, Any> { - // load the corresponding class - // based on the qualified name - val pair = loadClass(file, name) - - // status and object to be returned, - // it defaults to an object - var status = pair.first - var value = Any() - - // checks if the class actually - // exists, otherwise simply - // ignore instantiation - if (status == ClassLoadingStatus.SUCCESS) { - // object instantiation relies - // on the default constructor - // (without arguments), class - // must implement it - - // OBS: constructors with arguments - // must be invoked through reflection - try { - // get the class reference from - // the pair and instantiate it - // by invoking the default - // constructor (without arguments) - value = pair.second.getDeclaredConstructor().newInstance() - } catch (_: IllegalAccessException) { - status = ClassLoadingStatus.ILLEGAL_ACCESS - } catch (_: InstantiationException) { - // the user wanted to instantiate an abstract class - status = ClassLoadingStatus.INSTANTIATION_EXCEPTION - } catch (_: InvocationTargetException) { - // the underlying constructor caused an exception - status = ClassLoadingStatus.INSTANTIATION_EXCEPTION - } - } - - // return a new pair based on the - // current status and object holder - return status to value - } -} diff --git a/support/arara/source/src/main/kotlin/org/islandoftex/arara/utils/CommonUtils.kt b/support/arara/source/src/main/kotlin/org/islandoftex/arara/utils/CommonUtils.kt deleted file mode 100644 index 6887cd4e84..0000000000 --- a/support/arara/source/src/main/kotlin/org/islandoftex/arara/utils/CommonUtils.kt +++ /dev/null @@ -1,422 +0,0 @@ -// SPDX-License-Identifier: BSD-3-Clause -package org.islandoftex.arara.utils - -import java.io.File -import java.io.IOException -import java.util.MissingFormatArgumentException -import java.util.regex.Pattern -import kotlin.math.ln -import kotlin.math.pow -import org.islandoftex.arara.Arara -import org.islandoftex.arara.configuration.AraraSpec -import org.islandoftex.arara.filehandling.FileHandlingUtils -import org.islandoftex.arara.localization.LanguageController -import org.islandoftex.arara.localization.Messages -import org.islandoftex.arara.model.AraraException -import org.islandoftex.arara.ruleset.Argument - -/** - * Implements common utilitary methods. - * - * @author Island of TeX - * @version 5.0 - * @since 4.0 - */ -object CommonUtils { - // the application messages obtained from the - // language controller - private val messages = LanguageController - - /** - * Gets the list of file types as string, in order. - * - * @return A string representation of the list of file types, in order. - */ - val fileTypesList: String - get() = "[ " + Arara.config[AraraSpec.Execution.fileTypes] - .joinToString(" | ") + " ]" - - /** - * Gets the rule error header, containing the identifier and the path, if - * any. - * - * @return A string representation of the rule error header, containing the - * identifier and the path, if any. - */ - val ruleErrorHeader: String - get() { - return if (Arara.config[AraraSpec.Execution.InfoSpec.ruleId] != null && - Arara.config[AraraSpec.Execution.InfoSpec.rulePath] != null) { - val id = Arara.config[AraraSpec.Execution.InfoSpec.ruleId]!! - val path = Arara.config[AraraSpec.Execution.InfoSpec.rulePath]!! - messages.getMessage( - Messages.ERROR_RULE_IDENTIFIER_AND_PATH, - id, - path - ) + " " - } else { - "" - } - } - - /** - * Gets a list of all rule paths. - * - * @return A list of all rule paths. - * @throws AraraException Something wrong happened, to be caught in the - * higher levels. - */ - val allRulePaths: List<String> - @Throws(AraraException::class) - get() = Arara.config[AraraSpec.Execution.rulePaths].map { - val location = File(InterpreterUtils.construct(it, "quack")) - FileHandlingUtils.getParentCanonicalPath(location) - } - - /** - * Returns the exit status of the application. - * - * @return An integer representing the exit status of the application. - */ - val exitStatus: Int - get() = Arara.config[AraraSpec.Execution.status] - - /** - * Gets the preamble content, converting a single string into a list of - * strings, based on new lines. - * - * @return A list of strings representing the preamble content. - */ - val preambleContent: List<String> - get() = if (Arara.config[AraraSpec.Execution.preamblesActive]) { - Arara.config[AraraSpec.Execution.preamblesContent] - .split("\n") - .dropLastWhile { it.isEmpty() } - .toList() - } else { - listOf() - } - - /** - * Checks if the input string is equal to a valid boolean value. - * - * @param value The input string. - * @return A boolean value represented by the provided string. - * @throws AraraException Something wrong happened, to be caught in the - * higher levels. - */ - @Throws(AraraException::class) - fun checkBoolean(value: String): Boolean { - val yes = listOf("yes", "true", "1", "on") - val no = listOf("no", "false", "0", "off") - return if (!yes.union(no).contains(value.toLowerCase())) { - throw AraraException( - messages.getMessage( - Messages.ERROR_CHECKBOOLEAN_NOT_VALID_BOOLEAN, - value - ) - ) - } else { - yes.contains(value.toLowerCase()) - } - } - - /** - * Removes the keyword from the beginning of the provided string. - * - * @param line A string to be analyzed. - * @return The provided string without the keyword. - */ - fun removeKeyword(line: String?): String? { - return if (line == null) null - else removeKeywordNotNull(line) - } - - /** - * Removes the keyword from the beginning of the provided string. - * - * @param line A string to be analyzed. - * @return The provided string without the keyword. - */ - fun removeKeywordNotNull(line: String): String { - var tempLine = line - val pattern = "^(\\s)*<arara>\\s".toPattern() - val matcher = pattern.matcher(tempLine) - if (matcher.find()) { - tempLine = tempLine.substring(matcher.end()) - } - return tempLine.trim() - } - - /** - * Flattens a potential list of lists into a list of objects. - * - * @param list The list to be flattened. - * @return The flattened list. - */ - // TODO: check nullity - fun flatten(list: List<*>): List<Any> { - val result = mutableListOf<Any>() - list.forEach { item -> - if (item is List<*>) - result.addAll(flatten(item)) - else - result.add(item as Any) - } - return result - } - - /** - * Gets a set of strings containing unknown keys from a map and a list. It - * is a set difference from the keys in the map and the entries in the list. - * - * @param parameters The map of parameters. - * @param arguments The list of arguments. - * @return A set of strings representing unknown keys from a map and a list. - */ - fun getUnknownKeys( - parameters: Map<String, Any>, - arguments: List<Argument> - ): Set<String> { - val found = parameters.keys - val expected = arguments.mapNotNull { it.identifier } - return found.subtract(expected) - } - - /** - * Gets a human readable representation of a size. - * - * @param size The byte size to be converted. - * @return A string representation of the size. - */ - @Suppress("MagicNumber") - fun byteSizeToString(size: Long): String { - val language = Arara.config[AraraSpec.Execution.language] - val conversionFactor = 1000.0 - return if (size < conversionFactor) "$size B" - else - (ln(size.toDouble()) / ln(conversionFactor)).toInt().let { exp -> - "%.1f %sB".format(language.locale, - size / conversionFactor.pow(exp.toDouble()), - "kMGTPE"[exp - 1]) - } - } - - /** - * Generates a string based on a list of objects, separating each one of - * them by one space. - * - * @param objects A list of objects. - * @return A string based on the list of objects, separating each one of - * them by one space. Empty values are not considered. - */ - fun generateString(vararg objects: Any): String = objects - .map { it.toString() }.filter { it.isNotEmpty() } - .joinToString(" ") - - /** - * Checks if the file based on the provided extension contains the provided - * regex. - * - * @param extension The file extension. - * @param regex The regex. - * @return A boolean value indicating if the file contains the provided - * regex. - * @throws AraraException Something wrong happened, to be caught in the - * higher levels. - */ - @Throws(AraraException::class) - fun checkRegex(extension: String, regex: String): Boolean { - val file = File(FileHandlingUtils.getPath(extension)) - return checkRegex(file, regex) - } - - /** - * Checks if the file contains the provided regex. - * - * As we use [File.readText] this should not be called on files > 2GB. - * - * @param file The file. - * @param regex The regex. - * @return A boolean value indicating if the file contains the provided - * regex. - * @throws AraraException Something wrong happened, to be caught in the - * higher levels. - */ - @Throws(AraraException::class) - fun checkRegex(file: File, regex: String): Boolean { - try { - val text = file.readText() - val pattern = Pattern.compile(regex) - val matcher = pattern.matcher(text) - return matcher.find() - } catch (exception: IOException) { - throw AraraException( - messages.getMessage( - Messages.ERROR_CHECKREGEX_IO_EXCEPTION, - file.name - ), - exception - ) - } - } - - /** - * Replicates a string pattern based on a list of objects, generating a list - * as result. - * - * @param pattern The string pattern. - * @param values The list of objects to be merged with the pattern. - * @return A list containing the string pattern replicated to each object - * from the list. - * @throws AraraException Something wrong happened, to be caught in the - * higher levels. - */ - @Throws(AraraException::class) - fun replicateList( - pattern: String, - values: List<Any> - ): List<Any> { - return try { - values.map { String.format(pattern, it) } - } catch (exception: MissingFormatArgumentException) { - throw AraraException( - messages.getMessage( - Messages.ERROR_REPLICATELIST_MISSING_FORMAT_ARGUMENTS_EXCEPTION - ), - exception - ) - } - } - - /** - * Checks if the provided operating system string holds according to the - * underlying operating system. - * - * Supported operating systems: - * - * * Windows - * * Linux - * * Mac OS X - * * Unix (Linux || Mac OS) - * * Cygwin - * - * @param value A string representing an operating system. - * @return A boolean value indicating if the provided string refers to the - * underlying operating system. - * @throws AraraException Something wrong happened, to be caught in the - * higher levels. - */ - @Throws(AraraException::class) - fun checkOS(value: String): Boolean { - fun checkOSProperty(key: String): Boolean = - getSystemPropertyOrNull("os.name") - ?.toLowerCase()?.startsWith(key.toLowerCase()) ?: false - - val values = mutableMapOf<String, Boolean>() - values["windows"] = checkOSProperty("Windows") - values["linux"] = checkOSProperty("Linux") - values["mac"] = checkOSProperty("Mac OS X") - values["unix"] = checkOSProperty("Mac OS X") || - checkOSProperty("Linux") - values["cygwin"] = SystemCallUtils["cygwin"] as Boolean - if (!values.containsKey(value.toLowerCase())) { - throw AraraException( - messages.getMessage( - Messages.ERROR_CHECKOS_INVALID_OPERATING_SYSTEM, - value - ) - ) - } - // will never throw, see check above - return values.getValue(value.toLowerCase()) - } - - /** - * Gets the system property according to the provided key, or resort to the - * fallback value if an exception is thrown or if the key is invalid. - * - * @param key The system property key. - * @param fallback The fallback value. - * @return A string containing the system property value or the fallback. - */ - fun getSystemProperty(key: String, fallback: String): String = - System.getProperties().runCatching { - getOrDefault(key, fallback).toString().takeIf { it != "" } - }.getOrNull() ?: fallback - - /** - * Access a system property. - * - * @param key The key of the property. - * @return The value of the system property or null if there is an - * exception. - */ - fun getSystemPropertyOrNull(key: String): String? = - System.getProperties().runCatching { getValue(key).toString() } - .getOrNull() - - /** - * Generates a list of filenames from the provided command based on a list - * of extensions for each underlying operating system. - * - * @param command A string representing the command. - * @return A list of filenames. - */ - private fun appendExtensions(command: String): List<String> { - // list of extensions, specific for - // each operating system (in fact, it - // is more Windows specific) - val extensions = if (checkOS("windows")) { - // the application is running on - // Windows, so let's look for the - // following extensions in order - - // this list is actually a sublist from - // the original Windows PATHEXT environment - // variable which holds the list of executable - // extensions that Windows supports - listOf(".com", ".exe", ".bat", ".cmd") - } else { - // no Windows, so the default - // extension will be just an - // empty string - listOf("") - } - - // return the resulting list holding the - // filenames generated from the - // provided command - return extensions.map { "$command$it" } - } - - /** - * Checks if the provided command name is reachable from the system path. - * - * @param command A string representing the command. - * @return A logic value. - */ - fun isOnPath(command: String): Boolean { - // first and foremost, let's build the list - // of filenames based on the underlying - // operating system - val filenames = appendExtensions(command) - return kotlin.runCatching { - // break the path into several parts - // based on the path separator symbol - System.getenv("PATH").split(File.pathSeparator) - .asSequence() - .mapNotNull { File(it).listFiles() } - // if the search does not return an empty - // list, one of the filenames got a match, - // and the command is available somewhere - // in the system path - .firstOrNull { - it.any { file -> - filenames.contains(file.name) && !file.isDirectory - } - }?.let { true } - }.getOrNull() ?: false - // otherwise (and in case of an exception) it is not in the path - } -} diff --git a/support/arara/source/src/main/kotlin/org/islandoftex/arara/utils/DisplayUtils.kt b/support/arara/source/src/main/kotlin/org/islandoftex/arara/utils/DisplayUtils.kt deleted file mode 100644 index 213a9a21c3..0000000000 --- a/support/arara/source/src/main/kotlin/org/islandoftex/arara/utils/DisplayUtils.kt +++ /dev/null @@ -1,415 +0,0 @@ -// SPDX-License-Identifier: BSD-3-Clause -package org.islandoftex.arara.utils - -import org.islandoftex.arara.Arara -import org.islandoftex.arara.configuration.AraraSpec -import org.islandoftex.arara.configuration.ConfigurationUtils -import org.islandoftex.arara.filehandling.FileHandlingUtils -import org.islandoftex.arara.localization.LanguageController -import org.islandoftex.arara.localization.Messages -import org.islandoftex.arara.model.AraraException -import org.islandoftex.arara.ruleset.Conditional -import org.slf4j.LoggerFactory - -/** - * Implements display utilitary methods. - * - * @author Island of TeX - * @version 5.0 - * @since 4.0 - */ -object DisplayUtils { - // the application messages obtained from the - // language controller - private val messages = LanguageController - - // get the logger context from a factory - private val logger = LoggerFactory.getLogger(DisplayUtils::class.java) - - /** - * The length of the longest result match as integer. - */ - private val longestMatch: Int = listOf( - messages.getMessage(Messages.INFO_LABEL_ON_SUCCESS), - messages.getMessage(Messages.INFO_LABEL_ON_FAILURE), - messages.getMessage(Messages.INFO_LABEL_ON_ERROR)) - .map { it.length }.max()!! - /** - * If the longest match is longer than the width, then it will be truncated - * to this length. - */ - private const val shortenedLongestMatch = 10 - - /** - * The default terminal width defined in the settings. - */ - private val width: Int - get() = Arara.config[AraraSpec.Application.width] - - /** - * Checks if the execution is in dry-run mode. - */ - private val isDryRunMode: Boolean - get() = Arara.config[AraraSpec.Execution.dryrun] - - /** - * Checks if the execution is in verbose mode. - */ - private val isVerboseMode: Boolean - get() = Arara.config[AraraSpec.Execution.verbose] - - /** - * The application path. - */ - private val applicationPath: String - get() = try { - ConfigurationUtils.applicationPath.toString() - } catch (ae: AraraException) { - "[unknown application path]" - } - - /** - * Displays the short version of the current entry in the terminal. - * - * @param name Rule name. - * @param task Task name. - */ - private fun buildShortEntry(name: String, task: String) { - val result = if (longestMatch >= width) - shortenedLongestMatch - else - longestMatch - val space = width - result - 1 - val line = "($name) $task ".abbreviate(space - "... ".length) - print(line.padEnd(space, '.') + " ") - } - - /** - * Displays the short version of the current entry result in the terminal. - * - * @param value The boolean value to be displayed. - */ - private fun buildShortResult(value: Boolean) { - val result = longestMatch - println(getResult(value).padStart(result)) - } - - /** - * Displays the current entry result in the terminal. - * - * @param value The boolean value to be displayed. - */ - fun printEntryResult(value: Boolean) { - Arara.config[AraraSpec.UserInteraction.displayLine] = false - Arara.config[AraraSpec.UserInteraction.displayResult] = true - Arara.config[AraraSpec.Execution.status] = if (value) 0 else 1 - logger.info( - messages.getMessage( - Messages.LOG_INFO_TASK_RESULT - ) + " " + getResult(value) - ) - if (!isDryRunMode) { - if (!isVerboseMode) { - buildShortResult(value) - } else { - buildLongResult(value) - } - } - } - - /** - * Displays a long version of the current entry result in the terminal. - * - * @param value The boolean value to be displayed - */ - private fun buildLongResult(value: Boolean) { - val width = width - println("\n" + (" " + getResult(value)).padStart(width, '-')) - } - - /** - * Displays the current entry in the terminal. - * - * @param name The rule name. - * @param task The task name. - */ - fun printEntry(name: String, task: String) { - logger.info( - messages.getMessage( - Messages.LOG_INFO_INTERPRET_TASK, - task, - name - ) - ) - Arara.config[AraraSpec.UserInteraction.displayLine] = true - Arara.config[AraraSpec.UserInteraction.displayResult] = false - if (!isDryRunMode) { - if (!isVerboseMode) { - buildShortEntry(name, task) - } else { - buildLongEntry(name, task) - } - } else { - buildDryRunEntry(name, task) - } - } - - /** - * Displays a long version of the current entry in the terminal. - * - * @param name Rule name. - * @param task Task name. - */ - private fun buildLongEntry(name: String, task: String) { - if (Arara.config[AraraSpec.UserInteraction.displayRolling]) { - addNewLine() - } else { - Arara.config[AraraSpec.UserInteraction.displayRolling] = true - } - println(displaySeparator()) - println("($name) $task".abbreviate(width)) - println(displaySeparator()) - } - - /** - * Displays a dry-run version of the current entry in the terminal. - * - * @param name The rule name. - * @param task The task name. - */ - private fun buildDryRunEntry(name: String, task: String) { - if (Arara.config[AraraSpec.UserInteraction.displayRolling]) { - addNewLine() - } else { - Arara.config[AraraSpec.UserInteraction.displayRolling] = true - } - println("[DR] ($name) $task".abbreviate(width)) - println(displaySeparator()) - } - - /** - * Displays the exception in the terminal. - * - * @param exception The exception object. - */ - fun printException(exception: AraraException) { - Arara.config[AraraSpec.UserInteraction.displayException] = true - Arara.config[AraraSpec.Execution.status] = 2 - - val display = Arara.config[AraraSpec.UserInteraction.displayLine] - if (Arara.config[AraraSpec.UserInteraction.displayResult]) - addNewLine() - if (display) { - if (!isDryRunMode) { - if (!isVerboseMode) { - buildShortError() - } else { - buildLongError() - } - addNewLine() - } - } - val text = (if (exception.hasException()) - exception.message + " " + messages.getMessage( - Messages.INFO_DISPLAY_EXCEPTION_MORE_DETAILS) - else - exception.message) ?: "EXCEPTION PROVIDES NO MESSAGE" - // TODO: check null handling - logger.error(text) - wrapText(text) - if (exception.hasException()) { - addNewLine() - displayDetailsLine() - val details = exception.exception!!.message!! - logger.error(details) - wrapText(details) - } - } - - /** - * Gets the string representation of the provided boolean value. - * - * @param value The boolean value. - * @return The string representation. - */ - private fun getResult(value: Boolean): String { - return if (value) - messages.getMessage( - Messages.INFO_LABEL_ON_SUCCESS - ) - else - messages.getMessage(Messages.INFO_LABEL_ON_FAILURE) - } - - /** - * Displays the short version of an error in the terminal. - */ - private fun buildShortError() { - val result = longestMatch - println(messages.getMessage(Messages.INFO_LABEL_ON_ERROR) - .padStart(result)) - } - - /** - * Displays the long version of an error in the terminal. - */ - private fun buildLongError() { - println((" " + messages.getMessage(Messages.INFO_LABEL_ON_ERROR)) - .padStart(width, '-')) - } - - /** - * Displays the provided text wrapped nicely according to the default - * terminal width. - * - * @param text The text to be displayed. - */ - fun wrapText(text: String) = println(text.wrap(width)) - - /** - * Displays the rule authors in the terminal. - * - * @param authors The list of authors. - */ - fun printAuthors(authors: List<String>) { - val line = if (authors.size == 1) - messages.getMessage(Messages.INFO_LABEL_AUTHOR) - else - messages.getMessage(Messages.INFO_LABEL_AUTHORS) - val text = if (authors.isEmpty()) - messages.getMessage(Messages.INFO_LABEL_NO_AUTHORS) - else - authors.joinToString(", ") { it.trim() } - wrapText("$line $text") - } - - /** - * Displays the current conditional in the terminal. - * - * @param conditional The conditional object. - */ - fun printConditional(conditional: Conditional) { - if (conditional.type !== Conditional.ConditionalType.NONE) { - wrapText(messages.getMessage(Messages.INFO_LABEL_CONDITIONAL) + - " (" + conditional.type + ") " + - conditional.condition) - } - } - - /** - * Displays the file information in the terminal. - */ - fun printFileInformation() { - val file = Arara.config[AraraSpec.Execution.reference] - val version = Arara.config[AraraSpec.Application.version] - val line = messages.getMessage( - Messages.INFO_DISPLAY_FILE_INFORMATION, - file.name, - CommonUtils.byteSizeToString(file.length()), - FileHandlingUtils.getLastModifiedInformation(file) - ) - logger.info(messages.getMessage( - Messages.LOG_INFO_WELCOME_MESSAGE, - version - )) - logger.info(displaySeparator()) - logger.debug("::: arara @ $applicationPath") - logger.debug("::: Java %s, %s".format( - CommonUtils.getSystemProperty("java.version", - "[unknown version]"), - CommonUtils.getSystemProperty("java.vendor", - "[unknown vendor]") - )) - logger.debug("::: %s".format( - CommonUtils.getSystemProperty("java.home", - "[unknown location]") - )) - logger.debug("::: %s, %s, %s".format( - CommonUtils.getSystemProperty("os.name", - "[unknown OS name]"), - CommonUtils.getSystemProperty("os.arch", - "[unknown OS arch]"), - CommonUtils.getSystemProperty("os.version", - "[unknown OS version]") - )) - logger.debug("::: user.home @ %s".format( - CommonUtils.getSystemProperty("user.home", - "[unknown user's home directory]") - )) - logger.debug("::: CF @ %s".format(Arara.config[AraraSpec.Execution - .configurationName])) - logger.debug(displaySeparator()) - logger.info(line) - wrapText(line) - addNewLine() - } - - /** - * Displays the elapsed time in the terminal. - * - * @param seconds The elapsed seconds. - */ - fun printTime(seconds: Double) { - val language = Arara.config[AraraSpec.Execution.language] - - if (Arara.config[AraraSpec.UserInteraction.displayTime]) { - if (Arara.config[AraraSpec.UserInteraction.displayLine] || - Arara.config[AraraSpec.UserInteraction.displayException]) - addNewLine() - - val text = messages.getMessage( - Messages.INFO_DISPLAY_EXECUTION_TIME, - "%1.2f".format(language.locale, seconds)) - logger.info(text) - wrapText(text) - } - } - - /** - * Displays the application logo in the terminal. - */ - fun printLogo() { - println(""" - __ _ _ __ __ _ _ __ __ _ - / _` | '__/ _` | '__/ _` | - | (_| | | | (_| | | | (_| | - \__,_|_| \__,_|_| \__,_| - """.trimIndent()) - addNewLine() - } - - /** - * Adds a new line in the terminal. - */ - private fun addNewLine() { - println() - } - - /** - * Displays a line containing details. - */ - private fun displayDetailsLine() { - val line = messages.getMessage( - Messages.INFO_LABEL_ON_DETAILS) + " " - println(line.abbreviate(width).padEnd(width, '-')) - } - - /** - * Gets the output separator with the provided text. - * - * @param message The provided text. - * @return A string containing the output separator with the provided text. - */ - fun displayOutputSeparator(message: String): String { - return " $message ".center(width, '-') - } - - /** - * Gets the line separator. - * - * @return A string containing the line separator. - */ - fun displaySeparator(): String { - return "-".repeat(width) - } -} diff --git a/support/arara/source/src/main/kotlin/org/islandoftex/arara/utils/Extensions.kt b/support/arara/source/src/main/kotlin/org/islandoftex/arara/utils/Extensions.kt deleted file mode 100644 index 10ace0b7f1..0000000000 --- a/support/arara/source/src/main/kotlin/org/islandoftex/arara/utils/Extensions.kt +++ /dev/null @@ -1,68 +0,0 @@ -// SPDX-License-Identifier: BSD-3-Clause -package org.islandoftex.arara.utils - -import kotlin.math.ceil - -/** - * Abbreviate a String to a maximal width. - * - * @param maxWidth The maximal width to truncate to. - * @param ellipsis The string to use to indicate an ellipsis. - * @throws IllegalArgumentException If the string would consist only of the - * ellipsis after shortening. - * @return The abbreviated string. - */ -@Throws(IllegalArgumentException::class) -fun String.abbreviate(maxWidth: Int, ellipsis: String = "…"): String { - return when { - maxWidth < ellipsis.length + 1 -> - throw IllegalArgumentException("Can't abbreviate text further") - this.length < maxWidth -> this - else -> this.substring(0, maxWidth - ellipsis.length) + ellipsis - } -} - -/** - * Center a string within a specified number of columns. - * - * This does not center anything if the string is longer than the specified - * width. - * - * @param width The number of columns. - * @param padChar The char to pad with. - * @return The padded string. - */ -fun String.center(width: Int, padChar: Char): String { - return if (this.length > width) this - else { - val charsLeft = width - this.length - padChar.toString().repeat(charsLeft / 2) + this + - padChar.toString().repeat(ceil(charsLeft.toDouble() / 2.0).toInt()) - } -} - -/** - * Wrap text at a specified width. - * - * Algorithm from Wikipedia: - * https://en.wikipedia.org/wiki/Line_wrap_and_word_wrap#Minimum_number_of_lines - * - * @param width The width to wrap at. - * @return Wrapped text. - */ -fun String.wrap(width: Int): String { - val words = this.split(" ") - var wrapped = words[0] - var spaceLeft = width - wrapped.length - words.drop(1).forEach { - val len = it.length - wrapped += if (len + 1 > spaceLeft) { - spaceLeft = width - len - "\n$it" - } else { - spaceLeft -= len + 1 - " $it" - } - } - return wrapped -} diff --git a/support/arara/source/src/main/kotlin/org/islandoftex/arara/utils/InterpreterUtils.kt b/support/arara/source/src/main/kotlin/org/islandoftex/arara/utils/InterpreterUtils.kt deleted file mode 100644 index 00313a7714..0000000000 --- a/support/arara/source/src/main/kotlin/org/islandoftex/arara/utils/InterpreterUtils.kt +++ /dev/null @@ -1,164 +0,0 @@ -// 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() - } - } -} diff --git a/support/arara/source/src/main/kotlin/org/islandoftex/arara/utils/LoggingUtils.kt b/support/arara/source/src/main/kotlin/org/islandoftex/arara/utils/LoggingUtils.kt deleted file mode 100644 index f9719fa31f..0000000000 --- a/support/arara/source/src/main/kotlin/org/islandoftex/arara/utils/LoggingUtils.kt +++ /dev/null @@ -1,70 +0,0 @@ -// SPDX-License-Identifier: BSD-3-Clause -package org.islandoftex.arara.utils - -import ch.qos.logback.classic.LoggerContext -import ch.qos.logback.classic.joran.JoranConfigurator -import ch.qos.logback.core.joran.spi.JoranException -import java.io.InputStream -import org.islandoftex.arara.Arara -import org.islandoftex.arara.configuration.AraraSpec -import org.slf4j.LoggerFactory - -/** - * Implements the logging controller. This class actually sets the logging - * configuration in order to allow appending results to a file. - * - * @author Island of TeX - * @version 5.0 - * @since 4.0 - */ -object LoggingUtils { - // configuration resource as an input stream - // the configuration is actually a XML file. - private val resource: InputStream by lazy { - LoggingUtils::class.java - .getResourceAsStream("/org/islandoftex/arara/configuration/logback.xml") - } - - /** - * Sets the logging configuration according to the provided boolean value. - * If the value is set to true, the log entries will be appended to a file, - * otherwise the logging feature will keep silent. - * @param enable A boolean value that indicates the logging behaviour - * throughout the application. - */ - fun enableLogging(enable: Boolean) { - // get the logger context from a factory, set a - // new context and reset it - val loggerContext = LoggerFactory.getILoggerFactory() as LoggerContext - - try { - // get a new configuration and set - // the context - val configurator = JoranConfigurator() - configurator.context = loggerContext - loggerContext.reset() - - // if enabled, the log entries will be - // appended to a file, otherwise it will - // remain silent - if (enable) { - // set the file name and configure - // the logging controller to append - // entries to the file - val name = Arara.config[AraraSpec.Execution.logName] - loggerContext.putProperty("name", name) - configurator.doConfigure(resource) - } - } catch (_: JoranException) { - // quack, quack, quack! - } - } - - /** - * Initializes the logging controller by disabling it. I don't want an odd - * behaviour out of the box. - */ - fun init() { - enableLogging(false) - } -} diff --git a/support/arara/source/src/main/kotlin/org/islandoftex/arara/utils/MessageUtils.kt b/support/arara/source/src/main/kotlin/org/islandoftex/arara/utils/MessageUtils.kt deleted file mode 100644 index ae695c44fb..0000000000 --- a/support/arara/source/src/main/kotlin/org/islandoftex/arara/utils/MessageUtils.kt +++ /dev/null @@ -1,266 +0,0 @@ -// SPDX-License-Identifier: BSD-3-Clause -package org.islandoftex.arara.utils - -import javax.swing.JOptionPane -import javax.swing.UIManager -import org.islandoftex.arara.Arara -import org.islandoftex.arara.configuration.AraraSpec - -/** - * Implements utilitary methods for displaying messages. - * - * @author Island of TeX - * @version 5.0 - * @since 4.0 - */ -object MessageUtils { - // holds the default width for the - // message body, in pixels - private const val WIDTH = 250 - - // let's start the UI manager and set - // the default look and feel to be as - // close as possible to the system - init { - // get the current look and feel - var laf = Arara.config[AraraSpec.UserInteraction.lookAndFeel] - - // check if one is actually set - if (laf != "none") { - // use a special keyword to indicate - // the use of a system look and feel - if (laf == "system") { - laf = UIManager.getSystemLookAndFeelClassName() - } - - // let's try it, in case it fails, - // rely to the default look and feel - try { - // get the system look and feel name - // and try to set it as default - UIManager.setLookAndFeel(laf) - } catch (_: Exception) { - // quack, quack, quack - } - } - } - - /** - * Normalizes the icon type to one of the five available icons. - * @param value An integer value. - * @return The normalized integer value. - */ - @Suppress("MagicNumber") - private fun normalizeIconType(value: Int): Int { - // do the normalization according to the available - // icons in the underlying message implementation - return when (value) { - 1 -> JOptionPane.ERROR_MESSAGE - 2 -> JOptionPane.INFORMATION_MESSAGE - 3 -> JOptionPane.WARNING_MESSAGE - 4 -> JOptionPane.QUESTION_MESSAGE - else -> JOptionPane.PLAIN_MESSAGE - } - } - - /** - * Normalizes the message width, so only valid nonzero values are accepted. - * @param value An integer value corresponding to the message width. - * @return The normalized width. - */ - private fun normalizeMessageWidth(value: Int): Int { - return if (value > 0) value else WIDTH - } - - /** - * Shows the message. - * @param width Integer value, in pixels. - * @param type Type of message. - * @param title Title of the message. - * @param text Text of the message. - */ - fun showMessage( - width: Int, - type: Int, - title: String, - text: String - ) { - // effectively shows the message based - // on the provided parameters - JOptionPane.showMessageDialog(null, - String.format( - "<html><body style=\"width:%dpx\">%s</body></html>", - normalizeMessageWidth(width), - text), - title, - normalizeIconType(type) - ) - } - - /** - * Shows the message. It relies on the default width. - * @param type Type of message. - * @param title Title of the message. - * @param text Text of the message. - */ - fun showMessage(type: Int, title: String, text: String) { - showMessage(WIDTH, type, title, text) - } - - /** - * Shows a message with options presented as an array of buttons. - * @param width Integer value, in pixels. - * @param type Type of message. - * @param title Title of the message. - * @param text Text of the message. - * @param buttons An array of objects to be presented as buttons. - * @return The index of the selected button, starting from 1. - */ - fun showOptions( - width: Int, - type: Int, - title: String, - text: String, - vararg buttons: Any - ): Int { - // returns the index of the selected button, - // zero if nothing is selected - return JOptionPane.showOptionDialog(null, - String.format( - "<html><body style=\"width:%dpx\">%s</body></html>", - normalizeMessageWidth(width), - text), - title, - JOptionPane.DEFAULT_OPTION, - normalizeIconType(type), null, - buttons, - buttons[0] - ) + 1 - } - - /** - * Shows a message with options presented as an array of buttons. It relies - * on the default width. - * @param type Type of message. - * @param title Title of the message. - * @param text Text of the message. - * @param buttons An array of objects to be presented as buttons. - * @return The index of the selected button, starting from 1. - */ - @Suppress("SpreadOperator") - fun showOptions( - type: Int, - title: String, - text: String, - vararg buttons: Any - ): Int { - return showOptions(WIDTH, type, title, text, *buttons) - } - - /** - * Shows a message with a text input. - * @param width Integer value, in pixels. - * @param type Type of message. - * @param title Title of the message. - * @param text Text of the message. - * @return The string representing the input text. - */ - fun showInput( - width: Int, - type: Int, - title: String, - text: String - ): String { - // get the string from the - // input text, if any - val input = JOptionPane.showInputDialog(null, - String.format( - "<html><body style=\"width:%dpx\">%s</body></html>", - normalizeMessageWidth(width), - text), - title, - normalizeIconType(type)) - - // if the input is not null, that is, - // the user actually typed something - // return the trimmed string otherwise - // an empty string - return input?.trim() ?: "" - } - - /** - * Shows a message with a text input. It relies on the default width. - * @param type Type of message. - * @param title Title of the message. - * @param text Text of the message. - * @return The string representing the input text. - */ - fun showInput(type: Int, title: String, text: String): String { - return showInput(WIDTH, type, title, text) - } - - /** - * Shows a message with options presented as a dropdown list of elements. - * @param width Integer value, in pixels. - * @param type Type of message. - * @param title Title of the message. - * @param text Text of the message. - * @param elements An array of objects representing the elements. - * @return The index of the selected element, starting from 1. - */ - fun showDropdown( - width: Int, - type: Int, - title: String, - text: String, - vararg elements: Any - ): Int { - // show the dropdown list and get - // the selected object, if any - val index = JOptionPane.showInputDialog(null, - String.format( - "<html><body style=\"width:%dpx\">%s</body></html>", - normalizeMessageWidth(width), - text), - title, - normalizeIconType(type), null, - elements, - elements[0]) - - // if it's not a null object, let's - // find the corresponding index - if (index != null) { - elements.forEachIndexed { i, value -> - // if the element is found, simply - // return the index plus 1, as zero - // corresponds to no selection at all - if (value == index) { - return i + 1 - } - } - } - - // nothing was selected, - // simply return zero - return 0 - } - - /** - * Shows a message with options presented as a dropdown list of elements. It - * relies on the default width. - * @param type Type of message. - * @param title Title of the message. - * @param text Text of the message. - * @param elements An array of objects representing the elements. - * @return The index of the selected element, starting from 1. - */ - @Suppress("SpreadOperator") - fun showDropdown( - type: Int, - title: String, - text: String, - vararg elements: Any - ): Int { - return showDropdown(WIDTH, type, title, text, *elements) - } -} diff --git a/support/arara/source/src/main/kotlin/org/islandoftex/arara/utils/SystemCallUtils.kt b/support/arara/source/src/main/kotlin/org/islandoftex/arara/utils/SystemCallUtils.kt deleted file mode 100644 index 9b443736a1..0000000000 --- a/support/arara/source/src/main/kotlin/org/islandoftex/arara/utils/SystemCallUtils.kt +++ /dev/null @@ -1,105 +0,0 @@ -// SPDX-License-Identifier: BSD-3-Clause -package org.islandoftex.arara.utils - -import org.islandoftex.arara.model.AraraException -import org.islandoftex.arara.ruleset.Command -import org.zeroturnaround.exec.ProcessExecutor - -/** - * Implements a system call controller. - * - * This class wraps a map that holds the result of system specific variables - * not directly available at runtime and makes unsafe calling of system - * commands available to rules. - * - * @author Island of TeX - * @version 5.0 - * @since 4.0 - */ -object SystemCallUtils { - /** - * When executing a system call goes wrong, this status code is returned. - */ - const val errorExitStatus = -99 - /** - * When executing a system call goes wrong and the caller asked for output, - * this output will be returned. - */ - const val errorCommandOutput = "" - - // the system call map which holds the result of - // system specific variables not directly available - // at runtime; the idea here is to provide wrappers - // to the map getter, so it could be easily manipulated - // create the new map instance to be - // populated on demand - private val map: MutableMap<String, Any> = mutableMapOf() - - // the commands map will allow the system call map being - // populated only on demand, that is, if the key is not - // found, this map will provide the corresponding method - // and update the value - // create the new map of commands and - // add the corresponding system calls - private val commands: MutableMap<String, () -> Any> = mutableMapOf( - "cygwin" to { - // Implements the body of the command. In this particular - // instance, it checks if we are inside a Cygwin environment. - // Returns a boolean value indicating if we are inside a Cygwin - // environment. - - // execute a new system call to 'uname -s', read the output - // as an UTF-8 string, lowercase it and check if it starts - // with the 'cygwin' string; if so, we are inside Cygwin - executeSystemCommand(Command("uname", "-s")) - .second.toLowerCase().startsWith("cygwin") - }) - - /** - * Gets the object indexed by the provided key. This method actually holds - * the map method of the very same name. - * - * @param key The provided map key. - * @return The object indexed by the provided map key. - */ - @Throws(NoSuchElementException::class, AraraException::class) - operator fun get(key: String): Any { - // if key is not found, meaning that - // the value wasn't required before - if (!map.containsKey(key)) { - if (commands.containsKey(key)) - // perform the system call and - // populate the corresponding value - map[key] = commands[key]!!.invoke() - else - throw AraraException("The requested key could not be " + - "translated into a command to get the call value.") - } - - // simply return the corresponding - // value based on the provided key - return map.getValue(key) - } - - /** - * Executes a system command from the underlying operating system and - * returns a pair containing the exit status and the command output as a - * string. - * @param command The system command to be executed. - * @return A pair containing the exit status and the system command output - * as a string. - */ - fun executeSystemCommand(command: Command): Pair<Int, String> { - return ProcessExecutor(command.elements).runCatching { - directory(command.workingDirectory) - readOutput(true) - execute().run { - exitValue to outputUTF8() - } - }.getOrElse { - // quack, quack, do nothing, just - // return a default error code - errorExitStatus to errorCommandOutput - } - } -} diff --git a/support/arara/source/src/main/kotlin/org/islandoftex/arara/utils/TeeOutputStream.kt b/support/arara/source/src/main/kotlin/org/islandoftex/arara/utils/TeeOutputStream.kt deleted file mode 100644 index dee15176f2..0000000000 --- a/support/arara/source/src/main/kotlin/org/islandoftex/arara/utils/TeeOutputStream.kt +++ /dev/null @@ -1,66 +0,0 @@ -// SPDX-License-Identifier: BSD-3-Clause -package org.islandoftex.arara.utils - -import java.io.IOException -import java.io.OutputStream - -/** - * Implements a stream splitter. - * - * @author Island of TeX - * @version 5.0 - * @since 4.0 - */ -class TeeOutputStream( - /** - * The array of output streams holds every output stream that will be - * written to. - */ - vararg outputStreams: OutputStream -) : OutputStream() { - /** - * An array of streams in which an object of this class will split data. - */ - private val streams: List<OutputStream> = outputStreams.toList() - - /** - * Writes the provided integer to each stream. - * - * @param b The provided integer - * @throws IOException An IO exception. - */ - @Throws(IOException::class) - override fun write(b: Int) = streams.forEach { it.write(b) } - - /** - * Writes the provided byte array to each stream, with the provided offset - * and length. - * - * @param b The byte array. - * @param offset The offset. - * @param length The length. - * @throws IOException An IO exception. - */ - @Throws(IOException::class) - override fun write(b: ByteArray, offset: Int, length: Int) = - streams.forEach { it.write(b, offset, length) } - - /** - * Flushes every stream. - * - * @throws IOException An IO exception. - */ - @Throws(IOException::class) - override fun flush() = streams.forEach { it.flush() } - - /** - * Closes every stream silently. - */ - override fun close() = streams.forEach { - try { - it.close() - } catch (ignored: IOException) { - // do nothing on purpose - } - } -} |