diff options
author | Karl Berry <karl@freefriends.org> | 2020-01-17 22:31:04 +0000 |
---|---|---|
committer | Karl Berry <karl@freefriends.org> | 2020-01-17 22:31:04 +0000 |
commit | 212508b1290d1456481d21d128b839d6d22d8f94 (patch) | |
tree | c39df881107e04ecfddf4895e2430758b73428c4 /Master/texmf-dist/source/support | |
parent | d617d6d33cf75f9a3df502b0d4b527ed53ddee5a (diff) |
texplate (17jan20)
git-svn-id: svn://tug.org/texlive/trunk@53444 c570f23f-e606-0410-a88d-b1316a301751
Diffstat (limited to 'Master/texmf-dist/source/support')
15 files changed, 1518 insertions, 0 deletions
diff --git a/Master/texmf-dist/source/support/texplate/main/java/org/islandoftex/texplate/Main.java b/Master/texmf-dist/source/support/texplate/main/java/org/islandoftex/texplate/Main.java new file mode 100644 index 00000000000..5c27aab23c0 --- /dev/null +++ b/Master/texmf-dist/source/support/texplate/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/Master/texmf-dist/source/support/texplate/main/java/org/islandoftex/texplate/exceptions/InvalidKeySetException.java b/Master/texmf-dist/source/support/texplate/main/java/org/islandoftex/texplate/exceptions/InvalidKeySetException.java new file mode 100644 index 00000000000..3d113a25998 --- /dev/null +++ b/Master/texmf-dist/source/support/texplate/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/Master/texmf-dist/source/support/texplate/main/java/org/islandoftex/texplate/exceptions/InvalidTemplateException.java b/Master/texmf-dist/source/support/texplate/main/java/org/islandoftex/texplate/exceptions/InvalidTemplateException.java new file mode 100644 index 00000000000..73c82769980 --- /dev/null +++ b/Master/texmf-dist/source/support/texplate/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/Master/texmf-dist/source/support/texplate/main/java/org/islandoftex/texplate/exceptions/TemplateMergingException.java b/Master/texmf-dist/source/support/texplate/main/java/org/islandoftex/texplate/exceptions/TemplateMergingException.java new file mode 100644 index 00000000000..86dd99af00f --- /dev/null +++ b/Master/texmf-dist/source/support/texplate/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/Master/texmf-dist/source/support/texplate/main/java/org/islandoftex/texplate/model/Configuration.java b/Master/texmf-dist/source/support/texplate/main/java/org/islandoftex/texplate/model/Configuration.java new file mode 100644 index 00000000000..a0f8e837029 --- /dev/null +++ b/Master/texmf-dist/source/support/texplate/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/Master/texmf-dist/source/support/texplate/main/java/org/islandoftex/texplate/model/Template.java b/Master/texmf-dist/source/support/texplate/main/java/org/islandoftex/texplate/model/Template.java new file mode 100644 index 00000000000..ca92e046584 --- /dev/null +++ b/Master/texmf-dist/source/support/texplate/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/Master/texmf-dist/source/support/texplate/main/java/org/islandoftex/texplate/model/TemplateProcessing.java b/Master/texmf-dist/source/support/texplate/main/java/org/islandoftex/texplate/model/TemplateProcessing.java new file mode 100644 index 00000000000..eccaf6c7320 --- /dev/null +++ b/Master/texmf-dist/source/support/texplate/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/Master/texmf-dist/source/support/texplate/main/java/org/islandoftex/texplate/model/handlers/BooleanHandler.java b/Master/texmf-dist/source/support/texplate/main/java/org/islandoftex/texplate/model/handlers/BooleanHandler.java new file mode 100644 index 00000000000..461b9fd1705 --- /dev/null +++ b/Master/texmf-dist/source/support/texplate/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/Master/texmf-dist/source/support/texplate/main/java/org/islandoftex/texplate/model/handlers/CSVListHandler.java b/Master/texmf-dist/source/support/texplate/main/java/org/islandoftex/texplate/model/handlers/CSVListHandler.java new file mode 100644 index 00000000000..4b3d0c2e694 --- /dev/null +++ b/Master/texmf-dist/source/support/texplate/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/Master/texmf-dist/source/support/texplate/main/java/org/islandoftex/texplate/model/handlers/Handler.java b/Master/texmf-dist/source/support/texplate/main/java/org/islandoftex/texplate/model/handlers/Handler.java new file mode 100644 index 00000000000..721224a9781 --- /dev/null +++ b/Master/texmf-dist/source/support/texplate/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/Master/texmf-dist/source/support/texplate/main/java/org/islandoftex/texplate/util/HandlerUtils.java b/Master/texmf-dist/source/support/texplate/main/java/org/islandoftex/texplate/util/HandlerUtils.java new file mode 100644 index 00000000000..7ab01f73281 --- /dev/null +++ b/Master/texmf-dist/source/support/texplate/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/Master/texmf-dist/source/support/texplate/main/java/org/islandoftex/texplate/util/MergingUtils.java b/Master/texmf-dist/source/support/texplate/main/java/org/islandoftex/texplate/util/MergingUtils.java new file mode 100644 index 00000000000..d7ffbddc4da --- /dev/null +++ b/Master/texmf-dist/source/support/texplate/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/Master/texmf-dist/source/support/texplate/main/java/org/islandoftex/texplate/util/MessageUtils.java b/Master/texmf-dist/source/support/texplate/main/java/org/islandoftex/texplate/util/MessageUtils.java new file mode 100644 index 00000000000..7649a60e826 --- /dev/null +++ b/Master/texmf-dist/source/support/texplate/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.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/Master/texmf-dist/source/support/texplate/main/java/org/islandoftex/texplate/util/PathUtils.java b/Master/texmf-dist/source/support/texplate/main/java/org/islandoftex/texplate/util/PathUtils.java new file mode 100644 index 00000000000..fa242c49801 --- /dev/null +++ b/Master/texmf-dist/source/support/texplate/main/java/org/islandoftex/texplate/util/PathUtils.java @@ -0,0 +1,129 @@ +// 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/Master/texmf-dist/source/support/texplate/main/java/org/islandoftex/texplate/util/ValidatorUtils.java b/Master/texmf-dist/source/support/texplate/main/java/org/islandoftex/texplate/util/ValidatorUtils.java new file mode 100644 index 00000000000..b4007312425 --- /dev/null +++ b/Master/texmf-dist/source/support/texplate/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)); + } + +} |