diff options
Diffstat (limited to 'support/texplate/source/main/kotlin')
15 files changed, 901 insertions, 0 deletions
diff --git a/support/texplate/source/main/kotlin/org/islandoftex/texplate/Main.kt b/support/texplate/source/main/kotlin/org/islandoftex/texplate/Main.kt new file mode 100644 index 0000000000..73de15f8c3 --- /dev/null +++ b/support/texplate/source/main/kotlin/org/islandoftex/texplate/Main.kt @@ -0,0 +1,23 @@ +// SPDX-License-Identifier: BSD-3-Clause +package org.islandoftex.texplate + +import kotlin.system.exitProcess +import org.islandoftex.texplate.util.MessageUtils +import picocli.CommandLine + +/** + * Main method. Note that it simply passes the control to the template + * processing class. + * + * @param args The command line arguments. + */ +fun main(args: Array<String>) { + // draw the application logo in the terminal (please have fixed fonts + // in your terminal for a nice display) + MessageUtils.drawLogo() + // calls the command line processing method and performs the actual + // application logic + @Suppress("SpreadOperator") + val exitCode = CommandLine(TemplateProcessing()).execute(*args) + exitProcess(exitCode) +} diff --git a/support/texplate/source/main/kotlin/org/islandoftex/texplate/TemplateProcessing.kt b/support/texplate/source/main/kotlin/org/islandoftex/texplate/TemplateProcessing.kt new file mode 100644 index 0000000000..659a0df631 --- /dev/null +++ b/support/texplate/source/main/kotlin/org/islandoftex/texplate/TemplateProcessing.kt @@ -0,0 +1,228 @@ +// SPDX-License-Identifier: BSD-3-Clause +package org.islandoftex.texplate + +import java.nio.file.Path +import java.util.concurrent.Callable +import kotlin.math.ln +import kotlin.math.pow +import org.islandoftex.texplate.exceptions.InvalidTemplateException +import org.islandoftex.texplate.model.Configuration +import org.islandoftex.texplate.model.Template +import org.islandoftex.texplate.util.MergingUtils.mergeTemplate +import org.islandoftex.texplate.util.MessageUtils.error +import org.islandoftex.texplate.util.MessageUtils.line +import org.islandoftex.texplate.util.MessageUtils.status +import org.islandoftex.texplate.util.PathUtils.getTemplatePath +import org.islandoftex.texplate.util.ValidatorUtils.validate +import picocli.CommandLine + +/** + * The template processing class. + * + * @version 1.0 + * @since 1.0 + */ +@CommandLine.Command(usageHelpWidth = 70, name = "texplate") +class TemplateProcessing : Callable<Int> { + // the file output, which will hold the result of the merging of both template + // and context data map from command line + @CommandLine.Option( + names = ["-o", "--output"], + description = ["The output file in which the chosen " + + "template will be effectively written. Make sure " + + "the directory has the correct permissions for " + + "writing the output file."], + required = true, + type = [Path::class] + ) + private val output: Path? = null + + // the template name + @CommandLine.Option( + names = ["-t", "--template"], + description = ["The name of the template. The tool will " + + "search both user and system locations and set the " + + "template model accordingly, based on your specs."] + ) + private var template: String? = null + + // the context data map that holds a set of key/value pairs to be merged + // with the template + @CommandLine.Option( + names = ["-m", "--map"], + description = ["The contextual map that provides the " + + "data to be merged in the template. This parameter " + + "can be used multiple times. You can specify a map " + + "entry with the key=value syntax (mind the entry " + + "separator)."], + arity = "1..*" + ) + private var map: Map<String, String>? = null + + @CommandLine.Option( + names = ["-c", "--config"], + description = ["The configuration file in which the tool " + + "can read template data, for automation purposes. Make " + + "sure to follow the correct specification when writing " + + "a configuration file."], + type = [Path::class] + ) + private val configuration: Path? = null + + /** + * The application logic, enclosed as a call method. + * + * @return An integer value denoting the exit status. + * @throws Exception An exception was raised in the application logic. + */ + @Throws(Exception::class) + override fun call(): Int { + // the exit status, originally set as a valid value + var exit = 0 + // configuration halt flag, indicating whether the tool has to end earlier + var halt = false + var cmap: Map<String, Any> = mutableMapOf() + // ensure the context data map is at least instantiated + ensureMap() + // there is a configuration file found in the command line + if (has(configuration)) { + line("Checking configuration") + try { + val config = Configuration.fromPath(configuration!!) + // the configuration file seems to be valid, proceed + status(true) + // check if the configuration has a proper template + if (has(config.template)) { + // if so, build the template if, and only if, there's no one already + // set through command line + template = ensure(template, config.template) + } + // check if the configuration has a proper string/string map + if (has(config.map)) { + // set the main configuration map to be dealt later on + cmap = config.map + } + line("Adjusting variables from file") + status(true) + println() + } catch (e: InvalidTemplateException) { + // an error occurred, print it, set exit code and halt + status(false) + error(e) + exit = -1 + halt = true + } + } else { + line("Configuration file mode disabled") + status(true) + line("Entering full command line mode") + // there's no configuration file, so we need to check whether there is + // not a pattern set in the command line + if (!has(template)) { + status(false) + error(Exception("The template was not set " + + "in the command line through the -t/--template " + + "option. If not explicitly specified in a " + + "configuration file, this option becomes mandatory, " + + "so make sure to define it either in the command " + + "line or in a proper configuration file.")) + exit = -1 + halt = true + } else { + status(true) + println() + } + } + // check whether we should halt prematurely + if (!halt) { + println("Please, wait...") + println() + // now we need to obtain the actual template from a file stored either + // in the user home or in the system + line("Obtaining reference") + try { + // let us try to get the corresponding file from the template pattern + val file = getTemplatePath(template!!) + // the actual template file was found, so we can proceed to + // the next phase + status(true) + line("Composing template") + // attempts to retrieve the template attributes from the referenced file + // to the actual template object + val template = Template.fromPath(file) + // the template composition was successful, so we can move on to the + // next phase + status(true) + // once the template object is populated, we need to verify if both + // template and data map are not somehow conflicting + line("Validating data") + val validatedData = validate(template, map!!) + // the data validation was consistent, so now the merging can be + // applied + status(true) + line("Merging template and data") + val merged = mergeTemplate(template, validatedData, output!!, + cmap) + status(true) + println() + println("Done! Enjoy your template!") + println("Written: " + getSize(merged)) + } catch (e: InvalidTemplateException) { + status(false) + error(e) + exit = -1 + } + } + // the exit status is returned, denoting whether the application was able + // to merge both template and data accordingly + return exit + } + + /** + * Ensures the data map is never pointed to a null reference. + */ + private fun ensureMap() { + if (!has(map)) { + map = mutableMapOf() + } + } + + /** + * Gets the file size in a human readable format. + * + * @param bytes The file size, in bytes. + * @return The file size in a human readable format. + */ + @Suppress("MagicNumber") + private fun getSize(bytes: Long): String { + return if (bytes < 1024) { + "$bytes B" + } else { + val exponent = (ln(bytes.toDouble()) / ln(1024.0)).toInt() + "%.1f %cB".format(bytes / 1024.0.pow(exponent.toDouble()), + "KMGTPE"[exponent - 1]) + } + } + + /** + * Ensures the first parameter is not null, or sets it to the second one. + * + * @param <T> The type. + * @param first First parameter. + * @param second Second parameter. + * @return Either the first or the second one. + </T> */ + private fun <T> ensure(first: T, second: T): T { + return if (!has(first)) second else first + } + + /** + * Checks whether the object exists. + * + * @param obj The object. + * @return Boolean value indicating whether the object exists. + */ + private fun has(obj: Any?): Boolean { + return obj != null + } +} diff --git a/support/texplate/source/main/kotlin/org/islandoftex/texplate/exceptions/InvalidKeySetException.kt b/support/texplate/source/main/kotlin/org/islandoftex/texplate/exceptions/InvalidKeySetException.kt new file mode 100644 index 0000000000..72ff3bcbd4 --- /dev/null +++ b/support/texplate/source/main/kotlin/org/islandoftex/texplate/exceptions/InvalidKeySetException.kt @@ -0,0 +1,30 @@ +// SPDX-License-Identifier: BSD-3-Clause +package org.islandoftex.texplate.exceptions + +/** + * Handles exceptions when the context map contains invalid keys. + * + * @version 1.0 + * @since 1.0 + */ +class InvalidKeySetException : Exception { + /** + * Constructor. + */ + constructor() + + /** + * Constructor. + * + * @param message Message to be attached to the exception. + */ + constructor(message: String?) : super(message) + + /** + * Constructor. + * + * @param message Message to be attached to the exception. + * @param cause The throwable cause to be forwarded. + */ + constructor(message: String?, cause: Throwable?) : super(message, cause) +} diff --git a/support/texplate/source/main/kotlin/org/islandoftex/texplate/exceptions/InvalidTemplateException.kt b/support/texplate/source/main/kotlin/org/islandoftex/texplate/exceptions/InvalidTemplateException.kt new file mode 100644 index 0000000000..678c390055 --- /dev/null +++ b/support/texplate/source/main/kotlin/org/islandoftex/texplate/exceptions/InvalidTemplateException.kt @@ -0,0 +1,27 @@ +// SPDX-License-Identifier: BSD-3-Clause +package org.islandoftex.texplate.exceptions + +/** + * Handles exceptions when the template is somehow invalid. + * + * @version 1.0 + * @since 1.0 + */ +class InvalidTemplateException : Exception { + constructor() + + /** + * Constructor. + * + * @param message Message to be attached to the exception. + */ + constructor(message: String?) : super(message) + + /** + * Constructor. + * + * @param message Message to be attached to the exception. + * @param cause The throwable cause to be forwarded. + */ + constructor(message: String?, cause: Throwable?) : super(message, cause) +} diff --git a/support/texplate/source/main/kotlin/org/islandoftex/texplate/exceptions/TemplateMergingException.kt b/support/texplate/source/main/kotlin/org/islandoftex/texplate/exceptions/TemplateMergingException.kt new file mode 100644 index 0000000000..e1760f8732 --- /dev/null +++ b/support/texplate/source/main/kotlin/org/islandoftex/texplate/exceptions/TemplateMergingException.kt @@ -0,0 +1,30 @@ +// SPDX-License-Identifier: BSD-3-Clause +package org.islandoftex.texplate.exceptions + +/** + * Handles exceptions when the template merging failed. + * + * @version 1.0 + * @since 1.0 + */ +class TemplateMergingException : Exception { + /** + * Constructor. + */ + constructor() + + /** + * Constructor. + * + * @param message Message to be attached to the exception. + */ + constructor(message: String?) : super(message) + + /** + * Constructor. + * + * @param message Message to be attached to the exception. + * @param cause The throwable cause to be forwarded. + */ + constructor(message: String?, cause: Throwable?) : super(message, cause) +} diff --git a/support/texplate/source/main/kotlin/org/islandoftex/texplate/model/Configuration.kt b/support/texplate/source/main/kotlin/org/islandoftex/texplate/model/Configuration.kt new file mode 100644 index 0000000000..8d37cbb39a --- /dev/null +++ b/support/texplate/source/main/kotlin/org/islandoftex/texplate/model/Configuration.kt @@ -0,0 +1,67 @@ +// SPDX-License-Identifier: BSD-3-Clause +package org.islandoftex.texplate.model + +import com.moandjiezana.toml.Toml +import java.nio.file.Path +import org.islandoftex.texplate.exceptions.InvalidTemplateException + +/** + * The configuration model. + * + * @version 1.0 + * @since 1.0 + */ +data class Configuration( + /** + * The template of the template. + */ + val template: String? = null, + /** + * Map of variables for the configuration. + */ + val map: Map<String, Any> = mapOf() +) { + /** + * Whether the configuration is valid. + */ + private val isValid: Boolean + get() = template != null + + companion object { + /** + * Reads the configuration from path. + * + * @param path The path. + * @return Configuration. + * @throws InvalidTemplateException The configuration is invalid. + */ + @Throws(InvalidTemplateException::class) + fun fromPath(path: Path): Configuration { + val configuration: Configuration + val message = ("The provided configuration file looks invalid. " + + "Please make sure the configuration has a valid syntax and " + + "try again. ") + configuration = try { + // gets the configuration + Toml().read(path.toFile()).to(Configuration::class.java) + } catch (exception: IllegalStateException) { + // the configuration seems invalid + throw InvalidTemplateException(message + "In this particular " + + "scenario, there is a possibility that the configuration " + + "file does not follow the TOML specification. Please " + + "refer to the user manual for further details and a " + + "possible fix. Also, the raised exception message can " + + "give us some hints on what happened.", exception) + } + return if (configuration.isValid) { + configuration + } else { + throw InvalidTemplateException(message + "Specifically, some " + + "mandatory fields are either absent or empty in the " + + "configuration file. It is quite important to strictly " + + "follow the configuration specification, as detailed in " + + "the user manual, or the tool will not work at all.") + } + } + } +} diff --git a/support/texplate/source/main/kotlin/org/islandoftex/texplate/model/Template.kt b/support/texplate/source/main/kotlin/org/islandoftex/texplate/model/Template.kt new file mode 100644 index 0000000000..82616242de --- /dev/null +++ b/support/texplate/source/main/kotlin/org/islandoftex/texplate/model/Template.kt @@ -0,0 +1,90 @@ +// SPDX-License-Identifier: BSD-3-Clause +package org.islandoftex.texplate.model + +import com.moandjiezana.toml.Toml +import java.nio.file.Path +import org.islandoftex.texplate.exceptions.InvalidTemplateException + +/** + * The template model. + * + * @version 1.0 + * @since 1.0 + */ +data class Template( + /** + * Name of the template + */ + val name: String? = null, + /** + * Description of the template + */ + val description: String? = null, + /** + * List of authors who wrote the template + */ + val authors: List<String> = listOf(), + /** + * List of requirements for the template + */ + val requirements: List<String> = listOf(), + /** + * The document to be configured + */ + val document: String? = null, + /** + * The map handlers + */ + val handlers: Map<String, String> = mapOf() +) { + /** + * Checks whether the template is valid. + * + * @return A boolean value indicating whether the template is valid. + */ + private val isValid: Boolean + get() = !(name == null || description == null || + document == null || name.isBlank() || + description.isBlank() || authors.isEmpty() || + document.isBlank()) + + companion object { + /** + * Reads the template from the provided path. + * + * @param path The path to the template file. + * @return The template object from the provided path. + * @throws InvalidTemplateException The template is invalid. + */ + @JvmStatic + @Throws(InvalidTemplateException::class) + fun fromPath(path: Path): Template { + val template: Template + // the exception message, in case the conversion fails or if there are + // missing elements from the template + val message = ("The provided template file looks invalid. Please " + + "make sure the template has a valid syntax and try again. ") + template = try { + Toml().read(path.toFile()).to(Template::class.java) + } catch (exception: IllegalStateException) { + throw InvalidTemplateException(message + "In this particular " + + "scenario, there is a possibility that the template " + + "file does not follow the TOML specification. Please " + + "refer to the user manual for further details and a " + + "possible fix. Also, the raised exception message can " + + "give us some hints on what happened.", exception) + } + // the conversion hasn't failed, but we need to check whether the + // template is valid + return if (template.isValid) { + template + } else { + throw InvalidTemplateException(message + "Specifically, some " + + "mandatory fields are either absent or empty in the " + + "template file. It is quite important to strictly " + + "follow the template specification, as detailed in the " + + "user manual, or the tool will not work at all.") + } + } + } +} diff --git a/support/texplate/source/main/kotlin/org/islandoftex/texplate/model/handlers/BooleanHandler.kt b/support/texplate/source/main/kotlin/org/islandoftex/texplate/model/handlers/BooleanHandler.kt new file mode 100644 index 0000000000..cbb74a532f --- /dev/null +++ b/support/texplate/source/main/kotlin/org/islandoftex/texplate/model/handlers/BooleanHandler.kt @@ -0,0 +1,20 @@ +// SPDX-License-Identifier: BSD-3-Clause +package org.islandoftex.texplate.model.handlers + +/** + * Implements a boolean handler. + * + * @version 1.0 + * @since 1.0 + */ +class BooleanHandler : Handler { + /** + * Applies the conversion to the string. + * + * @param string The string. + * @return A list. + */ + override fun apply(string: String?): Any? { + return listOf("true", "1", "yes").contains(string!!.toLowerCase()) + } +} diff --git a/support/texplate/source/main/kotlin/org/islandoftex/texplate/model/handlers/CSVListHandler.kt b/support/texplate/source/main/kotlin/org/islandoftex/texplate/model/handlers/CSVListHandler.kt new file mode 100644 index 0000000000..77424ed463 --- /dev/null +++ b/support/texplate/source/main/kotlin/org/islandoftex/texplate/model/handlers/CSVListHandler.kt @@ -0,0 +1,22 @@ +// SPDX-License-Identifier: BSD-3-Clause +package org.islandoftex.texplate.model.handlers + +/** + * Implements a CSV list handler. + * + * @version 1.0 + * @since 1.0 + */ +class CSVListHandler : Handler { + /** + * Applies the conversion to the string. + * + * @param string The string. + * @return A list. + */ + override fun apply(string: String?): Any? { + return string?.split(",(?=(?:[^\"]*\"[^\"]*\")*[^\"]*$)".toRegex()) + ?.map { it.trim() } + ?.filter { it.isNotEmpty() } + } +} diff --git a/support/texplate/source/main/kotlin/org/islandoftex/texplate/model/handlers/Handler.kt b/support/texplate/source/main/kotlin/org/islandoftex/texplate/model/handlers/Handler.kt new file mode 100644 index 0000000000..47b59a09a5 --- /dev/null +++ b/support/texplate/source/main/kotlin/org/islandoftex/texplate/model/handlers/Handler.kt @@ -0,0 +1,18 @@ +// SPDX-License-Identifier: BSD-3-Clause +package org.islandoftex.texplate.model.handlers + +/** + * Interface for handlers. + * + * @version 1.0 + * @since 1.0 + */ +interface Handler { + /** + * Apply the handler in the string. + * + * @param string The string. + * @return The resulting object. + */ + fun apply(string: String?): Any? +} diff --git a/support/texplate/source/main/kotlin/org/islandoftex/texplate/util/HandlerUtils.kt b/support/texplate/source/main/kotlin/org/islandoftex/texplate/util/HandlerUtils.kt new file mode 100644 index 0000000000..44eebb2b69 --- /dev/null +++ b/support/texplate/source/main/kotlin/org/islandoftex/texplate/util/HandlerUtils.kt @@ -0,0 +1,25 @@ +// SPDX-License-Identifier: BSD-3-Clause +package org.islandoftex.texplate.util + +import org.islandoftex.texplate.model.handlers.BooleanHandler +import org.islandoftex.texplate.model.handlers.CSVListHandler +import org.islandoftex.texplate.model.handlers.Handler + +/** + * Provides the map of handlers. + * + * @version 1.0 + * @since 1.0 + */ +object HandlerUtils { + /** + * Gets the map of handlers. + * + * @return Map of handlers. + */ + @JvmStatic + val handlers: Map<String, Handler> = mapOf( + "to-csv-list" to CSVListHandler(), + "to-boolean" to BooleanHandler() + ) +} diff --git a/support/texplate/source/main/kotlin/org/islandoftex/texplate/util/MergingUtils.kt b/support/texplate/source/main/kotlin/org/islandoftex/texplate/util/MergingUtils.kt new file mode 100644 index 0000000000..ef02349d5d --- /dev/null +++ b/support/texplate/source/main/kotlin/org/islandoftex/texplate/util/MergingUtils.kt @@ -0,0 +1,126 @@ +// SPDX-License-Identifier: BSD-3-Clause +package org.islandoftex.texplate.util + +import java.io.IOException +import java.nio.file.Path +import org.apache.velocity.VelocityContext +import org.apache.velocity.exception.MethodInvocationException +import org.apache.velocity.exception.ParseErrorException +import org.apache.velocity.exception.ResourceNotFoundException +import org.apache.velocity.exception.TemplateInitException +import org.apache.velocity.runtime.RuntimeConstants +import org.apache.velocity.runtime.RuntimeSingleton +import org.apache.velocity.runtime.parser.ParseException +import org.islandoftex.texplate.exceptions.TemplateMergingException +import org.islandoftex.texplate.model.Template +import org.islandoftex.texplate.util.HandlerUtils.handlers +import org.slf4j.helpers.NOPLoggerFactory + +/** + * Merging utilities. + * + * @version 1.0 + * @since 1.0 + */ +object MergingUtils { + /** + * Merges both template and data. + * + * @param template The template object. + * @param map The data map. + * @param output The output path. + * @param cmap The configuration map. + * @return The length of the generated output. + * @throws TemplateMergingException The merging failed. + */ + @JvmStatic + @Throws(TemplateMergingException::class) + @Suppress("TooGenericExceptionCaught") + fun mergeTemplate( + template: Template, + map: Map<String, String?>, + output: Path, + cmap: Map<String, Any> + ): Long { + // create the context map + val context = handle(template, map, cmap) + // create a file writer for the output reference + try { + output.toFile().writer().use { writer -> + // the document is actually read into a string reader + val reader = template.document!!.reader() + // load both runtime services and the template model from Velocity + val services = RuntimeSingleton.getRuntimeServices() + services.addProperty(RuntimeConstants.RUNTIME_LOG_INSTANCE, + NOPLoggerFactory().getLogger("")) + val reference = org.apache.velocity.Template() + // set both runtime services and document data into the template document + reference.setRuntimeServices(services) + reference.data = services.parse(reader, reference) + reference.initDocument() + // create the context based on the data map previously set + val entries = VelocityContext(context) + // merge both template and data into the file writer + reference.merge(entries, writer) + } + } catch (exception: Exception) { + // TODO: simplify + when (exception) { + is IOException, is MethodInvocationException, is ParseErrorException, + is ParseException, is ResourceNotFoundException, is TemplateInitException -> + throw TemplateMergingException("An error occurred while " + + "trying to merge the template reference with the " + + "provided data. Make sure the template is correct " + + "and try again. The raised exception might give us " + + "some hints on what exactly happened. Typically, " + + "make sure the template strictly follows the " + + "Velocity 2.0 language syntax.", exception) + else -> throw TemplateMergingException("Fatal error occured. " + + "This error should never happen. Please make a detailed " + + "report to the developers.") + } + } + // simply return the length of the generated output file + return output.toFile().length() + } + + /** + * Handles the context map. + * + * @param template The template model. + * @param map The context map. + * @param configmap The map from a configuration file. + * @return The new context map. + */ + private fun handle( + template: Template, + map: Map<String, String?>, + configmap: Map<String, Any> + ): Map<String, Any?> { + // no handlers found + return if (template.handlers.isEmpty()) { + // create a new map from the command line map and put the values from + // the configuration file cmap, if absent + configmap.mapValues { it.value.toString() }.plus(map) + } else { + // get default handlers and set the resulting map + val result: MutableMap<String, Any?> = mutableMapOf() + // check each key from the map + map.forEach { (key: String, value: String?) -> + // there is a handler for the current key + if (template.handlers.containsKey(key) && + handlers.containsKey(template.handlers[key])) { + // apply the handler and store the value in the map + result[key] = handlers[template.handlers[key]]!!.apply(value) + } else { + // simply store the value + result[key] = value + // TODO: should we warn about an invalid handler? + } + } + + // put remaining values from the configuration file, if absent + configmap.plus(result) + } + } +} diff --git a/support/texplate/source/main/kotlin/org/islandoftex/texplate/util/MessageUtils.kt b/support/texplate/source/main/kotlin/org/islandoftex/texplate/util/MessageUtils.kt new file mode 100644 index 0000000000..fbb68a819e --- /dev/null +++ b/support/texplate/source/main/kotlin/org/islandoftex/texplate/util/MessageUtils.kt @@ -0,0 +1,72 @@ +// SPDX-License-Identifier: BSD-3-Clause +package org.islandoftex.texplate.util + +import java.time.LocalDate + +/** + * Message helper methods. + * + * @version 1.0 + * @since 1.0 + */ +object MessageUtils { + // the message width + private const val WIDTH = 60 + // the application version + private val VERSION = MessageUtils::class.java.`package`.implementationVersion + ?: "DEVELOPMENT BUILD" + + /** + * Prints a line in the terminal, without a line break. + * + * @param message The message to be printed. + */ + @JvmStatic + fun line(message: String) { + print("$message ".padEnd(WIDTH - " [FAILED]".length, '.') + " ") + } + + /** + * Prints the status in the terminal. + * + * @param result The boolean value. + */ + @JvmStatic + fun status(result: Boolean) { + println(if (result) "[ DONE ]" else "[FAILED]") + } + + /** + * Prints the error in the terminal. + * + * @param throwable The throwable reference. + */ + @JvmStatic + fun error(throwable: Throwable) { + println("\n" + "HOUSTON, WE'VE GOT A PROBLEM ".padEnd(WIDTH, '-') + + "\n" + throwable.message + "\n" + + "".padStart(WIDTH, '-') + "\n") + } + + /** + * Prints the application logo in the terminal. + */ + fun drawLogo() { + println( + " ______ __ __ ___ __ \n" + + "/\\__ _\\ /\\ \\ /\\ \\ /\\_ \\ /\\ \\__ \n" + + "\\/_/\\ \\/ __ \\ `\\`\\/'/' _____\\//\\ \\ __ \\ \\ ,_\\ __ \n" + + " \\ \\ \\ /'__`\\`\\/ > < /\\ '__`\\\\ \\ \\ /'__`\\ \\ \\ \\/ /'__`\\ \n" + + " \\ \\ \\/\\ __/ \\/'/\\`\\\\ \\ \\L\\ \\\\_\\ \\_/\\ \\L\\.\\_\\ \\ \\_/\\ __/ \n" + + " \\ \\_\\ \\____\\ /\\_\\\\ \\_\\ \\ ,__//\\____\\ \\__/.\\_\\\\ \\__\\ \\____\\\n" + + " \\/_/\\/____/ \\/_/ \\/_/\\ \\ \\/ \\/____/\\/__/\\/_/ \\/__/\\/____/\n" + + " \\ \\_\\ \n" + + " \\/_/ \n" + ) + println( + "TeXplate $VERSION, a document structure creation tool\n" + + "Copyright (c) ${LocalDate.now().year}, Island of TeX\n" + + "All rights reserved.\n" + ) + } +} diff --git a/support/texplate/source/main/kotlin/org/islandoftex/texplate/util/PathUtils.kt b/support/texplate/source/main/kotlin/org/islandoftex/texplate/util/PathUtils.kt new file mode 100644 index 0000000000..3a74603557 --- /dev/null +++ b/support/texplate/source/main/kotlin/org/islandoftex/texplate/util/PathUtils.kt @@ -0,0 +1,69 @@ +// SPDX-License-Identifier: BSD-3-Clause +package org.islandoftex.texplate.util + +import java.io.FileNotFoundException +import java.nio.file.Files +import java.nio.file.Path +import java.nio.file.Paths + +/** + * Helper methods for path handling. + * + * @version 1.0 + * @since 1.0 + */ +object PathUtils { + // the templates folder + private const val TEMPLATES_FOLDER = "templates" + // the user application folder + private const val USER_APPLICATION_FOLDER = ".texplate" + + /** + * The user's template path. + */ + private val userTemplatePath: Path + get() = try { + Paths.get(System.getProperty("user.home"), + USER_APPLICATION_FOLDER, TEMPLATES_FOLDER) + } catch (e: RuntimeException) { + Paths.get(".") + } + + /** + * Searches all paths looking for the provided template. + * + * @param name The name to be associated to a template file. + * @return The corresponding template file. + * @throws FileNotFoundException The template file could not be found. + */ + @JvmStatic + @Throws(FileNotFoundException::class) + fun getTemplatePath(name: String): Path { + // the file has to be a TOML format, so we add the extension + val fullName = "$name.toml" + // the first reference is based on the user template path resolved with the + // file name + val reference = userTemplatePath.resolve(fullName) + // if the file actually exists, the search is done! + return if (Files.exists(reference)) { + reference + } else { + // the reference was not found in the user location, so let us try the + // system counterpart + try { + val tempFile = Files.createTempFile(null, null) + tempFile.toFile().writeText(PathUtils::class.java + .getResource("/org/islandoftex/texplate/templates/texplate-$fullName") + .readText()) + tempFile + } catch (e: RuntimeException) { + throw FileNotFoundException("I am sorry, but the template " + + "file '" + fullName + "' could not be found in the " + + "default template locations (system and user). Make " + + "sure the reference is correct and try again. For " + + "reference, these are the paths I searched: '" + + userTemplatePath + "'.") + } + } + } +} diff --git a/support/texplate/source/main/kotlin/org/islandoftex/texplate/util/ValidatorUtils.kt b/support/texplate/source/main/kotlin/org/islandoftex/texplate/util/ValidatorUtils.kt new file mode 100644 index 0000000000..ef91a012c1 --- /dev/null +++ b/support/texplate/source/main/kotlin/org/islandoftex/texplate/util/ValidatorUtils.kt @@ -0,0 +1,54 @@ +// SPDX-License-Identifier: BSD-3-Clause +package org.islandoftex.texplate.util + +import org.islandoftex.texplate.exceptions.InvalidKeySetException +import org.islandoftex.texplate.model.Template + +/** + * Helper methods for validation. + * + * @version 1.0 + * @since 1.0 + */ +object ValidatorUtils { + /** + * Validates the data map based on the template requirements. + * + * @param template The template. + * @param map The data map. + * @return A boolean value indicating whether the data map is valid. + */ + private fun validateRequirements( + template: Template, + map: Map<String, String> + ): Boolean { + return template.requirements.isNullOrEmpty() || + template.requirements.containsAll(map.keys) + } + + /** + * Validates the template pattern and the data map and throws an exception + * in case of failure. + * + * @param template The template. + * @param map The data map. + * @return The data map. + * @throws InvalidKeySetException There are invalid keys in the map. + */ + @JvmStatic + @Throws(InvalidKeySetException::class) + fun validate( + template: Template, + map: Map<String, String> + ): Map<String, String> { + // for starters, we try to validate the template requirements + return if (validateRequirements(template, map)) { + map + } else { + throw InvalidKeySetException("The provided map does not " + + "contain all the keys required by the chosen " + + "template. Make sure to define such keys and try " + + "again. Check the user manual for further details.") + } + } +} |