diff options
author | Karl Berry <karl@freefriends.org> | 2020-02-02 22:30:34 +0000 |
---|---|---|
committer | Karl Berry <karl@freefriends.org> | 2020-02-02 22:30:34 +0000 |
commit | dcd5707512610f13d8c26713c40b390010aac13b (patch) | |
tree | f3854e7e5df178ba3e689bc31e360ddce813a5d0 /Master/texmf-dist/source/support | |
parent | 351e05dfb6e0abb819336efc60cd93ac1c478c1e (diff) |
texplate (2feb20)
git-svn-id: svn://tug.org/texlive/trunk@53637 c570f23f-e606-0410-a88d-b1316a301751
Diffstat (limited to 'Master/texmf-dist/source/support')
28 files changed, 913 insertions, 1431 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 deleted file mode 100644 index 5c27aab23c0..00000000000 --- a/Master/texmf-dist/source/support/texplate/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/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 deleted file mode 100644 index a0f8e837029..00000000000 --- a/Master/texmf-dist/source/support/texplate/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/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 deleted file mode 100644 index ca92e046584..00000000000 --- a/Master/texmf-dist/source/support/texplate/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/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 deleted file mode 100644 index eccaf6c7320..00000000000 --- a/Master/texmf-dist/source/support/texplate/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/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 deleted file mode 100644 index 461b9fd1705..00000000000 --- a/Master/texmf-dist/source/support/texplate/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/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 deleted file mode 100644 index 4b3d0c2e694..00000000000 --- a/Master/texmf-dist/source/support/texplate/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/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 deleted file mode 100644 index 7ab01f73281..00000000000 --- a/Master/texmf-dist/source/support/texplate/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/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 deleted file mode 100644 index d7ffbddc4da..00000000000 --- a/Master/texmf-dist/source/support/texplate/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/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 deleted file mode 100644 index 7649a60e826..00000000000 --- a/Master/texmf-dist/source/support/texplate/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/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 deleted file mode 100644 index fa242c49801..00000000000 --- a/Master/texmf-dist/source/support/texplate/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/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 deleted file mode 100644 index b4007312425..00000000000 --- a/Master/texmf-dist/source/support/texplate/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/Master/texmf-dist/source/support/texplate/main/kotlin/org/islandoftex/texplate/Main.kt b/Master/texmf-dist/source/support/texplate/main/kotlin/org/islandoftex/texplate/Main.kt new file mode 100644 index 00000000000..73de15f8c3e --- /dev/null +++ b/Master/texmf-dist/source/support/texplate/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/Master/texmf-dist/source/support/texplate/main/kotlin/org/islandoftex/texplate/TemplateProcessing.kt b/Master/texmf-dist/source/support/texplate/main/kotlin/org/islandoftex/texplate/TemplateProcessing.kt new file mode 100644 index 00000000000..659a0df6316 --- /dev/null +++ b/Master/texmf-dist/source/support/texplate/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/Master/texmf-dist/source/support/texplate/main/java/org/islandoftex/texplate/exceptions/InvalidKeySetException.java b/Master/texmf-dist/source/support/texplate/main/kotlin/org/islandoftex/texplate/exceptions/InvalidKeySetException.kt index 3d113a25998..72ff3bcbd4b 100644 --- a/Master/texmf-dist/source/support/texplate/main/java/org/islandoftex/texplate/exceptions/InvalidKeySetException.java +++ b/Master/texmf-dist/source/support/texplate/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/Master/texmf-dist/source/support/texplate/main/java/org/islandoftex/texplate/exceptions/InvalidTemplateException.java b/Master/texmf-dist/source/support/texplate/main/kotlin/org/islandoftex/texplate/exceptions/InvalidTemplateException.kt index 73c82769980..678c3900552 100644 --- a/Master/texmf-dist/source/support/texplate/main/java/org/islandoftex/texplate/exceptions/InvalidTemplateException.java +++ b/Master/texmf-dist/source/support/texplate/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/Master/texmf-dist/source/support/texplate/main/java/org/islandoftex/texplate/exceptions/TemplateMergingException.java b/Master/texmf-dist/source/support/texplate/main/kotlin/org/islandoftex/texplate/exceptions/TemplateMergingException.kt index 86dd99af00f..e1760f8732c 100644 --- a/Master/texmf-dist/source/support/texplate/main/java/org/islandoftex/texplate/exceptions/TemplateMergingException.java +++ b/Master/texmf-dist/source/support/texplate/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/Master/texmf-dist/source/support/texplate/main/kotlin/org/islandoftex/texplate/model/Configuration.kt b/Master/texmf-dist/source/support/texplate/main/kotlin/org/islandoftex/texplate/model/Configuration.kt new file mode 100644 index 00000000000..8d37cbb39a5 --- /dev/null +++ b/Master/texmf-dist/source/support/texplate/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/Master/texmf-dist/source/support/texplate/main/kotlin/org/islandoftex/texplate/model/Template.kt b/Master/texmf-dist/source/support/texplate/main/kotlin/org/islandoftex/texplate/model/Template.kt new file mode 100644 index 00000000000..82616242de0 --- /dev/null +++ b/Master/texmf-dist/source/support/texplate/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/Master/texmf-dist/source/support/texplate/main/kotlin/org/islandoftex/texplate/model/handlers/BooleanHandler.kt b/Master/texmf-dist/source/support/texplate/main/kotlin/org/islandoftex/texplate/model/handlers/BooleanHandler.kt new file mode 100644 index 00000000000..cbb74a532f9 --- /dev/null +++ b/Master/texmf-dist/source/support/texplate/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/Master/texmf-dist/source/support/texplate/main/kotlin/org/islandoftex/texplate/model/handlers/CSVListHandler.kt b/Master/texmf-dist/source/support/texplate/main/kotlin/org/islandoftex/texplate/model/handlers/CSVListHandler.kt new file mode 100644 index 00000000000..77424ed4633 --- /dev/null +++ b/Master/texmf-dist/source/support/texplate/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/Master/texmf-dist/source/support/texplate/main/java/org/islandoftex/texplate/model/handlers/Handler.java b/Master/texmf-dist/source/support/texplate/main/kotlin/org/islandoftex/texplate/model/handlers/Handler.kt index 721224a9781..47b59a09a51 100644 --- a/Master/texmf-dist/source/support/texplate/main/java/org/islandoftex/texplate/model/handlers/Handler.java +++ b/Master/texmf-dist/source/support/texplate/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/Master/texmf-dist/source/support/texplate/main/kotlin/org/islandoftex/texplate/util/HandlerUtils.kt b/Master/texmf-dist/source/support/texplate/main/kotlin/org/islandoftex/texplate/util/HandlerUtils.kt new file mode 100644 index 00000000000..44eebb2b690 --- /dev/null +++ b/Master/texmf-dist/source/support/texplate/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/Master/texmf-dist/source/support/texplate/main/kotlin/org/islandoftex/texplate/util/MergingUtils.kt b/Master/texmf-dist/source/support/texplate/main/kotlin/org/islandoftex/texplate/util/MergingUtils.kt new file mode 100644 index 00000000000..ef02349d5d6 --- /dev/null +++ b/Master/texmf-dist/source/support/texplate/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/Master/texmf-dist/source/support/texplate/main/kotlin/org/islandoftex/texplate/util/MessageUtils.kt b/Master/texmf-dist/source/support/texplate/main/kotlin/org/islandoftex/texplate/util/MessageUtils.kt new file mode 100644 index 00000000000..fbb68a819e5 --- /dev/null +++ b/Master/texmf-dist/source/support/texplate/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/Master/texmf-dist/source/support/texplate/main/kotlin/org/islandoftex/texplate/util/PathUtils.kt b/Master/texmf-dist/source/support/texplate/main/kotlin/org/islandoftex/texplate/util/PathUtils.kt new file mode 100644 index 00000000000..3a746035574 --- /dev/null +++ b/Master/texmf-dist/source/support/texplate/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/Master/texmf-dist/source/support/texplate/main/kotlin/org/islandoftex/texplate/util/ValidatorUtils.kt b/Master/texmf-dist/source/support/texplate/main/kotlin/org/islandoftex/texplate/util/ValidatorUtils.kt new file mode 100644 index 00000000000..ef91a012c13 --- /dev/null +++ b/Master/texmf-dist/source/support/texplate/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/Master/texmf-dist/source/support/texplate/main/resources/org/islandoftex/texplate/templates/texplate-article.toml b/Master/texmf-dist/source/support/texplate/main/resources/org/islandoftex/texplate/templates/texplate-article.toml new file mode 100644 index 00000000000..53f7796762c --- /dev/null +++ b/Master/texmf-dist/source/support/texplate/main/resources/org/islandoftex/texplate/templates/texplate-article.toml @@ -0,0 +1,54 @@ +name = "article" +description = """ +A simple template for the default article class, with support for new +engines (with fontspec fallback), babel languages, geometry options, +generic packages, and TikZ and corresponding libraries. +""" +authors = [ "Island of TeX" ] +requirements = [] +document = ''' +\documentclass#if($options)[$options]#{end}{article} + +#if ($xetex || $luatex) +\usepackage{fontspec} +#else +\usepackage[T1]{fontenc} +\usepackage[utf8]{inputenc} +#end +#if ($geometry) + +\usepackage[$geometry]{geometry} +#end +#if ($babel) +#if (!$geometry) + +#end +\usepackage[$babel]{babel} +#end +#if ($packages) + +#foreach ($package in $packages) +\usepackage{$package} +#end +#end +#if ($tikz) + +\usepackage{tikz} +#if ($libraries) +#foreach ($library in $libraries) +\usetikzlibrary{$library} +#end +#end +#end + +\begin{document} + +\end{document} +''' + +[handlers] +xetex = "to-boolean" +luatex = "to-boolean" +tikz = "to-boolean" +libraries = "to-csv-list" +packages = "to-csv-list" diff --git a/Master/texmf-dist/source/support/texplate/main/resources/org/islandoftex/texplate/templates/texplate-standalone.toml b/Master/texmf-dist/source/support/texplate/main/resources/org/islandoftex/texplate/templates/texplate-standalone.toml new file mode 100644 index 00000000000..dd8daea65f5 --- /dev/null +++ b/Master/texmf-dist/source/support/texplate/main/resources/org/islandoftex/texplate/templates/texplate-standalone.toml @@ -0,0 +1,45 @@ +name = "standalone" +description = """ +A simple template for the standalone class, with support for +class options, new engines (with fontspec fallback), list of +packages, and TikZ and corresponding libraries. +""" +authors = [ "Island of TeX" ] +requirements = [] +document = ''' +\documentclass#if($options)[$options]#{end}{standalone} + +#if ($xetex || $luatex) +\usepackage{fontspec} +#else +\usepackage[T1]{fontenc} +\usepackage[utf8]{inputenc} +#end +#if ($packages) + +#foreach ($package in $packages) +\usepackage{$package} +#end +#end +#if ($tikz) + +\usepackage{tikz} +#if ($libraries) +#foreach ($library in $libraries) +\usetikzlibrary{$library} +#end +#end +#end + +\begin{document} + +\end{document} +''' + +[handlers] +xetex = "to-boolean" +luatex = "to-boolean" +tikz = "to-boolean" +libraries = "to-csv-list" +packages = "to-csv-list" + |