summaryrefslogtreecommitdiff
path: root/support/texplate/source/main
diff options
context:
space:
mode:
Diffstat (limited to 'support/texplate/source/main')
-rw-r--r--support/texplate/source/main/java/org/islandoftex/texplate/Main.java36
-rw-r--r--support/texplate/source/main/java/org/islandoftex/texplate/exceptions/InvalidKeySetException.java37
-rw-r--r--support/texplate/source/main/java/org/islandoftex/texplate/exceptions/InvalidTemplateException.java34
-rw-r--r--support/texplate/source/main/java/org/islandoftex/texplate/exceptions/TemplateMergingException.java37
-rw-r--r--support/texplate/source/main/java/org/islandoftex/texplate/model/Configuration.java150
-rw-r--r--support/texplate/source/main/java/org/islandoftex/texplate/model/Template.java252
-rw-r--r--support/texplate/source/main/java/org/islandoftex/texplate/model/TemplateProcessing.java391
-rw-r--r--support/texplate/source/main/java/org/islandoftex/texplate/model/handlers/BooleanHandler.java26
-rw-r--r--support/texplate/source/main/java/org/islandoftex/texplate/model/handlers/CSVListHandler.java30
-rw-r--r--support/texplate/source/main/java/org/islandoftex/texplate/model/handlers/Handler.java20
-rw-r--r--support/texplate/source/main/java/org/islandoftex/texplate/util/HandlerUtils.java31
-rw-r--r--support/texplate/source/main/java/org/islandoftex/texplate/util/MergingUtils.java186
-rw-r--r--support/texplate/source/main/java/org/islandoftex/texplate/util/MessageUtils.java84
-rw-r--r--support/texplate/source/main/java/org/islandoftex/texplate/util/PathUtils.java123
-rw-r--r--support/texplate/source/main/java/org/islandoftex/texplate/util/ValidatorUtils.java75
15 files changed, 1512 insertions, 0 deletions
diff --git a/support/texplate/source/main/java/org/islandoftex/texplate/Main.java b/support/texplate/source/main/java/org/islandoftex/texplate/Main.java
new file mode 100644
index 0000000000..5c27aab23c
--- /dev/null
+++ b/support/texplate/source/main/java/org/islandoftex/texplate/Main.java
@@ -0,0 +1,36 @@
+// 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/exceptions/InvalidKeySetException.java b/support/texplate/source/main/java/org/islandoftex/texplate/exceptions/InvalidKeySetException.java
new file mode 100644
index 0000000000..3d113a2599
--- /dev/null
+++ b/support/texplate/source/main/java/org/islandoftex/texplate/exceptions/InvalidKeySetException.java
@@ -0,0 +1,37 @@
+// 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
+ */
+public class InvalidKeySetException extends Exception {
+
+ /**
+ * Constructor.
+ */
+ public InvalidKeySetException() {
+ }
+
+ /**
+ * Constructor.
+ *
+ * @param message Message to be attached to the exception.
+ */
+ public InvalidKeySetException(String message) {
+ super(message);
+ }
+
+ /**
+ * Constructor.
+ *
+ * @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);
+ }
+
+}
diff --git a/support/texplate/source/main/java/org/islandoftex/texplate/exceptions/InvalidTemplateException.java b/support/texplate/source/main/java/org/islandoftex/texplate/exceptions/InvalidTemplateException.java
new file mode 100644
index 0000000000..73c8276998
--- /dev/null
+++ b/support/texplate/source/main/java/org/islandoftex/texplate/exceptions/InvalidTemplateException.java
@@ -0,0 +1,34 @@
+// 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
+ */
+public class InvalidTemplateException extends Exception {
+
+ public InvalidTemplateException() {
+ }
+
+ /**
+ * Constructor.
+ *
+ * @param message Message to be attached to the exception.
+ */
+ public InvalidTemplateException(String message) {
+ super(message);
+ }
+
+ /**
+ * Constructor.
+ *
+ * @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);
+ }
+
+}
diff --git a/support/texplate/source/main/java/org/islandoftex/texplate/exceptions/TemplateMergingException.java b/support/texplate/source/main/java/org/islandoftex/texplate/exceptions/TemplateMergingException.java
new file mode 100644
index 0000000000..86dd99af00
--- /dev/null
+++ b/support/texplate/source/main/java/org/islandoftex/texplate/exceptions/TemplateMergingException.java
@@ -0,0 +1,37 @@
+// SPDX-License-Identifier: BSD-3-Clause
+package org.islandoftex.texplate.exceptions;
+
+/**
+ * Handles exceptions when the template merging failed.
+ *
+ * @version 1.0
+ * @since 1.0
+ */
+public class TemplateMergingException extends Exception {
+
+ /**
+ * Constructor.
+ */
+ public TemplateMergingException() {
+ }
+
+ /**
+ * Constructor.
+ *
+ * @param message Message to be attached to the exception.
+ */
+ public TemplateMergingException(String message) {
+ super(message);
+ }
+
+ /**
+ * Constructor.
+ *
+ * @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);
+ }
+
+}
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
new file mode 100644
index 0000000000..a0f8e83702
--- /dev/null
+++ b/support/texplate/source/main/java/org/islandoftex/texplate/model/Configuration.java
@@ -0,0 +1,150 @@
+// 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
new file mode 100644
index 0000000000..ca92e04658
--- /dev/null
+++ b/support/texplate/source/main/java/org/islandoftex/texplate/model/Template.java
@@ -0,0 +1,252 @@
+// 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
new file mode 100644
index 0000000000..eccaf6c732
--- /dev/null
+++ b/support/texplate/source/main/java/org/islandoftex/texplate/model/TemplateProcessing.java
@@ -0,0 +1,391 @@
+// 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
new file mode 100644
index 0000000000..461b9fd170
--- /dev/null
+++ b/support/texplate/source/main/java/org/islandoftex/texplate/model/handlers/BooleanHandler.java
@@ -0,0 +1,26 @@
+// 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
new file mode 100644
index 0000000000..4b3d0c2e69
--- /dev/null
+++ b/support/texplate/source/main/java/org/islandoftex/texplate/model/handlers/CSVListHandler.java
@@ -0,0 +1,30 @@
+// 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/model/handlers/Handler.java b/support/texplate/source/main/java/org/islandoftex/texplate/model/handlers/Handler.java
new file mode 100644
index 0000000000..721224a978
--- /dev/null
+++ b/support/texplate/source/main/java/org/islandoftex/texplate/model/handlers/Handler.java
@@ -0,0 +1,20 @@
+// SPDX-License-Identifier: BSD-3-Clause
+package org.islandoftex.texplate.model.handlers;
+
+/**
+ * Interface for handlers.
+ *
+ * @version 1.0
+ * @since 1.0
+ */
+public interface Handler {
+
+ /**
+ * Apply the handler in the string.
+ *
+ * @param string The string.
+ * @return The resulting object.
+ */
+ Object apply(String string);
+
+}
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
new file mode 100644
index 0000000000..7ab01f7328
--- /dev/null
+++ b/support/texplate/source/main/java/org/islandoftex/texplate/util/HandlerUtils.java
@@ -0,0 +1,31 @@
+// 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
new file mode 100644
index 0000000000..d7ffbddc4d
--- /dev/null
+++ b/support/texplate/source/main/java/org/islandoftex/texplate/util/MergingUtils.java
@@ -0,0 +1,186 @@
+// 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
new file mode 100644
index 0000000000..948d18d953
--- /dev/null
+++ b/support/texplate/source/main/java/org/islandoftex/texplate/util/MessageUtils.java
@@ -0,0 +1,84 @@
+// 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.0";
+
+ /**
+ * 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
new file mode 100644
index 0000000000..26e101b9ad
--- /dev/null
+++ b/support/texplate/source/main/java/org/islandoftex/texplate/util/PathUtils.java
@@ -0,0 +1,123 @@
+// SPDX-License-Identifier: BSD-3-Clause
+package org.islandoftex.texplate.util;
+
+import io.vavr.control.Try;
+import java.io.FileNotFoundException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import org.islandoftex.texplate.Main;
+
+/**
+ * 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 {
+
+ // 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
new file mode 100644
index 0000000000..b400731242
--- /dev/null
+++ b/support/texplate/source/main/java/org/islandoftex/texplate/util/ValidatorUtils.java
@@ -0,0 +1,75 @@
+// 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));
+ }
+
+}