diff options
author | Norbert Preining <norbert@preining.info> | 2020-02-03 03:02:01 +0000 |
---|---|---|
committer | Norbert Preining <norbert@preining.info> | 2020-02-03 03:02:01 +0000 |
commit | 34897eebd22c3de968b64f09bb4a371d96cb0b89 (patch) | |
tree | 19a50e54fca63f1f6482d6cb4c2fdee676fd97b1 /support/texplate | |
parent | e05dc0bb38b16bfb9ba35591285e40af0adaadc4 (diff) |
CTAN sync 202002030302
Diffstat (limited to 'support/texplate')
31 files changed, 858 insertions, 1439 deletions
diff --git a/support/texplate/README.md b/support/texplate/README.md index 7fea81da09..7428b21519 100644 --- a/support/texplate/README.md +++ b/support/texplate/README.md @@ -1,6 +1,8 @@ # TeXplate -![Language: Kotlin](https://img.shields.io/badge/Language-Kotlin-blue.svg?style=flat-square) ![Minimum JRE: 8.0](https://img.shields.io/badge/Minimum_JRE-8.0-blue.svg?style=flat-square) ![Current version: 1.0.1](https://img.shields.io/badge/Current_version-1.0.1-blue.svg?style=flat-square) +![Language: Kotlin](https://img.shields.io/badge/Language-Kotlin-blue.svg?style=flat-square) +![Minimum JRE: 8.0](https://img.shields.io/badge/Minimum_JRE-8.0-blue.svg?style=flat-square) +![Current version](https://img.shields.io/badge/dynamic/json.svg?color=blue&label=Latest%20release&query=%24.0.name&url=https%3A%2F%2Fgitlab.com%2Fapi%2Fv4%2Fprojects%2F13793875%2Frepository%2Ftags&style=flat-square) TeXplate is a tool for creating document structures based on templates. The application name is a word play on _TeX_ and _template_, so the purpose seems quite obvious: we want to provide an easy and straightforward framework for reducing the typical code boilerplate when writing TeX documents. Also note that one can easily extrapolate the use beyond articles and theses: the application is powerful enough to generate _any_ text-based structure, given that a corresponding template exists. @@ -20,7 +22,7 @@ $ texplate \ \_\ \/_/ -TeXplate 1.0.1, a document structure creation tool +TeXplate 1.0.2, a document structure creation tool Copyright (c) 2020, Island of TeX All rights reserved. @@ -83,7 +85,7 @@ $ texplate -t article -o doc1.tex \ \_\ \/_/ -TeXplate 1.0.1, a document structure creation tool +TeXplate 1.0.2, a document structure creation tool Copyright (c) 2020, Island of TeX All rights reserved. diff --git a/support/texplate/doc/texplate-manual.pdf b/support/texplate/doc/texplate-manual.pdf Binary files differindex 98d06b0ca4..19afbe5c8f 100644 --- a/support/texplate/doc/texplate-manual.pdf +++ b/support/texplate/doc/texplate-manual.pdf diff --git a/support/texplate/doc/texplate-manual.tex b/support/texplate/doc/texplate-manual.tex index 6fe19fb5e4..56dbe5e5f3 100644 --- a/support/texplate/doc/texplate-manual.tex +++ b/support/texplate/doc/texplate-manual.tex @@ -17,7 +17,7 @@ \newcommand{\shortopt}[1]{{\ttfamily-#1}} \newcommand{\longopt}[1]{{\ttfamily{-}{-}#1}} \newcommand{\macro}[1]{{\ttfamily\textbackslash#1}} -\newcommand{\texplateversion}{1.0.1} +\newcommand{\texplateversion}{1.0.2} \title{A gentle introduction to \texplate:\\ a document structure creation tool} \author{Island of \TeX} @@ -242,7 +242,7 @@ For starters, a template has an identifier, a name that logically represents the Observe that \texplate's lookup system is case-sensitive, so make sure to always reference the correct name. \end{disclaimer} -Although there are no hard limitations on a template name, it is advisable to keep it short and concise, with no spaces whatsoever\footnote{Another Carlislean statement would remind us that people that put spaces in their file names deserve no sympathy. At all.}. As a consequence, potential issues with extended characters in the Unicode range and command line escaping are avoided, and thus our beloved tool might just work as expected. +Although there are no hard limitations on a template name, it is advisable to keep it short and concise, with no spaces whatsoever\footnote{Another Carlislean statement would remind us that people who put spaces in their file names deserve no sympathy. At all.}. As a consequence, potential issues with extended characters in the Unicode range and command line escaping are avoided, and thus our beloved tool might just work as expected. \section{Directory lookup} \label{sec:directorylookup} @@ -252,7 +252,7 @@ Although there are no hard limitations on a template name, it is advisable to ke \begin{enumerate} \item\inline{\textasciitilde/.texplate/templates} which refers to a path structure from the user home directory. -\item\inline{<APP>/templates} which refers to a path structure from the application directory (with \inline{<APP>} denoting the location where \texplate\ is installed). +\item The application's resources which refers to a files within the JAR file. You can use a ZIP viewer to look at the templates there. \end{enumerate} For instance, as a means to illustrate the concept of directory lookup, consider the following command line command: @@ -396,7 +396,7 @@ The output (i.e., the template merged with the provided data) will be written to \item When running \texplate, this is the expected output to be displayed in the command line (note that the layout is slightly modified due to space constraints in this user manual): \begin{code} -TeXplate 1.0.1, a document structure creation tool +TeXplate 1.0.2, a document structure creation tool Copyright (c) 2020, Island of TeX All rights reserved. @@ -597,8 +597,42 @@ gitlab.com/islandoftex/texplate Happy \TeX ing with \texplate! -\section*{License} +\chapter*{License} \texplate\ is licensed under the New BSD License. Please note that the New BSD License has been verified as a GPL-compatible free software license by the Free Software Foundation, and has been vetted as an open source license by the Open Source Initiative. +\chapter*{Changelog} + +\section*{1.0.2 (current)} + +\subsection*{Fixed} + +\begin{itemize} +\item \texplate\ now finds its templates even on Windows. +\end{itemize} + +\subsection*{Changed} + +\begin{itemize} +\item \texplate\ now finishes its transition to Kotlin. We did not change any functionality in the course of this change. +\item Templates are now provided as resources from the JAR instead of a separate folder on the hard drive. +\end{itemize} + +\section*{1.0.1 (2020-01-17)} + +\subsection*{Changed} + +\begin{itemize} +\item \texplate\ will now distribute only non-generic template file names. In the system's template directory, we search for \inline{texplate-<name>.toml} as well. +\end{itemize} + +\section*{1.0.0 (2020-01-15)} + +\subsection*{Added} + +\begin{itemize} +\item Base functionality and default templates. +\item User manual. +\end{itemize} + \end{document} diff --git a/support/texplate/source/main/java/org/islandoftex/texplate/Main.java b/support/texplate/source/main/java/org/islandoftex/texplate/Main.java deleted file mode 100644 index 5c27aab23c..0000000000 --- a/support/texplate/source/main/java/org/islandoftex/texplate/Main.java +++ /dev/null @@ -1,36 +0,0 @@ -// SPDX-License-Identifier: BSD-3-Clause -package org.islandoftex.texplate; - -import org.islandoftex.texplate.model.TemplateProcessing; -import org.islandoftex.texplate.util.MessageUtils; -import picocli.CommandLine; - -/** - * The main class. The application logic is enclosed in the template processing - * class. - * - * @version 1.0 - * @since 1.0 - */ -public class Main { - - /** - * Main method. Note that it simply passes the control to the template - * processing class. - * - * @param args The command line arguments. - */ - public static void main(String[] args) { - - // 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 - int exitCode = new CommandLine(new TemplateProcessing()).execute(args); - System.exit(exitCode); - } - -} diff --git a/support/texplate/source/main/java/org/islandoftex/texplate/model/Configuration.java b/support/texplate/source/main/java/org/islandoftex/texplate/model/Configuration.java deleted file mode 100644 index a0f8e83702..0000000000 --- a/support/texplate/source/main/java/org/islandoftex/texplate/model/Configuration.java +++ /dev/null @@ -1,150 +0,0 @@ -// SPDX-License-Identifier: BSD-3-Clause -package org.islandoftex.texplate.model; - -import com.moandjiezana.toml.Toml; -import io.vavr.control.Try; -import org.islandoftex.texplate.exceptions.InvalidTemplateException; - -import java.nio.file.Path; -import java.util.Map; - -/** - * The configuration model. - * - * @version 1.0 - * @since 1.0 - */ -public class Configuration { - - // template of the template - private String template; - - // map of variables for the configuration - private Map<String, Object> map; - - /** - * Constructor. - */ - public Configuration() { - } - - /** - * Constructor. - * - * @param template The template. - * @param map The map. - */ - public Configuration(String template, Map<String, Object> map) { - this.template = template; - this.map = map; - } - - /** - * Gets the template. - * - * @return The template. - */ - public String getTemplate() { - return template; - } - - /** - * Sets the template. - * - * @param template The template. - */ - public void setTemplate(String template) { - this.template = template; - } - - /** - * Gets the map. - * - * @return The map. - */ - public Map<String, Object> getMap() { - return map; - } - - /** - * Sets the map. - * - * @param map The map. - */ - public void setMap(Map<String, Object> map) { - this.map = map; - } - - /** - * Checks whether the configuration is valid. - * - * @return A boolean value indicating whether the configuration is valid. - */ - private boolean isValid() { - return !(template == null || map == null); - } - - /** - * Reads the configuration from path. - * - * @param path The path. - * @return Configuration. - * @throws InvalidTemplateException The configuration is invalid. - */ - private static Configuration readFromPath(Path path) - throws InvalidTemplateException { - - // the actual configuration - Configuration configuration; - - // the default message - String message = "The provided configuration file looks invalid. " - + "Please make sure the configuration has a valid syntax and " - + "try again. "; - - try { - - // gets the configuration - configuration = new Toml().read(path.toFile()). - to(Configuration.class); - - } catch (Exception exception) { - - // the configuration - // seems invalid - throw new 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); - } - - // checks whether the - // configuration is valid - if (configuration.isValid()) { - - // returns the configuration - return configuration; - } else { - - // the configuration - // is invalid - throw new 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."); - } - } - - /** - * Gets the configuration from a path. - * - * @param path The path. - * @return The configuration. - */ - public static Try<Configuration> of(Path path) { - return Try.of(() -> readFromPath(path)); - } -} diff --git a/support/texplate/source/main/java/org/islandoftex/texplate/model/Template.java b/support/texplate/source/main/java/org/islandoftex/texplate/model/Template.java deleted file mode 100644 index ca92e04658..0000000000 --- a/support/texplate/source/main/java/org/islandoftex/texplate/model/Template.java +++ /dev/null @@ -1,252 +0,0 @@ -// SPDX-License-Identifier: BSD-3-Clause -package org.islandoftex.texplate.model; - -import com.moandjiezana.toml.Toml; -import io.vavr.control.Try; -import org.islandoftex.texplate.exceptions.InvalidTemplateException; - -import java.nio.file.Path; -import java.util.List; -import java.util.Map; - -/** - * The template model. - * - * @version 1.0 - * @since 1.0 - */ -public class Template { - - // name of the template - private String name; - - // description of the template - private String description; - - // list of authors who wrote the template - private List<String> authors; - - // list of requirements for the template - private List<String> requirements; - - // the document to be configured - private String document; - - // the map handlers - private Map<String, String> handlers; - - /** - * Constructor. - */ - public Template() { - } - - /** - * Constructor. - * - * @param name Name of the template. - * @param description Description of the template. - * @param authors List of authors of the template. - * @param requirements List of requirements for the template. - * @param document The document to be configured. - * @param handlers The optional map handlers. - */ - public Template(String name, String description, List<String> authors, - List<String> requirements, String document, - Map<String, String> handlers) { - this.name = name; - this.description = description; - this.authors = authors; - this.requirements = requirements; - this.document = document; - this.handlers = handlers; - } - - /** - * Gets the template name. - * - * @return The template name. - */ - public String getName() { - return name; - } - - /** - * Sets the template name. - * - * @param name The template name. - */ - public void setName(String name) { - this.name = name; - } - - /** - * Gets the template description. - * - * @return The template description. - */ - public String getDescription() { - return description; - } - - /** - * Sets the template description. - * - * @param description The template description. - */ - public void setDescription(String description) { - this.description = description; - } - - /** - * Gets the template authors. - * - * @return The template authors. - */ - public List<String> getAuthors() { - return authors; - } - - /** - * Sets the template authors. - * - * @param authors The template authors. - */ - public void setAuthors(List<String> authors) { - this.authors = authors; - } - - /** - * Gets the template requirements. - * - * @return The template requirements. - */ - public List<String> getRequirements() { - return requirements; - } - - /** - * Sets the template requirements. - * - * @param requirements The template requirements. - */ - public void setRequirements(List<String> requirements) { - this.requirements = requirements; - } - - /** - * Gets the template document. - * - * @return The template document. - */ - public String getDocument() { - return document; - } - - /** - * Sets the template document. - * - * @param document The template document. - */ - public void setDocument(String document) { - this.document = document; - } - - /** - * Gets the map handlers. - * - * @return The map handlers. - */ - public Map<String, String> getHandlers() { - return handlers; - } - - /** - * Sets the map handlers. - * - * @param handlers The map handlers. - */ - public void setHandlers(Map<String, String> handlers) { - this.handlers = handlers; - } - - /** - * Checks whether the template is valid. - * - * @return A boolean value indicating whether the template is valid. - */ - private boolean isValid() { - return !((name == null) || (description == null) - || (authors == null) || (requirements == null) - || (document == null) || name.trim().isEmpty() - || description.trim().isEmpty() || authors.isEmpty() - || document.trim().isEmpty()); - } - - /** - * 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. - */ - private static Template readFromPath(Path path) - throws InvalidTemplateException { - - // the actual template - Template template; - - // the exception message, in case the - // conversion fails or if there are - // missing elements from the template - String message = "The provided template file looks invalid. Please " - + "make sure the template has a valid syntax and try again. "; - - try { - - // reads the file and converts - // the TOML format into the object - template = new Toml().read(path.toFile()).to(Template.class); - - } catch (Exception exception) { - - // throws the new exception and - // attaches the original cause - throw new 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 - if (template.isValid()) { - - // everything went fine, so - // simply return the template - return template; - } else { - - // the template is invalid, so we - // need to throw an exception - throw new 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."); - } - } - - /** - * Returns the template from the provided path. - * - * @param path The path in which the template is retrieved. - * @return The corresponding template, enclosed in a Try object. - */ - public static Try<Template> of(Path path) { - return Try.of(() -> readFromPath(path)); - } - -} diff --git a/support/texplate/source/main/java/org/islandoftex/texplate/model/TemplateProcessing.java b/support/texplate/source/main/java/org/islandoftex/texplate/model/TemplateProcessing.java deleted file mode 100644 index eccaf6c732..0000000000 --- a/support/texplate/source/main/java/org/islandoftex/texplate/model/TemplateProcessing.java +++ /dev/null @@ -1,391 +0,0 @@ -// SPDX-License-Identifier: BSD-3-Clause -package org.islandoftex.texplate.model; - -import io.vavr.control.Either; -import org.islandoftex.texplate.util.MergingUtils; -import org.islandoftex.texplate.util.MessageUtils; -import org.islandoftex.texplate.util.PathUtils; -import org.islandoftex.texplate.util.ValidatorUtils; -import picocli.CommandLine; -import picocli.CommandLine.Option; - -import java.nio.file.Path; -import java.util.HashMap; -import java.util.Map; -import java.util.concurrent.Callable; - -/** - * The template processing class. - * - * @version 1.0 - * @since 1.0 - */ -@CommandLine.Command( - usageHelpWidth = 70, - name = "texplate" -) -public class TemplateProcessing implements Callable<Integer> { - - // the file output, which will hold the - // result of the merging of both template - // and context data map from command line - @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 Path output; - - // the template name - @Option( - names = {"-t", "--template"}, - description = "The template and. The tool will " - + "search both user and system locations and set the " - + "template model accordingly, based on your specs." - ) - private String template; - - // the context data map that holds - // a set of key/value pairs to be - // merged with the template - @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 Map<String, String> map; - - @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 Path configuration; - - /** - * 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. - */ - @Override - public Integer call() throws Exception { - - // the exit status, originally - // set as a valid value - int exit = 0; - - // configuration halt flag, indicating - // whether the tool has to end earlier - boolean halt = false; - - Map<String, Object> cmap = new HashMap<>(); - - // ensure the context data map - // is at least instantiated - ensureMap(); - - // there is a configuration file - // found in the command line - if (has(configuration)) { - - // print the configuration - // check line for status - MessageUtils.line("Checking configuration"); - - // try to read the configuration - // file into a proper object - Either<Throwable, Configuration> configChecking = Configuration. - of(configuration).toEither(); - - // the configuration file - // seems to be valid, proceed - if (configChecking.isRight()) { - - // print status line - MessageUtils.status(true); - - // get the configuration file - Configuration config = configChecking.get(); - - // check if the configuration - // has a proper template - if (has(config.getTemplate())) { - - // if so, build the template if, and - // only if, there's no one already - // set through command line - template = ensure(template, config.getTemplate()); - } - - // check if the configuration - // has a proper string/string map - if (has(config.getMap())) { - - // set the main configuration - // map to be dealt later on - cmap = config.getMap(); - } - - // print header about tidying - // up configuration variables - MessageUtils.line("Adjusting variables from file"); - MessageUtils.status(true); - System.out.println(); - - } else { - - // an error occurred, print it - // set exit code and halt - MessageUtils.status(false); - MessageUtils.error(configChecking.getLeft()); - exit = -1; - halt = true; - } - } else { - - // print header regarding - // no config file found - MessageUtils.line("Configuration file mode disabled"); - MessageUtils.status(true); - - // print a header regarding - // full command line mode - MessageUtils.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)) { - - // print status - MessageUtils.status(false); - - // print message - MessageUtils.error(new 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 { - - // print status - MessageUtils.status(true); - System.out.println(); - } - - } - - // check whether we should - // halt prematurely - if (!halt) { - - // initial message, preparing our - // hearts to the actual merging :) - System.out.println("Please, wait..."); - System.out.println(); - - // now we need to obtain the actual - // template from a file stored either - // in the user home or in the system - MessageUtils.line("Obtaining reference"); - - // let us try to get the corresponding - // file from the template pattern - Either<Throwable, Path> fileChecking = PathUtils. - getTemplatePath(template).toEither(); - - // the actual template file was - // found, so we can proceed to - // the next phase - if (fileChecking.isRight()) { - - // updates the current - // status accordingly - MessageUtils.status(true); - - // now it's time to compose the template - // object from its corresponding file - MessageUtils.line("Composing template"); - - // attempts to retrieve the template - // attributes from the referenced file - // to the actual template object - Either<Throwable, Template> templateComposition - = Template.of(fileChecking.get()).toEither(); - - // the template composition was successful, - // so we can move on to the next phase - if (templateComposition.isRight()) { - - // updates the current - // status accordingly - MessageUtils.status(true); - - // once the template object is populated, - // we need to verify if both template and - // data map are not somehow conflicting - MessageUtils.line("Validating data"); - Either<Throwable, Map<String, String>> dataValidation - = ValidatorUtils.validate(templateComposition.get(), - map).toEither(); - - // the data validation was consistent, - // so now the merging can be applied - if (dataValidation.isRight()) { - - // updates the current - // status accordingly - MessageUtils.status(true); - - // now it's the final phase, in which - // both template and data are merged - MessageUtils.line("Merging template and data"); - - // merge both template and context data - // map - Either<Throwable, Long> merging = MergingUtils. - merge(templateComposition.get(), - dataValidation.get(), - output, cmap).toEither(); - - // the merging was successful, - // so now there's nothing else - // to do, yay! - if (merging.isRight()) { - - // updates the current - // status accordingly - MessageUtils.status(true); - - // print the final messge and - // tell the user everything - // went smooth! - System.out.println(); - System.out.println("Done! Enjoy your template!"); - System.out.println("Written: " - + getSize(merging.get())); - } else { - - // updates the current - // status accordingly - MessageUtils.status(false); - - // the merging failed, so the - // exception is displayed and - // the exit status is updated - MessageUtils.error(merging.getLeft()); - exit = -1; - } - } else { - - // updates the current - // status accordingly - MessageUtils.status(false); - - // the data validation failed, so - // the exception is displayed and - // the exit status is updated - MessageUtils.error(dataValidation.getLeft()); - exit = -1; - } - } else { - - // updates the current - // status accordingly - MessageUtils.status(false); - - // the template composition failed, - // so the exception is displayed and - // the exit status is updated - MessageUtils.error(templateComposition.getLeft()); - exit = -1; - } - } else { - - // updates the current - // status accordingly - MessageUtils.status(false); - - // the file checking failed, so - // the exception is displayed and - // the exit status is updated - MessageUtils.error(fileChecking.getLeft()); - 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 void ensureMap() { - - // if the map is null, simply - // create a new instance - if (!has(map)) { - map = new HashMap<>(); - } - } - - /** - * 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. - */ - private String getSize(long bytes) { - if (bytes < 1024) { - return bytes + " B"; - } else { - int exponent = (int) (Math.log(bytes) / Math.log(1024)); - return String.format("%.1f %cB", bytes / Math.pow(1024, exponent), - "KMGTPE".charAt(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. - */ - private <T> T ensure(T first, T second) { - return !has(first) ? second : first; - } - - /** - * Checks whether the object exists. - * - * @param object The objects. - * @return Boolean value indicating whether the object exists. - */ - private boolean has(Object object) { - return object != null; - } - -} diff --git a/support/texplate/source/main/java/org/islandoftex/texplate/model/handlers/BooleanHandler.java b/support/texplate/source/main/java/org/islandoftex/texplate/model/handlers/BooleanHandler.java deleted file mode 100644 index 461b9fd170..0000000000 --- a/support/texplate/source/main/java/org/islandoftex/texplate/model/handlers/BooleanHandler.java +++ /dev/null @@ -1,26 +0,0 @@ -// SPDX-License-Identifier: BSD-3-Clause -package org.islandoftex.texplate.model.handlers; - -import java.util.Arrays; - -/** - * Implements a boolean handler. - * - * @version 1.0 - * @since 1.0 - */ -public class BooleanHandler implements Handler { - - /** - * Applies the conversion to the string. - * - * @param string The string. - * @return A list. - */ - @Override - public Object apply(String string) { - return Arrays.asList("true", "1", "yes") - .contains(string.toLowerCase()); - } - -} diff --git a/support/texplate/source/main/java/org/islandoftex/texplate/model/handlers/CSVListHandler.java b/support/texplate/source/main/java/org/islandoftex/texplate/model/handlers/CSVListHandler.java deleted file mode 100644 index 4b3d0c2e69..0000000000 --- a/support/texplate/source/main/java/org/islandoftex/texplate/model/handlers/CSVListHandler.java +++ /dev/null @@ -1,30 +0,0 @@ -// SPDX-License-Identifier: BSD-3-Clause -package org.islandoftex.texplate.model.handlers; - -import java.util.Arrays; -import java.util.stream.Collectors; - -/** - * Implements a CSV list handler. - * - * @version 1.0 - * @since 1.0 - */ -public class CSVListHandler implements Handler { - - /** - * Applies the conversion to the string. - * - * @param string The string. - * @return A list. - */ - @Override - public Object apply(String string) { - return Arrays.stream(string.split(",(?=(?:[^\"]*\"" - + "[^\"]*\")*[^\"]*$)")) - .map(String::trim) - .filter(s -> !s.isEmpty()) - .collect(Collectors.toList()); - } - -} diff --git a/support/texplate/source/main/java/org/islandoftex/texplate/util/HandlerUtils.java b/support/texplate/source/main/java/org/islandoftex/texplate/util/HandlerUtils.java deleted file mode 100644 index 7ab01f7328..0000000000 --- a/support/texplate/source/main/java/org/islandoftex/texplate/util/HandlerUtils.java +++ /dev/null @@ -1,31 +0,0 @@ -// SPDX-License-Identifier: BSD-3-Clause -package org.islandoftex.texplate.util; - -import java.util.HashMap; -import java.util.Map; - -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 - */ -public class HandlerUtils { - - /** - * Gets the map of handlers. - * - * @return Map of handlers. - */ - public static Map<String, Handler> getHandlers() { - Map<String, Handler> handlers = new HashMap<>(); - handlers.put("to-csv-list", new CSVListHandler()); - handlers.put("to-boolean", new BooleanHandler()); - return handlers; - } - -} diff --git a/support/texplate/source/main/java/org/islandoftex/texplate/util/MergingUtils.java b/support/texplate/source/main/java/org/islandoftex/texplate/util/MergingUtils.java deleted file mode 100644 index d7ffbddc4d..0000000000 --- a/support/texplate/source/main/java/org/islandoftex/texplate/util/MergingUtils.java +++ /dev/null @@ -1,186 +0,0 @@ -// SPDX-License-Identifier: BSD-3-Clause -package org.islandoftex.texplate.util; - -import io.vavr.control.Try; -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.RuntimeServices; -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.model.handlers.Handler; -import org.slf4j.helpers.NOPLoggerFactory; - -import java.io.FileWriter; -import java.io.IOException; -import java.io.StringReader; -import java.io.Writer; -import java.nio.file.Path; -import java.util.HashMap; -import java.util.Map; - -/** - * Merging utilities. - * - * @version 1.0 - * @since 1.0 - */ -public class 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. - */ - private static long mergeTemplate(Template template, - Map<String, String> map, Path output, - Map<String, Object> cmap) - throws TemplateMergingException { - - // create the context map - Map<String, Object> context = handle(template, map, cmap); - - // create a file writer for - // the output reference - try (Writer writer = new FileWriter(output.toFile())) { - - // the document is actually read - // into a string reader - StringReader reader = new StringReader(template.getDocument()); - - // load both runtime services and - // the template model from Velocity - RuntimeServices services = RuntimeSingleton.getRuntimeServices(); - services.addProperty(RuntimeConstants.RUNTIME_LOG_INSTANCE, - new NOPLoggerFactory().getLogger("")); - org.apache.velocity.Template reference - = new org.apache.velocity.Template(); - - // set both runtime services - // and document data into the - // template document - reference.setRuntimeServices(services); - reference.setData(services.parse(reader, reference)); - reference.initDocument(); - - // create the context based on - // the data map previously set - VelocityContext entries = new VelocityContext(context); - - // merge both template and data - // into the file writer - reference.merge(entries, writer); - - } catch (IOException | MethodInvocationException - | ParseErrorException | ParseException - | ResourceNotFoundException - | TemplateInitException exception) { - - // an exception has happened, so a - // new exception is thrown, attaching - // the original throwable cause - throw new 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); - } - - // simply return the length of - // the generated output file - return output.toFile().length(); - } - - /** - * Merges template and data. - * - * @param template The template model. - * @param map The data map. - * @param output The path reference. - * @param cmap The configuration map. - * @return The length of the generated output, enclosed as a Try object. - */ - public static Try<Long> merge(Template template, Map<String, String> map, - Path output, Map<String, Object> cmap) { - return Try.of(() -> mergeTemplate(template, map, output, cmap)); - } - - /** - * Handles the context map. - * - * @param template The template model. - * @param map The context map. - * @return The new context map. - */ - private static Map<String, Object> handle(Template template, - Map<String, String> map, - Map<String, Object> cmap) { - - // no handlers found - if (template.getHandlers() == null) { - - // create a new map from the - // command line map and put the - // values from the configuration - // file, if absent - Map<String, Object> result = new HashMap<>(map); - cmap.forEach(result::putIfAbsent); - return result; - - } else { - - // get default handlers and - // set the resulting map - Map<String, Handler> handlers = HandlerUtils.getHandlers(); - Map<String, Object> result = new HashMap<>(); - - // check each key from the map - map.forEach((String key, String value) -> { - - // there is a handler for - // the current key - if (template.getHandlers().containsKey(key)) { - - // the handler seems valid - if (handlers.containsKey(template.getHandlers().get(key))) { - - // apply the handler and - // store the value in the map - result.put(key, handlers.get(template. - getHandlers().get(key)).apply(value)); - } else { - - // simply store the value - result.put(key, value); - // TODO: should we warn about an invalid handler? - } - } else { - - // simply store the value - result.put(key, value); - } - }); - - // put remaining values from - // the configuration file, if - // absent - cmap.forEach(result::putIfAbsent); - - // return the map - return result; - } - } - -} diff --git a/support/texplate/source/main/java/org/islandoftex/texplate/util/MessageUtils.java b/support/texplate/source/main/java/org/islandoftex/texplate/util/MessageUtils.java deleted file mode 100644 index 7649a60e82..0000000000 --- a/support/texplate/source/main/java/org/islandoftex/texplate/util/MessageUtils.java +++ /dev/null @@ -1,84 +0,0 @@ -// SPDX-License-Identifier: BSD-3-Clause -package org.islandoftex.texplate.util; - -import org.apache.commons.text.TextStringBuilder; -import org.apache.commons.text.WordUtils; - -import java.time.LocalDate; - -/** - * Message helper methods. - * - * @version 1.0 - * @since 1.0 - */ -public class MessageUtils { - - // the message width - private static final int WIDTH = 60; - - // the application version - private static final String VERSION = "1.0.1"; - - /** - * Prints a line in the terminal, without a line break. - * - * @param message The message to be printed. - */ - public static void line(String message) { - System.out.print(new TextStringBuilder() - .appendFixedWidthPadRight(message.concat(" "), WIDTH - 9, '.') - .append(" ") - .toString() - ); - } - - /** - * Prints the status in the terminal. - * - * @param result The boolean value. - */ - public static void status(boolean result) { - System.out.println(result ? "[ DONE ]" : "[FAILED]"); - } - - /** - * Prints the error in the terminal. - * - * @param throwable The throwable reference. - */ - public static void error(Throwable throwable) { - System.out.println( - new TextStringBuilder("\n") - .appendFixedWidthPadRight("HOUSTON, WE'VE GOT" - + " A PROBLEM ", WIDTH, '-') - .append("\n") - .appendln(WordUtils.wrap(throwable.getMessage(), WIDTH)) - .appendFixedWidthPadLeft("", WIDTH, '-') - .append("\n") - .toString()); - } - - /** - * Prints the application logo in the terminal. - */ - public static void drawLogo() { - System.out.println( - " ______ __ __ ___ __ \n" + - "/\\__ _\\ /\\ \\ /\\ \\ /\\_ \\ /\\ \\__ \n" + - "\\/_/\\ \\/ __ \\ `\\`\\/'/' _____\\//\\ \\ __ \\ \\ ,_\\ __ \n" + - " \\ \\ \\ /'__`\\`\\/ > < /\\ '__`\\\\ \\ \\ /'__`\\ \\ \\ \\/ /'__`\\ \n" + - " \\ \\ \\/\\ __/ \\/'/\\`\\\\ \\ \\L\\ \\\\_\\ \\_/\\ \\L\\.\\_\\ \\ \\_/\\ __/ \n" + - " \\ \\_\\ \\____\\ /\\_\\\\ \\_\\ \\ ,__//\\____\\ \\__/.\\_\\\\ \\__\\ \\____\\\n" + - " \\/_/\\/____/ \\/_/ \\/_/\\ \\ \\/ \\/____/\\/__/\\/_/ \\/__/\\/____/\n" + - " \\ \\_\\ \n" + - " \\/_/ \n" - ); - System.out.println( - "TeXplate " + VERSION + ", a document structure creation tool\n" + - "Copyright (c) " + LocalDate.now().getYear() + ", Island of TeX\n" + - "All rights reserved.\n" - ); - } - -} diff --git a/support/texplate/source/main/java/org/islandoftex/texplate/util/PathUtils.java b/support/texplate/source/main/java/org/islandoftex/texplate/util/PathUtils.java deleted file mode 100644 index fa242c4980..0000000000 --- a/support/texplate/source/main/java/org/islandoftex/texplate/util/PathUtils.java +++ /dev/null @@ -1,129 +0,0 @@ -// SPDX-License-Identifier: BSD-3-Clause -package org.islandoftex.texplate.util; - -import io.vavr.control.Try; -import org.islandoftex.texplate.Main; - -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 - */ -public class PathUtils { - - // the templates folder - private static final String TEMPLATES_FOLDER = "templates"; - - // the user application folder - private static final String USER_APPLICATION_FOLDER = ".texplate"; - - /** - * Gets the application path. - * - * @return The application path. - */ - private static Path getApplicationPath() { - return Try.of(() -> Paths.get(Main.class.getProtectionDomain(). - getCodeSource().getLocation().toURI().getPath()).resolve(".."). - normalize()).getOrElse(Paths.get(".")); - } - - /** - * Gets the default template path. - * - * @return The default template path. - */ - private static Path getDefaultTemplatePath() { - return getApplicationPath().resolve(TEMPLATES_FOLDER); - } - - /** - * Gets the user template path. - * - * @return The user template path. - */ - private static Path getUserTemplatePath() { - return Try.of(() -> Paths.get(System.getProperty("user.home"), - USER_APPLICATION_FOLDER, TEMPLATES_FOLDER)). - getOrElse(Paths.get(".")); - } - - /** - * Searchs 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. - */ - private static Path searchTemplatePath(String name) - throws FileNotFoundException { - // the file has to be a TOML format, - // so we add the extension - name = name.concat(".toml"); - - // the first reference is based on the - // user template path resolved with the - // file name - Path reference = getUserTemplatePath().resolve(name); - - // if the file actually exists, - // the search is done! - if (Files.exists(reference)) { - // return the template - // file reference - return reference; - - } else { - // the reference was not found in the - // user location, so let us try the - // system counterpart - reference = getDefaultTemplatePath().resolve(name); - - // if the file actually exists, - // the search is done! - if (Files.exists(reference)) { - // return the template - // file reference - return reference; - - } else { - // in the system path, look for an `texplate` prefixed - // version as well - reference = getDefaultTemplatePath().resolve("texplate-" + name); - - if (Files.exists(reference)) { - // return the template - // file reference - return reference; - } else { - // the file reference could not be - // found, so an exception is thrown - throw new FileNotFoundException("I am sorry, but the template " - + "file '" + name + "' 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: '" - + getUserTemplatePath() + "' and '" - + getDefaultTemplatePath() + "' (in this order)."); - } - } - } - } - - /** - * Gets the template path based on the provided template name. - * - * @param name The template name. - * @return The template path, enclosed in a Try object. - */ - public static Try<Path> getTemplatePath(String name) { - return Try.of(() -> searchTemplatePath(name)); - } - -} diff --git a/support/texplate/source/main/java/org/islandoftex/texplate/util/ValidatorUtils.java b/support/texplate/source/main/java/org/islandoftex/texplate/util/ValidatorUtils.java deleted file mode 100644 index b400731242..0000000000 --- a/support/texplate/source/main/java/org/islandoftex/texplate/util/ValidatorUtils.java +++ /dev/null @@ -1,75 +0,0 @@ -// SPDX-License-Identifier: BSD-3-Clause -package org.islandoftex.texplate.util; - -import io.vavr.control.Try; -import org.islandoftex.texplate.exceptions.InvalidKeySetException; -import org.islandoftex.texplate.model.Template; - -import java.util.Map; - -/** - * Helper methods for validation. - * - * @version 1.0 - * @since 1.0 - */ -public class 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 static boolean validateRequirements(Template template, - Map<String, String> map) { - return template.getRequirements().isEmpty() || - template.getRequirements().containsAll(map.keySet()); - } - - /** - * 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. - */ - private static Map<String, String> checkValidation(Template template, - Map<String, String> map) - throws InvalidKeySetException { - - // for starters, we try to validate - // the template requirements - if (validateRequirements(template, map)) { - - // everything is validated, so - // we simply return the map - return map; - - } else { - - // the requirements were missing, - // so an exception is thrown - throw new 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."); - } - } - - /** - * Validates the template pattern and the data map. - * - * @param template The template. - * @param map The data map. - * @return The data map, enclosed in a Try object. - */ - public static Try<Map<String, String>> validate(Template template, - Map<String, String> map) { - return Try.of(() -> checkValidation(template, map)); - } - -} 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/java/org/islandoftex/texplate/exceptions/InvalidKeySetException.java b/support/texplate/source/main/kotlin/org/islandoftex/texplate/exceptions/InvalidKeySetException.kt index 3d113a2599..72ff3bcbd4 100644 --- a/support/texplate/source/main/java/org/islandoftex/texplate/exceptions/InvalidKeySetException.java +++ b/support/texplate/source/main/kotlin/org/islandoftex/texplate/exceptions/InvalidKeySetException.kt @@ -1,5 +1,5 @@ // SPDX-License-Identifier: BSD-3-Clause -package org.islandoftex.texplate.exceptions; +package org.islandoftex.texplate.exceptions /** * Handles exceptions when the context map contains invalid keys. @@ -7,22 +7,18 @@ package org.islandoftex.texplate.exceptions; * @version 1.0 * @since 1.0 */ -public class InvalidKeySetException extends Exception { - +class InvalidKeySetException : Exception { /** * Constructor. */ - public InvalidKeySetException() { - } + constructor() /** * Constructor. * * @param message Message to be attached to the exception. */ - public InvalidKeySetException(String message) { - super(message); - } + constructor(message: String?) : super(message) /** * Constructor. @@ -30,8 +26,5 @@ public class InvalidKeySetException extends Exception { * @param message Message to be attached to the exception. * @param cause The throwable cause to be forwarded. */ - public InvalidKeySetException(String message, Throwable cause) { - super(message, cause); - } - + constructor(message: String?, cause: Throwable?) : super(message, cause) } diff --git a/support/texplate/source/main/java/org/islandoftex/texplate/exceptions/InvalidTemplateException.java b/support/texplate/source/main/kotlin/org/islandoftex/texplate/exceptions/InvalidTemplateException.kt index 73c8276998..678c390055 100644 --- a/support/texplate/source/main/java/org/islandoftex/texplate/exceptions/InvalidTemplateException.java +++ b/support/texplate/source/main/kotlin/org/islandoftex/texplate/exceptions/InvalidTemplateException.kt @@ -1,5 +1,5 @@ // SPDX-License-Identifier: BSD-3-Clause -package org.islandoftex.texplate.exceptions; +package org.islandoftex.texplate.exceptions /** * Handles exceptions when the template is somehow invalid. @@ -7,19 +7,15 @@ package org.islandoftex.texplate.exceptions; * @version 1.0 * @since 1.0 */ -public class InvalidTemplateException extends Exception { - - public InvalidTemplateException() { - } +class InvalidTemplateException : Exception { + constructor() /** * Constructor. * * @param message Message to be attached to the exception. */ - public InvalidTemplateException(String message) { - super(message); - } + constructor(message: String?) : super(message) /** * Constructor. @@ -27,8 +23,5 @@ public class InvalidTemplateException extends Exception { * @param message Message to be attached to the exception. * @param cause The throwable cause to be forwarded. */ - public InvalidTemplateException(String message, Throwable cause) { - super(message, cause); - } - + constructor(message: String?, cause: Throwable?) : super(message, cause) } diff --git a/support/texplate/source/main/java/org/islandoftex/texplate/exceptions/TemplateMergingException.java b/support/texplate/source/main/kotlin/org/islandoftex/texplate/exceptions/TemplateMergingException.kt index 86dd99af00..e1760f8732 100644 --- a/support/texplate/source/main/java/org/islandoftex/texplate/exceptions/TemplateMergingException.java +++ b/support/texplate/source/main/kotlin/org/islandoftex/texplate/exceptions/TemplateMergingException.kt @@ -1,5 +1,5 @@ // SPDX-License-Identifier: BSD-3-Clause -package org.islandoftex.texplate.exceptions; +package org.islandoftex.texplate.exceptions /** * Handles exceptions when the template merging failed. @@ -7,22 +7,18 @@ package org.islandoftex.texplate.exceptions; * @version 1.0 * @since 1.0 */ -public class TemplateMergingException extends Exception { - +class TemplateMergingException : Exception { /** * Constructor. */ - public TemplateMergingException() { - } + constructor() /** * Constructor. * * @param message Message to be attached to the exception. */ - public TemplateMergingException(String message) { - super(message); - } + constructor(message: String?) : super(message) /** * Constructor. @@ -30,8 +26,5 @@ public class TemplateMergingException extends Exception { * @param message Message to be attached to the exception. * @param cause The throwable cause to be forwarded. */ - public TemplateMergingException(String message, Throwable cause) { - super(message, cause); - } - + 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/java/org/islandoftex/texplate/model/handlers/Handler.java b/support/texplate/source/main/kotlin/org/islandoftex/texplate/model/handlers/Handler.kt index 721224a978..47b59a09a5 100644 --- a/support/texplate/source/main/java/org/islandoftex/texplate/model/handlers/Handler.java +++ b/support/texplate/source/main/kotlin/org/islandoftex/texplate/model/handlers/Handler.kt @@ -1,5 +1,5 @@ // SPDX-License-Identifier: BSD-3-Clause -package org.islandoftex.texplate.model.handlers; +package org.islandoftex.texplate.model.handlers /** * Interface for handlers. @@ -7,14 +7,12 @@ package org.islandoftex.texplate.model.handlers; * @version 1.0 * @since 1.0 */ -public interface Handler { - +interface Handler { /** * Apply the handler in the string. * * @param string The string. * @return The resulting object. */ - Object apply(String string); - + 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.") + } + } +} diff --git a/support/texplate/scripts/templates/texplate-article.toml b/support/texplate/source/main/resources/org/islandoftex/texplate/templates/texplate-article.toml index 53f7796762..53f7796762 100644 --- a/support/texplate/scripts/templates/texplate-article.toml +++ b/support/texplate/source/main/resources/org/islandoftex/texplate/templates/texplate-article.toml diff --git a/support/texplate/scripts/templates/texplate-standalone.toml b/support/texplate/source/main/resources/org/islandoftex/texplate/templates/texplate-standalone.toml index dd8daea65f..dd8daea65f 100644 --- a/support/texplate/scripts/templates/texplate-standalone.toml +++ b/support/texplate/source/main/resources/org/islandoftex/texplate/templates/texplate-standalone.toml |