summaryrefslogtreecommitdiff
path: root/support/arara/source/src/main/java
diff options
context:
space:
mode:
authorNorbert Preining <norbert@preining.info>2019-09-02 13:46:59 +0900
committerNorbert Preining <norbert@preining.info>2019-09-02 13:46:59 +0900
commite0c6872cf40896c7be36b11dcc744620f10adf1d (patch)
tree60335e10d2f4354b0674ec22d7b53f0f8abee672 /support/arara/source/src/main/java
Initial commit
Diffstat (limited to 'support/arara/source/src/main/java')
-rw-r--r--support/arara/source/src/main/java/com/github/cereda/arara/Arara.java205
-rw-r--r--support/arara/source/src/main/java/com/github/cereda/arara/controller/ConfigurationController.java106
-rw-r--r--support/arara/source/src/main/java/com/github/cereda/arara/controller/LanguageController.java120
-rw-r--r--support/arara/source/src/main/java/com/github/cereda/arara/controller/LoggingController.java109
-rw-r--r--support/arara/source/src/main/java/com/github/cereda/arara/controller/SessionController.java124
-rw-r--r--support/arara/source/src/main/java/com/github/cereda/arara/controller/SystemCallController.java153
-rw-r--r--support/arara/source/src/main/java/com/github/cereda/arara/model/AraraException.java88
-rw-r--r--support/arara/source/src/main/java/com/github/cereda/arara/model/Argument.java125
-rw-r--r--support/arara/source/src/main/java/com/github/cereda/arara/model/Command.java122
-rw-r--r--support/arara/source/src/main/java/com/github/cereda/arara/model/Conditional.java139
-rw-r--r--support/arara/source/src/main/java/com/github/cereda/arara/model/Configuration.java246
-rw-r--r--support/arara/source/src/main/java/com/github/cereda/arara/model/Database.java80
-rw-r--r--support/arara/source/src/main/java/com/github/cereda/arara/model/Directive.java146
-rw-r--r--support/arara/source/src/main/java/com/github/cereda/arara/model/Evaluator.java158
-rw-r--r--support/arara/source/src/main/java/com/github/cereda/arara/model/Extractor.java88
-rw-r--r--support/arara/source/src/main/java/com/github/cereda/arara/model/FileType.java170
-rw-r--r--support/arara/source/src/main/java/com/github/cereda/arara/model/FileTypeResource.java84
-rw-r--r--support/arara/source/src/main/java/com/github/cereda/arara/model/Interpreter.java458
-rw-r--r--support/arara/source/src/main/java/com/github/cereda/arara/model/Language.java148
-rw-r--r--support/arara/source/src/main/java/com/github/cereda/arara/model/Messages.java170
-rw-r--r--support/arara/source/src/main/java/com/github/cereda/arara/model/Pair.java92
-rw-r--r--support/arara/source/src/main/java/com/github/cereda/arara/model/Parser.java410
-rw-r--r--support/arara/source/src/main/java/com/github/cereda/arara/model/Resource.java291
-rw-r--r--support/arara/source/src/main/java/com/github/cereda/arara/model/Rule.java155
-rw-r--r--support/arara/source/src/main/java/com/github/cereda/arara/model/RuleCommand.java103
-rw-r--r--support/arara/source/src/main/java/com/github/cereda/arara/model/Session.java122
-rw-r--r--support/arara/source/src/main/java/com/github/cereda/arara/model/StopWatch.java84
-rw-r--r--support/arara/source/src/main/java/com/github/cereda/arara/model/Trigger.java135
-rw-r--r--support/arara/source/src/main/java/com/github/cereda/arara/utils/ClassLoadingUtils.java166
-rw-r--r--support/arara/source/src/main/java/com/github/cereda/arara/utils/CommonUtils.java929
-rw-r--r--support/arara/source/src/main/java/com/github/cereda/arara/utils/ConfigurationUtils.java239
-rw-r--r--support/arara/source/src/main/java/com/github/cereda/arara/utils/DatabaseUtils.java140
-rw-r--r--support/arara/source/src/main/java/com/github/cereda/arara/utils/DirectiveAssembler.java106
-rw-r--r--support/arara/source/src/main/java/com/github/cereda/arara/utils/DirectiveResolver.java59
-rw-r--r--support/arara/source/src/main/java/com/github/cereda/arara/utils/DirectiveUtils.java438
-rw-r--r--support/arara/source/src/main/java/com/github/cereda/arara/utils/DisplayUtils.java502
-rw-r--r--support/arara/source/src/main/java/com/github/cereda/arara/utils/FileHandlingUtils.java124
-rw-r--r--support/arara/source/src/main/java/com/github/cereda/arara/utils/FileSearchingUtils.java117
-rw-r--r--support/arara/source/src/main/java/com/github/cereda/arara/utils/InterpreterUtils.java257
-rw-r--r--support/arara/source/src/main/java/com/github/cereda/arara/utils/MessageUtils.java313
-rw-r--r--support/arara/source/src/main/java/com/github/cereda/arara/utils/Methods.java1356
-rw-r--r--support/arara/source/src/main/java/com/github/cereda/arara/utils/RuleUtils.java244
-rw-r--r--support/arara/source/src/main/java/com/github/cereda/arara/utils/TeeOutputStream.java109
-rw-r--r--support/arara/source/src/main/java/com/github/cereda/arara/utils/UnsafeUtils.java87
-rw-r--r--support/arara/source/src/main/java/com/github/cereda/arara/utils/VelocityUtils.java157
45 files changed, 9774 insertions, 0 deletions
diff --git a/support/arara/source/src/main/java/com/github/cereda/arara/Arara.java b/support/arara/source/src/main/java/com/github/cereda/arara/Arara.java
new file mode 100644
index 0000000000..c644978c93
--- /dev/null
+++ b/support/arara/source/src/main/java/com/github/cereda/arara/Arara.java
@@ -0,0 +1,205 @@
+/**
+ * Arara, the cool TeX automation tool
+ * Copyright (c) 2012 -- 2018, Paulo Roberto Massa Cereda
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * 3. Neither the name of the project's author nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+ * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
+ * COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+ * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY
+ * WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+package com.github.cereda.arara;
+
+import com.github.cereda.arara.controller.LanguageController;
+import com.github.cereda.arara.controller.LoggingController;
+import com.github.cereda.arara.model.AraraException;
+import com.github.cereda.arara.model.Configuration;
+import com.github.cereda.arara.model.Directive;
+import com.github.cereda.arara.model.Extractor;
+import com.github.cereda.arara.model.Interpreter;
+import com.github.cereda.arara.model.Parser;
+import com.github.cereda.arara.model.StopWatch;
+import com.github.cereda.arara.utils.CommonUtils;
+import com.github.cereda.arara.utils.DirectiveUtils;
+import com.github.cereda.arara.utils.DisplayUtils;
+import java.util.List;
+
+/**
+ * Main class. This class wraps all classes from the application model as well
+ * as utilitary classes in order to provide modularity.
+ * @author Paulo Roberto Massa Cereda
+ * @version 4.0
+ * @since 4.0
+ */
+public class Arara {
+
+ /**
+ * Main method. This is the application entry point.
+ * @param args A string array containing all command line arguments.
+ */
+ public static void main(String[] args) {
+
+ // the first component to be initialized is the
+ // language controller; note that init() actually
+ // has no body at all, but it's a dirty maneuver to
+ // trigger the static class startup
+ LanguageController.init();
+
+ // the second component to be initalized is the
+ // logging controller; note init() actually disables
+ // the logging, so early exceptions won't generate
+ // a lot of noise in the terminal
+ LoggingController.init();
+
+ // print the arara logo in the terminal; I just
+ // hope people use this tool in a good terminal with
+ // fixed-width fonts, otherwise the logo will be messed
+ DisplayUtils.printLogo();
+
+ try {
+
+ // first of all, let's try to load a potential
+ // configuration file located at the current
+ // user's home directory; if there's a bad
+ // configuration file, arara will panic and
+ // end the execution
+ Configuration.load();
+
+ // if we are here, either there was no configuration
+ // file at all or we managed to load the settings; now,
+ // it's time to properly parse the command line arguments;
+ // this is done by creating a brand new instance of arara's
+ // command line parser and providing the string array to it
+ Parser parser = new Parser(args);
+
+ // now let's see if we are good to go; parse() will return
+ // a boolean value indicating if the provided arguments
+ // allow the tool to continue (we might reach some special
+ // flags as well, like --help or --version, which simply
+ // do their jobs and return false, since there's no point
+ // of continuing processing with such flags)
+ if (parser.parse()) {
+
+ // let's print the current file information; it is a
+ // basic display, just the file name, the size properly
+ // formatted as a human readable format, and the last
+ // modification date; also, in this point, the logging
+ // feature starts to collect data (of course, if enabled
+ // either through the configuration file or manually
+ // in the command line)
+ DisplayUtils.printFileInformation();
+
+ // time to read the file and try to extract the directives;
+ // this class does a pretty good job on finding directives,
+ // including the multiline ones; it was a long awaited
+ // feature people were asking me to implement, so here
+ // it is!
+ Extractor extractor = new Extractor();
+
+ // extract() brings us a list of directives properly parsed
+ // and almost ready to be handled; note that no directives
+ // in the provided file will raise an exception; this is
+ // by design and I opted to not include a default fallback
+ // (although it wouldn't be so difficult to write one,
+ // I decided not to take the risk)
+ List<Directive> directives = extractor.extract();
+
+ // once we have our nice list of directives, it is time to
+ // actually validate them (for example, we have a couple of
+ // keywords that cannot be used as directive parameters);
+ // another interesting feature of the validate() method is
+ // to replicate a directive that has the 'files' keyword on
+ // it, since it's the whole point of having 'files' in the
+ // first place; if you check the log file, you will see
+ // that the list of extracted directives might differ from
+ // the final list of directives to be effectively processed
+ // by arara
+ directives = DirectiveUtils.validate(directives);
+
+ // arara features now a cool stopwatch, so we can see how
+ // much time has passed since everything started; start(),
+ // for obvious reasons, starts the stopwatch and keeps track
+ // of time for us; internally, this class makes use of
+ // nano time, so we might get an interesting precision here
+ // (although timing is not a serious business in here, it's
+ // just a cool addition)
+ StopWatch.start();
+
+ // this is surely the golden heart of arara; this class
+ // implements a powerful interpreter that will handle all
+ // rules and their corresponding tasks
+ Interpreter interpreter = new Interpreter();
+
+ // once we have this bad boy ready, let's provide the list
+ // of directives previously extracted and validated; it is
+ // like loading a cannon, I guess
+ interpreter.setDirectives(directives);
+
+ // time to shine, now the interpreter class will interpret
+ // one directive at a time, get the corresponding rule,
+ // set the parameters, evaluate it, get the tasks, run them,
+ // evaluate the result and print the status; note that
+ // arara, from this version on, will try to evaluate things
+ // progressively, so in case of an error, the previous tasks
+ // were already processed and potentially executed
+ interpreter.execute();
+ }
+
+ } catch (AraraException exception) {
+
+ // something bad just happened, so arara will print the proper
+ // exception and provide details on it, if available; the idea
+ // here is to propagate an exception throughout the whole
+ // application and catch it here instead of a local treatment
+ DisplayUtils.printException(exception);
+ }
+
+ // we are done here (with or without errors, that makes no difference
+ // at this point), so let's stop our stopwatch; now it's just an easy
+ // subtraction to be made (note that the values are internally
+ // represented as nanoseconds, but the result is printed as seconds)
+ StopWatch.stop();
+
+ // this is the last command from arara; once the execution time is
+ // available, print it; note that this notification is suppressed
+ // when the command line parsing returns false as result (it makes
+ // no sense to print the execution time for a help message, I guess)
+ DisplayUtils.printTime();
+
+ // gets the application exit status; the rule here is:
+ // 0 : everything went just fine (note that the dry-run mode always
+ // makes arara exit with 0, unless it is an error in the directive
+ // builder itself).
+ // 1 : one of the tasks failed, so the execution ended abruptly. This
+ // means the error relies on the command line call, not with arara.
+ // 2 : arara just handled an exception, meaning that something bad
+ // just happened and might require user intervention.
+ System.exit(CommonUtils.getExitStatus());
+
+ // I owe David Carlisle $10
+ }
+
+}
diff --git a/support/arara/source/src/main/java/com/github/cereda/arara/controller/ConfigurationController.java b/support/arara/source/src/main/java/com/github/cereda/arara/controller/ConfigurationController.java
new file mode 100644
index 0000000000..f3868b488f
--- /dev/null
+++ b/support/arara/source/src/main/java/com/github/cereda/arara/controller/ConfigurationController.java
@@ -0,0 +1,106 @@
+/**
+ * Arara, the cool TeX automation tool
+ * Copyright (c) 2012 -- 2018, Paulo Roberto Massa Cereda
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * 3. Neither the name of the project's author nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+ * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
+ * COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+ * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY
+ * WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+package com.github.cereda.arara.controller;
+
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * Implements the configuration controller. The idea here is to provide a map
+ * that holds all configuration settings used by model and utilitary classes
+ * throughout the execution. This controller is implemented as a singleton.
+ * @author Paulo Roberto Massa Cereda
+ * @version 4.0
+ * @since 4.0
+ */
+public class ConfigurationController {
+
+ // the controller itself, since we have a singleton;
+ // this is the reference instance, instantiated once
+ private static final ConfigurationController instance =
+ new ConfigurationController();
+
+ // the configuration settings are stored in a map;
+ // pretty much everything can be stored in this map,
+ // as long as you know what to retrieve later on
+ private final Map<String, Object> map;
+
+ /**
+ * Private constructor. Called once for creating the proper singleton.
+ */
+ private ConfigurationController() {
+ map = new HashMap<String, Object>();
+ }
+
+ /**
+ * Gets the current configuration controller. Since this class is
+ * implemented as a singleton, you will get the same controller every
+ * single time, and that's good.
+ * @return The configuration controller, which hold the settings map.
+ */
+ public static ConfigurationController getInstance() {
+ return instance;
+ }
+
+ /**
+ * Returns the object indexed by the provided key. This method provides an
+ * easy access to the underlying map.
+ * @param key A string representing the key.
+ * @return An object indexed by the provided key.
+ */
+ public Object get(String key) {
+ return map.get(key);
+ }
+
+ /**
+ * Puts the object in the map and indexes it in the provided key. This
+ * method provides an easy access to the underlying map.
+ * @param key A string representing the key.
+ * @param value The object to be indexed by the provided key.
+ */
+ public void put(String key, Object value) {
+ map.put(key, value);
+ }
+
+ /**
+ * Checks if the map contains the provided key. This is actually a wrapper
+ * to the private map's method of the same name.
+ * @param key The key to be checked.
+ * @return A boolean value indicating if the map contains the provided key.
+ */
+ public boolean contains(String key) {
+ return map.containsKey(key);
+ }
+
+}
diff --git a/support/arara/source/src/main/java/com/github/cereda/arara/controller/LanguageController.java b/support/arara/source/src/main/java/com/github/cereda/arara/controller/LanguageController.java
new file mode 100644
index 0000000000..a0ccc2a958
--- /dev/null
+++ b/support/arara/source/src/main/java/com/github/cereda/arara/controller/LanguageController.java
@@ -0,0 +1,120 @@
+/**
+ * Arara, the cool TeX automation tool
+ * Copyright (c) 2012 -- 2018, Paulo Roberto Massa Cereda
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * 3. Neither the name of the project's author nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+ * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
+ * COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+ * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY
+ * WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+package com.github.cereda.arara.controller;
+
+import ch.qos.cal10n.IMessageConveyor;
+import ch.qos.cal10n.MessageConveyor;
+import java.util.Locale;
+
+/**
+ * Implements the language controller. This controller provides a singleton
+ * object that holds the application messages, easily available to all model
+ * and utilitary classes.
+ * @author Paulo Roberto Massa Cereda
+ * @version 4.0
+ * @since 4.0
+ */
+public class LanguageController {
+
+ // the controller itself, since we have a singleton;
+ // this is the reference instance, instantiated once
+ private static final LanguageController instance =
+ new LanguageController();
+
+ // the message conveyor helps us to get localized messages
+ // according to the provided locale
+ private IMessageConveyor conveyor;
+
+ /**
+ * Private constructor. The fallback language is set to English for all
+ * messages in arara.
+ */
+ private LanguageController() {
+ conveyor = new MessageConveyor(new Locale("en"));
+ }
+
+ /**
+ * Initializes the class. This method actually does nothing, it just
+ * triggers the static attributions. Dirty, isn't it?
+ */
+ public static void init() {
+ // quack, quack, quack!
+ }
+
+ /**
+ * Gets the singleton reference. Since this class is implemented as a
+ * singleton, you will get the same controller every single time.
+ * @return The language controller which holds the conveyor.
+ */
+ public static LanguageController getInstance() {
+ return instance;
+ }
+
+ /**
+ * Sets the current locale. This method actually resets the language
+ * conveyor in order to use the new locale. It's quite simple.
+ * @param locale The new locale for localized messages through the language
+ * conveyor.
+ */
+ public void setLocale(Locale locale) {
+ conveyor = new MessageConveyor(locale);
+ }
+
+ /**
+ * Gets the localized message indexed by the provided enumeration key,
+ * applying an array of objects as parameters. This method is a wrapper to
+ * the conveyor's method of the same name.
+ * @param <E> Enumeration type that represents the conveyor messages.
+ * @param key Key set in the provided enumeration type.
+ * @param parameters Array of objects to be used as parameters.
+ * @return A string containing a localized message indexed by the provided
+ * enumeration key and applied the array of objects as parameters.
+ */
+ public <E extends Enum<?>> String getMessage(E key, Object... parameters) {
+ return conveyor.getMessage(key, parameters);
+ }
+
+ /**
+ * Gets the localized message indexed by the provided enumeration key. This
+ * method is a wrapper to the conveyor's method of the same name.
+ * @param <E> Enumeration type that represents the conveyor messages.
+ * @param key Key set in the provided enumeration type.
+ * @return A string containing a localized message indexed by the provided
+ * enumeration key.
+ */
+ public <E extends Enum<?>> String getMessage(E key) {
+ return conveyor.getMessage(key);
+ }
+
+}
diff --git a/support/arara/source/src/main/java/com/github/cereda/arara/controller/LoggingController.java b/support/arara/source/src/main/java/com/github/cereda/arara/controller/LoggingController.java
new file mode 100644
index 0000000000..193726fcc4
--- /dev/null
+++ b/support/arara/source/src/main/java/com/github/cereda/arara/controller/LoggingController.java
@@ -0,0 +1,109 @@
+/**
+ * Arara, the cool TeX automation tool
+ * Copyright (c) 2012 -- 2018, Paulo Roberto Massa Cereda
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * 3. Neither the name of the project's author nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+ * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
+ * COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+ * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY
+ * WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+package com.github.cereda.arara.controller;
+
+import ch.qos.logback.classic.LoggerContext;
+import ch.qos.logback.classic.joran.JoranConfigurator;
+import ch.qos.logback.core.joran.spi.JoranException;
+import java.io.InputStream;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Implements the logging controller. This class actually sets the logging
+ * configuration in order to allow appending results to a file.
+ * @author Paulo Roberto Massa Cereda
+ * @version 4.0
+ * @since 4.0
+ */
+public class LoggingController {
+
+ /**
+ * Sets the logging configuration according to the provided boolean value.
+ * If the value is set to true, the log entries will be appended to a file,
+ * otherwise the logging feature will keep silent.
+ * @param enable A boolean value that indicates the logging behaviour
+ * throughout the application.
+ */
+ public static void enableLogging(boolean enable) {
+
+ // get the logger context from a factory, set a
+ // new context and reset it
+ LoggerContext loggerContext =
+ (LoggerContext) LoggerFactory.getILoggerFactory();
+
+ try {
+
+ // get a new configuration and set
+ // the context
+ JoranConfigurator configurator = new JoranConfigurator();
+ configurator.setContext(loggerContext);
+ loggerContext.reset();
+
+ // if enabled, the log entries will be
+ // appended to a file, otherwise it will
+ // remain silent
+ if (enable) {
+
+ // set the file name and configure
+ // the logging controller to append
+ // entries to the file
+ String name = (String) ConfigurationController.
+ getInstance().get("execution.log.name");
+ loggerContext.putProperty("name", name);
+ configurator.doConfigure(getResource());
+ }
+ } catch (JoranException exception) {
+ // quack, quack, quack!
+ }
+ }
+
+ /**
+ * Get the configuration resource as an input stream. The configuration
+ * is actually a XML file.
+ * @return An input stream of the provided configuration XML resource.
+ */
+ private static InputStream getResource() {
+ String resource = "/com/github/cereda/arara/configuration/logback.xml";
+ return LoggingController.class.getResourceAsStream(resource);
+ }
+
+ /**
+ * Initializes the logging controller by disabling it. I don't want an odd
+ * behaviour out of the box.
+ */
+ public static void init() {
+ enableLogging(false);
+ }
+
+}
diff --git a/support/arara/source/src/main/java/com/github/cereda/arara/controller/SessionController.java b/support/arara/source/src/main/java/com/github/cereda/arara/controller/SessionController.java
new file mode 100644
index 0000000000..ac5a45d2b3
--- /dev/null
+++ b/support/arara/source/src/main/java/com/github/cereda/arara/controller/SessionController.java
@@ -0,0 +1,124 @@
+/**
+ * Arara, the cool TeX automation tool
+ * Copyright (c) 2012 -- 2018, Paulo Roberto Massa Cereda
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * 3. Neither the name of the project's author nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+ * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
+ * COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+ * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY
+ * WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+package com.github.cereda.arara.controller;
+
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * Implements a session controller. This class wraps a map that holds the
+ * execution session, that is, a dirty maneuver to exchange pretty much any
+ * data between commands and even rules.
+ * @author Paulo Roberto Massa Cereda
+ * @version 4.0
+ * @since 4.0
+ */
+public class SessionController {
+
+ // the controller itself, since we have a singleton;
+ // this is the reference instance, instantiated once
+ private static final SessionController instance =
+ new SessionController();
+
+ // the session map which holds the execution session;
+ // the idea here is to provide wrappers to the map
+ // methods, so it could be easily manipulated
+ private final Map<String, Object> map;
+
+ /**
+ * Private constructor. Called once when the singleton is created.
+ */
+ private SessionController() {
+ map = new HashMap<String, Object>();
+ }
+
+ /**
+ * Gets the singleton reference. Since this class is implemented as a
+ * singleton, you will get the same controller every single time.
+ * @return The session controller which holds the session map.
+ */
+ public static SessionController getInstance() {
+ return instance;
+ }
+
+ /**
+ * Gets the object indexed by the provided key. This method actually holds
+ * the map method of the very same name.
+ * @param key The provided map key.
+ * @return The object indexed by the provided map key.
+ */
+ public Object get(String key) {
+ return map.get(key);
+ }
+
+ /**
+ * Puts the object in the session map and indexes it under the provided
+ * key. This method actually holds the map method of the very same name.
+ * @param key The provided map key.
+ * @param value The object to be indexed under the provided key.
+ */
+ public void put(String key, Object value) {
+ map.put(key, value);
+ }
+
+ /**
+ * Checks if the session map contains the provided key. This method holds
+ * the map method of the very same name.
+ * @param key The key to be checked.
+ * @return A boolean value indicating if the session map contains the
+ * provided key.
+ */
+ public boolean contains(String key) {
+ return map.containsKey(key);
+ }
+
+ /**
+ * Remove an entry from the map according to the provided key. This method
+ * holds the map method of the same name.
+ * @param key The provided key to indicate which session map entry should
+ * be removed.
+ */
+ public void remove(String key) {
+ map.remove(key);
+ }
+
+ /**
+ * Clears the session map. This method, as usual, holds the map method of
+ * the same name.
+ */
+ public void clear() {
+ map.clear();
+ }
+
+}
diff --git a/support/arara/source/src/main/java/com/github/cereda/arara/controller/SystemCallController.java b/support/arara/source/src/main/java/com/github/cereda/arara/controller/SystemCallController.java
new file mode 100644
index 0000000000..9a592853f6
--- /dev/null
+++ b/support/arara/source/src/main/java/com/github/cereda/arara/controller/SystemCallController.java
@@ -0,0 +1,153 @@
+/**
+ * Arara, the cool TeX automation tool
+ * Copyright (c) 2012 -- 2018, Paulo Roberto Massa Cereda
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * 3. Neither the name of the project's author nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+ * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
+ * COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+ * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY
+ * WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+package com.github.cereda.arara.controller;
+
+import java.util.HashMap;
+import java.util.Map;
+import org.zeroturnaround.exec.ProcessExecutor;
+
+/**
+ * Implements a system call controller. This class wraps a map that holds the
+ * result of system specific variables not directly available at runtime.
+ * @author Paulo Roberto Massa Cereda
+ * @version 4.0
+ * @since 4.0
+ */
+public class SystemCallController {
+
+ /**
+ * Implements a private command interface.
+ */
+ private interface Command {
+
+ /**
+ * Executes the command body.
+ * @return An object.
+ */
+ public Object execute();
+ }
+
+ // the controller itself, since we have a singleton;
+ // this is the reference instance, instantiated once
+ private static final SystemCallController instance =
+ new SystemCallController();
+
+ // the system call map which holds the result of
+ // system specific variables not directly available
+ // at runtime; the idea here is to provide wrappers
+ // to the map getter, so it could be easily manipulated
+ private final Map<String, Object> map;
+
+ // the commands map will allow the system call map being
+ // populated only on demand, that is, if the key is not
+ // found, this map will provide the corresponding method
+ // and update the value
+ private final Map<String, Command> commands;
+
+ /**
+ * Private constructor. Called once when the singleton is created.
+ */
+ private SystemCallController() {
+
+ // create the new map instance to be
+ // populated on demand
+ map = new HashMap<String, Object>();
+
+ // create the new map of commands and
+ // add the corresponding system calls
+ commands = new HashMap<String, Command>();
+
+ // add the check for a Cygwin
+ // environment in here
+ commands.put("cygwin", new Command() {
+
+ /**
+ * Implements the body of the command. In this particular
+ * instance, it checks if we are inside a Cygwin environment.
+ * @return A boolean value indicating if we are inside a Cygwin
+ * environment.
+ */
+ public Object execute() {
+ try {
+
+ // execute a new system call to 'uname -s', read the output
+ // as an UTF-8 string, lowercase it and check if it starts
+ // with the 'cygwin' string; if so, we are inside Cygwin
+ return (
+ new ProcessExecutor().command("uname", "-s").
+ readOutput(true).execute().outputUTF8()
+ ).toLowerCase().startsWith("cygwin");
+
+ } catch (Exception exception) {
+
+ // gracefully fallback in case of any nasty and evil
+ // exception, e.g, if the command is unavailable
+ return false;
+ }
+ }
+ });
+ }
+
+ /**
+ * Gets the singleton reference. Since this class is implemented as a
+ * singleton, you will get the same controller every single time.
+ * @return The system call controller which holds the system call map.
+ */
+ public static SystemCallController getInstance() {
+ return instance;
+ }
+
+ /**
+ * Gets the object indexed by the provided key. This method actually holds
+ * the map method of the very same name.
+ * @param key The provided map key.
+ * @return The object indexed by the provided map key.
+ */
+ public Object get(String key) {
+
+ // if key is not found, meaning that
+ // the value wasn't required before
+ if (!map.containsKey(key)) {
+
+ // perform the system call and
+ // populate the corresponding value
+ map.put(key, commands.get(key).execute());
+ }
+
+ // simply return the corresponding
+ // value based on the provided key
+ return map.get(key);
+ }
+
+}
diff --git a/support/arara/source/src/main/java/com/github/cereda/arara/model/AraraException.java b/support/arara/source/src/main/java/com/github/cereda/arara/model/AraraException.java
new file mode 100644
index 0000000000..f2a340fd01
--- /dev/null
+++ b/support/arara/source/src/main/java/com/github/cereda/arara/model/AraraException.java
@@ -0,0 +1,88 @@
+/**
+ * Arara, the cool TeX automation tool
+ * Copyright (c) 2012 -- 2018, Paulo Roberto Massa Cereda
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * 3. Neither the name of the project's author nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+ * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
+ * COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+ * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY
+ * WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+package com.github.cereda.arara.model;
+
+/**
+ * Implements the specific exception model for arara.
+ * @author Paulo Roberto Massa Cereda
+ * @version 4.0
+ * @since 4.0
+ */
+public class AraraException extends Exception {
+
+ // the underlying exception,
+ // used to hold more details
+ // on what really happened
+ private Exception exception;
+
+ /**
+ * Constructor. Takes the exception message.
+ * @param message The exception message.
+ */
+ public AraraException(String message) {
+ super(message);
+ }
+
+ /**
+ * Constructor. Takes the exception message and the underlying exception.
+ * @param message The exception message.
+ * @param exception The underlying exception object.
+ */
+ public AraraException(String message, Exception exception) {
+ super(message);
+ this.exception = exception;
+ }
+
+ /**
+ * Gets the underlying exception.
+ * @return The underlying message.
+ */
+ public Exception getException() {
+ return exception;
+ }
+
+ /**
+ * Checks if there is an underlying exception defined in the current object.
+ * @return A boolean value indicating if the current object has an
+ * underlying exception.
+ */
+ public boolean hasException() {
+ if (exception != null) {
+ return (exception.getMessage() != null);
+ } else {
+ return false;
+ }
+ }
+
+}
diff --git a/support/arara/source/src/main/java/com/github/cereda/arara/model/Argument.java b/support/arara/source/src/main/java/com/github/cereda/arara/model/Argument.java
new file mode 100644
index 0000000000..0b15baf9d7
--- /dev/null
+++ b/support/arara/source/src/main/java/com/github/cereda/arara/model/Argument.java
@@ -0,0 +1,125 @@
+/**
+ * Arara, the cool TeX automation tool
+ * Copyright (c) 2012 -- 2018, Paulo Roberto Massa Cereda
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * 3. Neither the name of the project's author nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+ * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
+ * COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+ * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY
+ * WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+package com.github.cereda.arara.model;
+
+import com.github.cereda.arara.utils.CommonUtils;
+
+/**
+ * The rule argument model.
+ * @author Paulo Roberto Massa Cereda
+ * @version 4.0
+ * @since 4.0
+ */
+public class Argument {
+
+ // the argument identifier
+ private String identifier;
+
+ // a boolean indicating if the
+ // current argument is required
+ private boolean required;
+
+ // the flag to hold the argument
+ // value manipulation
+ private String flag;
+
+ // the argument fallback if it is
+ // not defined in the directive
+ private String fallback;
+
+ /**
+ * Gets the identifier.
+ * @return The identifier.
+ */
+ public String getIdentifier() {
+ return CommonUtils.removeKeyword(identifier);
+ }
+
+ /**
+ * Sets the identifier.
+ * @param identifier The identifier.
+ */
+ public void setIdentifier(String identifier) {
+ this.identifier = identifier;
+ }
+
+ /**
+ * Checks if the argument is required.
+ * @return A boolean value indicating if the argument is required.
+ */
+ public boolean isRequired() {
+ return required;
+ }
+
+ /**
+ * Sets the argument requirement.
+ * @param required A boolean value.
+ */
+ public void setRequired(boolean required) {
+ this.required = required;
+ }
+
+ /**
+ * Gets the argument flag.
+ * @return The flag.
+ */
+ public String getFlag() {
+ return CommonUtils.removeKeyword(flag);
+ }
+
+ /**
+ * Sets the argument flag.
+ * @param flag The argument flag.
+ */
+ public void setFlag(String flag) {
+ this.flag = flag;
+ }
+
+ /**
+ * Gets the argument fallback.
+ * @return The argument fallback.
+ */
+ public String getDefault() {
+ return CommonUtils.removeKeyword(fallback);
+ }
+
+ /**
+ * Sets the argument fallback.
+ * @param fallback The argument fallback.
+ */
+ public void setDefault(String fallback) {
+ this.fallback = fallback;
+ }
+
+}
diff --git a/support/arara/source/src/main/java/com/github/cereda/arara/model/Command.java b/support/arara/source/src/main/java/com/github/cereda/arara/model/Command.java
new file mode 100644
index 0000000000..3058389832
--- /dev/null
+++ b/support/arara/source/src/main/java/com/github/cereda/arara/model/Command.java
@@ -0,0 +1,122 @@
+/**
+ * Arara, the cool TeX automation tool
+ * Copyright (c) 2012 -- 2018, Paulo Roberto Massa Cereda
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * 3. Neither the name of the project's author nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+ * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
+ * COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+ * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY
+ * WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+package com.github.cereda.arara.model;
+
+import com.github.cereda.arara.utils.CommonUtils;
+import java.io.File;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+/**
+ * Implements a command model, containing a list of strings.
+ * @author Paulo Roberto Massa Cereda
+ * @version 4.0
+ * @since 4.0
+ */
+public class Command {
+
+ // a list of elements which are components
+ // of a command and represented as strings
+ private final List<String> elements;
+
+ // an optional file acting as a reference
+ // for the default working directory
+ private File workingDirectory;
+
+ /**
+ * Constructor.
+ * @param values An array of objects.
+ */
+ public Command(Object... values) {
+ elements = new ArrayList<String>();
+ List result = CommonUtils.flatten(Arrays.asList(values));
+ for (Object value : result) {
+ String element = String.valueOf(value);
+ if (!CommonUtils.checkEmptyString(element)) {
+ elements.add(element);
+ }
+ }
+ }
+
+ /**
+ * Constructor.
+ * @param elements A list of strings.
+ */
+ public Command(List<String> elements) {
+ this.elements = elements;
+ }
+
+ /**
+ * Gets the list of strings representing each element of a command.
+ * @return A list of strings.
+ */
+ public List<String> getElements() {
+ return elements;
+ }
+
+ /**
+ * Sets the working directory.
+ * @param workingDirectory A file representing the working directory.
+ */
+ public void setWorkingDirectory(File workingDirectory) {
+ this.workingDirectory = workingDirectory;
+ }
+
+ /**
+ * Gets the working directory, if any.
+ * @return A file representing the working directory.
+ */
+ public File getWorkingDirectory() {
+ return workingDirectory;
+ }
+
+ /**
+ * Checks if a working directory was defined.
+ * @return A logic value indicating if a working directory was defined.
+ */
+ public boolean hasWorkingDirectory() {
+ return workingDirectory != null;
+ }
+
+ /**
+ * Provides a textual representation of the current command.
+ * @return A string representing the current command.
+ */
+ @Override
+ public String toString() {
+ return CommonUtils.getCollectionElements(elements, "[ ", " ]", ", ");
+ }
+
+}
diff --git a/support/arara/source/src/main/java/com/github/cereda/arara/model/Conditional.java b/support/arara/source/src/main/java/com/github/cereda/arara/model/Conditional.java
new file mode 100644
index 0000000000..4c830f0a1b
--- /dev/null
+++ b/support/arara/source/src/main/java/com/github/cereda/arara/model/Conditional.java
@@ -0,0 +1,139 @@
+/**
+ * Arara, the cool TeX automation tool
+ * Copyright (c) 2012 -- 2018, Paulo Roberto Massa Cereda
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * 3. Neither the name of the project's author nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+ * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
+ * COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+ * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY
+ * WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+package com.github.cereda.arara.model;
+
+/**
+ * The conditional class, it represents the type of conditional available
+ * for a directive and its corresponding expression to be evaluated.
+ * @author Paulo Roberto Massa Cereda
+ * @version 4.0
+ * @since 4.0
+ */
+public class Conditional {
+
+ // these are all types of conditionals arara
+ // is able to recognize; personally, I believe
+ // they are more than sufficient to cover the
+ // majority of test cases
+ public enum ConditionalType {
+
+ // evaluated beforehand, directive is interpreted
+ // if and only if the result is true
+ IF,
+
+ // there is no evaluation, directive is interpreted,
+ // no extra effort is needed
+ NONE,
+
+ // evaluated beforehand, directive is interpreted
+ // if and only if the result is false
+ UNLESS,
+
+ // directive is interpreted the first time, then the
+ // evaluation is done; while the result is false,
+ // the directive is interpreted again and again
+ UNTIL,
+
+ // evaluated beforehand, directive is interpreted if
+ // and oly if the result is true, and the process is
+ // repeated while the result still holds true
+ WHILE
+ }
+
+ // the conditional type, specified above; the
+ // default fallback, as seen in the constructor,
+ // is set to NONE, that is, no conditional at all
+ private ConditionalType type;
+
+ // the expression to be evaluated according to its
+ // type; the default fallback, as seen in the
+ // constructor, is set to an empty string
+ private String condition;
+
+ /**
+ * Constructor.
+ */
+ public Conditional() {
+ type = ConditionalType.NONE;
+ condition = "";
+ }
+
+ /**
+ * Gets the conditional type.
+ * @return The conditional type.
+ */
+ public ConditionalType getType() {
+ return type;
+ }
+
+ /**
+ * Sets the conditional type.
+ * @param type The conditional type.
+ */
+ public void setType(ConditionalType type) {
+ this.type = type;
+ }
+
+ /**
+ * Gets the condition, that is, the expression to be evaluated.
+ * @return A string representing the condition.
+ */
+ public String getCondition() {
+ return condition;
+ }
+
+ /**
+ * Sets the condition, that is, the expression to be evaluated.
+ * @param condition A string representing the condition.
+ */
+ public void setCondition(String condition) {
+ this.condition = condition;
+ }
+
+ /**
+ * Provides a textual representation of the conditional object.
+ * @return A string representation of this object.
+ */
+ @Override
+ public String toString() {
+ StringBuilder builder = new StringBuilder();
+ builder.append("{ ").append(type);
+ if (type != ConditionalType.NONE) {
+ builder.append(", expression: ").append(condition.trim());
+ }
+ builder.append(" }");
+ return builder.toString();
+ }
+
+}
diff --git a/support/arara/source/src/main/java/com/github/cereda/arara/model/Configuration.java b/support/arara/source/src/main/java/com/github/cereda/arara/model/Configuration.java
new file mode 100644
index 0000000000..0c2c294958
--- /dev/null
+++ b/support/arara/source/src/main/java/com/github/cereda/arara/model/Configuration.java
@@ -0,0 +1,246 @@
+/**
+ * Arara, the cool TeX automation tool
+ * Copyright (c) 2012 -- 2018, Paulo Roberto Massa Cereda
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * 3. Neither the name of the project's author nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+ * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
+ * COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+ * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY
+ * WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+package com.github.cereda.arara.model;
+
+import com.github.cereda.arara.controller.ConfigurationController;
+import com.github.cereda.arara.controller.LanguageController;
+import com.github.cereda.arara.utils.CommonUtils;
+import com.github.cereda.arara.utils.ConfigurationUtils;
+import java.io.File;
+import java.nio.charset.Charset;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * Implements the configuration model, which holds the default settings and can
+ * load the configuration file.
+ * @author Paulo Roberto Massa Cereda
+ * @version 4.0
+ * @since 4.0
+ */
+public class Configuration {
+
+ // the application messages obtained from the
+ // language controller
+ private static final LanguageController messages =
+ LanguageController.getInstance();
+
+ /**
+ * Loads the application configuration.
+ * @throws AraraException Something wrong happened, to be caught in the
+ * higher levels.
+ */
+ public static void load() throws AraraException {
+
+ // initialize both file type and language models,
+ // since we can track errors from there instead
+ // of relying on a check on this level
+ FileType.init();
+ Language.init();
+
+ // reset everything
+ reset();
+
+ // get the configuration file, if any
+ File file = ConfigurationUtils.getConfigFile();
+ if (file != null) {
+
+ // set the configuration file name for
+ // logging purposes
+ ConfigurationController.getInstance().
+ put("execution.configuration.name",
+ CommonUtils.getCanonicalPath(file)
+ );
+
+ // then validate it and update the
+ // configuration accordingly
+ Resource resource = ConfigurationUtils.validateConfiguration(file);
+ update(resource);
+ }
+
+ // just to be sure, update the
+ // current locale in order to
+ // display localized messages
+ Locale locale = ((Language) ConfigurationController.
+ getInstance().get("execution.language")).getLocale();
+ LanguageController.getInstance().setLocale(locale);
+ }
+
+ /**
+ * Resets the configuration to initial settings.
+ * @throws AraraException Something wrong happened, to be caught in the
+ * higher levels.
+ */
+ private static void reset() throws AraraException {
+
+ // put everything in a map to be
+ // later assigned to the configuration
+ // controller, which holds the settings
+ Map<String, Object> mapping = new HashMap<String, Object>();
+
+ mapping.put("execution.loops", 10L);
+ mapping.put("directives.charset", Charset.forName("UTF-8"));
+ mapping.put("execution.errors.halt", true);
+ mapping.put("execution.timeout", false);
+ mapping.put("execution.timeout.value", 0L);
+ mapping.put("execution.timeout.unit", TimeUnit.MILLISECONDS);
+ mapping.put("application.version", "4.0");
+ mapping.put("application.revision", "1");
+ mapping.put("directives.linebreak.pattern", "^\\s*-->\\s(.*)$");
+
+ String directive = "^\\s*(\\w+)\\s*(:\\s*(\\{.*\\})\\s*)?";
+ String pattern = "(\\s+(if|while|until|unless)\\s+(\\S.*))?$";
+
+ mapping.put("directives.pattern", directive.concat(pattern));
+ mapping.put("application.pattern", "arara:\\s");
+ mapping.put("application.width", 65);
+ mapping.put("execution.database.name", "arara");
+ mapping.put("execution.log.name", "arara");
+ mapping.put("execution.verbose", false);
+
+ mapping.put("trigger.halt", false);
+ mapping.put("execution.language", new Language("en"));
+ mapping.put("execution.logging", false);
+ mapping.put("execution.dryrun", false);
+ mapping.put("execution.status", 0);
+ mapping.put("application.copyright.year", "2012-2018");
+ mapping.put("execution.filetypes", ConfigurationUtils.
+ getDefaultFileTypes());
+ mapping.put("execution.rule.paths",
+ Arrays.asList(
+ CommonUtils.buildPath(
+ ConfigurationUtils.getApplicationPath(),
+ "rules"
+ )
+ )
+ );
+
+ mapping.put("execution.preambles", new HashMap<String, String>());
+ mapping.put("execution.preamble.active", false);
+ mapping.put("execution.configuration.name", "[none]");
+ mapping.put("execution.header", false);
+ mapping.put("ui.lookandfeel", "none");
+
+ // get the configuration controller and
+ // set every map key to it
+ ConfigurationController controller =
+ ConfigurationController.getInstance();
+ for (String key : mapping.keySet()) {
+ controller.put(key, mapping.get(key));
+ }
+ }
+
+ /**
+ * Update the configuration based on the provided map.
+ * @param data Map containing the new configuration settings.
+ * @throws AraraException Something wrong happened, to be caught in the
+ * higher levels.
+ */
+ private static void update(Resource resource) throws AraraException {
+
+ ConfigurationController controller =
+ ConfigurationController.getInstance();
+
+ if (resource.getPaths() != null) {
+ List<String> paths = resource.getPaths();
+ paths = ConfigurationUtils.normalizePaths(paths);
+ controller.put("execution.rule.paths", paths);
+ }
+
+ if (resource.getFiletypes() != null) {
+ List<FileTypeResource> resources = resource.getFiletypes();
+ List<FileType> filetypes = new ArrayList<FileType>();
+ for (FileTypeResource type : resources) {
+ if (type.getPattern() != null) {
+ filetypes.add(
+ new FileType(type.getExtension(), type.getPattern())
+ );
+ } else {
+ filetypes.add(new FileType(type.getExtension()));
+ }
+ }
+ filetypes = ConfigurationUtils.normalizeFileTypes(filetypes);
+ controller.put("execution.filetypes", filetypes);
+ }
+
+ controller.put("execution.verbose", resource.isVerbose());
+ controller.put("execution.logging", resource.isLogging());
+ controller.put("execution.header", resource.isHeader());
+
+ if (resource.getDbname() != null) {
+ controller.put("execution.database.name",
+ ConfigurationUtils.cleanFileName(resource.getDbname()));
+ }
+
+ if (resource.getLogname() != null) {
+ controller.put("execution.log.name",
+ ConfigurationUtils.cleanFileName(resource.getLogname()));
+ }
+
+ if (resource.getLanguage() != null) {
+ controller.put("execution.language",
+ new Language(resource.getLanguage()));
+ }
+
+ long loops = resource.getLoops();
+ if (loops > 0) {
+ controller.put("execution.loops", loops);
+ } else {
+ if (loops < 0) {
+ throw new AraraException(
+ messages.getMessage(
+ Messages.ERROR_CONFIGURATION_LOOPS_INVALID_RANGE
+ )
+ );
+ }
+ }
+
+ if (resource.getPreambles() != null) {
+ controller.put("execution.preambles",
+ resource.getPreambles());
+ }
+
+ if (resource.getLaf() != null) {
+ controller.put("ui.lookandfeel",
+ resource.getLaf());
+ }
+
+ }
+
+}
diff --git a/support/arara/source/src/main/java/com/github/cereda/arara/model/Database.java b/support/arara/source/src/main/java/com/github/cereda/arara/model/Database.java
new file mode 100644
index 0000000000..66d360fdc0
--- /dev/null
+++ b/support/arara/source/src/main/java/com/github/cereda/arara/model/Database.java
@@ -0,0 +1,80 @@
+/**
+ * Arara, the cool TeX automation tool
+ * Copyright (c) 2012 -- 2018, Paulo Roberto Massa Cereda
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * 3. Neither the name of the project's author nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+ * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
+ * COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+ * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY
+ * WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+package com.github.cereda.arara.model;
+
+import java.util.HashMap;
+import org.simpleframework.xml.ElementMap;
+import org.simpleframework.xml.Root;
+
+/**
+ * The XML database model, which keeps track on file changes. I am using the
+ * Simple framework to marshall and unmarshall objects and XML files.
+ * @author Paulo Roberto Massa Cereda
+ * @version 4.0
+ * @since 4.0
+ */
+@Root(name = "status")
+public class Database {
+
+ // the whole database is implemented as a map, where
+ // the key is the absolute canonical file and the value
+ // is its corresponding CRC32 hash; the XML map is done
+ // inline, so it does not clutter the output a lot
+ @ElementMap(entry = "hash", key = "file", attribute = true, inline = true)
+ private HashMap<String, String> map;
+
+ /**
+ * Constructor. It creates a new map.
+ */
+ public Database() {
+ map = new HashMap<String, String>();
+ }
+
+ /**
+ * Gets the map.
+ * @return The map.
+ */
+ public HashMap<String, String> getMap() {
+ return map;
+ }
+
+ /**
+ * Sets the map.
+ * @param map The map.
+ */
+ public void setMap(HashMap<String, String> map) {
+ this.map = map;
+ }
+
+}
diff --git a/support/arara/source/src/main/java/com/github/cereda/arara/model/Directive.java b/support/arara/source/src/main/java/com/github/cereda/arara/model/Directive.java
new file mode 100644
index 0000000000..e311477125
--- /dev/null
+++ b/support/arara/source/src/main/java/com/github/cereda/arara/model/Directive.java
@@ -0,0 +1,146 @@
+/**
+ * Arara, the cool TeX automation tool
+ * Copyright (c) 2012 -- 2018, Paulo Roberto Massa Cereda
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * 3. Neither the name of the project's author nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+ * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
+ * COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+ * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY
+ * WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+package com.github.cereda.arara.model;
+
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Implements the directive model.
+ * @author Paulo Roberto Massa Cereda
+ * @version 4.0
+ * @since 4.0
+ */
+public class Directive {
+
+ // the directive identifier, it is resolved
+ // to the rule identifier later on
+ private String identifier;
+
+ // a map containing the parameters; they
+ // are validated later on in order to
+ // ensure they are valid
+ private Map<String, Object> parameters;
+
+ // a conditional containing the type and
+ // the expression to be evaluated later on
+ private Conditional conditional;
+
+ // a list contained all line numbers from
+ // the main file which built the current
+ // directive
+ private List<Integer> lineNumbers;
+
+ /**
+ * Gets the directive identifier.
+ * @return A string representing the directive identifier.
+ */
+ public String getIdentifier() {
+ return identifier;
+ }
+
+ /**
+ * Sets the directive identifier.
+ * @param identifier A string representing the directive identifier.
+ */
+ public void setIdentifier(String identifier) {
+ this.identifier = identifier;
+ }
+
+ /**
+ * Gets the directive parameters.
+ * @return A map containing the directive parameters.
+ */
+ public Map<String, Object> getParameters() {
+ return parameters;
+ }
+
+ /**
+ * Sets the directive parameters.
+ * @param parameters A map containing the directive parameters.
+ */
+ public void setParameters(Map<String, Object> parameters) {
+ this.parameters = parameters;
+ }
+
+ /**
+ * Gets the conditional object from the current directive.
+ * @return The conditional object from the current directive.
+ */
+ public Conditional getConditional() {
+ return conditional;
+ }
+
+ /**
+ * Sets the conditional object from the current directive.
+ * @param conditional The conditional object from the current directive.
+ */
+ public void setConditional(Conditional conditional) {
+ this.conditional = conditional;
+ }
+
+ /**
+ * Gets the list containing all line numbers from the current directive.
+ * @return A list containing all line numbers from the current directive.
+ */
+ public List<Integer> getLineNumbers() {
+ return lineNumbers;
+ }
+
+ /**
+ * Sets the list containing all line numbers from the current directive.
+ * @param lineNumbers A list containing all line numbers from the current
+ * directive.
+ */
+ public void setLineNumbers(List<Integer> lineNumbers) {
+ this.lineNumbers = lineNumbers;
+ }
+
+ /**
+ * Provides a textual representation of the current directive.
+ * @return A string containing a textual representation of the current
+ * directive.
+ */
+ @Override
+ public String toString() {
+ StringBuilder builder = new StringBuilder();
+ builder.append("Directive: { ");
+ builder.append("identifier: ").append(identifier).append(", ");
+ builder.append("parameters: ").append(parameters).append(", ");
+ builder.append("conditional: ").append(conditional).append(", ");
+ builder.append("lines: ").append(lineNumbers).append(" }");
+ return builder.toString();
+ }
+
+}
diff --git a/support/arara/source/src/main/java/com/github/cereda/arara/model/Evaluator.java b/support/arara/source/src/main/java/com/github/cereda/arara/model/Evaluator.java
new file mode 100644
index 0000000000..41f8d09943
--- /dev/null
+++ b/support/arara/source/src/main/java/com/github/cereda/arara/model/Evaluator.java
@@ -0,0 +1,158 @@
+/**
+ * Arara, the cool TeX automation tool
+ * Copyright (c) 2012 -- 2018, Paulo Roberto Massa Cereda
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * 3. Neither the name of the project's author nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+ * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
+ * COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+ * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY
+ * WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+package com.github.cereda.arara.model;
+
+import com.github.cereda.arara.controller.ConfigurationController;
+import com.github.cereda.arara.controller.LanguageController;
+import com.github.cereda.arara.utils.CommonUtils;
+import com.github.cereda.arara.utils.Methods;
+import java.util.HashMap;
+import java.util.Map;
+import org.mvel2.templates.TemplateRuntime;
+
+/**
+ * Implements the evaluator model, on which a conditional can be analyzed and
+ * processed.
+ * @author Paulo Roberto Massa Cereda
+ * @version 4.0
+ * @since 4.0
+ */
+public class Evaluator {
+
+ // this attribute holds the maximum number of
+ // loops arara will accept; it's like
+ // reaching infinity
+ private final long loops;
+
+ // the counter for the current execution, it
+ // helps us keep track of the number of times
+ // this evaluation has happened, and also to
+ // prevent potential infinite loops
+ private long counter;
+
+ // a flag that indicates the
+ // evaluation to halt regardless
+ // of the the result
+ private boolean halt;
+
+ // the application messages obtained from the
+ // language controller
+ private static final LanguageController messages =
+ LanguageController.getInstance();
+
+ /**
+ * Constructor. It gets the application maximum number of loops and reset
+ * all counters.
+ */
+ public Evaluator() {
+ loops = (Long) ConfigurationController.getInstance().
+ get("execution.loops");
+ counter = 0;
+ halt = false;
+ }
+
+ /**
+ * Evaluate the provided conditional.
+ * @param conditional The conditional object.
+ * @return A boolean value indicating if the conditional holds.
+ * @throws AraraException Something wrong happened, to be caught in the
+ * higher levels.
+ */
+ public boolean evaluate(Conditional conditional) throws AraraException {
+
+ // when in dry-run mode, arara
+ // always ignore conditional evaluations
+ if (((Boolean) ConfigurationController.
+ getInstance().get("execution.dryrun")) == true) {
+ return false;
+ }
+
+ switch (conditional.getType()) {
+ case NONE:
+ return false;
+ case IF:
+ case UNLESS:
+ if (!halt) {
+ halt = true;
+ } else {
+ return false;
+ }
+ break;
+ }
+
+ // check counters and see if the execution
+ // has reached our concept of infinity,
+ // thus breaking the cycles
+ counter++;
+ if (((conditional.getType() == Conditional.ConditionalType.WHILE) &&
+ (counter > loops)) ||
+ ((conditional.getType() == Conditional.ConditionalType.UNTIL) &&
+ (counter >= loops))) {
+ return false;
+ } else {
+
+ Map<String, Object> context = new HashMap<String, Object>();
+ Methods.addConditionalMethods(context);
+
+ try {
+ Object result = TemplateRuntime.eval("@{ ".concat(
+ conditional.getCondition()).concat(" }"), context);
+ if (!CommonUtils.checkClass(Boolean.class, result)) {
+ throw new AraraException(
+ messages.getMessage(
+ Messages.ERROR_EVALUATE_NOT_BOOLEAN_VALUE
+ )
+ );
+ } else {
+ boolean value = (Boolean) result;
+ switch (conditional.getType()) {
+ case UNLESS:
+ case UNTIL:
+ value = !value;
+ break;
+ }
+ return value;
+ }
+ } catch (RuntimeException exception) {
+ throw new AraraException(
+ messages.getMessage(
+ Messages.ERROR_EVALUATE_COMPILATION_FAILED
+ ),
+ exception
+ );
+ }
+ }
+ }
+
+}
diff --git a/support/arara/source/src/main/java/com/github/cereda/arara/model/Extractor.java b/support/arara/source/src/main/java/com/github/cereda/arara/model/Extractor.java
new file mode 100644
index 0000000000..84edef1eb0
--- /dev/null
+++ b/support/arara/source/src/main/java/com/github/cereda/arara/model/Extractor.java
@@ -0,0 +1,88 @@
+/**
+ * Arara, the cool TeX automation tool
+ * Copyright (c) 2012 -- 2018, Paulo Roberto Massa Cereda
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * 3. Neither the name of the project's author nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+ * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
+ * COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+ * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY
+ * WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+package com.github.cereda.arara.model;
+
+import com.github.cereda.arara.controller.ConfigurationController;
+import com.github.cereda.arara.controller.LanguageController;
+import com.github.cereda.arara.utils.CommonUtils;
+import com.github.cereda.arara.utils.DirectiveUtils;
+import java.io.File;
+import java.io.IOException;
+import java.nio.charset.Charset;
+import java.util.List;
+import org.apache.commons.io.FileUtils;
+
+/**
+ * It extracts directives from the provided main file.
+ * @author Paulo Roberto Massa Cereda
+ * @version 4.0
+ * @since 4.0
+ */
+public class Extractor {
+
+ // the application messages obtained from the
+ // language controller
+ private static final LanguageController messages =
+ LanguageController.getInstance();
+
+ /**
+ * Extracts a list of directives from the provided main file, obtained from
+ * the configuration controller.
+ * @return A list of directives.
+ * @throws AraraException Something wrong happened, to be caught in the
+ * higher levels.
+ */
+ public List<Directive> extract() throws AraraException {
+
+ File file = (File) ConfigurationController.
+ getInstance().get("execution.reference");
+ Charset charset = (Charset) ConfigurationController.
+ getInstance().get("directives.charset");
+
+ try {
+ List<String> content = CommonUtils.getPreambleContent();
+ List<String> lines = FileUtils.readLines(file, charset.name());
+ content.addAll(lines);
+ return DirectiveUtils.extractDirectives(content);
+ } catch (IOException ioexception) {
+ throw new AraraException(
+ messages.getMessage(
+ Messages.ERROR_EXTRACTOR_IO_ERROR
+ ),
+ ioexception
+ );
+ }
+ }
+
+}
diff --git a/support/arara/source/src/main/java/com/github/cereda/arara/model/FileType.java b/support/arara/source/src/main/java/com/github/cereda/arara/model/FileType.java
new file mode 100644
index 0000000000..b9f2a01e03
--- /dev/null
+++ b/support/arara/source/src/main/java/com/github/cereda/arara/model/FileType.java
@@ -0,0 +1,170 @@
+/**
+ * Arara, the cool TeX automation tool
+ * Copyright (c) 2012 -- 2018, Paulo Roberto Massa Cereda
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * 3. Neither the name of the project's author nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+ * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
+ * COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+ * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY
+ * WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+package com.github.cereda.arara.model;
+
+import com.github.cereda.arara.controller.LanguageController;
+import com.github.cereda.arara.utils.CommonUtils;
+import java.util.HashMap;
+import java.util.Map;
+import org.apache.commons.lang.builder.EqualsBuilder;
+import org.apache.commons.lang.builder.HashCodeBuilder;
+
+/**
+ * Implements the file type model.
+ * @author Paulo Roberto Massa Cereda
+ * @version 4.0
+ * @since 4.0
+ */
+public class FileType {
+
+ // string representing the
+ // file extension
+ private String extension;
+
+ // string representing the
+ // file pattern to be used
+ // as directive lookup
+ private String pattern;
+
+ // the application messages obtained from the
+ // language controller
+ private static final LanguageController messages =
+ LanguageController.getInstance();
+
+ // a map containing all file
+ // types that arara accepts
+ private static final Map<String, String> types =
+ new HashMap<String, String>();
+
+ /**
+ * Initializes the file type class by setting the default file types and
+ * their corresponding patterns.
+ */
+ public static void init() {
+ types.put("tex", "^\\s*%\\s+");
+ types.put("dtx", "^\\s*%\\s+");
+ types.put("ltx", "^\\s*%\\s+");
+ types.put("drv", "^\\s*%\\s+");
+ types.put("ins", "^\\s*%\\s+");
+ }
+
+ /**
+ * Constructor. It takes both file extension and pattern lookup.
+ * @param extension The file extension.
+ * @param pattern The file pattern.
+ */
+ public FileType(String extension, String pattern) {
+ this.extension = extension;
+ this.pattern = pattern;
+ }
+
+ /**
+ * Constructor. It takes the extension, but it might raise an exception if
+ * the extension is unknown. This constructor is used when you just want
+ * to reorganize the file lookup priority without the need of changing the
+ * default extensions.
+ * @param extension The file extension.
+ * @throws AraraException Something wrong happened, to be caught in the
+ * higher levels.
+ */
+ public FileType(String extension) throws AraraException {
+ if (types.containsKey(extension)) {
+ this.extension = extension;
+ this.pattern = types.get(extension);
+ } else {
+ throw new AraraException(
+ messages.getMessage(
+ Messages.ERROR_FILETYPE_UNKNOWN_EXTENSION,
+ extension,
+ CommonUtils.getFileTypesList()
+ )
+ );
+ }
+ }
+
+ /**
+ * Implements the file type hash code. Note that only the file extension is
+ * considered.
+ * @return An integer representing the file type hash code.
+ */
+ @Override
+ public int hashCode() {
+ return new HashCodeBuilder().append(extension).toHashCode();
+ }
+
+ /**
+ * Implements the file type equals method, checking if one file type is
+ * equal to another. Note that only the file extension is considered.
+ * @param object The object to be analyzed.
+ * @return A boolean value indicating if those two objects are equal.
+ */
+ @Override
+ public boolean equals(Object object) {
+ if (object == null) {
+ return false;
+ }
+ if (getClass() != object.getClass()) {
+ return false;
+ }
+ final FileType reference = (FileType) object;
+ return new EqualsBuilder().append(extension, reference.extension).isEquals();
+ }
+
+ /**
+ * Gets the file type extension.
+ * @return String representing the file type extension.
+ */
+ public String getExtension() {
+ return extension;
+ }
+
+ /**
+ * Gets the file type pattern.
+ * @return String representing the file type pattern.
+ */
+ public String getPattern() {
+ return pattern;
+ }
+
+ /**
+ * Provides a textual representation of the current file type object.
+ * @return A string containing a textual representation of the current file
+ * type object.
+ */
+ @Override
+ public String toString() {
+ return ".".concat(extension);
+ }
+
+}
diff --git a/support/arara/source/src/main/java/com/github/cereda/arara/model/FileTypeResource.java b/support/arara/source/src/main/java/com/github/cereda/arara/model/FileTypeResource.java
new file mode 100644
index 0000000000..2934035858
--- /dev/null
+++ b/support/arara/source/src/main/java/com/github/cereda/arara/model/FileTypeResource.java
@@ -0,0 +1,84 @@
+/**
+ * Arara, the cool TeX automation tool
+ * Copyright (c) 2012 -- 2018, Paulo Roberto Massa Cereda
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * 3. Neither the name of the project's author nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+ * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
+ * COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+ * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY
+ * WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+package com.github.cereda.arara.model;
+
+import com.github.cereda.arara.utils.CommonUtils;
+
+/**
+ * Implements the file type resource model.
+ * @author Paulo Roberto Massa Cereda
+ * @version 4.0
+ * @since 4.0
+ */
+public class FileTypeResource {
+
+ // the file extension
+ private String extension;
+
+ // the file pattern
+ private String pattern;
+
+ /**
+ * Gets the extension.
+ * @return The extension.
+ */
+ public String getExtension() {
+ return CommonUtils.removeKeyword(extension);
+ }
+
+ /**
+ * Sets the extension.
+ * @param extension The extension.
+ */
+ public void setExtension(String extension) {
+ this.extension = extension;
+ }
+
+ /**
+ * Gets the pattern.
+ * @return The pattern.
+ */
+ public String getPattern() {
+ return CommonUtils.removeKeyword(pattern);
+ }
+
+ /**
+ * Sets the pattern.
+ * @param pattern The pattern.
+ */
+ public void setPattern(String pattern) {
+ this.pattern = pattern;
+ }
+
+}
diff --git a/support/arara/source/src/main/java/com/github/cereda/arara/model/Interpreter.java b/support/arara/source/src/main/java/com/github/cereda/arara/model/Interpreter.java
new file mode 100644
index 0000000000..7c8f6e3928
--- /dev/null
+++ b/support/arara/source/src/main/java/com/github/cereda/arara/model/Interpreter.java
@@ -0,0 +1,458 @@
+/**
+ * Arara, the cool TeX automation tool
+ * Copyright (c) 2012 -- 2018, Paulo Roberto Massa Cereda
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * 3. Neither the name of the project's author nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+ * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
+ * COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+ * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY
+ * WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+package com.github.cereda.arara.model;
+
+import com.github.cereda.arara.controller.ConfigurationController;
+import com.github.cereda.arara.controller.LanguageController;
+import com.github.cereda.arara.utils.CommonUtils;
+import com.github.cereda.arara.utils.DisplayUtils;
+import com.github.cereda.arara.utils.InterpreterUtils;
+import com.github.cereda.arara.utils.Methods;
+import com.github.cereda.arara.utils.RuleUtils;
+import java.io.File;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import org.mvel2.templates.TemplateRuntime;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Interprets the list of directives.
+ * @author Paulo Roberto Massa Cereda
+ * @version 4.0
+ * @since 4.0
+ */
+public class Interpreter {
+
+ // the application messages obtained from the
+ // language controller
+ private static final LanguageController messages =
+ LanguageController.getInstance();
+
+ // the class logger obtained from
+ // the logger factory
+ private static final Logger logger =
+ LoggerFactory.getLogger(Interpreter.class);
+
+ // list of directives to be
+ // interpreted in here
+ private List<Directive> directives;
+
+ /**
+ * Sets the list of directives.
+ * @param directives The list of directives.
+ */
+ public void setDirectives(List<Directive> directives) {
+ this.directives = directives;
+ }
+
+ /**
+ * Executes each directive, throwing an exception if something bad has
+ * happened.
+ * @throws AraraException Something wrong happened, to be caught in the
+ * higher levels.
+ */
+ public void execute() throws AraraException {
+
+ for (Directive directive : directives) {
+
+ logger.info(
+ messages.getMessage(
+ Messages.LOG_INFO_INTERPRET_RULE,
+ directive.getIdentifier()
+ )
+ );
+
+ ConfigurationController.getInstance().
+ put("execution.file",
+ directive.getParameters().get("reference")
+ );
+ File file = getRule(directive);
+
+ logger.info(
+ messages.getMessage(
+ Messages.LOG_INFO_RULE_LOCATION,
+ file.getParent()
+ )
+ );
+
+ ConfigurationController.getInstance().
+ put("execution.info.rule.id", directive.getIdentifier());
+ ConfigurationController.getInstance().
+ put("execution.info.rule.path", file.getParent());
+ ConfigurationController.getInstance().
+ put("execution.directive.lines",
+ directive.getLineNumbers()
+ );
+ ConfigurationController.getInstance().
+ put("execution.directive.reference",
+ directive.getParameters().get("reference")
+ );
+
+ Rule rule = parseRule(file, directive);
+ Map<String, Object> parameters = parseArguments(rule, directive);
+ Methods.addRuleMethods(parameters);
+
+ String name = rule.getName();
+ List<String> authors = rule.getAuthors() == null ?
+ new ArrayList<String>() : rule.getAuthors();
+ ConfigurationController.getInstance().
+ put("execution.rule.arguments",
+ InterpreterUtils.getRuleArguments(rule)
+ );
+
+ Evaluator evaluator = new Evaluator();
+
+ boolean available = true;
+ if (InterpreterUtils.
+ runPriorEvaluation(directive.getConditional())) {
+ available = evaluator.evaluate(directive.getConditional());
+ }
+
+ if (available) {
+
+ do {
+
+ List<RuleCommand> commands = rule.getCommands();
+ for (RuleCommand command : commands) {
+ String closure = command.getCommand();
+ Object result = null;
+ try {
+ result = TemplateRuntime.eval(closure, parameters);
+ } catch (RuntimeException exception) {
+ throw new AraraException(
+ CommonUtils.getRuleErrorHeader().concat(
+ messages.getMessage(
+ Messages.ERROR_INTERPRETER_COMMAND_RUNTIME_ERROR
+ )
+ ),
+ exception
+ );
+ }
+
+ List<Object> execution = new ArrayList<Object>();
+ if (CommonUtils.checkClass(List.class, result)) {
+ execution = CommonUtils.flatten((List<?>) result);
+ } else {
+ execution.add(result);
+ }
+
+ for (Object current : execution) {
+
+ if (current == null) {
+ throw new AraraException(
+ CommonUtils.getRuleErrorHeader().concat(
+ messages.getMessage(
+ Messages.ERROR_INTERPRETER_NULL_COMMAND
+ )
+ )
+ );
+ } else {
+
+ if (!CommonUtils.checkEmptyString(
+ String.valueOf(current))) {
+
+ DisplayUtils.printEntry(name,
+ command.getName() == null ?
+ messages.getMessage(
+ Messages.INFO_LABEL_UNNAMED_TASK
+ )
+ : command.getName()
+ );
+ boolean success = true;
+
+ if (CommonUtils.checkClass(
+ Trigger.class, current)) {
+ if (((Boolean) ConfigurationController.
+ getInstance().
+ get("execution.dryrun")) == false) {
+ if (((Boolean) ConfigurationController.
+ getInstance().
+ get("execution.verbose")) == true) {
+ DisplayUtils.wrapText(
+ messages.getMessage(
+ Messages.INFO_INTERPRETER_VERBOSE_MODE_TRIGGER_MODE
+ )
+ );
+ }
+ } else {
+ DisplayUtils.printAuthors(authors);
+ DisplayUtils.wrapText(
+ messages.getMessage(
+ Messages.INFO_INTERPRETER_DRYRUN_MODE_TRIGGER_MODE
+ )
+ );
+ DisplayUtils.printConditional(
+ directive.getConditional()
+ );
+ }
+ Trigger trigger = (Trigger) current;
+ trigger.process();
+ } else {
+ Object representation =
+ CommonUtils.checkClass(
+ Command.class,
+ current
+ ) ?
+ current
+ : String.valueOf(current);
+ logger.info(
+ messages.getMessage(
+ Messages.LOG_INFO_SYSTEM_COMMAND,
+ representation
+ )
+ );
+
+ if (((Boolean) ConfigurationController.
+ getInstance().
+ get("execution.dryrun")) == false) {
+ int code = InterpreterUtils.
+ run(representation);
+ Object check = null;
+ try {
+ Map<String, Object> context =
+ new HashMap<String, Object>();
+ context.put("value", code);
+ check = TemplateRuntime.eval(
+ "@{ ".concat(
+ command.getExit() == null ?
+ "value == 0"
+ : command.getExit()).concat(" }"),
+ context);
+ } catch (RuntimeException exception) {
+ throw new AraraException(
+ CommonUtils.getRuleErrorHeader().
+ concat(
+ messages.getMessage(
+ Messages.ERROR_INTERPRETER_EXIT_RUNTIME_ERROR
+ )
+ ),
+ exception
+ );
+ }
+ if (CommonUtils.
+ checkClass(
+ Boolean.class,
+ check)) {
+ success = (Boolean) check;
+ } else {
+ throw new AraraException(
+ CommonUtils.getRuleErrorHeader().concat(
+ messages.getMessage(
+ Messages.ERROR_INTERPRETER_WRONG_EXIT_CLOSURE_RETURN
+ )
+ )
+ );
+ }
+ } else {
+ DisplayUtils.printAuthors(authors);
+ DisplayUtils.wrapText(
+ messages.getMessage(
+ Messages.INFO_INTERPRETER_DRYRUN_MODE_SYSTEM_COMMAND,
+ representation
+ )
+ );
+ DisplayUtils.printConditional(
+ directive.getConditional()
+ );
+ }
+ }
+
+ DisplayUtils.printEntryResult(success);
+
+ if (((Boolean) ConfigurationController.
+ getInstance().get("trigger.halt"))
+ || (((Boolean) ConfigurationController.
+ getInstance().
+ get("execution.errors.halt")
+ && !success))) {
+ return;
+ }
+ }
+ }
+ }
+ }
+ } while (evaluator.evaluate(directive.getConditional()));
+ }
+ }
+ }
+
+ /**
+ * Gets the rule according to the provided directive.
+ * @param directive The provided directive.
+ * @return The absolute canonical path of the rule, given the provided
+ * directive.
+ * @throws AraraException Something wrong happened, to be caught in the
+ * higher levels.
+ */
+ private File getRule(Directive directive) throws AraraException {
+ File file = InterpreterUtils.buildRulePath(directive.getIdentifier());
+ if (file == null) {
+ throw new AraraException(
+ messages.getMessage(
+ Messages.ERROR_INTERPRETER_RULE_NOT_FOUND,
+ directive.getIdentifier(),
+ CommonUtils.getCollectionElements(
+ CommonUtils.getAllRulePaths(),
+ "(",
+ ")",
+ "; "
+ )
+ )
+ );
+ } else {
+ return file;
+ }
+ }
+
+ /**
+ * Parses the rule against the provided directive.
+ * @param file The file representing the rule.
+ * @param directive The directive to be analyzed.
+ * @return A rule object.
+ * @throws AraraException Something wrong happened, to be caught in the
+ * higher levels.
+ */
+ private Rule parseRule(File file, Directive directive)
+ throws AraraException {
+ return RuleUtils.parseRule(file, directive.getIdentifier());
+ }
+
+ /**
+ * Parses the rule arguments against the provided directive.
+ * @param rule The rule object.
+ * @param directive The directive.
+ * @return A map containing all arguments resolved according to the
+ * directive parameters.
+ * @throws AraraException Something wrong happened, to be caught in the
+ * higher levels.
+ */
+ private Map<String, Object> parseArguments(Rule rule, Directive directive)
+ throws AraraException {
+
+ List<Argument> arguments = rule.getArguments();
+
+ Set<String> unknown = CommonUtils.
+ getUnknownKeys(directive.getParameters(), arguments);
+ unknown.remove("file");
+ unknown.remove("reference");
+ if (!unknown.isEmpty()) {
+ throw new AraraException(
+ CommonUtils.getRuleErrorHeader().concat(
+ messages.getMessage(
+ Messages.ERROR_INTERPRETER_UNKNOWN_KEYS,
+ CommonUtils.getCollectionElements(
+ unknown,
+ "(",
+ ")",
+ ", "
+ )
+ )
+ )
+ );
+ }
+
+ Map<String, Object> mapping = new HashMap<String, Object>();
+ mapping.put("file", directive.getParameters().get("file"));
+ mapping.put("reference", directive.getParameters().get("reference"));
+
+ Map<String, Object> context = new HashMap<String, Object>();
+ context.put("parameters", directive.getParameters());
+ context.put("file", directive.getParameters().get("file"));
+ context.put("reference", directive.getParameters().get("reference"));
+ Methods.addRuleMethods(context);
+
+ for (Argument argument : arguments) {
+ if ((argument.isRequired()) &&
+ (!directive.getParameters().containsKey(
+ argument.getIdentifier()))) {
+ throw new AraraException(
+ CommonUtils.getRuleErrorHeader().concat(
+ messages.getMessage(
+ Messages.ERROR_INTERPRETER_ARGUMENT_IS_REQUIRED,
+ argument.getIdentifier()
+ )
+ )
+ );
+ }
+
+ if (argument.getDefault() != null) {
+ try {
+ Object result = TemplateRuntime.
+ eval(argument.getDefault(), context);
+ mapping.put(argument.getIdentifier(), result);
+ } catch (RuntimeException exception) {
+ throw new AraraException(
+ CommonUtils.getRuleErrorHeader().
+ concat(messages.getMessage(
+ Messages.ERROR_INTERPRETER_DEFAULT_VALUE_RUNTIME_ERROR
+ )
+ ),
+ exception
+ );
+ }
+ } else {
+ mapping.put(argument.getIdentifier(), "");
+ }
+
+ if ((argument.getFlag() != null) &&
+ (directive.getParameters().containsKey(
+ argument.getIdentifier()))) {
+
+ try {
+ Object result = TemplateRuntime.eval(
+ argument.getFlag(),
+ context
+ );
+ mapping.put(argument.getIdentifier(), result);
+ } catch (RuntimeException exception) {
+ throw new AraraException(CommonUtils.getRuleErrorHeader().
+ concat(
+ messages.getMessage(
+ Messages.ERROR_INTERPRETER_FLAG_RUNTIME_EXCEPTION
+ )
+ ),
+ exception
+ );
+ }
+ }
+ }
+
+ return mapping;
+ }
+
+}
diff --git a/support/arara/source/src/main/java/com/github/cereda/arara/model/Language.java b/support/arara/source/src/main/java/com/github/cereda/arara/model/Language.java
new file mode 100644
index 0000000000..2b0fc8266e
--- /dev/null
+++ b/support/arara/source/src/main/java/com/github/cereda/arara/model/Language.java
@@ -0,0 +1,148 @@
+/**
+ * Arara, the cool TeX automation tool
+ * Copyright (c) 2012 -- 2018, Paulo Roberto Massa Cereda
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * 3. Neither the name of the project's author nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+ * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
+ * COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+ * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY
+ * WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+package com.github.cereda.arara.model;
+
+import com.github.cereda.arara.controller.LanguageController;
+import com.github.cereda.arara.utils.CommonUtils;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+
+/**
+ * Implements the language model.
+ * @author Paulo Roberto Massa Cereda
+ * @version 4.0
+ * @since 4.0
+ */
+public class Language {
+
+ // the language code, based on
+ // ISO 639-1 and language variants
+ private final String code;
+
+ // map containing all languages
+ // supported by nightingale
+ private static final Map<String, Pair<String, Locale>> languages =
+ new HashMap<String, Pair<String, Locale>>();
+
+ // the application messages obtained from the
+ // language controller
+ private static final LanguageController messages =
+ LanguageController.getInstance();
+
+ /**
+ * Initialize the language model. All supported languages are added in here.
+ */
+ public static void init() {
+ languages.put("en", new Pair<String, Locale>(
+ "English",
+ new Locale("en")
+ ));
+ languages.put("de", new Pair<String, Locale>(
+ "German",
+ new Locale("de")
+ ));
+ languages.put("nl", new Pair<String, Locale>(
+ "Dutch",
+ new Locale("nl")
+ ));
+ languages.put("qn", new Pair<String, Locale>(
+ "Broad Norfolk",
+ new Locale("en", "QN")
+ ));
+ languages.put("ptbr", new Pair<String, Locale>(
+ "Brazilian Portuguese",
+ new Locale("pt", "BR")
+ ));
+ languages.put("it", new Pair<String, Locale>(
+ "Italian",
+ new Locale("it")
+ ));
+ }
+
+ /**
+ * Creates a new language object. It might raise an exception if the
+ * provided language does not exist in the map.
+ * @param code The language code, based on ISO 639-1 and language variants.
+ * @throws AraraException Something wrong happened, to be caught in the
+ * higher levels.
+ */
+ public Language(String code) throws AraraException {
+ if (languages.containsKey(code)) {
+ this.code = code;
+ } else {
+ throw new AraraException(
+ messages.getMessage(
+ Messages.ERROR_LANGUAGE_INVALID_CODE,
+ getLanguagesList()
+ )
+ );
+ }
+ }
+
+ /**
+ * Gets the language name.
+ * @return A string representing the language name.
+ */
+ public String getName() {
+ return languages.get(code).getFirstElement();
+ }
+
+ /**
+ * Gets the language locale.
+ * @return The language locale.
+ */
+ public Locale getLocale() {
+ return languages.get(code).getSecondElement();
+ }
+
+ /**
+ * Gets a string representing the list of available languages.
+ * @return String representing the list of available languages.
+ */
+ public static String getLanguagesList() {
+ List<String> entries = new ArrayList<String>();
+ for (String key : languages.keySet()) {
+ entries.add(languages.get(key).
+ getFirstElement().
+ concat(": ").
+ concat(key)
+ );
+ }
+ return CommonUtils.getCollectionElements(entries, "(", ")", ", ");
+ }
+
+}
diff --git a/support/arara/source/src/main/java/com/github/cereda/arara/model/Messages.java b/support/arara/source/src/main/java/com/github/cereda/arara/model/Messages.java
new file mode 100644
index 0000000000..9916c3c145
--- /dev/null
+++ b/support/arara/source/src/main/java/com/github/cereda/arara/model/Messages.java
@@ -0,0 +1,170 @@
+/**
+ * Arara, the cool TeX automation tool
+ * Copyright (c) 2012 -- 2018, Paulo Roberto Massa Cereda
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * 3. Neither the name of the project's author nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+ * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
+ * COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+ * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY
+ * WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+package com.github.cereda.arara.model;
+
+import ch.qos.cal10n.BaseName;
+import ch.qos.cal10n.Locale;
+import ch.qos.cal10n.LocaleData;
+
+/**
+ * This enumeration contains all application messages.
+ * @author Paulo Roberto Massa Cereda
+ * @version 4.0
+ * @since 4.0
+ */
+@BaseName("com.github.cereda.arara.localization.messages")
+@LocaleData({
+ @Locale(value = "de", charset = "UTF-8"),
+ @Locale(value = "en", charset = "UTF-8"),
+ @Locale(value = "en_QN", charset = "UTF-8"),
+ @Locale(value = "it", charset = "UTF-8"),
+ @Locale(value = "nl", charset = "UTF-8"),
+ @Locale(value = "pt_BR", charset = "UTF-8")
+})
+public enum Messages {
+
+ ERROR_BASENAME_NOT_A_FILE,
+ ERROR_CALCULATEHASH_IO_EXCEPTION,
+ ERROR_CHECKBOOLEAN_NOT_VALID_BOOLEAN,
+ ERROR_CHECKOS_INVALID_OPERATING_SYSTEM,
+ ERROR_CHECKREGEX_IO_EXCEPTION,
+ ERROR_CONFIGURATION_GENERIC_ERROR,
+ ERROR_CONFIGURATION_INVALID_YAML,
+ ERROR_CONFIGURATION_LOOPS_INVALID_RANGE,
+ ERROR_DISCOVERFILE_FILE_NOT_FOUND,
+ ERROR_EVALUATE_COMPILATION_FAILED,
+ ERROR_EVALUATE_NOT_BOOLEAN_VALUE,
+ ERROR_EXTRACTOR_IO_ERROR,
+ ERROR_FILETYPE_NOT_A_FILE,
+ ERROR_FILETYPE_UNKNOWN_EXTENSION,
+ ERROR_GETAPPLICATIONPATH_ENCODING_EXCEPTION,
+ ERROR_GETCANONICALFILE_IO_EXCEPTION,
+ ERROR_GETCANONICALPATH_IO_EXCEPTION,
+ ERROR_GETPARENTCANONICALPATH_IO_EXCEPTION,
+ ERROR_INTERPRETER_ARGUMENT_IS_REQUIRED,
+ ERROR_INTERPRETER_COMMAND_RUNTIME_ERROR,
+ ERROR_INTERPRETER_DEFAULT_VALUE_RUNTIME_ERROR,
+ ERROR_INTERPRETER_EXIT_RUNTIME_ERROR,
+ ERROR_INTERPRETER_FLAG_RUNTIME_EXCEPTION,
+ ERROR_INTERPRETER_NULL_COMMAND,
+ ERROR_INTERPRETER_RULE_NOT_FOUND,
+ ERROR_INTERPRETER_UNKNOWN_KEYS,
+ ERROR_INTERPRETER_WRONG_EXIT_CLOSURE_RETURN,
+ ERROR_LANGUAGE_INVALID_CODE,
+ ERROR_LOAD_COULD_NOT_LOAD_XML,
+ ERROR_PARSER_INVALID_PREAMBLE,
+ ERROR_PARSER_LOOPS_INVALID_RANGE,
+ ERROR_PARSER_LOOPS_NAN,
+ ERROR_PARSER_TIMEOUT_INVALID_RANGE,
+ ERROR_PARSER_TIMEOUT_NAN,
+ ERROR_PARSERULE_GENERIC_ERROR,
+ ERROR_PARSERULE_INVALID_YAML,
+ ERROR_REPLICATELIST_MISSING_FORMAT_ARGUMENTS_EXCEPTION,
+ ERROR_RULE_IDENTIFIER_AND_PATH,
+ ERROR_RUN_GENERIC_EXCEPTION,
+ ERROR_RUN_INTERRUPTED_EXCEPTION,
+ ERROR_RUN_INVALID_EXIT_VALUE_EXCEPTION,
+ ERROR_RUN_IO_EXCEPTION,
+ ERROR_RUN_TIMEOUT_EXCEPTION,
+ ERROR_RUN_TIMEOUT_INVALID_RANGE,
+ ERROR_SAVE_COULD_NOT_SAVE_XML,
+ ERROR_SESSION_OBTAIN_UNKNOWN_KEY,
+ ERROR_SESSION_REMOVE_UNKNOWN_KEY,
+ ERROR_TRIGGER_ACTION_NOT_FOUND,
+ ERROR_TRIGGER_CALL_EXCEPTION,
+ ERROR_VALIDATE_EMPTY_FILES_LIST,
+ ERROR_VALIDATE_FILE_IS_RESERVED,
+ ERROR_VALIDATE_FILES_IS_NOT_A_LIST,
+ ERROR_VALIDATE_INVALID_DIRECTIVE_FORMAT,
+ ERROR_VALIDATE_NO_DIRECTIVES_FOUND,
+ ERROR_VALIDATE_ORPHAN_LINEBREAK,
+ ERROR_VALIDATE_REFERENCE_IS_RESERVED,
+ ERROR_VALIDATE_YAML_EXCEPTION,
+ ERROR_VALIDATEBODY_ARGUMENT_ID_IS_RESERVED,
+ ERROR_VALIDATEBODY_ARGUMENTS_LIST,
+ ERROR_VALIDATEBODY_DUPLICATE_ARGUMENT_IDENTIFIERS,
+ ERROR_VALIDATEBODY_MISSING_KEYS,
+ ERROR_VALIDATEBODY_NULL_ARGUMENT_ID,
+ ERROR_VALIDATEBODY_NULL_COMMAND,
+ ERROR_VALIDATEBODY_NULL_COMMANDS_LIST,
+ ERROR_VALIDATEHEADER_NULL_ID,
+ ERROR_VALIDATEHEADER_NULL_NAME,
+ ERROR_VALIDATEHEADER_WRONG_IDENTIFIER,
+ ERROR_VELOCITY_FILE_NOT_FOUND,
+ ERROR_VELOCITY_PARSE_EXCEPTION,
+ ERROR_VELOCITY_METHOD_INVOCATION_EXCEPTION,
+ ERROR_VELOCITY_IO_EXCEPTION,
+ INFO_DISPLAY_EXCEPTION_MORE_DETAILS,
+ INFO_DISPLAY_EXECUTION_TIME,
+ INFO_DISPLAY_FILE_INFORMATION,
+ INFO_INTERPRETER_DRYRUN_MODE_SYSTEM_COMMAND,
+ INFO_INTERPRETER_DRYRUN_MODE_TRIGGER_MODE,
+ INFO_INTERPRETER_VERBOSE_MODE_TRIGGER_MODE,
+ INFO_LABEL_AUTHOR,
+ INFO_LABEL_AUTHORS,
+ INFO_LABEL_CONDITIONAL,
+ INFO_LABEL_NO_AUTHORS,
+ INFO_LABEL_ON_DETAILS,
+ INFO_LABEL_ON_ERROR,
+ INFO_LABEL_ON_FAILURE,
+ INFO_LABEL_ON_SUCCESS,
+ INFO_LABEL_UNNAMED_TASK,
+ INFO_PARSER_ALL_RIGHTS_RESERVED,
+ INFO_PARSER_DRYRUN_MODE_DESCRIPTION,
+ INFO_PARSER_HELP_DESCRIPTION,
+ INFO_PARSER_LANGUAGE_DESCRIPTION,
+ INFO_PARSER_LOG_DESCRIPTION,
+ INFO_PARSER_LOOPS_DESCRIPTION,
+ INFO_PARSER_NOTES,
+ INFO_PARSER_ONLY_HEADER,
+ INFO_PARSER_PREAMBLE_DESCRIPTION,
+ INFO_PARSER_SILENT_MODE_DESCRIPTION,
+ INFO_PARSER_TIMEOUT_DESCRIPTION,
+ INFO_PARSER_VERBOSE_MODE_DESCRIPTION,
+ INFO_PARSER_VERSION_DESCRIPTION,
+ LOG_INFO_BEGIN_BUFFER,
+ LOG_INFO_DIRECTIVES_BLOCK,
+ LOG_INFO_END_BUFFER,
+ LOG_INFO_INTERPRET_RULE,
+ LOG_INFO_INTERPRET_TASK,
+ LOG_INFO_POTENTIAL_DIRECTIVE_FOUND,
+ LOG_INFO_POTENTIAL_PATTERN_FOUND,
+ LOG_INFO_RULE_LOCATION,
+ LOG_INFO_SYSTEM_COMMAND,
+ LOG_INFO_TASK_RESULT,
+ LOG_INFO_VALIDATED_DIRECTIVES,
+ LOG_INFO_WELCOME_MESSAGE,
+ ERROR_CONFIGURATION_FILETYPE_MISSING_EXTENSION
+
+}
diff --git a/support/arara/source/src/main/java/com/github/cereda/arara/model/Pair.java b/support/arara/source/src/main/java/com/github/cereda/arara/model/Pair.java
new file mode 100644
index 0000000000..d8591b6ef7
--- /dev/null
+++ b/support/arara/source/src/main/java/com/github/cereda/arara/model/Pair.java
@@ -0,0 +1,92 @@
+/**
+ * Arara, the cool TeX automation tool
+ * Copyright (c) 2012 -- 2018, Paulo Roberto Massa Cereda
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * 3. Neither the name of the project's author nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+ * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
+ * COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+ * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY
+ * WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+package com.github.cereda.arara.model;
+
+/**
+ * Implements a pair of objects.
+ * @author Paulo Roberto Massa Cereda
+ * @version 4.0
+ * @since 4.0
+ */
+public class Pair<T, V> {
+
+ // first element of the pair
+ private final T firstElement;
+
+ // second element of the pair
+ private final V secondElement;
+
+ /**
+ * Constructor.
+ * @param firstElement The first element.
+ * @param secondElement The second element.
+ */
+ public Pair(T firstElement, V secondElement) {
+ this.firstElement = firstElement;
+ this.secondElement = secondElement;
+ }
+
+ /**
+ * Gets the first element.
+ * @return The first element.
+ */
+ public T getFirstElement() {
+ return firstElement;
+ }
+
+ /**
+ * Gets the second element.
+ * @return The second element.
+ */
+ public V getSecondElement() {
+ return secondElement;
+ }
+
+ /**
+ * A shorter version for getting the first element.
+ * @return The first element.
+ */
+ public T first() {
+ return getFirstElement();
+ }
+
+ /**
+ * A shorter version for getting the second element.
+ * @return The second element.
+ */
+ public V second() {
+ return getSecondElement();
+ }
+
+}
diff --git a/support/arara/source/src/main/java/com/github/cereda/arara/model/Parser.java b/support/arara/source/src/main/java/com/github/cereda/arara/model/Parser.java
new file mode 100644
index 0000000000..723e24e76e
--- /dev/null
+++ b/support/arara/source/src/main/java/com/github/cereda/arara/model/Parser.java
@@ -0,0 +1,410 @@
+/**
+ * Arara, the cool TeX automation tool
+ * Copyright (c) 2012 -- 2018, Paulo Roberto Massa Cereda
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * 3. Neither the name of the project's author nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+ * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
+ * COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+ * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY
+ * WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+package com.github.cereda.arara.model;
+
+import com.github.cereda.arara.controller.ConfigurationController;
+import com.github.cereda.arara.controller.LanguageController;
+import com.github.cereda.arara.controller.LoggingController;
+import com.github.cereda.arara.utils.CommonUtils;
+import com.github.cereda.arara.utils.DisplayUtils;
+import java.util.Locale;
+import java.util.Map;
+import org.apache.commons.cli.CommandLine;
+import org.apache.commons.cli.CommandLineParser;
+import org.apache.commons.cli.DefaultParser;
+import org.apache.commons.cli.HelpFormatter;
+import org.apache.commons.cli.Option;
+import org.apache.commons.cli.Options;
+import org.apache.commons.cli.ParseException;
+
+/**
+ * Implements the command line parser.
+ * @author Paulo Roberto Massa Cereda
+ * @version 4.0
+ * @since 4.0
+ */
+public class Parser {
+
+ // command line arguments to be
+ // processed by this parser
+ private final String[] arguments;
+
+ // command line options, it will
+ // group each option available
+ // in arara
+ private Options options;
+
+ // each option available in
+ // arara
+ private Option version;
+ private Option help;
+ private Option log;
+ private Option verbose;
+ private Option silent;
+ private Option dryrun;
+ private Option timeout;
+ private Option language;
+ private Option loops;
+ private Option preamble;
+ private Option onlyheader;
+
+ public Parser() {
+ this.arguments = null;
+ }
+
+ // the application messages obtained from the
+ // language controller
+ private static final LanguageController messages =
+ LanguageController.getInstance();
+
+ /**
+ * Constructor.
+ * @param arguments Array of strings representing the command line
+ * arguments.
+ */
+ public Parser(String[] arguments) {
+ this.arguments = arguments;
+ }
+
+ /**
+ * Parses the command line arguments.
+ * @return A boolean value indicating if the parsing should allow the
+ * application to look for directives in the provided main file.
+ * @throws AraraException Something wrong happened, to be caught in the
+ * higher levels.
+ */
+ public boolean parse() throws AraraException {
+
+ // create new instances of the
+ // command line options, including
+ // the ones that require arguments
+ version = new Option("V", "version", false, "");
+ help = new Option("h", "help", false, "");
+ log = new Option("l", "log", false, "");
+ verbose = new Option("v", "verbose", false, "");
+ silent = new Option("s", "silent", false, "");
+ dryrun = new Option("n", "dry-run", false, "");
+ timeout = new Option("t", "timeout", true, "");
+ timeout.setArgName("number");
+ language = new Option("L", "language", true, "");
+ language.setArgName("code");
+ loops = new Option("m", "max-loops", true, "");
+ loops.setArgName("number");
+ preamble = new Option("p", "preamble", true, "");
+ preamble.setArgName("name");
+ onlyheader = new Option("H", "header", false, "");
+
+ // add all options to the options
+ // group, so they are recognized
+ // by the command line parser
+ options = new Options();
+ options.addOption(version);
+ options.addOption(help);
+ options.addOption(log);
+ options.addOption(verbose);
+ options.addOption(silent);
+ options.addOption(dryrun);
+ options.addOption(timeout);
+ options.addOption(language);
+ options.addOption(loops);
+ options.addOption(preamble);
+ options.addOption(onlyheader);
+
+ // update all descriptions based
+ // on the localized messages
+ updateDescriptions();
+
+ // a new default command line
+ // parser is created and the
+ // arguments are parsed
+ CommandLineParser parser = new DefaultParser();
+
+ try {
+
+ CommandLine line = parser.parse(options, arguments);
+
+ String reference;
+ if (line.hasOption("language")) {
+ ConfigurationController.getInstance().
+ put("execution.language",
+ new Language(line.getOptionValue("language"))
+ );
+ Locale locale = ((Language) ConfigurationController.
+ getInstance().get("execution.language")).getLocale();
+ messages.setLocale(locale);
+ updateDescriptions();
+ }
+
+ if (line.hasOption("help")) {
+ printVersion();
+ printUsage();
+ return false;
+ }
+
+ if (line.hasOption("version")) {
+ printVersion();
+ printNotes();
+ return false;
+ }
+
+ if (line.getArgs().length != 1) {
+ printVersion();
+ printUsage();
+ return false;
+ } else {
+ reference = line.getArgs()[0];
+ }
+
+ if (line.hasOption("timeout")) {
+ try {
+ long value = Long.parseLong(line.getOptionValue("timeout"));
+ if (value <= 0) {
+ throw new AraraException(
+ messages.getMessage(
+ Messages.ERROR_PARSER_TIMEOUT_INVALID_RANGE
+ )
+ );
+ } else {
+ ConfigurationController.getInstance().
+ put("execution.timeout", true);
+ ConfigurationController.getInstance().
+ put("execution.timeout.value", value);
+ }
+ } catch (NumberFormatException nfexception) {
+ throw new AraraException(
+ messages.getMessage(
+ Messages.ERROR_PARSER_TIMEOUT_NAN
+ )
+ );
+ }
+ }
+
+ if (line.hasOption("max-loops")) {
+ try {
+ long value = Long.parseLong(
+ line.getOptionValue("max-loops")
+ );
+ if (value <= 0) {
+ throw new AraraException(
+ messages.getMessage(
+ Messages.ERROR_PARSER_LOOPS_INVALID_RANGE
+ )
+ );
+ } else {
+ ConfigurationController.getInstance().
+ put("execution.loops", value);
+ }
+ } catch (NumberFormatException nfexception) {
+ throw new AraraException(
+ messages.getMessage(
+ Messages.ERROR_PARSER_LOOPS_NAN
+ )
+ );
+ }
+ }
+
+ if (line.hasOption("verbose")) {
+ ConfigurationController.getInstance().
+ put("execution.verbose", true);
+ }
+
+ if (line.hasOption("silent")) {
+ ConfigurationController.getInstance().
+ put("execution.verbose", false);
+ }
+
+ if (line.hasOption("dry-run")) {
+ ConfigurationController.getInstance().
+ put("execution.dryrun", true);
+ ConfigurationController.getInstance().
+ put("execution.errors.halt", false);
+ }
+
+ if (line.hasOption("log")) {
+ ConfigurationController.getInstance().
+ put("execution.logging", true);
+ }
+
+ if (line.hasOption("preamble")) {
+ @SuppressWarnings("unchecked")
+ Map<String, String> preambles = (Map<String, String>)
+ ConfigurationController.getInstance().
+ get("execution.preambles");
+ if (preambles.containsKey(line.getOptionValue("preamble"))) {
+ ConfigurationController.getInstance().
+ put("execution.preamble.active", true);
+ ConfigurationController.getInstance().
+ put("execution.preamble.content",
+ preambles.get(line.getOptionValue("preamble"))
+ );
+ }
+ else {
+ throw new AraraException(
+ messages.getMessage(
+ Messages.ERROR_PARSER_INVALID_PREAMBLE,
+ line.getOptionValue("preamble")
+ )
+ );
+ }
+ }
+
+ if (line.hasOption("header")) {
+ ConfigurationController.getInstance().
+ put("execution.header", true);
+ }
+
+ CommonUtils.discoverFile(reference);
+ LoggingController.enableLogging((Boolean) ConfigurationController.
+ getInstance().get("execution.logging"));
+ ConfigurationController.getInstance().put("display.time", true);
+
+ return true;
+
+ } catch (ParseException pexception) {
+ printVersion();
+ printUsage();
+ return false;
+ }
+ }
+
+ /**
+ * Prints the application usage.
+ */
+ private void printUsage() {
+ HelpFormatter formatter = new HelpFormatter();
+ StringBuilder builder = new StringBuilder();
+ builder.append("arara [file [--dry-run] [--log] ");
+ builder.append("[--verbose | --silent] [--timeout N] ");
+ builder.append("[--max-loops N] [--language L] ");
+ builder.append("[ --preamble P ] [--header] | --help | --version]");
+ formatter.printHelp(builder.toString(), options);
+ }
+
+ /**
+ * Prints the application version.
+ */
+ private void printVersion() {
+ String year = (String) ConfigurationController.getInstance().
+ get("application.copyright.year");
+ String number = (String) ConfigurationController.getInstance().
+ get("application.version");
+ String revision = (String) ConfigurationController.getInstance().
+ get("application.revision");
+ StringBuilder builder = new StringBuilder();
+ builder.append("arara ");
+ builder.append(number);
+ builder.append(" (revision ");
+ builder.append(revision);
+ builder.append(")");
+ builder.append("\n");
+ builder.append("Copyright (c) ").append(year).append(", ");
+ builder.append("Paulo Roberto Massa Cereda");
+ builder.append("\n");
+ builder.append(messages.getMessage(
+ Messages.INFO_PARSER_ALL_RIGHTS_RESERVED)
+ );
+ builder.append("\n");
+ System.out.println(builder.toString());
+ }
+
+ /**
+ * Print the application notes.
+ */
+ private void printNotes() {
+ DisplayUtils.wrapText(messages.getMessage(Messages.INFO_PARSER_NOTES));
+ }
+
+ /**
+ * Updates all the descriptions in order to make them reflect the current
+ * language setting.
+ */
+ private void updateDescriptions() {
+ version.setDescription(
+ messages.getMessage(
+ Messages.INFO_PARSER_VERSION_DESCRIPTION
+ )
+ );
+ help.setDescription(
+ messages.getMessage(
+ Messages.INFO_PARSER_HELP_DESCRIPTION
+ )
+ );
+ log.setDescription(
+ messages.getMessage(
+ Messages.INFO_PARSER_LOG_DESCRIPTION
+ )
+ );
+ verbose.setDescription(
+ messages.getMessage(
+ Messages.INFO_PARSER_VERBOSE_MODE_DESCRIPTION
+ )
+ );
+ silent.setDescription(
+ messages.getMessage(
+ Messages.INFO_PARSER_SILENT_MODE_DESCRIPTION
+ )
+ );
+ dryrun.setDescription(
+ messages.getMessage(
+ Messages.INFO_PARSER_DRYRUN_MODE_DESCRIPTION
+ )
+ );
+ timeout.setDescription(
+ messages.getMessage(
+ Messages.INFO_PARSER_TIMEOUT_DESCRIPTION
+ )
+ );
+ language.setDescription(
+ messages.getMessage(
+ Messages.INFO_PARSER_LANGUAGE_DESCRIPTION
+ )
+ );
+ loops.setDescription(
+ messages.getMessage(
+ Messages.INFO_PARSER_LOOPS_DESCRIPTION
+ )
+ );
+ preamble.setDescription(
+ messages.getMessage(
+ Messages.INFO_PARSER_PREAMBLE_DESCRIPTION
+ )
+ );
+ onlyheader.setDescription(
+ messages.getMessage(
+ Messages.INFO_PARSER_ONLY_HEADER
+ )
+ );
+ }
+
+}
diff --git a/support/arara/source/src/main/java/com/github/cereda/arara/model/Resource.java b/support/arara/source/src/main/java/com/github/cereda/arara/model/Resource.java
new file mode 100644
index 0000000000..2994a4d691
--- /dev/null
+++ b/support/arara/source/src/main/java/com/github/cereda/arara/model/Resource.java
@@ -0,0 +1,291 @@
+/**
+ * Arara, the cool TeX automation tool
+ * Copyright (c) 2012 -- 2018, Paulo Roberto Massa Cereda
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * 3. Neither the name of the project's author nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+ * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
+ * COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+ * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY
+ * WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+package com.github.cereda.arara.model;
+
+import com.github.cereda.arara.utils.CommonUtils;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import org.apache.commons.collections4.CollectionUtils;
+import org.apache.commons.collections4.Transformer;
+import org.apache.commons.lang.SystemUtils;
+import org.mvel2.templates.TemplateRuntime;
+
+/**
+ * Implements the configuration resource model.
+ * @author Paulo Roberto Massa Cereda
+ * @version 4.0
+ * @since 4.0
+ */
+public class Resource {
+
+ // rule paths
+ private List<String> paths;
+
+ // file types
+ private List<FileTypeResource> filetypes;
+
+ // the application language
+ private String language;
+
+ // maximum number of loops
+ private long loops;
+
+ // verbose flag
+ private boolean verbose;
+
+ // logging flag
+ private boolean logging;
+
+ // database name
+ private String dbname;
+
+ // log name
+ private String logname;
+
+ // header flag
+ private boolean header;
+
+ // map of preambles
+ private Map<String, String> preambles;
+
+ // look and feel
+ private String laf;
+
+ /**
+ * Gets the rule paths.
+ * @return The rule paths.
+ */
+ public List<String> getPaths() {
+ if (paths != null) {
+
+ final Map<String, Object> map = new HashMap<String, Object>();
+ Map<String, Object> user = new HashMap<String, Object>();
+ user.put("home", SystemUtils.USER_HOME);
+ user.put("dir", SystemUtils.USER_DIR);
+ user.put("name", SystemUtils.USER_NAME);
+ map.put("user", user);
+
+ Collection<String> result = CollectionUtils.collect(
+ paths, new Transformer<String, String>() {
+ public String transform(String input) {
+ String path = CommonUtils.removeKeyword(input);
+ try {
+ path = (String) TemplateRuntime.eval(path, map);
+ }
+ catch (RuntimeException nothandled) {
+ // do nothing, gracefully fallback to
+ // the default, unparsed path
+ }
+ return path;
+ }
+ });
+ paths = new ArrayList<String>(result);
+ }
+ return paths;
+ }
+
+ /**
+ * Sets the rule paths.
+ * @param paths The rule paths.
+ */
+ public void setPaths(List<String> paths) {
+ this.paths = paths;
+ }
+
+ /**
+ * Gets the list of file types.
+ * @return The list of file types.
+ */
+ public List<FileTypeResource> getFiletypes() {
+ return filetypes;
+ }
+
+ /**
+ * Sets the list of file types.
+ * @param filetypes The list of file types.
+ */
+ public void setFiletypes(List<FileTypeResource> filetypes) {
+ this.filetypes = filetypes;
+ }
+
+ /**
+ * Gets the language.
+ * @return The language.
+ */
+ public String getLanguage() {
+ return CommonUtils.removeKeyword(language);
+ }
+
+ /**
+ * Sets the language.
+ * @param language The language.
+ */
+ public void setLanguage(String language) {
+ this.language = language;
+ }
+
+ /**
+ * Get the maximum number of loops.
+ * @return The maximum number of loops.
+ */
+ public long getLoops() {
+ return loops;
+ }
+
+ /**
+ * Sets the maximum number of loops.
+ * @param loops The maximum number of loops.
+ */
+ public void setLoops(long loops) {
+ this.loops = loops;
+ }
+
+ /**
+ * Checks if verbose mode is active.
+ * @return A boolean value.
+ */
+ public boolean isVerbose() {
+ return verbose;
+ }
+
+ /**
+ * Sets the verbose mode.
+ * @param verbose A boolean value.
+ */
+ public void setVerbose(boolean verbose) {
+ this.verbose = verbose;
+ }
+
+ /**
+ * Checks if logging mode is active.
+ * @return A boolean value.
+ */
+ public boolean isLogging() {
+ return logging;
+ }
+
+ /**
+ * Sets the logging mode.
+ * @param logging A boolean value.
+ */
+ public void setLogging(boolean logging) {
+ this.logging = logging;
+ }
+
+ /**
+ * Gets the database name.
+ * @return The database name.
+ */
+ public String getDbname() {
+ return CommonUtils.removeKeyword(dbname);
+ }
+
+ /**
+ * Sets the database name.
+ * @param dbname The database name.
+ */
+ public void setDbname(String dbname) {
+ this.dbname = dbname;
+ }
+
+ /**
+ * Gets the log name.
+ * @return The log name.
+ */
+ public String getLogname() {
+ return CommonUtils.removeKeyword(logname);
+ }
+
+ /**
+ * Sets the log name.
+ * @param logname The log name.
+ */
+ public void setLogname(String logname) {
+ this.logname = logname;
+ }
+
+ /**
+ * Gets the map of preambles.
+ * @return Map of preambles.
+ */
+ public Map<String, String> getPreambles() {
+ return preambles;
+ }
+
+ /**
+ * Sets the map of preambles.
+ * @param preambles Map of preambles.
+ */
+ public void setPreambles(Map<String, String> preambles) {
+ this.preambles = preambles;
+ }
+
+ /**
+ * Gets the logical value of the header flag.
+ * @return Logical value of the header flag.
+ */
+ public boolean isHeader() {
+ return header;
+ }
+
+ /**
+ * Sets the logical value of the header flag.
+ * @param header The header flag.
+ */
+ public void setHeader(boolean header) {
+ this.header = header;
+ }
+
+ /**
+ * Gets the look and feel reference.
+ * @return The look and feel reference.
+ */
+ public String getLaf() {
+ return CommonUtils.removeKeyword(laf);
+ }
+
+ /**
+ * Sets the look and feel reference.
+ * @param laf The look and feel reference.
+ */
+ public void setLaf(String laf) {
+ this.laf = laf;
+ }
+
+
+
+}
diff --git a/support/arara/source/src/main/java/com/github/cereda/arara/model/Rule.java b/support/arara/source/src/main/java/com/github/cereda/arara/model/Rule.java
new file mode 100644
index 0000000000..af3ad19950
--- /dev/null
+++ b/support/arara/source/src/main/java/com/github/cereda/arara/model/Rule.java
@@ -0,0 +1,155 @@
+/**
+ * Arara, the cool TeX automation tool
+ * Copyright (c) 2012 -- 2018, Paulo Roberto Massa Cereda
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * 3. Neither the name of the project's author nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+ * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
+ * COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+ * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY
+ * WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+package com.github.cereda.arara.model;
+
+import com.github.cereda.arara.utils.CommonUtils;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+import org.apache.commons.collections4.CollectionUtils;
+import org.apache.commons.collections4.Transformer;
+
+/**
+ * Implements the rule model.
+ * @author Paulo Roberto Massa Cereda
+ * @version 4.0
+ * @since 4.0
+ */
+public class Rule {
+
+ // the rule identifier
+ private String identifier;
+
+ // the rule name
+ private String name;
+
+ // the list of authors
+ private List<String> authors;
+
+ // the list of commands
+ private List<RuleCommand> commands;
+
+ // the list of arguments
+ private List<Argument> arguments;
+
+ /**
+ * Gets the rule identifier.
+ * @return The rule identifier.
+ */
+ public String getIdentifier() {
+ return CommonUtils.removeKeyword(identifier);
+ }
+
+ /**
+ * Sets the rule identifier.
+ * @param identifier The rule identifier.
+ */
+ public void setIdentifier(String identifier) {
+ this.identifier = identifier;
+ }
+
+ /**
+ * Gets the rule identifier.
+ * @return The rule identifier.
+ */
+ public String getName() {
+ return CommonUtils.removeKeyword(name);
+ }
+
+ /**
+ * Sets the rule name.
+ * @param name The rule name.
+ */
+ public void setName(String name) {
+ this.name = name;
+ }
+
+ /**
+ * Gets the list of authors.
+ * @return A list of authors.
+ */
+ public List<String> getAuthors() {
+ if (authors != null) {
+ Collection<String> result = CollectionUtils.collect(
+ authors, new Transformer<String, String>() {
+ public String transform(String input) {
+ return CommonUtils.removeKeyword(input);
+ }
+ });
+ authors = new ArrayList<String>(result);
+ }
+ return authors;
+ }
+
+ /**
+ * Sets the list of authors.
+ * @param authors The list of authors.
+ */
+ public void setAuthors(List<String> authors) {
+ this.authors = authors;
+ }
+
+ /**
+ * Gets the list of commands.
+ * @return The list of commands.
+ */
+ public List<RuleCommand> getCommands() {
+ return commands;
+ }
+
+ /**
+ * Sets the list of commands.
+ * @param commands The list of commands.
+ */
+ public void setCommands(List<RuleCommand> commands) {
+ this.commands = commands;
+ }
+
+ /**
+ * Gets the list of arguments.
+ * @return The list of arguments.
+ */
+ public List<Argument> getArguments() {
+ return arguments;
+ }
+
+ /**
+ * Sets the list of arguments.
+ * @param arguments The list of arguments.
+ */
+ public void setArguments(List<Argument> arguments) {
+ this.arguments = arguments;
+ }
+
+}
diff --git a/support/arara/source/src/main/java/com/github/cereda/arara/model/RuleCommand.java b/support/arara/source/src/main/java/com/github/cereda/arara/model/RuleCommand.java
new file mode 100644
index 0000000000..b209d19bcb
--- /dev/null
+++ b/support/arara/source/src/main/java/com/github/cereda/arara/model/RuleCommand.java
@@ -0,0 +1,103 @@
+/**
+ * Arara, the cool TeX automation tool
+ * Copyright (c) 2012 -- 2018, Paulo Roberto Massa Cereda
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * 3. Neither the name of the project's author nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+ * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
+ * COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+ * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY
+ * WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+package com.github.cereda.arara.model;
+
+import com.github.cereda.arara.utils.CommonUtils;
+
+/**
+ * Implements the rule command model.
+ * @author Paulo Roberto Massa Cereda
+ * @version 4.0
+ * @since 4.0
+ */
+public class RuleCommand {
+
+ // the command name
+ private String name;
+
+ // the command instruction
+ private String command;
+
+ // the exit status expression
+ private String exit;
+
+ /**
+ * Gets the command name.
+ * @return The command name.
+ */
+ public String getName() {
+ return CommonUtils.removeKeyword(name);
+ }
+
+ /**
+ * Sets the command name.
+ * @param name The command name.
+ */
+ public void setName(String name) {
+ this.name = name;
+ }
+
+ /**
+ * Gets the command instruction.
+ * @return The command instruction.
+ */
+ public String getCommand() {
+ return CommonUtils.removeKeyword(command);
+ }
+
+ /**
+ * Sets the command instruction.
+ * @param command The command instruction.
+ */
+ public void setCommand(String command) {
+ this.command = command;
+ }
+
+ /**
+ * Gets the exit status expression.
+ * @return The exit status expression.
+ */
+ public String getExit() {
+ return CommonUtils.removeKeyword(exit);
+ }
+
+ /**
+ * Sets the exit status expression.
+ * @param exit The exit status expression.
+ */
+ public void setExit(String exit) {
+ this.exit = exit;
+ }
+
+}
diff --git a/support/arara/source/src/main/java/com/github/cereda/arara/model/Session.java b/support/arara/source/src/main/java/com/github/cereda/arara/model/Session.java
new file mode 100644
index 0000000000..c756ae4550
--- /dev/null
+++ b/support/arara/source/src/main/java/com/github/cereda/arara/model/Session.java
@@ -0,0 +1,122 @@
+/**
+ * Arara, the cool TeX automation tool
+ * Copyright (c) 2012 -- 2018, Paulo Roberto Massa Cereda
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * 3. Neither the name of the project's author nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+ * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
+ * COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+ * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY
+ * WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+package com.github.cereda.arara.model;
+
+import com.github.cereda.arara.controller.LanguageController;
+import com.github.cereda.arara.controller.SessionController;
+
+/**
+ * Implements the session model.
+ * @author Paulo Roberto Massa Cereda
+ * @version 4.0
+ * @since 4.0
+ */
+public class Session {
+
+ // get the current instance from the
+ // session controller
+ private static final SessionController session =
+ SessionController.getInstance();
+
+ // the application messages obtained from the
+ // language controller
+ private static final LanguageController messages =
+ LanguageController.getInstance();
+
+ /**
+ * Inserts the object into the session, indexed by the provided key.
+ * @param key The provided key.
+ * @param value The value to be inserted.
+ */
+ public void insert(String key, Object value) {
+ session.put(key, value);
+ }
+
+ /**
+ * Removes the entry indexed by the provided key from the session.
+ * @param key The provided key.
+ * @throws AraraException Something wrong happened, to be caught in the
+ * higher levels.
+ */
+ public void remove(String key) throws AraraException {
+ if (session.contains(key)) {
+ session.remove(key);
+ } else {
+ throw new AraraException(
+ messages.getMessage(
+ Messages.ERROR_SESSION_REMOVE_UNKNOWN_KEY,
+ key
+ )
+ );
+ }
+ }
+
+ /**
+ * Checks if the provided key exists in the session.
+ * @param key The provided key.
+ * @return A boolean value indicating if the provided key exists in the
+ * session.
+ */
+ public boolean exists(String key) {
+ return session.contains(key);
+ }
+
+ /**
+ * Clears the session.
+ */
+ public void forget() {
+ session.clear();
+ }
+
+ /**
+ * Gets the object indexed by the provided key from the session.
+ * @param key The provided key.
+ * @return The object indexed by the provided key.
+ * @throws AraraException Something wrong happened, to be caught in the
+ * higher levels.
+ */
+ public Object obtain(String key) throws AraraException {
+ if (session.contains(key)) {
+ return session.get(key);
+ } else {
+ throw new AraraException(
+ messages.getMessage(
+ Messages.ERROR_SESSION_OBTAIN_UNKNOWN_KEY,
+ key
+ )
+ );
+ }
+ }
+
+}
diff --git a/support/arara/source/src/main/java/com/github/cereda/arara/model/StopWatch.java b/support/arara/source/src/main/java/com/github/cereda/arara/model/StopWatch.java
new file mode 100644
index 0000000000..45e0969d0b
--- /dev/null
+++ b/support/arara/source/src/main/java/com/github/cereda/arara/model/StopWatch.java
@@ -0,0 +1,84 @@
+/**
+ * Arara, the cool TeX automation tool
+ * Copyright (c) 2012 -- 2018, Paulo Roberto Massa Cereda
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * 3. Neither the name of the project's author nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+ * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
+ * COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+ * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY
+ * WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+package com.github.cereda.arara.model;
+
+import com.github.cereda.arara.controller.ConfigurationController;
+
+/**
+ * Implements a stopwatch.
+ * @author Paulo Roberto Massa Cereda
+ * @version 4.0
+ * @since 4.0
+ */
+public class StopWatch {
+
+ // two variables indicating the
+ // times the stopwatch started
+ // and stopped
+ private static long beginning = 0;
+ private static long end = 0;
+
+ // a variable to indicate the
+ // stopwatch is enabled; so far,
+ // it hasn't started, then it is
+ // not enabled
+ private static boolean enabled = false;
+
+ /**
+ * Starts the stopwatch.
+ */
+ public static void start() {
+ beginning = System.nanoTime();
+ enabled = true;
+ }
+
+ /**
+ * Stops the stopwatch.
+ */
+ public static void stop() {
+ end = System.nanoTime();
+ }
+
+ /**
+ * Gets the string representation of the elapsed time.
+ * @return A string representation of the elapsed time.
+ */
+ public static String getTime() {
+ Language language = (Language) ConfigurationController.
+ getInstance().get("execution.language");
+ double result = enabled ? (double) (end - beginning) / 1000000000 : 0.0;
+ return String.format(language.getLocale(), "%1.2f", result);
+ }
+
+}
diff --git a/support/arara/source/src/main/java/com/github/cereda/arara/model/Trigger.java b/support/arara/source/src/main/java/com/github/cereda/arara/model/Trigger.java
new file mode 100644
index 0000000000..11da04bfcb
--- /dev/null
+++ b/support/arara/source/src/main/java/com/github/cereda/arara/model/Trigger.java
@@ -0,0 +1,135 @@
+/**
+ * Arara, the cool TeX automation tool
+ * Copyright (c) 2012 -- 2018, Paulo Roberto Massa Cereda
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * 3. Neither the name of the project's author nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+ * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
+ * COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+ * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY
+ * WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+package com.github.cereda.arara.model;
+
+import com.github.cereda.arara.controller.ConfigurationController;
+import com.github.cereda.arara.controller.LanguageController;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.Callable;
+
+/**
+ * Implements the trigger model. The tool provides triggers, which are a way
+ * to alter its internal behaviour according to a list of parameters.
+ * @author Paulo Roberto Massa Cereda
+ * @version 4.0
+ * @since 4.0
+ */
+public class Trigger {
+
+ // the action name and its
+ // list of parameters
+ private final String action;
+ private final List<Object> parameters;
+
+ // the application messages obtained from the
+ // language controller
+ private static final LanguageController messages =
+ LanguageController.getInstance();
+
+ /**
+ * Constructor.
+ * @param action The action name.
+ * @param parameters The list of parameters.
+ */
+ public Trigger(String action, List<Object> parameters) {
+ this.action = action;
+ this.parameters = parameters;
+ }
+
+ /**
+ * Gets the action name.
+ * @return The action name.
+ */
+ public String getAction() {
+ return action;
+ }
+
+ /**
+ * Gets the list of parameters.
+ * @return The list of parameters.
+ */
+ public List<Object> getParameters() {
+ return parameters;
+ }
+
+ /**
+ * Returns a textual representation of the current trigger.
+ * @return A string containing a textual representation of the current
+ * trigger.
+ */
+ @Override
+ public String toString() {
+ return "trigger";
+ }
+
+ /**
+ * Processes the current trigger.
+ * @throws AraraException Something wrong happened, to be caught in the
+ * higher levels.
+ */
+ public void process() throws AraraException {
+
+ Map<String, Callable<Object>> mapping =
+ new HashMap<String, Callable<Object>>();
+ mapping.put("halt", new Callable<Object>() {
+ public Object call() {
+ ConfigurationController.getInstance().put("trigger.halt", true);
+ return null;
+ }
+ });
+ if (mapping.containsKey(action)) {
+ try {
+ mapping.get(action).call();
+ } catch (Exception exception) {
+ throw new AraraException(
+ messages.getMessage(
+ Messages.ERROR_TRIGGER_CALL_EXCEPTION,
+ action
+ ),
+ exception
+ );
+ }
+ } else {
+ throw new AraraException(
+ messages.getMessage(
+ Messages.ERROR_TRIGGER_ACTION_NOT_FOUND,
+ action
+ )
+ );
+ }
+ }
+
+}
diff --git a/support/arara/source/src/main/java/com/github/cereda/arara/utils/ClassLoadingUtils.java b/support/arara/source/src/main/java/com/github/cereda/arara/utils/ClassLoadingUtils.java
new file mode 100644
index 0000000000..c40536ce08
--- /dev/null
+++ b/support/arara/source/src/main/java/com/github/cereda/arara/utils/ClassLoadingUtils.java
@@ -0,0 +1,166 @@
+/**
+ * Arara, the cool TeX automation tool
+ * Copyright (c) 2012 -- 2018, Paulo Roberto Massa Cereda
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * 3. Neither the name of the project's author nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+ * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
+ * COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+ * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY
+ * WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+package com.github.cereda.arara.utils;
+
+import com.github.cereda.arara.model.Pair;
+import java.io.File;
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.net.URLClassLoader;
+
+/**
+ * Implements utilitary methods for classloading and object instantiation.
+ * @author Paulo Roberto Massa Cereda
+ * @version 4.0
+ * @since 4.0
+ */
+public class ClassLoadingUtils {
+
+ /**
+ * Loads a class from the provided file, potentially a Java archive.
+ * @param file File containing the Java bytecode (namely, a JAR).
+ * @param name The canonical name of the class.
+ * @return A pair representing the status and the class.
+ */
+ public static Pair<Integer, Class> loadClass(File file, String name) {
+
+ // status and class to be returned,
+ // it defaults to an object class
+ int status = 0;
+ Class value = Object.class;
+
+ // if file does not exist, nothing
+ // can be done, status is changed
+ if (!file.exists()) {
+ status = 1;
+ } else {
+
+ // classloading involves defining
+ // a classloader and fetching the
+ // desired class from it, based on
+ // the provided file archive
+ try {
+
+ // creates a new classloader with
+ // the provided file (potentially
+ // a JAR file)
+ URLClassLoader classloader = new URLClassLoader(
+ new URL[]{
+ file.toURI().toURL()
+ },
+ ClassLoadingUtils.class.getClassLoader()
+ );
+
+ // fetches the class from the
+ // instantiated classloader
+ value = Class.forName(name, true, classloader);
+
+ } catch (MalformedURLException nothandled1) {
+
+ // the file URL is incorrect,
+ // update status accordingly
+ status = 2;
+
+ } catch (ClassNotFoundException nothandled2) {
+
+ // the class was not found,
+ // update status accordingly
+ status = 3;
+
+ }
+ }
+
+ // return a new pair based on the
+ // current status and class holder
+ return new Pair<Integer, Class>(status, value);
+ }
+
+ /**
+ * Loads a class from the provided file, instantiating it.
+ * @param file File containing the Java bytecode (namely, a JAR).
+ * @param name The canonical name of the class.
+ * @return A pair representing the status and the class object.
+ */
+ public static Pair<Integer, Object> loadObject(File file, String name) {
+
+ // load the corresponding class
+ // based on the qualified name
+ Pair<Integer, Class> pair = loadClass(file, name);
+
+ // status and object to be returned,
+ // it defaults to an object
+ int status = pair.getFirstElement();
+ Object value = new Object();
+
+ // checks if the class actually
+ // exists, otherwise simply
+ // ignore instantiation
+ if (status == 0) {
+
+ // object instantiation relies
+ // on the default constructor
+ // (without arguments), class
+ // must implement it
+
+ // OBS: constructors with arguments
+ // must be invoked through reflection
+ try {
+
+ // get the class reference from
+ // the pair and instantiate it
+ // by invoking the default
+ // constructor (without arguments)
+ value = pair.getSecondElement().newInstance();
+
+ } catch (IllegalAccessException nothandled1) {
+
+ // the object instantiation violated
+ // an access policy, status is updated
+ status = 4;
+
+ } catch (InstantiationException nothandled2) {
+
+ // an instantiation exception has
+ // occurred, status is updated
+ status = 5;
+
+ }
+ }
+
+ // return a new pair based on the
+ // current status and object holder
+ return new Pair<Integer, Object>(status, value);
+ }
+
+}
diff --git a/support/arara/source/src/main/java/com/github/cereda/arara/utils/CommonUtils.java b/support/arara/source/src/main/java/com/github/cereda/arara/utils/CommonUtils.java
new file mode 100644
index 0000000000..621634749e
--- /dev/null
+++ b/support/arara/source/src/main/java/com/github/cereda/arara/utils/CommonUtils.java
@@ -0,0 +1,929 @@
+/**
+ * Arara, the cool TeX automation tool
+ * Copyright (c) 2012 -- 2018, Paulo Roberto Massa Cereda
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * 3. Neither the name of the project's author nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+ * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
+ * COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+ * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY
+ * WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+package com.github.cereda.arara.utils;
+
+import com.github.cereda.arara.controller.ConfigurationController;
+import com.github.cereda.arara.controller.LanguageController;
+import com.github.cereda.arara.controller.SystemCallController;
+import com.github.cereda.arara.model.AraraException;
+import com.github.cereda.arara.model.Argument;
+import com.github.cereda.arara.model.Database;
+import com.github.cereda.arara.model.FileType;
+import com.github.cereda.arara.model.Messages;
+import java.io.File;
+import java.io.IOException;
+import java.nio.charset.Charset;
+import java.text.SimpleDateFormat;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.MissingFormatArgumentException;
+import java.util.Set;
+import java.util.StringTokenizer;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+import org.apache.commons.collections4.CollectionUtils;
+import org.apache.commons.collections4.Transformer;
+import org.apache.commons.io.FileUtils;
+import org.apache.commons.io.filefilter.NameFileFilter;
+import org.apache.commons.lang.StringUtils;
+import org.apache.commons.lang.SystemUtils;
+
+/**
+ * Implements common utilitary methods.
+ * @author Paulo Roberto Massa Cereda
+ * @version 4.0
+ * @since 4.0
+ */
+public class CommonUtils {
+
+ // the application messages obtained from the
+ // language controller
+ private static final LanguageController messages =
+ LanguageController.getInstance();
+
+ /**
+ * Checks if the input string is equal to a valid boolean value.
+ * @param value The input string.
+ * @return A boolean value represented by the provided string.
+ * @throws AraraException Something wrong happened, to be caught in the
+ * higher levels.
+ */
+ public static boolean checkBoolean(String value) throws AraraException {
+ List<String> yes = Arrays.asList(
+ new String[]{"yes", "true", "1", "on"}
+ );
+ List<String> no = Arrays.asList(
+ new String[]{"no", "false", "0", "off"}
+ );
+ if (!union(yes, no).contains(value.toLowerCase())) {
+ throw new AraraException(
+ messages.getMessage(
+ Messages.ERROR_CHECKBOOLEAN_NOT_VALID_BOOLEAN,
+ value
+ )
+ );
+ } else {
+ return yes.contains(value.toLowerCase());
+ }
+ }
+
+ /**
+ * Provides a union set operation between two lists.
+ * @param <T> The list type.
+ * @param list1 The first list.
+ * @param list2 The second list.
+ * @return The union of those two lists.
+ */
+ private static <T> List<T> union(List<T> list1, List<T> list2) {
+ Set<T> elements = new HashSet<T>();
+ elements.addAll(list1);
+ elements.addAll(list2);
+ return new ArrayList<T>(elements);
+ }
+
+ /**
+ * Build a system-dependant path based on the path and the file.
+ * @param path A string representing the path to be prepended.
+ * @param file A string representing the file to be appended.
+ * @return The full path as a string.
+ */
+ public static String buildPath(String path, String file) {
+ return path.endsWith(File.separator) ?
+ path.concat(file) : path.concat(File.separator).concat(file);
+ }
+
+ /**
+ * Checks if the provided string is empty. It does not handle a null value.
+ * @param string A string.
+ * @return A boolean value indicating if the string is empty.
+ */
+ public static boolean checkEmptyString(String string) {
+ return "".equals(string);
+ }
+
+ /**
+ * Removes the keyword from the beginning of the provided string.
+ * @param line A string to be analyzed.
+ * @return The provided string without the keyword.
+ */
+ public static String removeKeyword(String line) {
+ if (line != null) {
+ Pattern pattern = Pattern.compile("^(\\s)*<arara>\\s");
+ Matcher matcher = pattern.matcher(line);
+ if (matcher.find()) {
+ line = (line.substring(matcher.end(), line.length()));
+ }
+ line = line.trim();
+ }
+ return line;
+ }
+
+ /**
+ * Discovers the file through string reference lookup and sets the
+ * configuration accordingly.
+ * @param reference The string reference.
+ * @throws AraraException Something wrong happened, to be caught in the
+ * higher levels.
+ */
+ public static void discoverFile(String reference) throws AraraException {
+ File file = lookupFile(reference);
+ if (file == null) {
+ throw new AraraException(
+ messages.getMessage(
+ Messages.ERROR_DISCOVERFILE_FILE_NOT_FOUND,
+ reference,
+ getFileTypesList()
+ )
+ );
+ }
+ }
+
+ /**
+ * Performs a file lookup based on a string reference.
+ * @param reference The file reference as a string.
+ * @return The file as result of the lookup operation.
+ * @throws AraraException Something wrong happened, to be caught in the
+ * higher levels.
+ */
+ private static File lookupFile(String reference) throws AraraException {
+ @SuppressWarnings("unchecked")
+ List<FileType> types = (List<FileType>) ConfigurationController.
+ getInstance().get("execution.filetypes");
+ File file = new File(reference);
+ String name = file.getName();
+ String parent = getParentCanonicalPath(file);
+ String path = buildPath(parent, name);
+
+ // direct search, so we are considering
+ // the reference as a complete name
+ for (FileType type : types) {
+ if (path.endsWith(".".concat(type.getExtension()))) {
+ file = new File(path);
+ if (file.exists()) {
+ if (file.isFile()) {
+ ConfigurationController.
+ getInstance().
+ put("execution.file.pattern",
+ type.getPattern());
+ ConfigurationController.
+ getInstance().
+ put("execution.reference", file);
+ return file;
+ }
+ }
+ }
+ }
+
+ // indirect search; in this case, we are considering
+ // that the file reference has an implict extension,
+ // so we need to add it and look again
+ for (FileType type : types) {
+ path = buildPath(parent, name.concat(".").
+ concat(type.getExtension()));
+ file = new File(path);
+ if (file.exists()) {
+ if (file.isFile()) {
+ ConfigurationController.getInstance().
+ put("execution.file.pattern", type.getPattern());
+ ConfigurationController.getInstance().
+ put("execution.reference", file);
+ return file;
+ }
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Gets the parent canonical path of a file.
+ * @param file The file.
+ * @return The parent canonical path of a file.
+ * @throws AraraException Something wrong happened, to be caught in the
+ * higher levels.
+ */
+ public static String getParentCanonicalPath(File file)
+ throws AraraException {
+ try {
+ String path = file.getCanonicalFile().getParent();
+ return path;
+ } catch (IOException exception) {
+ throw new AraraException(
+ messages.getMessage(
+ Messages.ERROR_GETPARENTCANONICALPATH_IO_EXCEPTION
+ ),
+ exception
+ );
+ }
+ }
+
+ /**
+ * Gets the canonical file from a file.
+ * @param file The file.
+ * @return The canonical file.
+ * @throws AraraException Something wrong happened, to be caught in the
+ * higher levels.
+ */
+ public static File getCanonicalFile(String file) throws AraraException {
+ try {
+ return (new File(file)).getCanonicalFile();
+ } catch (IOException exception) {
+ throw new AraraException(
+ messages.getMessage(
+ Messages.ERROR_GETCANONICALFILE_IO_EXCEPTION
+ ),
+ exception
+ );
+ }
+ }
+
+ /**
+ * Checks if the provided object is from a certain class.
+ * @param clazz The class.
+ * @param object The object.
+ * @return A boolean value indicating if the provided object is from a
+ * certain class.
+ */
+ public static boolean checkClass(Class clazz, Object object) {
+ return clazz.isInstance(object);
+ }
+
+ /**
+ * Helper method to flatten a potential list of lists into a list of
+ * objects.
+ * @param list First list.
+ * @param flat Second list.
+ */
+ private static void flatten(List<?> list, List<Object> flat) {
+ for (Object item : list) {
+ if (item instanceof List<?>) {
+ flatten((List<?>) item, flat);
+ } else {
+ flat.add(item);
+ }
+ }
+ }
+
+ /**
+ * Flattens a potential list of lists into a list of objects.
+ * @param list The list to be flattened.
+ * @return The flattened list.
+ */
+ public static List<Object> flatten(List<?> list) {
+ List<Object> result = new ArrayList<Object>();
+ flatten(list, result);
+ return result;
+ }
+
+ /**
+ * Gets the list of file types, in order.
+ * @return A string representation of the list of file types, in order.
+ */
+ public static String getFileTypesList() {
+ @SuppressWarnings("unchecked")
+ List<FileType> types = (List<FileType>) ConfigurationController.
+ getInstance().get("execution.filetypes");
+ return getCollectionElements(types, "[ ", " ]", " | ");
+ }
+
+ /**
+ * Gets a string representation of a collection.
+ * @param collection The collection.
+ * @param open The opening string.
+ * @param close The closing string.
+ * @param separator The element separator.
+ * @return A string representation of the provided collection.
+ */
+ public static String getCollectionElements(Collection collection,
+ String open, String close, String separator) {
+ StringBuilder builder = new StringBuilder();
+ builder.append(open);
+ builder.append(StringUtils.join(collection, separator));
+ builder.append(close);
+ return builder.toString();
+ }
+
+ /**
+ * Gets a set of strings containing unknown keys from a map and a list. It
+ * is a set difference from the keys in the map and the entries in the list.
+ * @param parameters The map of parameters.
+ * @param arguments The list of arguments.
+ * @return A set of strings representing unknown keys from a map and a list.
+ */
+ public static Set<String> getUnknownKeys(Map<String, Object> parameters,
+ List<Argument> arguments) {
+ Collection<String> found = parameters.keySet();
+ Collection<String> expected = CollectionUtils.collect(
+ arguments, new Transformer<Argument, String>() {
+ public String transform(Argument argument) {
+ return argument.getIdentifier();
+ }
+ });
+ Collection<String> difference = CollectionUtils.
+ subtract(found, expected);
+ return new HashSet<String>(difference);
+ }
+
+ /**
+ * Gets the rule error header, containing the identifier and the path, if
+ * any.
+ * @return A string representation of the rule error header, containing the
+ * identifier and the path, if any.
+ */
+ public static String getRuleErrorHeader() {
+ if ((ConfigurationController.getInstance().
+ contains("execution.info.rule.id")) &&
+ (ConfigurationController.getInstance().
+ contains("execution.info.rule.path"))) {
+ String id = (String) ConfigurationController.getInstance().
+ get("execution.info.rule.id");
+ String path = (String) ConfigurationController.getInstance().
+ get("execution.info.rule.path");
+ return messages.getMessage(
+ Messages.ERROR_RULE_IDENTIFIER_AND_PATH,
+ id,
+ path
+ ).concat(" ");
+ } else {
+ return "";
+ }
+ }
+
+ /**
+ * Trims spaces from every string of a list of strings.
+ * @param input The list of strings.
+ * @return A new list of strings, with each element trimmed.
+ */
+ public static List<String> trimSpaces(List<String> input) {
+ Collection<String> result = CollectionUtils.collect(
+ input, new Transformer<String, String>() {
+ public String transform(String input) {
+ return input.trim();
+ }
+ });
+ return new ArrayList<String>(result);
+ }
+
+ /**
+ * Gets a human readable representation of a file size.
+ * @param file The file.
+ * @return A string representation of the file size.
+ */
+ public static String calculateFileSize(File file) {
+ return FileUtils.byteCountToDisplaySize(file.length());
+ }
+
+ /**
+ * Gets the date the provided file was last modified.
+ * @param file The file.
+ * @return A string representation of the date the provided file was last
+ * modified.
+ */
+ public static String getLastModifiedInformation(File file) {
+ SimpleDateFormat format = new SimpleDateFormat("MM/dd/yyyy HH:mm:ss");
+ return format.format(file.lastModified());
+ }
+
+ /**
+ * Gets a list of all rule paths.
+ * @return A list of all rule paths.
+ * @throws AraraException Something wrong happened, to be caught in the
+ * higher levels.
+ */
+ public static List<String> getAllRulePaths() throws AraraException {
+ @SuppressWarnings("unchecked")
+ List<String> paths = (List<String>) ConfigurationController.
+ getInstance().get("execution.rule.paths");
+ List<String> result = new ArrayList<String>();
+ for (String path : paths) {
+ File location = new File(InterpreterUtils.construct(path, "quack"));
+ result.add(getParentCanonicalPath(location));
+ }
+ return result;
+ }
+
+ /**
+ * Gets the reference of the current file in execution. Note that this
+ * method might return a value different than the main file provided in
+ * the command line.
+ * @return A reference of the current file in execution. Might be different
+ * than the main file provided in the command line.
+ */
+ private static File getCurrentReference() {
+ return (File) ConfigurationController.getInstance().
+ get("execution.file");
+ }
+
+ /**
+ * Calculates the CRC32 checksum of the provided file.
+ * @param file The file.
+ * @return A string containing the CRC32 checksum of the provided file.
+ * @throws AraraException Something wrong happened, to be caught in the
+ * higher levels.
+ */
+ public static String calculateHash(File file) throws AraraException {
+ try {
+ long result = FileUtils.checksumCRC32(file);
+ return String.format("%08x", result);
+ } catch (IOException exception) {
+ throw new AraraException(
+ messages.getMessage(
+ Messages.ERROR_CALCULATEHASH_IO_EXCEPTION
+ ),
+ exception
+ );
+ }
+ }
+
+ /**
+ * Gets the file type of a file.
+ * @param file The file.
+ * @return The corresponding file type.
+ */
+ public static String getFiletype(File file) {
+ return getFiletype(file.getName());
+ }
+
+ /**
+ * Gets the file type of a string representing the file.
+ * @param name A string representing the file.
+ * @return The corresponding file type.
+ */
+ public static String getFiletype(String name) {
+ name = name.lastIndexOf(".") != -1 ?
+ name.substring(name.lastIndexOf(".") + 1, name.length()) : "";
+ return name;
+ }
+
+ /**
+ * Gets the base name of a file.
+ * @param file The file.
+ * @return The corresponding base name.
+ */
+ public static String getBasename(File file) {
+ return getBasename(file.getName());
+ }
+
+ /**
+ * Gets the base name of a string representing the file.
+ * @param name A string representing the file.
+ * @return The corresponding base name.
+ */
+ public static String getBasename(String name) {
+ int index = name.lastIndexOf(".") != -1 ?
+ name.lastIndexOf(".") : name.length();
+ return name.substring(0, index);
+ }
+
+ /**
+ * Encloses the provided object in double quotes.
+ * @param object The object.
+ * @return A string representation of the provided object enclosed in double
+ * quotes.
+ */
+ public static String addQuotes(Object object) {
+ return "\"".concat(String.valueOf(object)).concat("\"");
+ }
+
+ /**
+ * Generates a string based on a list of objects, separating each one of
+ * them by one space.
+ * @param objects A list of objects.
+ * @return A string based on the list of objects, separating each one of
+ * them by one space. Empty values are not considered.
+ */
+ public static String generateString(Object... objects) {
+ List<String> values = new ArrayList<String>();
+ for (Object object : objects) {
+ if (!CommonUtils.checkEmptyString(String.valueOf(object))) {
+ values.add(String.valueOf(object));
+ }
+ }
+ return StringUtils.join(values, " ");
+ }
+
+ /**
+ * Checks if a file exists.
+ * @param file The file.
+ * @return A boolean value indicating if the file exists.
+ */
+ public static boolean exists(File file) {
+ return file.exists();
+ }
+
+ /**
+ * Checks if a file exists based on its extension.
+ * @param extension The extension.
+ * @return A boolean value indicating if the file exists.
+ * @throws AraraException Something wrong happened, to be caught in the
+ * higher levels.
+ */
+ public static boolean exists(String extension) throws AraraException {
+ File file = new File(getPath(extension));
+ return file.exists();
+ }
+
+ /**
+ * Checks if a file has changed since the last verification.
+ * @param file The file.
+ * @return A boolean value indicating if the file has changed since the last
+ * verification.
+ * @throws AraraException Something wrong happened, to be caught in the
+ * higher levels.
+ */
+ public static boolean hasChanged(File file) throws AraraException {
+ Database database = DatabaseUtils.load();
+ HashMap<String, String> map = database.getMap();
+ String path = getCanonicalPath(file);
+ if (!file.exists()) {
+ if (map.containsKey(path)) {
+ map.remove(path);
+ database.setMap(map);
+ DatabaseUtils.save(database);
+ return true;
+ } else {
+ return false;
+ }
+ } else {
+ String hash = calculateHash(file);
+ if (map.containsKey(path)) {
+ String value = map.get(path);
+ if (hash.equals(value)) {
+ return false;
+ } else {
+ map.put(path, hash);
+ database.setMap(map);
+ DatabaseUtils.save(database);
+ return true;
+ }
+ } else {
+ map.put(path, hash);
+ database.setMap(map);
+ DatabaseUtils.save(database);
+ return true;
+ }
+ }
+ }
+
+ /**
+ * Checks if the file has changed since the last verification based on the
+ * provided extension.
+ * @param extension The provided extension.
+ * @return A boolean value indicating if the file has changed since the last
+ * verification.
+ * @throws AraraException Something wrong happened, to be caught in the
+ * higher levels.
+ */
+ public static boolean hasChanged(String extension) throws AraraException {
+ File file = new File(getPath(extension));
+ return hasChanged(file);
+ }
+
+ /**
+ * Gets the full file path based on the provided extension.
+ * @param extension The extension.
+ * @return A string containing the full file path.
+ * @throws AraraException Something wrong happened, to be caught in the
+ * higher levels.
+ */
+ private static String getPath(String extension) throws AraraException {
+ String name = getBasename(getCurrentReference());
+ String path = getParentCanonicalPath(getCurrentReference());
+ name = name.concat(".").concat(extension);
+ return buildPath(path, name);
+ }
+
+ /**
+ * Gets the canonical path from the provided file.
+ * @param file The file.
+ * @return The canonical path from the provided file.
+ * @throws AraraException Something wrong happened, to be caught in the
+ * higher levels.
+ */
+ public static String getCanonicalPath(File file) throws AraraException {
+ try {
+ return file.getCanonicalPath();
+ } catch (IOException exception) {
+ throw new AraraException(
+ messages.getMessage(
+ Messages.ERROR_GETCANONICALPATH_IO_EXCEPTION
+ ),
+ exception
+ );
+ }
+ }
+
+ /**
+ * Checks if the file based on the provided extension contains the provided
+ * regex.
+ * @param extension The file extension.
+ * @param regex The regex.
+ * @return A boolean value indicating if the file contains the provided
+ * regex.
+ * @throws AraraException Something wrong happened, to be caught in the
+ * higher levels.
+ */
+ public static boolean checkRegex(String extension, String regex)
+ throws AraraException {
+ File file = new File(getPath(extension));
+ return checkRegex(file, regex);
+ }
+
+ /**
+ * Checks if the file contains the provided regex.
+ * @param file The file.
+ * @param regex The regex.
+ * @return A boolean value indicating if the file contains the provided
+ * regex.
+ * @throws AraraException Something wrong happened, to be caught in the
+ * higher levels.
+ */
+ public static boolean checkRegex(File file, String regex)
+ throws AraraException {
+ Charset charset = (Charset) ConfigurationController.
+ getInstance().get("directives.charset");
+ try {
+ String text = FileUtils.readFileToString(file, charset.name());
+ Pattern pattern = Pattern.compile(regex);
+ Matcher matcher = pattern.matcher(text);
+ return matcher.find();
+ } catch (IOException exception) {
+ throw new AraraException(
+ messages.getMessage(
+ Messages.ERROR_CHECKREGEX_IO_EXCEPTION,
+ file.getName()
+ ),
+ exception
+ );
+ }
+ }
+
+ /**
+ * Replicates a string pattern based on a list of objects, generating a list
+ * as result.
+ * @param pattern The string pattern.
+ * @param values The list of objects to be merged with the pattern.
+ * @return A list containing the string pattern replicated to each object
+ * from the list.
+ * @throws AraraException Something wrong happened, to be caught in the
+ * higher levels.
+ */
+ public static List<Object> replicateList(String pattern,
+ List<Object> values) throws AraraException {
+ List<Object> result = new ArrayList<Object>();
+ for (Object value : values) {
+ try {
+ result.add(String.format(pattern, value));
+ } catch (MissingFormatArgumentException exception) {
+ throw new AraraException(
+ messages.getMessage(
+ Messages.ERROR_REPLICATELIST_MISSING_FORMAT_ARGUMENTS_EXCEPTION
+ ),
+ exception
+ );
+ }
+ }
+ return result;
+ }
+
+ /**
+ * Checks if the provided operating system string holds according to the
+ * underlying operating system.
+ * @param value A string representing an operating system.
+ * @return A boolean value indicating if the provided string refers to the
+ * underlying operating system.
+ * @throws AraraException Something wrong happened, to be caught in the
+ * higher levels.
+ */
+ public static boolean checkOS(String value) throws AraraException {
+ Map<String, Boolean> values = new HashMap<String, Boolean>();
+ values.put("windows", SystemUtils.IS_OS_WINDOWS);
+ values.put("linux", SystemUtils.IS_OS_LINUX);
+ values.put("mac", SystemUtils.IS_OS_MAC_OSX);
+ values.put("unix", SystemUtils.IS_OS_UNIX);
+ values.put("aix", SystemUtils.IS_OS_AIX);
+ values.put("irix", SystemUtils.IS_OS_IRIX);
+ values.put("os2", SystemUtils.IS_OS_OS2);
+ values.put("solaris", SystemUtils.IS_OS_SOLARIS);
+ values.put("cygwin", (Boolean) SystemCallController.
+ getInstance().get("cygwin"));
+ if (!values.containsKey(value.toLowerCase())) {
+ throw new AraraException(
+ messages.getMessage(
+ Messages.ERROR_CHECKOS_INVALID_OPERATING_SYSTEM,
+ value
+ )
+ );
+ }
+ return values.get(value.toLowerCase());
+ }
+
+ /**
+ * Returns the exit status of the application.
+ * @return An integer representing the exit status of the application.
+ */
+ public static int getExitStatus() {
+ return (Integer) ConfigurationController.
+ getInstance().get("execution.status");
+ }
+
+ /**
+ * Gets the system property according to the provided key, or resort to the
+ * fallback value if an exception is thrown or if the key is invalid.
+ * @param key The system property key.
+ * @param fallback The fallback value.
+ * @return A string containing the system property value or the fallback.
+ */
+ public static String getSystemProperty(String key, String fallback) {
+ try {
+ String result = System.getProperty(key, fallback);
+ return result.equals("") ? fallback : result;
+ } catch (Exception exception) {
+ return fallback;
+ }
+ }
+
+ /**
+ * Gets the preamble content, converting a single string into a list of
+ * strings, based on new lines.
+ * @return A list of strings representing the preamble content.
+ */
+ public static List<String> getPreambleContent() {
+ if (((Boolean) ConfigurationController.
+ getInstance().get("execution.preamble.active")) == true) {
+ return new ArrayList<String>(
+ Arrays.asList(
+ ((String) ConfigurationController.getInstance().
+ get("execution.preamble.content")
+ ).split("\n"))
+ );
+ }
+ else {
+ return new ArrayList<String>();
+ }
+ }
+
+ /**
+ * Generates a list of filenames from the provided command based on a list
+ * of extensions for each underlying operating system.
+ * @param command A string representing the command.
+ * @return A list of filenames.
+ */
+ private static List<String> appendExtensions(String command) {
+
+ // the resulting list, to hold the
+ // filenames generated from the
+ // provided command
+ List<String> result = new ArrayList<String>();
+
+ // list of extensions, specific for
+ // each operating system (in fact, it
+ // is more Windows specific)
+ List<String> extensions;
+
+ // the application is running on
+ // Windows, so let's look for the
+ // following extensions in order
+ if (SystemUtils.IS_OS_WINDOWS) {
+
+ // this list is actually a sublist from
+ // the original Windows PATHEXT environment
+ // variable which holds the list of executable
+ // extensions that Windows supports
+ extensions = Arrays.asList(".com", ".exe", ".bat", ".cmd");
+ }
+ else {
+
+ // no Windows, so the default
+ // extension will be just an
+ // empty string
+ extensions = Arrays.asList("");
+ }
+
+ // for each and every extension in the
+ // list, let's build the corresponding
+ // filename and add to the result
+ for (String extension : extensions) {
+ result.add(command.concat(extension));
+ }
+
+ // return the resulting list
+ // holding the filenames
+ return result;
+ }
+
+ /**
+ * Checks if the provided command name is reachable from the system path.
+ * @param command A string representing the command.
+ * @return A logic value.
+ */
+ public static boolean isOnPath(String command) {
+ try {
+
+ // first and foremost, let's build the list
+ // of filenames based on the underlying
+ // operating system
+ List<String> filenames = appendExtensions(command);
+
+ // break the path into several parts
+ // based on the path separator symbol
+ StringTokenizer tokenizer = new StringTokenizer(
+ System.getenv("PATH"),
+ File.pathSeparator
+ );
+
+ // iterate through every part of the
+ // path looking for each filename
+ while (tokenizer.hasMoreTokens()) {
+
+ // if the search does not return an empty
+ // list, one of the filenames got a match,
+ // and the command is available somewhere
+ // in the system path
+ if (
+ !FileUtils.listFiles(
+ new File(tokenizer.nextToken()),
+ new NameFileFilter(filenames),
+ null
+ ).isEmpty()) {
+
+ // command is found somewhere,
+ // so it is on path
+ return true;
+ }
+ }
+
+ // nothing was found,
+ // command is not on path
+ return false;
+ }
+ catch (Exception exception) {
+
+ // an exception was raised, simply
+ // return and forget about it
+ return false;
+ }
+ }
+
+ /**
+ * Gets the full base name of a file.
+ * @param file The file.
+ * @return The corresponding full base name.
+ * @throws AraraException Something wrong happened, to be caught in the
+ * higher levels.
+ */
+ public static String getFullBasename(File file) throws AraraException {
+
+ // if the provided file does not contain a
+ // file separator, fallback to the usual
+ // base name lookup
+ if (!file.toString().contains(File.separator)) {
+ return getBasename(file);
+ }
+ else {
+
+ // we need to get the parent file, get the
+ // canonical path and build the corresponding
+ // full base name path
+ File parent = file.getParentFile();
+ String path = getCanonicalPath(parent == null ? file : parent);
+ return buildPath(path, getBasename(file));
+ }
+ }
+
+}
diff --git a/support/arara/source/src/main/java/com/github/cereda/arara/utils/ConfigurationUtils.java b/support/arara/source/src/main/java/com/github/cereda/arara/utils/ConfigurationUtils.java
new file mode 100644
index 0000000000..0b7d3b8a5b
--- /dev/null
+++ b/support/arara/source/src/main/java/com/github/cereda/arara/utils/ConfigurationUtils.java
@@ -0,0 +1,239 @@
+/**
+ * Arara, the cool TeX automation tool
+ * Copyright (c) 2012 -- 2018, Paulo Roberto Massa Cereda
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * 3. Neither the name of the project's author nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+ * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
+ * COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+ * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY
+ * WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+package com.github.cereda.arara.utils;
+
+import com.github.cereda.arara.Arara;
+import com.github.cereda.arara.controller.LanguageController;
+import com.github.cereda.arara.model.AraraException;
+import com.github.cereda.arara.model.FileType;
+import com.github.cereda.arara.model.FileTypeResource;
+import com.github.cereda.arara.model.Messages;
+import com.github.cereda.arara.model.Resource;
+import java.io.File;
+import java.io.FileReader;
+import java.io.UnsupportedEncodingException;
+import java.net.URLDecoder;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.Set;
+import org.apache.commons.collections4.CollectionUtils;
+import org.apache.commons.collections4.Predicate;
+import org.apache.commons.lang.SystemUtils;
+import org.yaml.snakeyaml.Yaml;
+import org.yaml.snakeyaml.constructor.Constructor;
+import org.yaml.snakeyaml.error.MarkedYAMLException;
+import org.yaml.snakeyaml.nodes.Tag;
+import org.yaml.snakeyaml.representer.Representer;
+
+/**
+ * Implements configuration utilitary methods.
+ * @author Paulo Roberto Massa Cereda
+ * @version 4.0
+ * @since 4.0
+ */
+public class ConfigurationUtils {
+
+ // the application messages obtained from the
+ // language controller
+ private static final LanguageController messages =
+ LanguageController.getInstance();
+
+ /**
+ * Gets the list of default file types provided by nightingale, in order.
+ * @return The list of default file types, in order.
+ * @throws AraraException Something wrong happened, to be caught in the
+ * higher levels.
+ */
+ public static List<FileType> getDefaultFileTypes() throws AraraException {
+ return Arrays.asList(
+ new FileType("tex"),
+ new FileType("dtx"),
+ new FileType("ltx"),
+ new FileType("drv"),
+ new FileType("ins")
+ );
+ }
+
+ /**
+ * Gets the configuration file located at the user home directory, if any.
+ * @return The file reference to the external configuration, if any.
+ */
+ public static File getConfigFile() {
+ List<String> names = Arrays.asList(
+ ".araraconfig.yaml",
+ "araraconfig.yaml",
+ ".arararc.yaml",
+ "arararc.yaml"
+ );
+
+ // look for configuration files in the user's working directory first
+ for (String name : names) {
+ String path = CommonUtils.buildPath(SystemUtils.USER_DIR, name);
+ File file = new File(path);
+ if (file.exists()) {
+ return file;
+ }
+ }
+
+ // if no configuration files are found in the user's working directory,
+ // try to look up in a global directory, that is, the user home
+ for (String name : names) {
+ String path = CommonUtils.buildPath(SystemUtils.USER_HOME, name);
+ File file = new File(path);
+ if (file.exists()) {
+ return file;
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Validates the configuration file.
+ * @param file The configuration file.
+ * @return The configuration file as a resource.
+ * @throws AraraException Something wrong happened, to be caught in the
+ * higher levels.
+ */
+ public static Resource validateConfiguration(File file)
+ throws AraraException {
+
+ Representer representer = new Representer();
+ representer.addClassTag(Resource.class, new Tag("!config"));
+ Yaml yaml = new Yaml(new Constructor(Resource.class), representer);
+ try {
+ Resource resource = yaml.loadAs(new FileReader(file),
+ Resource.class);
+ if (resource.getFiletypes() != null) {
+ List<FileTypeResource> filetypes = resource.getFiletypes();
+ if (CollectionUtils.exists(filetypes,
+ new Predicate<FileTypeResource>() {
+ public boolean evaluate(FileTypeResource filetype) {
+ return (filetype.getExtension() == null);
+ }
+ })) {
+ throw new AraraException(
+ messages.getMessage(
+ Messages.ERROR_CONFIGURATION_FILETYPE_MISSING_EXTENSION
+ )
+ );
+ }
+ }
+ return resource;
+ } catch (MarkedYAMLException yamlException) {
+ throw new AraraException(
+ messages.getMessage(
+ Messages.ERROR_CONFIGURATION_INVALID_YAML
+ ),
+ yamlException
+ );
+ } catch (Exception exception) {
+ throw new AraraException(
+ messages.getMessage(
+ Messages.ERROR_CONFIGURATION_GENERIC_ERROR
+ )
+ );
+ }
+ }
+
+ /**
+ * Normalize a list of rule paths, removing all duplicates.
+ * @param paths The list of rule paths.
+ * @return A list of normalized paths, without duplicates.
+ * @throws AraraException Something wrong happened, to be caught in the
+ * higher levels.
+ */
+ public static List<String> normalizePaths(List<String> paths)
+ throws AraraException {
+ paths.add(CommonUtils.buildPath(getApplicationPath(), "rules"));
+ Set<String> set = new LinkedHashSet<String>(paths);
+ List<String> result = new ArrayList<String>(set);
+ return result;
+ }
+
+ /**
+ * Normalize a list of file types, removing all duplicates.
+ * @param types The list of file types.
+ * @return A list of normalized file types, without duplicates.
+ * @throws AraraException Something wrong happened, to be caught in the
+ * higher levels.
+ */
+ public static List<FileType> normalizeFileTypes(List<FileType> types)
+ throws AraraException {
+ types.addAll(getDefaultFileTypes());
+ Set<FileType> set = new LinkedHashSet<FileType>(types);
+ List<FileType> result = new ArrayList<FileType>(set);
+ return result;
+ }
+
+ /**
+ * Gets the canonical absolute application path.
+ * @return The string representation of the canonical absolute application
+ * path.
+ * @throws AraraException Something wrong happened, to be caught in the
+ * higher levels.
+ */
+ public static String getApplicationPath() throws AraraException {
+ try {
+ String path = Arara.class.getProtectionDomain().
+ getCodeSource().getLocation().getPath();
+ path = URLDecoder.decode(path, "UTF-8");
+ path = new File(path).getParentFile().getPath();
+ return path;
+ } catch (UnsupportedEncodingException exception) {
+ throw new AraraException(
+ messages.getMessage(
+ Messages.ERROR_GETAPPLICATIONPATH_ENCODING_EXCEPTION
+ ),
+ exception
+ );
+ }
+ }
+
+ /**
+ * Cleans the file name to avoid invalid entries.
+ * @param name The file name.
+ * @return A cleaned file name.
+ */
+ public static String cleanFileName(String name) {
+ String result = (new File(name)).getName().trim();
+ if (CommonUtils.checkEmptyString(result)) {
+ return "arara";
+ } else {
+ return result.trim();
+ }
+ }
+
+}
diff --git a/support/arara/source/src/main/java/com/github/cereda/arara/utils/DatabaseUtils.java b/support/arara/source/src/main/java/com/github/cereda/arara/utils/DatabaseUtils.java
new file mode 100644
index 0000000000..8861ff7e5e
--- /dev/null
+++ b/support/arara/source/src/main/java/com/github/cereda/arara/utils/DatabaseUtils.java
@@ -0,0 +1,140 @@
+/**
+ * Arara, the cool TeX automation tool
+ * Copyright (c) 2012 -- 2018, Paulo Roberto Massa Cereda
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * 3. Neither the name of the project's author nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+ * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
+ * COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+ * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY
+ * WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+package com.github.cereda.arara.utils;
+
+import com.github.cereda.arara.controller.ConfigurationController;
+import com.github.cereda.arara.controller.LanguageController;
+import com.github.cereda.arara.model.AraraException;
+import com.github.cereda.arara.model.Database;
+import com.github.cereda.arara.model.Messages;
+import java.io.File;
+import org.simpleframework.xml.Serializer;
+import org.simpleframework.xml.core.Persister;
+
+/**
+ * Implements database utilitary methods.
+ * @author Paulo Roberto Massa Cereda
+ * @version 4.0
+ * @since 4.0
+ */
+public class DatabaseUtils {
+
+ // the application messages obtained from the
+ // language controller
+ private static final LanguageController messages =
+ LanguageController.getInstance();
+
+ /**
+ * Loads the XML file representing the database.
+ * @return The database object.
+ * @throws AraraException Something wrong happened, to be caught in the
+ * higher levels.
+ */
+ public static Database load() throws AraraException {
+ if (!exists()) {
+ return new Database();
+ } else {
+ File file = new File(getPath());
+ try {
+ Serializer serializer = new Persister();
+ Database database = serializer.read(Database.class, file);
+ return database;
+ } catch (Exception exception) {
+ throw new AraraException(
+ messages.getMessage(
+ Messages.ERROR_LOAD_COULD_NOT_LOAD_XML,
+ file.getName()
+ ),
+ exception
+ );
+ }
+ }
+ }
+
+ /**
+ * Saves the database on a XML file.
+ * @param database The database object.
+ * @throws AraraException Something wrong happened, to be caught in the
+ * higher levels.
+ */
+ public static void save(Database database) throws AraraException {
+ File file = new File(getPath());
+ try {
+ Serializer serializer = new Persister();
+ serializer.write(database, file);
+ } catch (Exception exception) {
+ throw new AraraException(
+ messages.getMessage(
+ Messages.ERROR_SAVE_COULD_NOT_SAVE_XML,
+ file.getName()
+ ),
+ exception
+ );
+ }
+ }
+
+ /**
+ * Checks if the XML file representing the database exists.
+ * @return A boolean value indicating if the XML file exists.
+ * @throws AraraException Something wrong happened, to be caught in the
+ * higher levels.
+ */
+ private static boolean exists() throws AraraException {
+ File file = new File(getPath());
+ return file.exists();
+ }
+
+ /**
+ * Gets the path to the XML file representing the database.
+ * @return A string representing the path to the XML file.
+ * @throws AraraException Something wrong happened, to be caught in the
+ * higher levels.
+ */
+ private static String getPath() throws AraraException {
+ String name = ((String) ConfigurationController.
+ getInstance().get("execution.database.name")).concat(".xml");
+ String path = CommonUtils.getParentCanonicalPath(getReference());
+ return CommonUtils.buildPath(path, name);
+ }
+
+ /**
+ * Gets the main file reference.
+ * @return The main file reference.
+ */
+ private static File getReference() {
+ return (File) ConfigurationController.
+ getInstance().get("execution.reference");
+ }
+
+}
diff --git a/support/arara/source/src/main/java/com/github/cereda/arara/utils/DirectiveAssembler.java b/support/arara/source/src/main/java/com/github/cereda/arara/utils/DirectiveAssembler.java
new file mode 100644
index 0000000000..d2dd588996
--- /dev/null
+++ b/support/arara/source/src/main/java/com/github/cereda/arara/utils/DirectiveAssembler.java
@@ -0,0 +1,106 @@
+/**
+ * Arara, the cool TeX automation tool
+ * Copyright (c) 2012 -- 2018, Paulo Roberto Massa Cereda
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * 3. Neither the name of the project's author nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+ * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
+ * COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+ * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY
+ * WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+package com.github.cereda.arara.utils;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Implements a directive assembler in order to help build a directive from a
+ * list of strings.
+ * @author Paulo Roberto Massa Cereda
+ * @version 4.0
+ * @since 4.0
+ */
+public class DirectiveAssembler {
+
+ // this variable holds a list of
+ // line numbers indicating which
+ // lines composed the resulting
+ // potential directive
+ private final List<Integer> lineNumbers;
+
+ // this variable holds the textual
+ // representation of the directive
+ private String text;
+
+ /**
+ * Constructor.
+ */
+ public DirectiveAssembler() {
+ lineNumbers = new ArrayList<Integer>();
+ text = "";
+ }
+
+ /**
+ * Checks if an append operation is allowed.
+ * @return A boolean value indicating if an append operation is allowed.
+ */
+ public boolean isAppendAllowed() {
+ return !lineNumbers.isEmpty();
+ }
+
+ /**
+ * Adds a line number to the assembler.
+ * @param line An integer representing the line number.
+ */
+ public void addLineNumber(int line) {
+ lineNumbers.add(line);
+ }
+
+ /**
+ * Appends the provided line to the assembler text.
+ * @param line The provided line.
+ */
+ public void appendLine(String line) {
+ text = text.concat(" ").concat(line.trim());
+ }
+
+ /**
+ * Gets the list of line numbers.
+ * @return The list of line numbers.
+ */
+ public List<Integer> getLineNumbers() {
+ return lineNumbers;
+ }
+
+ /**
+ * Gets the text.
+ * @return The assembler text, properly trimmed.
+ */
+ public String getText() {
+ return text.trim();
+ }
+
+}
diff --git a/support/arara/source/src/main/java/com/github/cereda/arara/utils/DirectiveResolver.java b/support/arara/source/src/main/java/com/github/cereda/arara/utils/DirectiveResolver.java
new file mode 100644
index 0000000000..6ea18e31ba
--- /dev/null
+++ b/support/arara/source/src/main/java/com/github/cereda/arara/utils/DirectiveResolver.java
@@ -0,0 +1,59 @@
+/**
+ * Arara, the cool TeX automation tool
+ * Copyright (c) 2012 -- 2018, Paulo Roberto Massa Cereda
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * 3. Neither the name of the project's author nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+ * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
+ * COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+ * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY
+ * WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+package com.github.cereda.arara.utils;
+
+import org.yaml.snakeyaml.nodes.Tag;
+import org.yaml.snakeyaml.resolver.Resolver;
+
+/**
+ * This class implements a directive resolver.
+ * @author Paulo Roberto Massa Cereda
+ * @version 4.0
+ * @since 4.0
+ */
+public class DirectiveResolver extends Resolver {
+
+ /**
+ * Adds implicit resolvers to the YAML model. For arara, I disabled
+ * boolean and numeric values to be automatically parsed. They still can
+ * be used through an explicit conversion in the rule context.
+ */
+ @Override
+ protected void addImplicitResolvers() {
+ addImplicitResolver(Tag.MERGE, MERGE, "<");
+ addImplicitResolver(Tag.NULL, NULL, "~nN\0");
+ addImplicitResolver(Tag.NULL, EMPTY, null);
+ }
+
+}
diff --git a/support/arara/source/src/main/java/com/github/cereda/arara/utils/DirectiveUtils.java b/support/arara/source/src/main/java/com/github/cereda/arara/utils/DirectiveUtils.java
new file mode 100644
index 0000000000..d4d2a88ac7
--- /dev/null
+++ b/support/arara/source/src/main/java/com/github/cereda/arara/utils/DirectiveUtils.java
@@ -0,0 +1,438 @@
+/**
+ * Arara, the cool TeX automation tool
+ * Copyright (c) 2012 -- 2018, Paulo Roberto Massa Cereda
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * 3. Neither the name of the project's author nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+ * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
+ * COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+ * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY
+ * WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+package com.github.cereda.arara.utils;
+
+import com.github.cereda.arara.controller.ConfigurationController;
+import com.github.cereda.arara.controller.LanguageController;
+import com.github.cereda.arara.model.AraraException;
+import com.github.cereda.arara.model.Conditional;
+import com.github.cereda.arara.model.Directive;
+import com.github.cereda.arara.model.Messages;
+import com.github.cereda.arara.model.Pair;
+import java.io.File;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.yaml.snakeyaml.DumperOptions;
+import org.yaml.snakeyaml.Yaml;
+import org.yaml.snakeyaml.constructor.Constructor;
+import org.yaml.snakeyaml.error.MarkedYAMLException;
+import org.yaml.snakeyaml.representer.Representer;
+
+/**
+ * Implements directive utilitary methods.
+ * @author Paulo Roberto Massa Cereda
+ * @version 4.0
+ * @since 4.0
+ */
+public class DirectiveUtils {
+
+ // the application messages obtained from the
+ // language controller
+ private static final LanguageController messages =
+ LanguageController.getInstance();
+
+ // get the logger context from a factory
+ private static final Logger logger =
+ LoggerFactory.getLogger(DirectiveUtils.class);
+
+ /**
+ * Extracts a list of directives from a list of strings.
+ * @param lines List of strings.
+ * @return A list of directives.
+ * @throws AraraException Something wrong happened, to be caught in the
+ * higher levels.
+ */
+ public static List<Directive> extractDirectives(List<String> lines)
+ throws AraraException {
+
+ boolean header = (Boolean) ConfigurationController.
+ getInstance().get("execution.header");
+ String regex = (String) ConfigurationController.
+ getInstance().get("execution.file.pattern");
+ Pattern linecheck = Pattern.compile(regex);
+ regex = regex.concat((String) ConfigurationController.
+ getInstance().get("application.pattern"));
+ Pattern pattern = Pattern.compile(regex);
+ List<Pair<Integer, String>> pairs =
+ new ArrayList<Pair<Integer, String>>();
+ Matcher matcher;
+ for (int i = 0; i < lines.size(); i++) {
+ matcher = pattern.matcher(lines.get(i));
+ if (matcher.find()) {
+ String line = lines.get(i).substring(
+ matcher.end(),
+ lines.get(i).length()
+ );
+ Pair<Integer, String> pair =
+ new Pair<Integer, String>(i + 1, line);
+ pairs.add(pair);
+
+ logger.info(
+ messages.getMessage(
+ Messages.LOG_INFO_POTENTIAL_PATTERN_FOUND,
+ (i + 1),
+ line.trim()
+ )
+ );
+ }
+ else {
+ if (header) {
+ if (!checkLinePattern(linecheck, lines.get(i))) {
+ break;
+ }
+ }
+ }
+ }
+
+ if (pairs.isEmpty()) {
+ throw new AraraException(
+ messages.getMessage(
+ Messages.ERROR_VALIDATE_NO_DIRECTIVES_FOUND
+ )
+ );
+ }
+
+ List<DirectiveAssembler> assemblers
+ = new ArrayList<DirectiveAssembler>();
+ DirectiveAssembler assembler = new DirectiveAssembler();
+ regex = (String) ConfigurationController.getInstance().
+ get("directives.linebreak.pattern");
+ pattern = Pattern.compile(regex);
+ for (Pair<Integer, String> pair : pairs) {
+ matcher = pattern.matcher(pair.getSecondElement());
+ if (matcher.find()) {
+ if (!assembler.isAppendAllowed()) {
+ throw new AraraException(
+ messages.getMessage(
+ Messages.ERROR_VALIDATE_ORPHAN_LINEBREAK,
+ pair.getFirstElement()
+ )
+ );
+ } else {
+ assembler.addLineNumber(pair.getFirstElement());
+ assembler.appendLine(matcher.group(1));
+ }
+ } else {
+ if (assembler.isAppendAllowed()) {
+ assemblers.add(assembler);
+ }
+ assembler = new DirectiveAssembler();
+ assembler.addLineNumber(pair.getFirstElement());
+ assembler.appendLine(pair.getSecondElement());
+ }
+ }
+ if (assembler.isAppendAllowed()) {
+ assemblers.add(assembler);
+ }
+
+ List<Directive> directives = new ArrayList<Directive>();
+ for (DirectiveAssembler current : assemblers) {
+ directives.add(generateDirective(current));
+ }
+ return directives;
+
+ }
+
+ /**
+ * Generates a directive from a directive assembler.
+ * @param assembler The directive assembler.
+ * @return The corresponding directive.
+ * @throws AraraException Something wrong happened, to be caught in the
+ * higher levels.
+ */
+ public static Directive generateDirective(DirectiveAssembler assembler)
+ throws AraraException {
+ String regex = (String) ConfigurationController.getInstance().
+ get("directives.pattern");
+ Pattern pattern = Pattern.compile(regex);
+ Matcher matcher = pattern.matcher(assembler.getText());
+ if (matcher.find()) {
+ Directive directive = new Directive();
+ directive.setIdentifier(matcher.group(1));
+ directive.setParameters(
+ getParameters(matcher.group(3), assembler.getLineNumbers())
+ );
+ Conditional conditional = new Conditional();
+ conditional.setType(getType(matcher.group(5)));
+ conditional.setCondition(getCondition(matcher.group(6)));
+ directive.setConditional(conditional);
+ directive.setLineNumbers(assembler.getLineNumbers());
+
+ logger.info(
+ messages.getMessage(
+ Messages.LOG_INFO_POTENTIAL_DIRECTIVE_FOUND,
+ directive
+ )
+ );
+
+ return directive;
+ } else {
+ throw new AraraException(
+ messages.getMessage(
+ Messages.ERROR_VALIDATE_INVALID_DIRECTIVE_FORMAT,
+ CommonUtils.getCollectionElements(
+ assembler.getLineNumbers(),
+ "(",
+ ")",
+ ", "
+ )
+ )
+ );
+ }
+ }
+
+ /**
+ * Gets the conditional type based on the input string.
+ * @param text The input string.
+ * @return The conditional type.
+ */
+ private static Conditional.ConditionalType getType(String text) {
+ if (text == null) {
+ return Conditional.ConditionalType.NONE;
+ } else {
+ if (text.equals("if")) {
+ return Conditional.ConditionalType.IF;
+ } else {
+ if (text.equals("while")) {
+ return Conditional.ConditionalType.WHILE;
+ } else {
+ if (text.equals("until")) {
+ return Conditional.ConditionalType.UNTIL;
+ } else {
+ return Conditional.ConditionalType.UNLESS;
+ }
+ }
+ }
+ }
+ }
+
+ /**
+ * Gets the condition from the input string.
+ * @param text The input string.
+ * @return A string representing the condition.
+ */
+ private static String getCondition(String text) {
+ return text == null ? "" : text;
+ }
+
+ /**
+ * Gets the parameters from the input string.
+ * @param text The input string.
+ * @param numbers The list of line numbers.
+ * @return A map containing the directive parameters.
+ * @throws AraraException Something wrong happened, to be caught in the
+ * higher levels.
+ */
+ private static Map<String, Object> getParameters(String text,
+ List<Integer> numbers) throws AraraException {
+
+ if (text == null) {
+ return new HashMap<String, Object>();
+ }
+
+ Yaml yaml = new Yaml(
+ new Constructor(),
+ new Representer(),
+ new DumperOptions(),
+ new DirectiveResolver()
+ );
+ try {
+ @SuppressWarnings("unchecked")
+ HashMap<String, Object> map = yaml.loadAs(text, HashMap.class);
+ return map;
+ } catch (MarkedYAMLException exception) {
+ throw new AraraException(
+ messages.getMessage(
+ Messages.ERROR_VALIDATE_YAML_EXCEPTION,
+ CommonUtils.getCollectionElements(
+ numbers,
+ "(",
+ ")",
+ ", "
+ )
+ ),
+ exception
+ );
+ }
+ }
+
+ /**
+ * Validates the list of directives, returning a new list.
+ * @param directives The list of directives.
+ * @return A new list of directives.
+ * @throws AraraException Something wrong happened, to be caught in the
+ * higher levels.
+ */
+ public static List<Directive> validate(List<Directive> directives)
+ throws AraraException {
+
+ ArrayList<Directive> result = new ArrayList<Directive>();
+ for (Directive directive : directives) {
+ Map<String, Object> parameters = directive.getParameters();
+
+ if (parameters.containsKey("file")) {
+ throw new AraraException(
+ messages.getMessage(
+ Messages.ERROR_VALIDATE_FILE_IS_RESERVED,
+ CommonUtils.getCollectionElements(
+ directive.getLineNumbers(),
+ "(",
+ ")",
+ ", "
+ )
+ )
+ );
+ }
+
+ if (parameters.containsKey("reference")) {
+ throw new AraraException(
+ messages.getMessage(
+ Messages.ERROR_VALIDATE_REFERENCE_IS_RESERVED,
+ CommonUtils.getCollectionElements(
+ directive.getLineNumbers(),
+ "(",
+ ")",
+ ", "
+ )
+ )
+ );
+ }
+
+ if (parameters.containsKey("files")) {
+
+ Object holder = parameters.get("files");
+ if (holder instanceof List) {
+ @SuppressWarnings("unchecked")
+ List<Object> files = (List<Object>) holder;
+ parameters.remove("files");
+ if (files.isEmpty()) {
+ throw new AraraException(
+ messages.getMessage(
+ Messages.ERROR_VALIDATE_EMPTY_FILES_LIST,
+ CommonUtils.getCollectionElements(
+ directive.getLineNumbers(),
+ "(",
+ ")",
+ ", "
+ )
+ )
+ );
+ }
+ for (Object file : files) {
+ Map<String, Object> map = new HashMap<String, Object>();
+ for (String key : parameters.keySet()) {
+ map.put(key, parameters.get(key));
+ }
+ File representation = CommonUtils.
+ getCanonicalFile(String.valueOf(file));
+
+ map.put("reference", representation);
+ map.put("file", representation.getName());
+
+ Directive addition = new Directive();
+ Conditional conditional = new Conditional();
+ conditional.setCondition(directive.getConditional().
+ getCondition()
+ );
+ conditional.setType(directive.getConditional().
+ getType()
+ );
+ addition.setIdentifier(directive.getIdentifier());
+ addition.setConditional(conditional);
+ addition.setParameters(map);
+ addition.setLineNumbers(directive.getLineNumbers());
+ result.add(addition);
+ }
+ } else {
+ throw new AraraException(
+ messages.getMessage(
+ Messages.ERROR_VALIDATE_FILES_IS_NOT_A_LIST,
+ CommonUtils.getCollectionElements(
+ directive.getLineNumbers(),
+ "(",
+ ")",
+ ", "
+ )
+ )
+ );
+ }
+ } else {
+ File representation = (File) ConfigurationController.
+ getInstance().get("execution.reference");
+ parameters.put("file", representation.getName());
+ parameters.put("reference", representation);
+ directive.setParameters(parameters);
+ result.add(directive);
+ }
+ }
+
+ logger.info(
+ messages.getMessage(
+ Messages.LOG_INFO_VALIDATED_DIRECTIVES
+ )
+ );
+ logger.info(DisplayUtils.displayOutputSeparator(
+ messages.getMessage(
+ Messages.LOG_INFO_DIRECTIVES_BLOCK
+ )
+ ));
+ for (Directive directive : result) {
+ logger.info(directive.toString());
+ }
+
+ logger.info(DisplayUtils.displaySeparator());
+
+ return result;
+ }
+
+ /**
+ * Checks if the provided line contains the corresponding pattern, based on
+ * the file type, or an empty line.
+ * @param pattern Pattern to be matched, based on the file type.
+ * @param line Provided line.
+ * @return Logical value indicating if the provided line contains the
+ * corresponding pattern, based on the file type, or an empty line.
+ */
+ private static boolean checkLinePattern(Pattern pattern, String line) {
+ return CommonUtils.checkEmptyString(line.trim()) ||
+ pattern.matcher(line).find();
+ }
+
+}
diff --git a/support/arara/source/src/main/java/com/github/cereda/arara/utils/DisplayUtils.java b/support/arara/source/src/main/java/com/github/cereda/arara/utils/DisplayUtils.java
new file mode 100644
index 0000000000..879119e60e
--- /dev/null
+++ b/support/arara/source/src/main/java/com/github/cereda/arara/utils/DisplayUtils.java
@@ -0,0 +1,502 @@
+/**
+ * Arara, the cool TeX automation tool
+ * Copyright (c) 2012 -- 2018, Paulo Roberto Massa Cereda
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * 3. Neither the name of the project's author nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+ * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
+ * COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+ * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY
+ * WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+package com.github.cereda.arara.utils;
+
+import com.github.cereda.arara.controller.ConfigurationController;
+import com.github.cereda.arara.controller.LanguageController;
+import com.github.cereda.arara.model.AraraException;
+import com.github.cereda.arara.model.Conditional;
+import com.github.cereda.arara.model.Messages;
+import com.github.cereda.arara.model.StopWatch;
+import java.io.File;
+import java.util.List;
+import org.apache.commons.lang.StringUtils;
+import org.apache.commons.lang.WordUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Implements display utilitary methods.
+ * @author Paulo Roberto Massa Cereda
+ * @version 4.0
+ * @since 4.0
+ */
+public class DisplayUtils {
+
+ // the application messages obtained from the
+ // language controller
+ private static final LanguageController messages =
+ LanguageController.getInstance();
+
+ // get the logger context from a factory
+ private static final Logger logger =
+ LoggerFactory.getLogger(DisplayUtils.class);
+
+ /**
+ * Displays the short version of the current entry in the terminal.
+ * @param name Rule name.
+ * @param task Task name.
+ */
+ private static void buildShortEntry(String name, String task) {
+ int width = getWidth();
+ int result = getLongestMatch();
+ if (result >= width) {
+ result = 10;
+ }
+ int space = width - result - 1;
+ StringBuilder entry = new StringBuilder();
+ entry.append("(").append(name).append(") ");
+ entry.append(task).append(" ");
+ String line = StringUtils.abbreviate(entry.toString(), space - 4);
+ entry = new StringBuilder();
+ entry.append(StringUtils.rightPad(line, space, ".")).append(" ");
+ System.out.print(entry);
+ }
+
+ /**
+ * Displays the short version of the current entry result in the terminal.
+ * @param value The boolean value to be displayed.
+ */
+ private static void buildShortResult(boolean value) {
+ int result = getLongestMatch();
+ System.out.println(StringUtils.leftPad(getResult(value), result));
+ }
+
+ /**
+ * Displays the current entry result in the terminal.
+ * @param value The boolean value to be displayed.
+ */
+ public static void printEntryResult(boolean value) {
+ ConfigurationController.getInstance().put("display.line", false);
+ ConfigurationController.getInstance().put("display.result", true);
+ ConfigurationController.getInstance()
+ .put("execution.status", value ? 0 : 1);
+ logger.info(
+ messages.getMessage(
+ Messages.LOG_INFO_TASK_RESULT
+ ).concat(" ").concat(getResult(value))
+ );
+ if (!isDryRunMode()) {
+ if (!isVerboseMode()) {
+ buildShortResult(value);
+ } else {
+ buildLongResult(value);
+ }
+ }
+ }
+
+ /**
+ * Displays a long version of the current entry result in the terminal.
+ * @param value The boolean value to be displayed
+ */
+ private static void buildLongResult(boolean value) {
+ int width = getWidth();
+ System.out.println("\n".concat(StringUtils.leftPad(
+ " ".concat(getResult(value)), width, "-"
+ )));
+ }
+
+ /**
+ * Displays the current entry in the terminal.
+ * @param name The rule name.
+ * @param task The task name.
+ */
+ public static void printEntry(String name, String task) {
+ logger.info(
+ messages.getMessage(
+ Messages.LOG_INFO_INTERPRET_TASK,
+ task,
+ name
+ )
+ );
+ ConfigurationController.getInstance().put("display.line", true);
+ ConfigurationController.getInstance().put("display.result", false);
+ if (!isDryRunMode()) {
+ if (!isVerboseMode()) {
+ buildShortEntry(name, task);
+ } else {
+ buildLongEntry(name, task);
+ }
+ } else {
+ buildDryRunEntry(name, task);
+ }
+ }
+
+ /**
+ * Gets the length of the longest result match.
+ * @return An integer value representing the longest result match.
+ */
+ private static int getLongestMatch() {
+ String[] values = new String[]{
+ messages.getMessage(Messages.INFO_LABEL_ON_SUCCESS),
+ messages.getMessage(Messages.INFO_LABEL_ON_FAILURE),
+ messages.getMessage(Messages.INFO_LABEL_ON_ERROR)
+ };
+ int max = values[0].length();
+ for (String value : values) {
+ if (max < value.length()) {
+ max = value.length();
+ }
+ }
+ return max;
+ }
+
+ /**
+ * Displays a long version of the current entry in the terminal.
+ * @param name Rule name.
+ * @param task Task name.
+ */
+ private static void buildLongEntry(String name, String task) {
+ if (ConfigurationController.getInstance().contains("display.rolling")) {
+ addNewLine();
+ } else {
+ ConfigurationController.getInstance().put("display.rolling", true);
+ }
+ StringBuilder line = new StringBuilder();
+ line.append("(").append(name).append(") ");
+ line.append(task);
+ System.out.println(displaySeparator());
+ System.out.println(StringUtils.abbreviate(line.toString(), getWidth()));
+ System.out.println(displaySeparator());
+ }
+
+ /**
+ * Displays a dry-run version of the current entry in the terminal.
+ * @param name The rule name.
+ * @param task The task name.
+ */
+ private static void buildDryRunEntry(String name, String task) {
+ if (ConfigurationController.getInstance().contains("display.rolling")) {
+ addNewLine();
+ } else {
+ ConfigurationController.getInstance().put("display.rolling", true);
+ }
+ StringBuilder line = new StringBuilder();
+ line.append("[DR] (").append(name).append(") ");
+ line.append(task);
+ System.out.println(StringUtils.abbreviate(line.toString(), getWidth()));
+ System.out.println(displaySeparator());
+ }
+
+ /**
+ * Displays the exception in the terminal.
+ * @param exception The exception object.
+ */
+ public static void printException(AraraException exception) {
+ ConfigurationController.getInstance().put("display.exception", true);
+ ConfigurationController.getInstance().put("execution.status", 2);
+ boolean display = false;
+ if (ConfigurationController.getInstance().contains("display.line")) {
+ display = (Boolean) ConfigurationController.
+ getInstance().get("display.line");
+ }
+ if (ConfigurationController.getInstance().contains("display.result")) {
+ if (((Boolean) ConfigurationController.
+ getInstance().get("display.result")) == true) {
+ addNewLine();
+ }
+ }
+ if (display) {
+ if (!isDryRunMode()) {
+ if (!isVerboseMode()) {
+ buildShortError();
+ } else {
+ buildLongError();
+ }
+ addNewLine();
+ }
+ }
+ String text = exception.hasException() ?
+ exception.getMessage().concat(" ").concat(
+ messages.getMessage(
+ Messages.INFO_DISPLAY_EXCEPTION_MORE_DETAILS
+ )
+ )
+ : exception.getMessage();
+ logger.error(text);
+ wrapText(text);
+ if (exception.hasException()) {
+ addNewLine();
+ displayDetailsLine();
+ String details = exception.getException().getMessage();
+ logger.error(details);
+ wrapText(details);
+ }
+ }
+
+ /**
+ * Gets the string representation of the provided boolean value.
+ * @param value The boolean value.
+ * @return The string representation.
+ */
+ private static String getResult(boolean value) {
+ return (value == true ?
+ messages.getMessage(
+ Messages.INFO_LABEL_ON_SUCCESS
+ )
+ : messages.getMessage(Messages.INFO_LABEL_ON_FAILURE));
+ }
+
+ /**
+ * Displays the short version of an error in the terminal.
+ */
+ private static void buildShortError() {
+ int result = getLongestMatch();
+ System.out.println(StringUtils.leftPad(
+ messages.getMessage(
+ Messages.INFO_LABEL_ON_ERROR
+ ),
+ result
+ ));
+ }
+
+ /**
+ * Displays the long version of an error in the terminal.
+ */
+ private static void buildLongError() {
+ String line = StringUtils.leftPad(
+ " ".concat(messages.getMessage(Messages.INFO_LABEL_ON_ERROR)),
+ getWidth(), "-");
+ System.out.println(line);
+ }
+
+ /**
+ * Gets the default terminal width defined in the settings.
+ * @return An integer representing the terminal width.
+ */
+ private static int getWidth() {
+ return (Integer) ConfigurationController.
+ getInstance().get("application.width");
+ }
+
+ /**
+ * Displays the provided text wrapped nicely according to the default
+ * terminal width.
+ * @param text The text to be displayed.
+ */
+ public static void wrapText(String text) {
+ System.out.println(WordUtils.wrap(text, getWidth()));
+ }
+
+ /**
+ * Checks if the execution is in dry-run mode.
+ * @return A boolean value indicating if the execution is in dry-run mode.
+ */
+ private static boolean isDryRunMode() {
+ return (Boolean) ConfigurationController.
+ getInstance().get("execution.dryrun");
+ }
+
+ /**
+ * Checks if the execution is in verbose mode.
+ * @return A boolean value indicating if the execution is in verbose mode.
+ */
+ private static boolean isVerboseMode() {
+ return (Boolean) ConfigurationController.
+ getInstance().get("execution.verbose");
+ }
+
+ /**
+ * Displays the rule authors in the terminal.
+ * @param authors The list of authors.
+ */
+ public static void printAuthors(List<String> authors) {
+ StringBuilder line = new StringBuilder();
+ line.append(authors.size() == 1 ?
+ messages.getMessage(Messages.INFO_LABEL_AUTHOR)
+ : messages.getMessage(Messages.INFO_LABEL_AUTHORS));
+ String text = authors.isEmpty() ?
+ messages.getMessage(Messages.INFO_LABEL_NO_AUTHORS)
+ : CommonUtils.getCollectionElements(
+ CommonUtils.trimSpaces(authors), "", "", ", ");
+ line.append(" ").append(text);
+ wrapText(line.toString());
+ }
+
+ /**
+ * Displays the current conditional in the terminal.
+ * @param conditional The conditional object.
+ */
+ public static void printConditional(Conditional conditional) {
+ if (conditional.getType() != Conditional.ConditionalType.NONE) {
+ StringBuilder line = new StringBuilder();
+ line.append(messages.getMessage(Messages.INFO_LABEL_CONDITIONAL));
+ line.append(" (");
+ line.append(String.valueOf(conditional.getType()));
+ line.append(") ").append(conditional.getCondition());
+ wrapText(line.toString());
+ }
+ }
+
+ /**
+ * Displays the file information in the terminal.
+ */
+ public static void printFileInformation() {
+ File file = (File) ConfigurationController.
+ getInstance().get("execution.reference");
+ String version = (String) ConfigurationController.
+ getInstance().get("application.version");
+ String revision = (String) ConfigurationController.
+ getInstance().get("application.revision");
+ String line = messages.getMessage(
+ Messages.INFO_DISPLAY_FILE_INFORMATION,
+ file.getName(),
+ CommonUtils.calculateFileSize(file),
+ CommonUtils.getLastModifiedInformation(file)
+ );
+ logger.info(messages.getMessage(
+ Messages.LOG_INFO_WELCOME_MESSAGE,
+ version,
+ revision
+ ));
+ logger.info(displaySeparator());
+ logger.info(String.format("::: arara @ %s",
+ getApplicationPath()
+ ));
+ logger.info(String.format("::: Java %s, %s",
+ CommonUtils.getSystemProperty("java.version",
+ "[unknown version]"),
+ CommonUtils.getSystemProperty("java.vendor",
+ "[unknown vendor]")
+ ));
+ logger.info(String.format("::: %s",
+ CommonUtils.getSystemProperty("java.home",
+ "[unknown location]")
+ ));
+ logger.info(String.format("::: %s, %s, %s",
+ CommonUtils.getSystemProperty("os.name",
+ "[unknown OS name]"),
+ CommonUtils.getSystemProperty("os.arch",
+ "[unknown OS arch]"),
+ CommonUtils.getSystemProperty("os.version",
+ "[unknown OS version]")
+ ));
+ logger.info(String.format("::: user.home @ %s",
+ CommonUtils.getSystemProperty("user.home",
+ "[unknown user's home directory]")
+ ));
+ logger.info(String.format("::: user.dir @ %s",
+ CommonUtils.getSystemProperty("user.dir",
+ "[unknown user's working directory]")
+ ));
+ logger.info(String.format("::: CF @ %s",
+ (String) ConfigurationController.
+ getInstance().get("execution.configuration.name")
+ ));
+ logger.info(displaySeparator());
+ logger.info(line);
+ wrapText(line);
+ addNewLine();
+ }
+
+ /**
+ * Displays the elapsed time in the terminal.
+ */
+ public static void printTime() {
+ if (ConfigurationController.getInstance().contains("display.time")) {
+ if ((ConfigurationController.getInstance().contains("display.line"))
+ || (ConfigurationController.getInstance().
+ contains("display.exception"))) {
+ addNewLine();
+ }
+ String text = messages.getMessage(
+ Messages.INFO_DISPLAY_EXECUTION_TIME, StopWatch.getTime());
+ logger.info(text);
+ wrapText(text);
+ }
+ }
+
+ /**
+ * Displays the application logo in the terminal.
+ */
+ public static void printLogo() {
+ StringBuilder builder = new StringBuilder();
+ builder.append(" __ _ _ __ __ _ _ __ __ _ ").append("\n");
+ builder.append(" / _` | '__/ _` | '__/ _` |").append("\n");
+ builder.append("| (_| | | | (_| | | | (_| |").append("\n");
+ builder.append(" \\__,_|_| \\__,_|_| \\__,_|");
+ System.out.println(builder.toString());
+ addNewLine();
+ }
+
+ /**
+ * Adds a new line in the terminal.
+ */
+ private static void addNewLine() {
+ System.out.println();
+ }
+
+ /**
+ * Displays a line containing details.
+ */
+ private static void displayDetailsLine() {
+ String line = messages.getMessage(
+ Messages.INFO_LABEL_ON_DETAILS).concat(" ");
+ line = StringUtils.rightPad(
+ StringUtils.abbreviate(line, getWidth()), getWidth(), "-");
+ System.out.println(line);
+ }
+
+ /**
+ * Gets the output separator with the provided text.
+ * @param message The provided text.
+ * @return A string containing the output separator with the provided text.
+ */
+ public static String displayOutputSeparator(String message) {
+ return StringUtils.center(" ".concat(message).concat(" "),
+ getWidth(), "-");
+ }
+
+ /**
+ * Gets the line separator.
+ * @return A string containing the line separator.
+ */
+ public static String displaySeparator() {
+ return StringUtils.repeat("-", getWidth());
+ }
+
+ /**
+ * Gets the application path.
+ * @return A string containing the application path.
+ */
+ private static String getApplicationPath() {
+ try {
+ return ConfigurationUtils.getApplicationPath();
+ }
+ catch (AraraException ae) {
+ return "[unknown application path]";
+ }
+ }
+
+}
diff --git a/support/arara/source/src/main/java/com/github/cereda/arara/utils/FileHandlingUtils.java b/support/arara/source/src/main/java/com/github/cereda/arara/utils/FileHandlingUtils.java
new file mode 100644
index 0000000000..9f56c92f5d
--- /dev/null
+++ b/support/arara/source/src/main/java/com/github/cereda/arara/utils/FileHandlingUtils.java
@@ -0,0 +1,124 @@
+/**
+ * Arara, the cool TeX automation tool
+ * Copyright (c) 2012 -- 2018, Paulo Roberto Massa Cereda
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * 3. Neither the name of the project's author nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+ * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
+ * COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+ * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY
+ * WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+package com.github.cereda.arara.utils;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+import org.apache.commons.io.FileUtils;
+
+/**
+ * Implements file handling utilitary methods.
+ *
+ * @author Paulo Roberto Massa Cereda
+ * @version 4.0
+ * @since 4.0
+ */
+public class FileHandlingUtils {
+
+ /**
+ * Writes the string to a file, using UTF-8 as default encoding.
+ * @param file The file.
+ * @param text The string to be written.
+ * @param append A flag whether to append the content.
+ * @return A logical value indicating whether it was successful.
+ */
+ public static boolean writeToFile(File file, String text, boolean append) {
+ try {
+
+ // try to write the provided
+ // string to the file, with
+ // UTF-8 as encoding
+ FileUtils.writeStringToFile(file, text, "UTF-8", append);
+ return true;
+
+ } catch (IOException nothandled) {
+
+ // if something bad happens,
+ // gracefully fallback to
+ // reporting the failure
+ return false;
+ }
+ }
+
+ /**
+ * Writes the string list to a file, using UTF-8 as default encoding.
+ * @param file The file.
+ * @param lines The string list to be written.
+ * @param append A flag whether to append the content.
+ * @return A logical value indicating whether it was successful.
+ */
+ public static boolean writeToFile(File file, List<String> lines,
+ boolean append) {
+ try {
+
+ // try to write the provided
+ // string lists to the file,
+ // with UTF-8 as encoding
+ FileUtils.writeLines(file, "UTF-8", lines, append);
+ return true;
+
+ } catch (IOException nothandled) {
+
+ // if something bad happens,
+ // gracefully fallback to
+ // reporting the failure
+ return false;
+ }
+ }
+
+ /**
+ * Reads the provided file into a list of strings.
+ * @param file The file.
+ * @return A list of strings.
+ */
+ public static List<String> readFromFile(File file) {
+ try {
+
+ // returns the contents of
+ // the provided file as
+ // a list of strings
+ return FileUtils.readLines(file, "UTF-8");
+
+ } catch (IOException nothandled) {
+
+ // if something bad happens,
+ // gracefully fallback to
+ // an empty file list
+ return new ArrayList<String>();
+ }
+ }
+
+}
diff --git a/support/arara/source/src/main/java/com/github/cereda/arara/utils/FileSearchingUtils.java b/support/arara/source/src/main/java/com/github/cereda/arara/utils/FileSearchingUtils.java
new file mode 100644
index 0000000000..473674cbc5
--- /dev/null
+++ b/support/arara/source/src/main/java/com/github/cereda/arara/utils/FileSearchingUtils.java
@@ -0,0 +1,117 @@
+/**
+ * Arara, the cool TeX automation tool
+ * Copyright (c) 2012 -- 2018, Paulo Roberto Massa Cereda
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * 3. Neither the name of the project's author nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+ * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
+ * COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+ * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY
+ * WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+package com.github.cereda.arara.utils;
+
+import java.io.File;
+import java.util.ArrayList;
+import java.util.List;
+import org.apache.commons.io.FileUtils;
+import org.apache.commons.io.filefilter.FalseFileFilter;
+import org.apache.commons.io.filefilter.TrueFileFilter;
+import org.apache.commons.io.filefilter.WildcardFileFilter;
+
+/**
+ * Implements file searching utilitary methods.
+ * @author Paulo Roberto Massa Cereda
+ * @version 4.0
+ * @since 4.0
+ */
+public class FileSearchingUtils {
+
+ /**
+ * List all files from the provided directory according to the list of
+ * extensions. The leading dot must be omitted, unless it is part of the
+ * extension.
+ * @param directory The provided directory.
+ * @param extensions The list of extensions.
+ * @param recursive A flag indicating whether the search is recursive.
+ * @return A list of files.
+ */
+ public static List<File> listFilesByExtensions(File directory,
+ List<String> extensions, boolean recursive) {
+ try {
+
+ // convert the provided extension
+ // list to an extension array
+ String[] array = new String[extensions.size()];
+ array = extensions.toArray(array);
+
+ // return the result of the
+ // provided search
+ return new ArrayList<File>(
+ FileUtils.listFiles(directory, array, recursive)
+ );
+ } catch (Exception nothandled) {
+
+ // if something bad happens,
+ // gracefully fallback to
+ // an empty file list
+ return new ArrayList<File>();
+ }
+ }
+
+ /**
+ * List all files from the provided directory matching the list of file
+ * name patterns. Such list can contain wildcards.
+ * @param directory The provided directory.
+ * @param patterns The list of file name patterns.
+ * @param recursive A flag indicating whether the search is recursive.
+ * @return A list of files.
+ */
+ public static List<File> listFilesByPatterns(File directory,
+ List<String> patterns, boolean recursive) {
+ try {
+
+ // return the result of the provided
+ // search, with the wildcard filter
+ // and a potential recursive search
+ return new ArrayList<File>(
+ FileUtils.listFiles(
+ directory,
+ new WildcardFileFilter(patterns),
+ recursive
+ ? TrueFileFilter.INSTANCE
+ : FalseFileFilter.INSTANCE
+ )
+ );
+ } catch (Exception nothandled) {
+
+ // if something bad happens,
+ // gracefully fallback to
+ // an empty file list
+ return new ArrayList<File>();
+ }
+ }
+
+}
diff --git a/support/arara/source/src/main/java/com/github/cereda/arara/utils/InterpreterUtils.java b/support/arara/source/src/main/java/com/github/cereda/arara/utils/InterpreterUtils.java
new file mode 100644
index 0000000000..978cb07374
--- /dev/null
+++ b/support/arara/source/src/main/java/com/github/cereda/arara/utils/InterpreterUtils.java
@@ -0,0 +1,257 @@
+/**
+ * Arara, the cool TeX automation tool
+ * Copyright (c) 2012 -- 2018, Paulo Roberto Massa Cereda
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * 3. Neither the name of the project's author nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+ * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
+ * COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+ * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY
+ * WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+package com.github.cereda.arara.utils;
+
+import com.github.cereda.arara.controller.ConfigurationController;
+import com.github.cereda.arara.controller.LanguageController;
+import com.github.cereda.arara.model.AraraException;
+import com.github.cereda.arara.model.Argument;
+import com.github.cereda.arara.model.Command;
+import com.github.cereda.arara.model.Conditional;
+import com.github.cereda.arara.model.Messages;
+import com.github.cereda.arara.model.Rule;
+import java.io.ByteArrayOutputStream;
+import java.io.File;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
+import org.apache.commons.collections4.CollectionUtils;
+import org.apache.commons.collections4.Transformer;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.zeroturnaround.exec.InvalidExitValueException;
+import org.zeroturnaround.exec.ProcessExecutor;
+import org.zeroturnaround.exec.listener.ShutdownHookProcessDestroyer;
+
+/**
+ * Implements interpreter utilitary methods.
+ * @author Paulo Roberto Massa Cereda
+ * @version 4.0
+ * @since 4.0
+ */
+public class InterpreterUtils {
+
+ // the application messages obtained from the
+ // language controller
+ private static final LanguageController messages =
+ LanguageController.getInstance();
+
+ // get the logger context from a factory
+ private static final Logger logger =
+ LoggerFactory.getLogger(InterpreterUtils.class);
+
+ /**
+ * Gets a list of all rule arguments.
+ * @param rule The provided rule.
+ * @return A list of strings containing all rule arguments.
+ */
+ public static List<String> getRuleArguments(Rule rule) {
+ Collection<String> result = CollectionUtils.collect(
+ rule.getArguments(), new Transformer<Argument, String>() {
+ public String transform(Argument input) {
+ return input.getIdentifier();
+ }
+ });
+ return new ArrayList<String>(result);
+ }
+
+ /**
+ * Checks if the current conditional has a prior evaluation.
+ * @param conditional The current conditional object.
+ * @return A boolean value indicating if the current conditional has a prior
+ * evaluation.
+ */
+ public static boolean runPriorEvaluation(Conditional conditional) {
+ if (((Boolean) ConfigurationController.getInstance().
+ get("execution.dryrun")) == true) {
+ return false;
+ }
+ switch (conditional.getType()) {
+ case IF:
+ case WHILE:
+ case UNLESS:
+ return true;
+ default:
+ return false;
+ }
+ }
+
+ /**
+ * Runs the command in the underlying operating system.
+ * @param command An object representing the command.
+ * @return An integer value representing the exit code.
+ * @throws AraraException Something wrong happened, to be caught in the
+ * higher levels.
+ */
+ public static int run(Object command) throws AraraException {
+ boolean verbose = (Boolean) ConfigurationController.
+ getInstance().get("execution.verbose");
+ boolean timeout = (Boolean) ConfigurationController.
+ getInstance().get("execution.timeout");
+ long value = (Long) ConfigurationController.
+ getInstance().get("execution.timeout.value");
+ TimeUnit unit = (TimeUnit) ConfigurationController.
+ getInstance().get("execution.timeout.unit");
+ ByteArrayOutputStream buffer = new ByteArrayOutputStream();
+ ProcessExecutor executor = new ProcessExecutor();
+ if (CommonUtils.checkClass(Command.class, command)) {
+ executor = executor.command(((Command) command).getElements());
+ if (((Command) command).hasWorkingDirectory()) {
+ executor = executor.directory(
+ ((Command) command).getWorkingDirectory()
+ );
+ }
+ } else {
+ executor = executor.commandSplit((String) command);
+ }
+ if (timeout) {
+ if (value == 0) {
+ throw new AraraException(
+ messages.getMessage(
+ Messages.ERROR_RUN_TIMEOUT_INVALID_RANGE
+ )
+ );
+ }
+ executor = executor.timeout(value, unit);
+ }
+ TeeOutputStream tee;
+ if (verbose) {
+ tee = new TeeOutputStream(System.out, buffer);
+ executor = executor.redirectInput(System.in);
+ } else {
+ tee = new TeeOutputStream(buffer);
+ }
+ executor = executor.redirectOutput(tee).redirectError(tee);
+ ShutdownHookProcessDestroyer hook = new ShutdownHookProcessDestroyer();
+ executor = executor.addDestroyer(hook);
+ try {
+ int exit = executor.execute().getExitValue();
+ logger.info(DisplayUtils.displayOutputSeparator(
+ messages.getMessage(
+ Messages.LOG_INFO_BEGIN_BUFFER
+ )
+ ));
+ logger.info(buffer.toString());
+ logger.info(DisplayUtils.displayOutputSeparator(
+ messages.getMessage(
+ Messages.LOG_INFO_END_BUFFER
+ )
+ ));
+ return exit;
+ } catch (IOException ioexception) {
+ throw new AraraException(
+ messages.getMessage(
+ Messages.ERROR_RUN_IO_EXCEPTION
+ ),
+ ioexception
+ );
+ } catch (InterruptedException iexception) {
+ throw new AraraException(
+ messages.getMessage(
+ Messages.ERROR_RUN_INTERRUPTED_EXCEPTION
+ ),
+ iexception
+ );
+ } catch (InvalidExitValueException ievexception) {
+ throw new AraraException(
+ messages.getMessage(
+ Messages.ERROR_RUN_INVALID_EXIT_VALUE_EXCEPTION
+ ),
+ ievexception
+ );
+ } catch (TimeoutException texception) {
+ throw new AraraException(
+ messages.getMessage(
+ Messages.ERROR_RUN_TIMEOUT_EXCEPTION
+ ),
+ texception
+ );
+ } catch (Exception exception) {
+ throw new AraraException(
+ messages.getMessage(
+ Messages.ERROR_RUN_GENERIC_EXCEPTION
+ ),
+ exception
+ );
+ }
+ }
+
+ /**
+ * Builds the rule path based on the rule name and returns the corresponding
+ * file location.
+ * @param name The rule name.
+ * @return The rule file.
+ * @throws AraraException Something wrong happened, to be caught in the
+ * higher levels.
+ */
+ public static File buildRulePath(String name) throws AraraException {
+ @SuppressWarnings("unchecked")
+ List<String> paths = (List<String>) ConfigurationController.
+ getInstance().get("execution.rule.paths");
+ for (String path : paths) {
+ File location = new File(construct(path, name));
+ if (location.exists()) {
+ return location;
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Constructs the path given the current path and the rule name.
+ * @param path The current path.
+ * @param name The rule name.
+ * @return The constructed path.
+ * @throws AraraException Something wrong happened, to be caught in the
+ * higher levels.
+ */
+ public static String construct(String path, String name)
+ throws AraraException {
+ name = name.concat(".yaml");
+ File location = new File(path);
+ if (location.isAbsolute()) {
+ return CommonUtils.buildPath(path, name);
+ } else {
+ File reference = (File) ConfigurationController.
+ getInstance().get("execution.reference");
+ String parent = CommonUtils.buildPath(
+ CommonUtils.getParentCanonicalPath(reference), path);
+ return CommonUtils.buildPath(parent, name);
+ }
+ }
+
+}
diff --git a/support/arara/source/src/main/java/com/github/cereda/arara/utils/MessageUtils.java b/support/arara/source/src/main/java/com/github/cereda/arara/utils/MessageUtils.java
new file mode 100644
index 0000000000..52a5d095a8
--- /dev/null
+++ b/support/arara/source/src/main/java/com/github/cereda/arara/utils/MessageUtils.java
@@ -0,0 +1,313 @@
+/**
+ * Arara, the cool TeX automation tool
+ * Copyright (c) 2012 -- 2018, Paulo Roberto Massa Cereda
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * 3. Neither the name of the project's author nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+ * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
+ * COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+ * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY
+ * WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+package com.github.cereda.arara.utils;
+
+import com.github.cereda.arara.controller.ConfigurationController;
+import javax.swing.JOptionPane;
+import javax.swing.UIManager;
+
+/**
+ * Implements utilitary methods for displaying messages.
+ * @author Paulo Roberto Massa Cereda
+ * @version 4.0
+ * @since 4.0
+ */
+public class MessageUtils {
+
+ // holds the default width for the
+ // message body, in pixels
+ private static final int WIDTH = 250;
+
+ // let's start the UI manager and set
+ // the default look and feel to be as
+ // close as possible to the system
+ static {
+
+ // get the current look and feel
+ String laf = (String) ConfigurationController.
+ getInstance().get("ui.lookandfeel");
+
+ // check if one is actually set
+ if (!laf.equals("none")) {
+
+ // use a special keyword to indicate
+ // the use of a system look and feel
+ if (laf.equals("system")) {
+ laf = UIManager.getSystemLookAndFeelClassName();
+ }
+
+ // let's try it, in case it fails,
+ // rely to the default look and feel
+ try {
+
+ // get the system look and feel name
+ // and try to set it as default
+ UIManager.setLookAndFeel(laf);
+ }
+ catch (Exception exception) {
+ // quack, quack, quack
+ }
+
+ }
+ }
+
+ /**
+ * Normalizes the icon type to one of the five available icons.
+ * @param value An integer value.
+ * @return The normalized integer value.
+ */
+ private static int normalizeIconType(int value) {
+
+ // do the normalization according to the available
+ // icons in the underlying message implementation
+ switch (value) {
+ case 1:
+ value = JOptionPane.ERROR_MESSAGE;
+ break;
+ case 2:
+ value = JOptionPane.INFORMATION_MESSAGE;
+ break;
+ case 3:
+ value = JOptionPane.WARNING_MESSAGE;
+ break;
+ case 4:
+ value = JOptionPane.QUESTION_MESSAGE;
+ break;
+ default:
+ value = JOptionPane.PLAIN_MESSAGE;
+ break;
+ }
+ return value;
+ }
+
+ /**
+ * Normalizes the message width, so only valid nonzero values are accepted.
+ * @param value An integer value corresponding to the message width.
+ * @return The normalized width.
+ */
+ private static int normalizeMessageWidth(int value) {
+ return (value > 0 ? value : WIDTH);
+ }
+
+ /**
+ * Shows the message.
+ * @param width Integer value, in pixels.
+ * @param type Type of message.
+ * @param title Title of the message.
+ * @param text Text of the message.
+ */
+ public static void showMessage(int width, int type,
+ String title, String text) {
+
+ // effectively shows the message based
+ // on the provided parameters
+ JOptionPane.showMessageDialog(
+ null,
+ String.format(
+ "<html><body style=\"width:%dpx\">%s</body></html>",
+ normalizeMessageWidth(width),
+ text
+ ),
+ title,
+ normalizeIconType(type)
+ );
+ }
+
+ /**
+ * Shows the message. It relies on the default width.
+ * @param type Type of message.
+ * @param title Title of the message.
+ * @param text Text of the message.
+ */
+ public static void showMessage(int type, String title, String text) {
+ showMessage(WIDTH, type, title, text);
+ }
+
+ /**
+ * Shows a message with options presented as an array of buttons.
+ * @param width Integer value, in pixels.
+ * @param type Type of message.
+ * @param title Title of the message.
+ * @param text Text of the message.
+ * @param buttons An array of objects to be presented as buttons.
+ * @return The index of the selected button, starting from 1.
+ */
+ public static int showOptions(int width, int type, String title,
+ String text, Object... buttons) {
+
+ // returns the index of the selected button,
+ // zero if nothing is selected
+ return JOptionPane.showOptionDialog(
+ null,
+ String.format(
+ "<html><body style=\"width:%dpx\">%s</body></html>",
+ normalizeMessageWidth(width),
+ text
+ ),
+ title,
+ JOptionPane.DEFAULT_OPTION,
+ normalizeIconType(type),
+ null,
+ buttons,
+ buttons[0]
+ ) + 1;
+ }
+
+ /**
+ * Shows a message with options presented as an array of buttons. It relies
+ * on the default width.
+ * @param type Type of message.
+ * @param title Title of the message.
+ * @param text Text of the message.
+ * @param buttons An array of objects to be presented as buttons.
+ * @return The index of the selected button, starting from 1.
+ */
+ public static int showOptions(int type, String title,
+ String text, Object... buttons) {
+ return showOptions(WIDTH, type, title, text, buttons);
+ }
+
+ /**
+ * Shows a message with a text input.
+ * @param width Integer value, in pixels.
+ * @param type Type of message.
+ * @param title Title of the message.
+ * @param text Text of the message.
+ * @return The string representing the input text.
+ */
+ public static String showInput(int width, int type,
+ String title, String text) {
+
+ // get the string from the
+ // input text, if any
+ String input = JOptionPane.showInputDialog(
+ null,
+ String.format(
+ "<html><body style=\"width:%dpx\">%s</body></html>",
+ normalizeMessageWidth(width),
+ text
+ ),
+ title,
+ normalizeIconType(type)
+ );
+
+ // if the input is not null, that is,
+ // the user actually typed something
+ if (input != null) {
+
+ // return the trimmed string
+ return input.trim();
+ }
+
+ // nothing was typed, so let's
+ // return an empty string
+ return "";
+ }
+
+ /**
+ * Shows a message with a text input. It relies on the default width.
+ * @param type Type of message.
+ * @param title Title of the message.
+ * @param text Text of the message.
+ * @return The string representing the input text.
+ */
+ public static String showInput(int type, String title, String text) {
+ return showInput(WIDTH, type, title, text);
+ }
+
+ /**
+ * Shows a message with options presented as a dropdown list of elements.
+ * @param width Integer value, in pixels.
+ * @param type Type of message.
+ * @param title Title of the message.
+ * @param text Text of the message.
+ * @param elements An array of objects representing the elements.
+ * @return The index of the selected element, starting from 1.
+ */
+ public static int showDropdown(int width, int type, String title,
+ String text, Object... elements) {
+
+ // show the dropdown list and get
+ // the selected object, if any
+ Object index = JOptionPane.showInputDialog(
+ null,
+ String.format(
+ "<html><body style=\"width:%dpx\">%s</body></html>",
+ normalizeMessageWidth(width),
+ text
+ ),
+ title,
+ normalizeIconType(type),
+ null,
+ elements,
+ elements[0]
+ );
+
+ // if it's not a null object, let's
+ // find the corresponding index
+ if (index != null) {
+
+ // iterate through the array of elements
+ for (int i = 0; i < elements.length; i++) {
+
+ // if the element is found, simply
+ // return the index plus 1, as zero
+ // corresponds to no selection at all
+ if (elements[i].equals(index)) {
+ return i + 1;
+ }
+
+ }
+ }
+
+ // nothing was selected,
+ // simply return zero
+ return 0;
+ }
+
+ /**
+ * Shows a message with options presented as a dropdown list of elements. It
+ * relies on the default width.
+ * @param type Type of message.
+ * @param title Title of the message.
+ * @param text Text of the message.
+ * @param elements An array of objects representing the elements.
+ * @return The index of the selected element, starting from 1.
+ */
+ public static int showDropdown(int type, String title,
+ String text, Object... elements) {
+ return showDropdown(WIDTH, type, title, text, elements);
+ }
+
+}
diff --git a/support/arara/source/src/main/java/com/github/cereda/arara/utils/Methods.java b/support/arara/source/src/main/java/com/github/cereda/arara/utils/Methods.java
new file mode 100644
index 0000000000..15c6604934
--- /dev/null
+++ b/support/arara/source/src/main/java/com/github/cereda/arara/utils/Methods.java
@@ -0,0 +1,1356 @@
+/**
+ * Arara, the cool TeX automation tool
+ * Copyright (c) 2012 -- 2018, Paulo Roberto Massa Cereda
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * 3. Neither the name of the project's author nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+ * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
+ * COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+ * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY
+ * WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+package com.github.cereda.arara.utils;
+
+import com.github.cereda.arara.controller.ConfigurationController;
+import com.github.cereda.arara.controller.LanguageController;
+import com.github.cereda.arara.model.AraraException;
+import com.github.cereda.arara.model.Command;
+import com.github.cereda.arara.model.Messages;
+import com.github.cereda.arara.model.Pair;
+import com.github.cereda.arara.model.Session;
+import com.github.cereda.arara.model.Trigger;
+import java.io.File;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Implements some auxiliary methods for runtime evaluation.
+ * @author Paulo Roberto Massa Cereda
+ * @version 4.0
+ * @since 4.0
+ */
+public class Methods {
+
+ // the language controller
+ private static final LanguageController messages =
+ LanguageController.getInstance();
+
+ // the session controller
+ private static final Session session = new Session();
+
+ /**
+ * Adds the rule methods to the provided map.
+ * @param map The map.
+ */
+ public static void addRuleMethods(Map<String, Object> map) {
+ addConditionalMethods(map);
+ try {
+ map.put("getOriginalFile", Methods.class.getMethod("getOriginalFile"));
+ map.put("getOriginalReference", Methods.class.getMethod("getOriginalReference"));
+ map.put("isEmpty", Methods.class.getMethod("isEmpty", String.class));
+ map.put("isNotEmpty", Methods.class.getMethod("isNotEmpty", String.class));
+ map.put("isEmpty", Methods.class.getMethod("isEmpty", String.class, Object.class));
+ map.put("isNotEmpty", Methods.class.getMethod("isNotEmpty", String.class, Object.class));
+ map.put("isEmpty", Methods.class.getMethod("isEmpty", String.class, Object.class, Object.class));
+ map.put("isNotEmpty", Methods.class.getMethod("isNotEmpty", String.class, Object.class, Object.class));
+ map.put("isTrue", Methods.class.getMethod("isTrue", String.class));
+ map.put("isFalse", Methods.class.getMethod("isFalse", String.class));
+ map.put("isTrue", Methods.class.getMethod("isTrue", String.class, Object.class));
+ map.put("isFalse", Methods.class.getMethod("isFalse", String.class, Object.class));
+ map.put("isTrue", Methods.class.getMethod("isTrue", String.class, Object.class, Object.class));
+ map.put("isFalse", Methods.class.getMethod("isFalse", String.class, Object.class, Object.class));
+ map.put("isTrue", Methods.class.getMethod("isTrue", String.class, Object.class, Object.class, Object.class));
+ map.put("isFalse", Methods.class.getMethod("isFalse", String.class, Object.class, Object.class, Object.class));
+ map.put("trimSpaces", Methods.class.getMethod("trimSpaces", String.class));
+ map.put("isTrue", Methods.class.getMethod("isTrue", boolean.class, Object.class));
+ map.put("isFalse", Methods.class.getMethod("isFalse", boolean.class, Object.class));
+ map.put("isTrue", Methods.class.getMethod("isTrue", boolean.class, Object.class, Object.class));
+ map.put("isFalse", Methods.class.getMethod("isFalse", boolean.class, Object.class, Object.class));
+ map.put("getBasename", Methods.class.getMethod("getBasename", File.class));
+ map.put("getBasename", Methods.class.getMethod("getBasename", String.class));
+ map.put("getFullBasename", Methods.class.getMethod("getFullBasename", File.class));
+ map.put("getFullBasename", Methods.class.getMethod("getFullBasename", String.class));
+ map.put("getFiletype", Methods.class.getMethod("getFiletype", File.class));
+ map.put("getFiletype", Methods.class.getMethod("getFiletype", String.class));
+ map.put("throwError", Methods.class.getMethod("throwError", String.class));
+ map.put("getSession", Methods.class.getMethod("getSession"));
+ map.put("isWindows", Methods.class.getMethod("isWindows"));
+ map.put("isLinux", Methods.class.getMethod("isLinux"));
+ map.put("isMac", Methods.class.getMethod("isMac"));
+ map.put("isUnix", Methods.class.getMethod("isUnix"));
+ map.put("isAIX", Methods.class.getMethod("isAIX"));
+ map.put("isIrix", Methods.class.getMethod("isIrix"));
+ map.put("isOS2", Methods.class.getMethod("isOS2"));
+ map.put("isSolaris", Methods.class.getMethod("isSolaris"));
+ map.put("isCygwin", Methods.class.getMethod("isCygwin"));
+ map.put("isWindows", Methods.class.getMethod("isWindows", Object.class, Object.class));
+ map.put("isLinux", Methods.class.getMethod("isLinux", Object.class, Object.class));
+ map.put("isMac", Methods.class.getMethod("isMac", Object.class, Object.class));
+ map.put("isUnix", Methods.class.getMethod("isUnix", Object.class, Object.class));
+ map.put("isAIX", Methods.class.getMethod("isAIX", Object.class, Object.class));
+ map.put("isIrix", Methods.class.getMethod("isIrix", Object.class, Object.class));
+ map.put("isOS2", Methods.class.getMethod("isOS2", Object.class, Object.class));
+ map.put("isSolaris", Methods.class.getMethod("isSolaris", Object.class, Object.class));
+ map.put("isCygwin", Methods.class.getMethod("isCygwin", Object.class, Object.class));
+ map.put("replicatePattern", Methods.class.getMethod("replicatePattern", String.class, List.class));
+ map.put("buildString", Methods.class.getMethod("buildString", Object[].class));
+ map.put("addQuotes", Methods.class.getMethod("addQuotes", Object.class));
+ map.put("getCommand", Methods.class.getMethod("getCommand", List.class));
+ map.put("getCommand", Methods.class.getMethod("getCommand", Object[].class));
+ map.put("getTrigger", Methods.class.getMethod("getTrigger", String.class));
+ map.put("getTrigger", Methods.class.getMethod("getTrigger", String.class, Object[].class));
+ map.put("checkClass", Methods.class.getMethod("checkClass", Class.class, Object.class));
+ map.put("isString", Methods.class.getMethod("isString", Object.class));
+ map.put("isList", Methods.class.getMethod("isList", Object.class));
+ map.put("isMap", Methods.class.getMethod("isMap", Object.class));
+ map.put("isBoolean", Methods.class.getMethod("isBoolean", Object.class));
+ map.put("isVerboseMode", Methods.class.getMethod("isVerboseMode"));
+ map.put("showMessage", Methods.class.getMethod("showMessage", int.class, String.class, String.class));
+ map.put("showMessage", Methods.class.getMethod("showMessage", int.class, int.class, String.class, String.class));
+ map.put("isOnPath", Methods.class.getMethod("isOnPath", String.class));
+ map.put("unsafelyExecuteSystemCommand", Methods.class.getMethod("unsafelyExecuteSystemCommand", Command.class));
+ map.put("mergeVelocityTemplate", Methods.class.getMethod("mergeVelocityTemplate", File.class, File.class, Map.class));
+ map.put("getCommandWithWorkingDirectory", Methods.class.getMethod("getCommandWithWorkingDirectory", String.class, List.class));
+ map.put("getCommandWithWorkingDirectory", Methods.class.getMethod("getCommandWithWorkingDirectory", String.class, Object[].class));
+ map.put("getCommandWithWorkingDirectory", Methods.class.getMethod("getCommandWithWorkingDirectory", File.class, List.class));
+ map.put("getCommandWithWorkingDirectory", Methods.class.getMethod("getCommandWithWorkingDirectory", File.class, Object[].class));
+ map.put("listFilesByExtensions", Methods.class.getMethod("listFilesByExtensions", File.class, List.class, boolean.class));
+ map.put("listFilesByExtensions", Methods.class.getMethod("listFilesByExtensions", String.class, List.class, boolean.class));
+ map.put("listFilesByPatterns", Methods.class.getMethod("listFilesByPatterns", File.class, List.class, boolean.class));
+ map.put("listFilesByPatterns", Methods.class.getMethod("listFilesByPatterns", String.class, List.class, boolean.class));
+ map.put("writeToFile", Methods.class.getMethod("writeToFile", File.class, String.class, boolean.class));
+ map.put("writeToFile", Methods.class.getMethod("writeToFile", File.class, List.class, boolean.class));
+ map.put("writeToFile", Methods.class.getMethod("writeToFile", String.class, String.class, boolean.class));
+ map.put("writeToFile", Methods.class.getMethod("writeToFile", String.class, List.class, boolean.class));
+ map.put("readFromFile", Methods.class.getMethod("readFromFile", File.class));
+ map.put("readFromFile", Methods.class.getMethod("readFromFile", String.class));
+ } catch (Exception exception) {
+ // quack, quack, quack
+ }
+ }
+
+ /**
+ * Adds conditional methods to the provided map.
+ * @param map The map.
+ */
+ public static void addConditionalMethods(Map<String, Object> map) {
+ try {
+ map.put("exists", Methods.class.getMethod("exists", String.class));
+ map.put("exists", Methods.class.getMethod("exists", File.class));
+ map.put("missing", Methods.class.getMethod("missing", String.class));
+ map.put("missing", Methods.class.getMethod("missing", File.class));
+ map.put("changed", Methods.class.getMethod("changed", String.class));
+ map.put("changed", Methods.class.getMethod("changed", File.class));
+ map.put("unchanged", Methods.class.getMethod("unchanged", String.class));
+ map.put("unchanged", Methods.class.getMethod("unchanged", File.class));
+ map.put("found", Methods.class.getMethod("found", String.class, String.class));
+ map.put("found", Methods.class.getMethod("found", File.class, String.class));
+ map.put("toFile", Methods.class.getMethod("toFile", String.class));
+ map.put("showDropdown", Methods.class.getMethod("showDropdown", int.class, String.class, String.class, Object[].class));
+ map.put("showDropdown", Methods.class.getMethod("showDropdown", int.class, int.class, String.class, String.class, Object[].class));
+ map.put("showInput", Methods.class.getMethod("showInput", int.class, String.class, String.class));
+ map.put("showInput", Methods.class.getMethod("showInput", int.class, int.class, String.class, String.class));
+ map.put("showOptions", Methods.class.getMethod("showOptions", int.class, String.class, String.class, Object[].class));
+ map.put("showOptions", Methods.class.getMethod("showOptions", int.class, int.class, String.class, String.class, Object[].class));
+ map.put("currentFile", Methods.class.getMethod("currentFile"));
+ map.put("loadClass", Methods.class.getMethod("loadClass", File.class, String.class));
+ map.put("loadClass", Methods.class.getMethod("loadClass", String.class, String.class));
+ map.put("loadObject", Methods.class.getMethod("loadObject", File.class, String.class));
+ map.put("loadObject", Methods.class.getMethod("loadObject", String.class, String.class));
+ } catch (Exception exception) {
+ // quack, quack, quack
+ }
+ }
+
+ /**
+ * Gets the original file.
+ * @return The original file.
+ */
+ public static String getOriginalFile() {
+ File file = (File) ConfigurationController.getInstance().get("execution.reference");
+ return file.getName();
+ }
+
+ /**
+ * Gets the original reference.
+ * @return The original reference.
+ */
+ public static File getOriginalReference() {
+ return (File) ConfigurationController.getInstance().get("execution.reference");
+ }
+
+ /**
+ * Checks if the string is empty.
+ * @param string The string.
+ * @return A boolean value.
+ */
+ public static boolean isEmpty(String string) {
+ return CommonUtils.checkEmptyString(string);
+ }
+
+ /**
+ * Checks if the string is not empty.
+ * @param string The string.
+ * @return A boolean value.
+ */
+ public static boolean isNotEmpty(String string) {
+ return !isEmpty(string);
+ }
+
+ /**
+ * Checks if the string is empty.
+ * @param string The string.
+ * @param yes Object to return if true.
+ * @return An object or empty string.
+ */
+ public static Object isEmpty(String string, Object yes) {
+ return isEmpty(string) ? yes : "";
+ }
+
+ /**
+ * Checks if the string is not empty.
+ * @param string The string.
+ * @param yes Object to return if true.
+ * @return An object or empty string.
+ */
+ public static Object isNotEmpty(String string, Object yes) {
+ return isNotEmpty(string) ? yes : "";
+ }
+
+ /**
+ * Checks if the string is empty.
+ * @param string The string.
+ * @param yes Object to return if true.
+ * @param no Object to return if false.
+ * @return One of the two objects.
+ */
+ public static Object isEmpty(String string, Object yes, Object no) {
+ return isEmpty(string) ? yes : no;
+ }
+
+ /**
+ * Checks if the string is not empty.
+ * @param string The string.
+ * @param yes Object to return if true.
+ * @param no Object to return if false.
+ * @return One of the two objects.
+ */
+ public static Object isNotEmpty(String string, Object yes, Object no) {
+ return isNotEmpty(string) ? yes : no;
+ }
+
+ /**
+ * Checks if the string holds a true value.
+ * @param string The string.
+ * @return A boolean value.
+ * @throws AraraException Something wrong happened, to be caught in the
+ * higher levels.
+ */
+ public static boolean isTrue(String string) throws AraraException {
+ return isEmpty(string) ? false : CommonUtils.checkBoolean(string);
+ }
+
+ /**
+ * Checks if the string holds a false value.
+ * @param string The string.
+ * @return A boolean value.
+ * @throws AraraException Something wrong happened, to be caught in the
+ * higher levels.
+ */
+ public static boolean isFalse(String string) throws AraraException {
+ return isEmpty(string) ? false : !CommonUtils.checkBoolean(string);
+ }
+
+ /**
+ * Checks if the string holds a true value.
+ * @param string The string.
+ * @param yes Object to return if true.
+ * @return An object or an empty string.
+ * @throws AraraException Something wrong happened, to be caught in the
+ * higher levels.
+ */
+ public static Object isTrue(String string, Object yes)
+ throws AraraException {
+ return isTrue(string) ? yes : "";
+ }
+
+ /**
+ * Checks if the string holds a false value.
+ * @param string The string.
+ * @param yes Object to return if true.
+ * @return An object or an empty string.
+ * @throws AraraException Something wrong happened, to be caught in the
+ * higher levels.
+ */
+ public static Object isFalse(String string, Object yes)
+ throws AraraException {
+ return (isFalse(string) ? yes : "");
+ }
+
+ /**
+ * Checks if the string holds a true value.
+ * @param string The string.
+ * @param yes Object to return if true.
+ * @param no Object to return if false.
+ * @return One of the two objects.
+ * @throws AraraException Something wrong happened, to be caught in the
+ * higher levels.
+ */
+ public static Object isTrue(String string, Object yes, Object no)
+ throws AraraException {
+ return (isTrue(string) ? yes : no);
+ }
+
+ /**
+ * Checks if the string holds a false value.
+ * @param string The string.
+ * @param yes Object to return if true.
+ * @param no Object to return if false.
+ * @return One of the two objects.
+ * @throws AraraException Something wrong happened, to be caught in the
+ * higher levels.
+ */
+ public static Object isFalse(String string, Object yes, Object no)
+ throws AraraException {
+ return (isFalse(string) ? yes : no);
+ }
+
+ /**
+ * Checks if the string holds a true value.
+ * @param string The string.
+ * @param yes Object to return if true.
+ * @param no Object to return if false.
+ * @param fallback Object to return if string is empty.
+ * @return One of the three options.
+ * @throws AraraException Something wrong happened, to be caught in the
+ * higher levels.
+ */
+ public static Object isTrue(String string, Object yes, Object no,
+ Object fallback) throws AraraException {
+ return isEmpty(string) ? fallback : (isTrue(string) ? yes : no);
+ }
+
+ /**
+ * Checks if the string holds a false value.
+ * @param string The string.
+ * @param yes Object to return if true.
+ * @param no Object to return if false.
+ * @param fallback Object to return if string is empty.
+ * @return One of the three options.
+ * @throws AraraException Something wrong happened, to be caught in the
+ * higher levels.
+ */
+ public static Object isFalse(String string, Object yes, Object no,
+ Object fallback) throws AraraException {
+ return isEmpty(string) ? fallback : (isFalse(string) ? yes : no);
+ }
+
+ /**
+ * Trim spaces from the string.
+ * @param string The string.
+ * @return A trimmed string.
+ */
+ public static String trimSpaces(String string) {
+ return string.trim();
+ }
+
+ /**
+ * Checks if the expression resolves to true.
+ * @param value The expression.
+ * @param yes Object to return if true.
+ * @return An object or an empty string.
+ */
+ public static Object isTrue(boolean value, Object yes) {
+ return value ? yes : "";
+ }
+
+ /**
+ * Checks if the expression resolves to false.
+ * @param value The expression.
+ * @param yes Object to return if true.
+ * @return An object or an empty string.
+ */
+ public static Object isFalse(boolean value, Object yes) {
+ return !value ? yes : "";
+ }
+
+ /**
+ * Checks if the expression resolves to true.
+ * @param value The expression.
+ * @param yes Object to return if true.
+ * @param no Object to return if false.
+ * @return One of the two objects.
+ */
+ public static Object isTrue(boolean value, Object yes, Object no) {
+ return value ? yes : no;
+ }
+
+ /**
+ * Checks if the expression resolves to false.
+ * @param value The expression.
+ * @param yes Object to return if true.
+ * @param no Object to return if false.
+ * @return One of the two objects.
+ */
+ public static Object isFalse(boolean value, Object yes, Object no) {
+ return !value ? yes : no;
+ }
+
+ /**
+ * Gets the basename.
+ * @param file The file.
+ * @return The basename of the provided file.
+ * @throws AraraException Something wrong happened, to be caught in the
+ * higher levels.
+ */
+ public static String getBasename(File file) throws AraraException {
+ if (file.isFile()) {
+ return CommonUtils.getBasename(file);
+ } else {
+ throw new AraraException(
+ CommonUtils.getRuleErrorHeader().concat(
+ messages.getMessage(
+ Messages.ERROR_BASENAME_NOT_A_FILE,
+ file.getName()
+ )
+ )
+ );
+ }
+ }
+
+ /**
+ * Gets the basename.
+ * @param filename The string.
+ * @return The basename.
+ */
+ public static String getBasename(String filename) {
+ return CommonUtils.getBasename(filename);
+ }
+
+ /**
+ * Gets the file type.
+ * @param file The provided file.
+ * @return The file type.
+ * @throws AraraException Something wrong happened, to be caught in the
+ * higher levels.
+ */
+ public static String getFiletype(File file) throws AraraException {
+ if (file.isFile()) {
+ return CommonUtils.getFiletype(file);
+ } else {
+ throw new AraraException(
+ CommonUtils.getRuleErrorHeader().concat(
+ messages.getMessage(
+ Messages.ERROR_FILETYPE_NOT_A_FILE,
+ file.getName()
+ )
+ )
+ );
+ }
+ }
+
+ /**
+ * Gets the file type.
+ * @param filename The provided string.
+ * @return The file type.
+ */
+ public static String getFiletype(String filename) {
+ return CommonUtils.getFiletype(filename);
+ }
+
+ /**
+ * Replicates the pattern to each element of a list.
+ * @param pattern The pattern.
+ * @param values The list.
+ * @return A list of strings containing the pattern applied to the list.
+ * @throws AraraException Something wrong happened, to be caught in the
+ * higher levels.
+ */
+ public static List<Object> replicatePattern(String pattern,
+ List<Object> values) throws AraraException {
+ return CommonUtils.replicateList(pattern, values);
+ }
+
+ /**
+ * Throws an exception.
+ * @param text The text to be thrown as the exception message.
+ * @throws AraraException The exception to be thrown by this method.
+ */
+ public static void throwError(String text) throws AraraException {
+ throw new AraraException(text);
+ }
+
+ /**
+ * Gets the session.
+ * @return The session.
+ */
+ public static Session getSession() {
+ return session;
+ }
+
+ /**
+ * Checks if Windows is the underlying operating system.
+ * @return A boolean value.
+ * @throws AraraException Something wrong happened, to be caught in the
+ * higher levels.
+ */
+ public static boolean isWindows() throws AraraException {
+ return CommonUtils.checkOS("windows");
+ }
+
+ /**
+ * Checks if we are inside a Cygwin environment.
+ * @return A boolean value.
+ * @throws AraraException Something wrong happened, to be caught in the
+ * higher levels.
+ */
+ public static boolean isCygwin() throws AraraException {
+ return CommonUtils.checkOS("cygwin");
+ }
+
+ /**
+ * Checks if Linux is the underlying operating system.
+ * @return A boolean value.
+ * @throws AraraException Something wrong happened, to be caught in the
+ * higher levels.
+ */
+ public static boolean isLinux() throws AraraException {
+ return CommonUtils.checkOS("linux");
+ }
+
+ /**
+ * Checks if Mac is the underlying operating system.
+ * @return A boolean value.
+ * @throws AraraException Something wrong happened, to be caught in the
+ * higher levels.
+ */
+ public static boolean isMac() throws AraraException {
+ return CommonUtils.checkOS("mac");
+ }
+
+ /**
+ * Checks if Unix is the underlying operating system.
+ * @return A boolean value.
+ * @throws AraraException Something wrong happened, to be caught in the
+ * higher levels.
+ */
+ public static boolean isUnix() throws AraraException {
+ return CommonUtils.checkOS("unix");
+ }
+
+ /**
+ * Checks if AIX is the underlying operating system.
+ * @return A boolean value.
+ * @throws AraraException Something wrong happened, to be caught in the
+ * higher levels.
+ */
+ public static boolean isAIX() throws AraraException {
+ return CommonUtils.checkOS("aix");
+ }
+
+ /**
+ * Checks if Irix is the underlying operating system.
+ * @return A boolean value.
+ * @throws AraraException Something wrong happened, to be caught in the
+ * higher levels.
+ */
+ public static boolean isIrix() throws AraraException {
+ return CommonUtils.checkOS("irix");
+ }
+
+ /**
+ * Checks if OS2 is the underlying operating system.
+ * @return A boolean value.
+ * @throws AraraException Something wrong happened, to be caught in the
+ * higher levels.
+ */
+ public static boolean isOS2() throws AraraException {
+ return CommonUtils.checkOS("os2");
+ }
+
+ /**
+ * Checks if Solaris is the underlying operating system.
+ * @return A boolean value.
+ * @throws AraraException Something wrong happened, to be caught in the
+ * higher levels.
+ */
+ public static boolean isSolaris() throws AraraException {
+ return CommonUtils.checkOS("solaris");
+ }
+
+ /**
+ * Checks if Windows is the underlying operating system.
+ * @param yes Object to return if true.
+ * @param no Object to return if false.
+ * @return One of the two objects.
+ * @throws AraraException Something wrong happened, to be caught in the
+ * higher levels.
+ */
+ public static Object isWindows(Object yes, Object no)
+ throws AraraException {
+ return CommonUtils.checkOS("windows") ? yes : no;
+ }
+
+ /**
+ * Checks if we are inside a Cygwin environment.
+ * @param yes Object to return if true.
+ * @param no Object to return if false.
+ * @return One of the two objects.
+ * @throws AraraException Something wrong happened, to be caught in the
+ * higher levels.
+ */
+ public static Object isCygwin(Object yes, Object no) throws AraraException {
+ return CommonUtils.checkOS("cygwin") ? yes : no;
+ }
+
+ /**
+ * Checks if Linux is the underlying operating system.
+ * @param yes Object to return if true.
+ * @param no Object to return if false.
+ * @return One of the two objects.
+ * @throws AraraException Something wrong happened, to be caught in the
+ * higher levels.
+ */
+ public static Object isLinux(Object yes, Object no) throws AraraException {
+ return CommonUtils.checkOS("linux") ? yes : no;
+ }
+
+ /**
+ * Checks if Mac is the underlying operating system.
+ * @param yes Object to return if true.
+ * @param no Object to return if false.
+ * @return One of the two objects.
+ * @throws AraraException Something wrong happened, to be caught in the
+ * higher levels.
+ */
+ public static Object isMac(Object yes, Object no) throws AraraException {
+ return CommonUtils.checkOS("mac") ? yes : no;
+ }
+
+ /**
+ * Checks if Unix is the underlying operating system.
+ * @param yes Object to return if true.
+ * @param no Object to return if false.
+ * @return One of the two objects.
+ * @throws AraraException Something wrong happened, to be caught in the
+ * higher levels.
+ */
+ public static Object isUnix(Object yes, Object no) throws AraraException {
+ return CommonUtils.checkOS("unix") ? yes : no;
+ }
+
+ /**
+ * Checks if AIX is the underlying operating system.
+ * @param yes Object to return if true.
+ * @param no Object to return if false.
+ * @return One of the two objects.
+ * @throws AraraException Something wrong happened, to be caught in the
+ * higher levels.
+ */
+ public static Object isAIX(Object yes, Object no) throws AraraException {
+ return CommonUtils.checkOS("aix") ? yes : no;
+ }
+
+ /**
+ * Checks if Irix is the underlying operating system.
+ * @param yes Object to return if true.
+ * @param no Object to return if false.
+ * @return One of the two objects.
+ * @throws AraraException Something wrong happened, to be caught in the
+ * higher levels.
+ */
+ public static Object isIrix(Object yes, Object no) throws AraraException {
+ return CommonUtils.checkOS("irix") ? yes : no;
+ }
+
+ /**
+ * Checks if OS2 is the underlying operating system.
+ * @param yes Object to return if true.
+ * @param no Object to return if false.
+ * @return One of the two objects.
+ * @throws AraraException Something wrong happened, to be caught in the
+ * higher levels.
+ */
+ public static Object isOS2(Object yes, Object no) throws AraraException {
+ return CommonUtils.checkOS("os2") ? yes : no;
+ }
+
+ /**
+ * Checks if Solaris is the underlying operating system.
+ * @param yes Object to return if true.
+ * @param no Object to return if false.
+ * @return One of the two objects.
+ * @throws AraraException Something wrong happened, to be caught in the
+ * higher levels.
+ */
+ public static Object isSolaris(Object yes, Object no) throws AraraException {
+ return CommonUtils.checkOS("solaris") ? yes : no;
+ }
+
+ /**
+ * Checks if the file exists according to its extension.
+ * @param extension The extension.
+ * @return A boolean value.
+ * @throws AraraException Something wrong happened, to be caught in the
+ * higher levels.
+ */
+ public static boolean exists(String extension) throws AraraException {
+ return CommonUtils.exists(extension);
+ }
+
+ /**
+ * Checks if the file is missing according to its extension.
+ * @param extension The extension.
+ * @return A boolean value.
+ * @throws AraraException Something wrong happened, to be caught in the
+ * higher levels.
+ */
+ public static boolean missing(String extension) throws AraraException {
+ return !exists(extension);
+ }
+
+ /**
+ * Checks if the file has changed, according to its extension.
+ * @param extension The extension.
+ * @return A boolean value.
+ * @throws AraraException Something wrong happened, to be caught in the
+ * higher levels.
+ */
+ public static boolean changed(String extension) throws AraraException {
+ return CommonUtils.hasChanged(extension);
+ }
+
+ /**
+ * Checks if the file is unchanged according to its extension.
+ * @param extension The extension.
+ * @return A boolean value.
+ * @throws AraraException Something wrong happened, to be caught in the
+ * higher levels.
+ */
+ public static boolean unchanged(String extension) throws AraraException {
+ return !changed(extension);
+ }
+
+ /**
+ * Checks if the file exists.
+ * @param filename The file.
+ * @return A boolean value.
+ */
+ public static boolean exists(File filename) {
+ return CommonUtils.exists(filename);
+ }
+
+ /**
+ * Checks if the file is missing.
+ * @param filename The file.
+ * @return A boolean value.
+ */
+ public static boolean missing(File filename) {
+ return !exists(filename);
+ }
+
+ /**
+ * Checks if the file has changed.
+ * @param filename The file.
+ * @return A boolean value.
+ * @throws AraraException Something wrong happened, to be caught in the
+ * higher levels.
+ */
+ public static boolean changed(File filename) throws AraraException {
+ return CommonUtils.hasChanged(filename);
+ }
+
+ /**
+ * Checks if the file is unchanged.
+ * @param filename The file.
+ * @return A boolean value.
+ * @throws AraraException Something wrong happened, to be caught in the
+ * higher levels.
+ */
+ public static boolean unchanged(File filename) throws AraraException {
+ return !changed(filename);
+ }
+
+ /**
+ * Build a string based on an array of objects.
+ * @param objects Array of objects.
+ * @return A string built from the array.
+ */
+ public static String buildString(Object... objects) {
+ return CommonUtils.generateString(objects);
+ }
+
+ /**
+ * Encloses the provided object into double quotes.
+ * @param object The object.
+ * @return The object enclosed in double quotes.
+ */
+ public static String addQuotes(Object object) {
+ return CommonUtils.addQuotes(object);
+ }
+
+ /**
+ * Checks if the file contains the regex, based on its extension.
+ * @param extension The extension.
+ * @param regex The regex.
+ * @return A boolean value.
+ * @throws AraraException Something wrong happened, to be caught in the
+ * higher levels.
+ */
+ public static boolean found(String extension, String regex)
+ throws AraraException {
+ return CommonUtils.checkRegex(extension, regex);
+ }
+
+ /**
+ * Checks if the file contains the provided regex.
+ * @param file The file.
+ * @param regex The regex.
+ * @return A boolean value.
+ * @throws AraraException Something wrong happened, to be caught in the
+ * higher levels.
+ */
+ public static boolean found(File file, String regex)
+ throws AraraException {
+ return CommonUtils.checkRegex(file, regex);
+ }
+
+ /**
+ * Gets the command based on a list of strings.
+ * @param elements The list of strings.
+ * @return A command.
+ */
+ public static Command getCommand(List<String> elements) {
+ return new Command(elements);
+ }
+
+ /**
+ * Gets the command based on an array of objects.
+ * @param elements Array of objects.
+ * @return A command.
+ */
+ public static Command getCommand(Object... elements) {
+ return new Command(elements);
+ }
+
+ /**
+ * Gets the command based on an array of objects and with the provided
+ * working directory as string.
+ * @param path String path representing the working directory.
+ * @param elements Array of elements.
+ * @return A command.
+ */
+ public static Command getCommandWithWorkingDirectory(String path,
+ Object... elements) {
+ Command command = new Command(elements);
+ command.setWorkingDirectory(new File(path));
+ return command;
+ }
+
+ /**
+ * Gets the command based on an array of objects and with the provided
+ * working directory as file.
+ * @param file File representing the working directory.
+ * @param elements Array of elements.
+ * @return A command.
+ */
+ public static Command getCommandWithWorkingDirectory(File file,
+ Object... elements) {
+ Command command = new Command(elements);
+ command.setWorkingDirectory(file);
+ return command;
+ }
+
+ /**
+ * Gets the command based on a list of strings and with the provided
+ * working directory as string.
+ * @param path String path representing the working directory.
+ * @param elements List of strings.
+ * @return A command.
+ */
+ public static Command getCommandWithWorkingDirectory(String path,
+ List<String> elements) {
+ Command command = new Command(elements);
+ command.setWorkingDirectory(new File(path));
+ return command;
+ }
+
+ /**
+ * Gets the command based on a list of strings and with the provided
+ * working directory as file.
+ * @param file File representing the working directory.
+ * @param elements List of strings.
+ * @return A command.
+ */
+ public static Command getCommandWithWorkingDirectory(File file,
+ List<String> elements) {
+ Command command = new Command(elements);
+ command.setWorkingDirectory(file);
+ return command;
+ }
+
+ /**
+ * Gets the trigger.
+ * @param action The action name.
+ * @return The trigger.
+ */
+ public static Trigger getTrigger(String action) {
+ return new Trigger(action, null);
+ }
+
+ /**
+ * Gets the trigger.
+ * @param action The action name.
+ * @param parameters The trigger parameters.
+ * @return A trigger.
+ */
+ public static Trigger getTrigger(String action, Object... parameters) {
+ return new Trigger(action, Arrays.asList(parameters));
+ }
+
+ /**
+ * Checks if the object is an intance of the provided class.
+ * @param clazz The class.
+ * @param object The object.
+ * @return A boolean value.
+ */
+ public static boolean checkClass(Class clazz, Object object) {
+ return CommonUtils.checkClass(clazz, object);
+ }
+
+ /**
+ * Checks if the object is a string.
+ * @param object The object.
+ * @return A boolean value.
+ */
+ public static boolean isString(Object object) {
+ return checkClass(String.class, object);
+ }
+
+ /**
+ * Checks if the object is a list.
+ * @param object The object.
+ * @return A boolean value.
+ */
+ public static boolean isList(Object object) {
+ return checkClass(List.class, object);
+ }
+
+ /**
+ * Checks if the object is a map.
+ * @param object The object.
+ * @return A boolean value.
+ */
+ public static boolean isMap(Object object) {
+ return checkClass(Map.class, object);
+ }
+
+ /**
+ * Checks if the object is a boolean.
+ * @param object The object.
+ * @return A boolean value.
+ */
+ public static boolean isBoolean(Object object) {
+ return checkClass(Boolean.class, object);
+ }
+
+ /**
+ * Checks if the execution is in verbose mode.
+ * @return A boolean value indicating if the execution is in verbose mode.
+ */
+ public static boolean isVerboseMode() {
+ return (Boolean) ConfigurationController.
+ getInstance().get("execution.verbose");
+ }
+
+ /**
+ * Returns a file object based on the provided name.
+ * @param name The file name.
+ * @return A file object.
+ */
+ public static File toFile(String name) {
+ return new File(name);
+ }
+
+ /**
+ * Shows the message.
+ * @param width Integer value, in pixels.
+ * @param type Type of message.
+ * @param title Title of the message.
+ * @param text Text of the message.
+ */
+ public static void showMessage(int width, int type,
+ String title, String text) {
+ MessageUtils.showMessage(width, type, title, text);
+ }
+
+ /**
+ * Shows the message. It relies on the default width.
+ * @param type Type of message.
+ * @param title Title of the message.
+ * @param text Text of the message.
+ */
+ public static void showMessage(int type, String title, String text) {
+ MessageUtils.showMessage(type, title, text);
+ }
+
+ /**
+ * Shows a message with options presented as an array of buttons.
+ * @param width Integer value, in pixels.
+ * @param type Type of message.
+ * @param title Title of the message.
+ * @param text Text of the message.
+ * @param buttons An array of objects to be presented as buttons.
+ * @return The index of the selected button, starting from 1.
+ */
+ public static int showOptions(int width, int type, String title,
+ String text, Object... buttons) {
+ return MessageUtils.showOptions(width, type, title, text, buttons);
+ }
+
+ /**
+ * Shows a message with options presented as an array of buttons. It relies
+ * on the default width.
+ * @param type Type of message.
+ * @param title Title of the message.
+ * @param text Text of the message.
+ * @param buttons An array of objects to be presented as buttons.
+ * @return The index of the selected button, starting from 1.
+ */
+ public static int showOptions(int type, String title,
+ String text, Object... buttons) {
+ return MessageUtils.showOptions(type, title, text, buttons);
+ }
+
+ /**
+ * Shows a message with a text input.
+ * @param width Integer value, in pixels.
+ * @param type Type of message.
+ * @param title Title of the message.
+ * @param text Text of the message.
+ * @return The string representing the input text.
+ */
+ public static String showInput(int width, int type,
+ String title, String text) {
+ return MessageUtils.showInput(width, type, title, text);
+ }
+
+ /**
+ * Shows a message with a text input. It relies on the default width.
+ * @param type Type of message.
+ * @param title Title of the message.
+ * @param text Text of the message.
+ * @return The string representing the input text.
+ */
+ public static String showInput(int type, String title, String text) {
+ return MessageUtils.showInput(type, title, text);
+ }
+
+ /**
+ * Shows a message with options presented as a dropdown list of elements.
+ * @param width Integer value, in pixels.
+ * @param type Type of message.
+ * @param title Title of the message.
+ * @param text Text of the message.
+ * @param elements An array of objects representing the elements.
+ * @return The index of the selected element, starting from 1.
+ */
+ public static int showDropdown(int width, int type, String title,
+ String text, Object... elements) {
+ return MessageUtils.showDropdown(width, type, title, text, elements);
+ }
+
+ /**
+ * Shows a message with options presented as a dropdown list of elements. It
+ * relies on the default width.
+ * @param type Type of message.
+ * @param title Title of the message.
+ * @param text Text of the message.
+ * @param elements An array of objects representing the elements.
+ * @return The index of the selected element, starting from 1.
+ */
+ public static int showDropdown(int type, String title,
+ String text, Object... elements) {
+ return MessageUtils.showDropdown(type, title, text, elements);
+ }
+
+ /**
+ * Checks if the provided command name is reachable from the system path.
+ * @param command A string representing the command.
+ * @return A logic value.
+ */
+ public static boolean isOnPath(String command) {
+ return CommonUtils.isOnPath(command);
+ }
+
+ /**
+ * Gets the full basename.
+ * @param file The file.
+ * @return The full basename of the provided file.
+ * @throws AraraException Something wrong happened, to be caught in the
+ * higher levels.
+ */
+ public static String getFullBasename(File file) throws AraraException {
+ if (file.isFile()) {
+ return CommonUtils.getFullBasename(file);
+ } else {
+ throw new AraraException(
+ CommonUtils.getRuleErrorHeader().concat(
+ messages.getMessage(
+ Messages.ERROR_BASENAME_NOT_A_FILE,
+ file.getName()
+ )
+ )
+ );
+ }
+ }
+
+ /**
+ * Gets the full basename.
+ * @param name The string.
+ * @return The full basename.
+ * @throws AraraException Something wrong happened, to be caught in the
+ * higher levels.
+ */
+ public static String getFullBasename(String name) throws AraraException {
+ return getFullBasename(new File(name));
+ }
+
+ /**
+ * Unsafely executes a system command from the underlying operating system
+ * and returns a pair containing the exit status and the command output as a
+ * string.
+ * @param command The system command to be executed.
+ * @return A pair containing the exit status and the system command output
+ * as a string.
+ */
+ public static Pair<Integer, String>
+ unsafelyExecuteSystemCommand(Command command) {
+ return UnsafeUtils.executeSystemCommand(command);
+ }
+
+ /**
+ * Merges the provided template with a context map and writes the result in
+ * an output file. This method relies on Apache Velocity.
+ * @param input The input file.
+ * @param output The output file.
+ * @param map The context map.
+ * @throws AraraException Something wrong happened, to be caught in the
+ * higher levels.
+ */
+ public static void mergeVelocityTemplate(File input, File output,
+ Map<String, Object> map) throws AraraException {
+ VelocityUtils.mergeVelocityTemplate(input, output, map);
+ }
+
+ /**
+ * Gets the file reference for the current directive. It is important to
+ * observe that version 4.0 of arara replicates the directive when 'files'
+ * is detected amongst the parameters, so each instance will have a
+ * different reference.
+ * @return A file reference for the current directive.
+ */
+ public static File currentFile() {
+ return (File) ConfigurationController.getInstance().
+ get("execution.directive.reference");
+ }
+
+ /**
+ * Loads a class from the provided file, potentially a Java archive.
+ * @param file File containing the Java bytecode (namely, a JAR).
+ * @param name The canonical name of the class.
+ * @return A pair representing the status and the class.
+ */
+ public static Pair<Integer, Class> loadClass(File file, String name) {
+ return ClassLoadingUtils.loadClass(file, name);
+ }
+
+ /**
+ * Loads a class from the provided string reference, representing a file.
+ * @param ref String reference representing a file.
+ * @param name The canonical name of the class.
+ * @return A pair representing the status and the class.
+ */
+ public static Pair<Integer, Class> loadClass(String ref, String name) {
+ return ClassLoadingUtils.loadClass(new File(ref), name);
+ }
+
+ /**
+ * Loads a class from the provided file, instantiating it.
+ * @param file File containing the Java bytecode (namely, a JAR).
+ * @param name The canonical name of the class.
+ * @return A pair representing the status and the class object.
+ */
+ public static Pair<Integer, Object> loadObject(File file, String name) {
+ return ClassLoadingUtils.loadObject(file, name);
+ }
+
+ /**
+ * Loads a class from the provided string reference, instantiating it.
+ * @param ref String reference representing a file.
+ * @param name The canonical name of the class.
+ * @return A pair representing the status and the class object.
+ */
+ public static Pair<Integer, Object> loadObject(String ref, String name) {
+ return ClassLoadingUtils.loadObject(new File(ref), name);
+ }
+
+ /**
+ * List all files from the provided directory according to the list of
+ * extensions. The leading dot must be omitted, unless it is part of the
+ * extension.
+ * @param directory The provided directory.
+ * @param extensions The list of extensions.
+ * @param recursive A flag indicating whether the search is recursive.
+ * @return A list of files.
+ */
+ public static List<File> listFilesByExtensions(File directory,
+ List<String> extensions, boolean recursive) {
+ return FileSearchingUtils.listFilesByExtensions(
+ directory,
+ extensions,
+ recursive
+ );
+ }
+
+ /**
+ * List all files from the provided string path according to the list of
+ * extensions. The leading dot must be omitted, unless it is part of the
+ * extension.
+ * @param path The provided path as plain string.
+ * @param extensions The list of extensions.
+ * @param recursive A flag indicating whether the search is recursive.
+ * @return A list of files.
+ */
+ public static List<File> listFilesByExtensions(String path,
+ List<String> extensions, boolean recursive) {
+ return FileSearchingUtils.listFilesByExtensions(
+ new File(path),
+ extensions,
+ recursive
+ );
+ }
+
+ /**
+ * List all files from the provided directory matching the list of file
+ * name patterns. Such list can contain wildcards.
+ * @param directory The provided directory.
+ * @param patterns The list of file name patterns.
+ * @param recursive A flag indicating whether the search is recursive.
+ * @return A list of files.
+ */
+ public static List<File> listFilesByPatterns(File directory,
+ List<String> patterns, boolean recursive) {
+ return FileSearchingUtils.listFilesByPatterns(
+ directory,
+ patterns,
+ recursive
+ );
+ }
+
+ /**
+ * List all files from the provided path matching the list of file
+ * name patterns. Such list can contain wildcards.
+ * @param path The provided path as plain string.
+ * @param patterns The list of file name patterns.
+ * @param recursive A flag indicating whether the search is recursive.
+ * @return A list of files.
+ */
+ public static List<File> listFilesByPatterns(String path,
+ List<String> patterns, boolean recursive) {
+ return FileSearchingUtils.listFilesByPatterns(
+ new File(path),
+ patterns,
+ recursive
+ );
+ }
+
+ /**
+ * Writes the string to a file, using UTF-8 as default encoding.
+ * @param file The file.
+ * @param text The string to be written.
+ * @param append A flag whether to append the content.
+ * @return A logical value indicating whether it was successful.
+ */
+ public static boolean writeToFile(File file, String text, boolean append) {
+ return FileHandlingUtils.writeToFile(file, text, append);
+ }
+
+ /**
+ * Writes the string to a file, using UTF-8 as default encoding.
+ * @param path The path.
+ * @param text The string to be written.
+ * @param append A flag whether to append the content.
+ * @return A logical value indicating whether it was successful.
+ */
+ public static boolean writeToFile(String path, String text,
+ boolean append) {
+ return FileHandlingUtils.writeToFile(new File(path), text, append);
+ }
+
+ /**
+ * Writes the string list to a file, using UTF-8 as default encoding.
+ * @param file The file.
+ * @param lines The string list to be written.
+ * @param append A flag whether to append the content.
+ * @return A logical value indicating whether it was successful.
+ */
+ public static boolean writeToFile(File file, List<String> lines,
+ boolean append) {
+ return FileHandlingUtils.writeToFile(file, lines, append);
+ }
+
+ /**
+ * Writes the string list to a file, using UTF-8 as default encoding.
+ * @param path The path.
+ * @param lines The string list to be written.
+ * @param append A flag whether to append the content.
+ * @return A logical value indicating whether it was successful.
+ */
+ public static boolean writeToFile(String path, List<String> lines,
+ boolean append) {
+ return FileHandlingUtils.writeToFile(new File(path), lines, append);
+ }
+
+ /**
+ * Reads the provided file into a list of strings.
+ * @param file The file.
+ * @return A list of strings.
+ */
+ public static List<String> readFromFile(File file) {
+ return FileHandlingUtils.readFromFile(file);
+ }
+
+ /**
+ * Reads the provided file into a list of strings.
+ * @param path The path.
+ * @return A list of strings.
+ */
+ public static List<String> readFromFile(String path) {
+ return FileHandlingUtils.readFromFile(new File(path));
+ }
+
+}
diff --git a/support/arara/source/src/main/java/com/github/cereda/arara/utils/RuleUtils.java b/support/arara/source/src/main/java/com/github/cereda/arara/utils/RuleUtils.java
new file mode 100644
index 0000000000..d855344762
--- /dev/null
+++ b/support/arara/source/src/main/java/com/github/cereda/arara/utils/RuleUtils.java
@@ -0,0 +1,244 @@
+/**
+ * Arara, the cool TeX automation tool
+ * Copyright (c) 2012 -- 2018, Paulo Roberto Massa Cereda
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * 3. Neither the name of the project's author nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+ * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
+ * COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+ * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY
+ * WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+package com.github.cereda.arara.utils;
+
+import com.github.cereda.arara.controller.LanguageController;
+import com.github.cereda.arara.model.AraraException;
+import com.github.cereda.arara.model.Argument;
+import com.github.cereda.arara.model.Messages;
+import com.github.cereda.arara.model.RuleCommand;
+import com.github.cereda.arara.model.Rule;
+import java.io.File;
+import java.io.FileReader;
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.List;
+import org.apache.commons.collections4.CollectionUtils;
+import org.apache.commons.collections4.Predicate;
+import org.yaml.snakeyaml.Yaml;
+import org.yaml.snakeyaml.constructor.Constructor;
+import org.yaml.snakeyaml.error.MarkedYAMLException;
+import org.yaml.snakeyaml.nodes.Tag;
+import org.yaml.snakeyaml.representer.Representer;
+
+/**
+ * Implements rule utilitary methods.
+ * @author Paulo Roberto Massa Cereda
+ * @version 4.0
+ * @since 4.0
+ */
+public class RuleUtils {
+
+ // the application messages obtained from the
+ // language controller
+ private static final LanguageController messages =
+ LanguageController.getInstance();
+
+ /**
+ * Parses the provided file, checks the identifier and returns a rule
+ * representation.
+ * @param file The rule file.
+ * @param identifier The directive identifier.
+ * @return The rule object.
+ * @throws AraraException Something wrong happened, to be caught in the
+ * higher levels.
+ */
+ public static Rule parseRule(File file, String identifier)
+ throws AraraException {
+ Representer representer = new Representer();
+ representer.addClassTag(Rule.class, new Tag("!config"));
+ Yaml yaml = new Yaml(new Constructor(Rule.class), representer);
+ Rule rule = null;
+ try {
+ rule = yaml.loadAs(new FileReader(file), Rule.class);
+ } catch (MarkedYAMLException yamlException) {
+ throw new AraraException(
+ CommonUtils.getRuleErrorHeader().concat(
+ messages.getMessage(
+ Messages.ERROR_PARSERULE_INVALID_YAML
+ )
+ ),
+ yamlException
+ );
+ } catch (Exception exception) {
+ throw new AraraException(
+ CommonUtils.getRuleErrorHeader().concat(
+ messages.getMessage(
+ Messages.ERROR_PARSERULE_GENERIC_ERROR
+ )
+ )
+ );
+ }
+ validateHeader(rule, identifier);
+ validateBody(rule);
+ return rule;
+ }
+
+ /**
+ * Validates the rule header according to the directive identifier.
+ * @param rule The rule object.
+ * @param identifier The directive identifier.
+ * @throws AraraException Something wrong happened, to be caught in the
+ * higher levels.
+ */
+ private static void validateHeader(Rule rule, String identifier)
+ throws AraraException {
+ if (rule.getIdentifier() != null) {
+ if (!rule.getIdentifier().equals(identifier)) {
+ throw new AraraException(CommonUtils.getRuleErrorHeader().
+ concat(
+ messages.getMessage(
+ Messages.ERROR_VALIDATEHEADER_WRONG_IDENTIFIER,
+ rule.getIdentifier(),
+ identifier
+ )
+ )
+ );
+ }
+ } else {
+ throw new AraraException(
+ CommonUtils.getRuleErrorHeader().concat(
+ messages.getMessage(
+ Messages.ERROR_VALIDATEHEADER_NULL_ID
+ )
+ )
+ );
+ }
+ if (rule.getName() == null) {
+ throw new AraraException(
+ CommonUtils.getRuleErrorHeader().concat(
+ messages.getMessage(
+ Messages.ERROR_VALIDATEHEADER_NULL_NAME
+ )
+ )
+ );
+ }
+ }
+
+ /**
+ * Validates the rule body.
+ * @param rule The rule object.
+ * @throws AraraException Something wrong happened, to be caught in the
+ * higher levels.
+ */
+ private static void validateBody(Rule rule) throws AraraException {
+ if (rule.getCommands() == null) {
+ throw new AraraException(
+ CommonUtils.getRuleErrorHeader().concat(
+ messages.getMessage(
+ Messages.ERROR_VALIDATEBODY_NULL_COMMANDS_LIST
+ )
+ )
+ );
+ } else {
+ if (CollectionUtils.exists(rule.getCommands(),
+ new Predicate<RuleCommand>() {
+ public boolean evaluate(RuleCommand command) {
+ return (command.getCommand() == null);
+ }
+ })) {
+ throw new AraraException(CommonUtils.getRuleErrorHeader().
+ concat(
+ messages.getMessage(
+ Messages.ERROR_VALIDATEBODY_NULL_COMMAND
+ )
+ )
+ );
+ }
+ }
+ if (rule.getArguments() == null) {
+ throw new AraraException(
+ CommonUtils.getRuleErrorHeader().concat(
+ messages.getMessage(
+ Messages.ERROR_VALIDATEBODY_ARGUMENTS_LIST
+ )
+ )
+ );
+ } else {
+ String[] keywords = new String[]{"file", "files", "reference"};
+
+ List<String> arguments = new ArrayList<String>();
+ for (Argument argument : rule.getArguments()) {
+ if (argument.getIdentifier() != null) {
+ if ((argument.getFlag() != null) ||
+ (argument.getDefault() != null)) {
+ arguments.add(argument.getIdentifier());
+ } else {
+ throw new AraraException(
+ CommonUtils.getRuleErrorHeader().concat(
+ messages.getMessage(
+ Messages.ERROR_VALIDATEBODY_MISSING_KEYS
+ )
+ )
+ );
+ }
+ } else {
+ throw new AraraException(
+ CommonUtils.getRuleErrorHeader().concat(
+ messages.getMessage(
+ Messages.ERROR_VALIDATEBODY_NULL_ARGUMENT_ID
+ )
+ )
+ );
+ }
+ }
+
+ for (String keyword : keywords) {
+ if (arguments.contains(keyword)) {
+ throw new AraraException(
+ CommonUtils.getRuleErrorHeader().concat(
+ messages.getMessage(
+ Messages.ERROR_VALIDATEBODY_ARGUMENT_ID_IS_RESERVED,
+ keyword
+ )
+ )
+ );
+ }
+ }
+
+ int expected = arguments.size();
+ int found = (new HashSet<String>(arguments)).size();
+ if (expected != found) {
+ throw new AraraException(
+ CommonUtils.getRuleErrorHeader().concat(
+ messages.getMessage(
+ Messages.ERROR_VALIDATEBODY_DUPLICATE_ARGUMENT_IDENTIFIERS
+ )
+ )
+ );
+ }
+ }
+ }
+
+}
diff --git a/support/arara/source/src/main/java/com/github/cereda/arara/utils/TeeOutputStream.java b/support/arara/source/src/main/java/com/github/cereda/arara/utils/TeeOutputStream.java
new file mode 100644
index 0000000000..65d3d58ffb
--- /dev/null
+++ b/support/arara/source/src/main/java/com/github/cereda/arara/utils/TeeOutputStream.java
@@ -0,0 +1,109 @@
+/**
+ * Arara, the cool TeX automation tool
+ * Copyright (c) 2012 -- 2018, Paulo Roberto Massa Cereda
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * 3. Neither the name of the project's author nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+ * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
+ * COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+ * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY
+ * WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+package com.github.cereda.arara.utils;
+
+import java.io.IOException;
+import java.io.OutputStream;
+import org.apache.commons.io.IOUtils;
+
+/**
+ * Implements a stream splitter.
+ * @author Paulo Roberto Massa Cereda
+ * @version 4.0
+ * @since 4.0
+ */
+public class TeeOutputStream extends OutputStream {
+
+ // an array of streams in which
+ // an object of this class will
+ // split data
+ private final OutputStream[] streams;
+
+ /**
+ * Constructor.
+ * @param outputStreams An array of output streams.
+ */
+ public TeeOutputStream(OutputStream... outputStreams) {
+ streams = outputStreams;
+ }
+
+ /**
+ * Writes the provided integer to each stream.
+ * @param b The provided integer
+ * @throws IOException An IO exception.
+ */
+ @Override
+ public void write(int b) throws IOException {
+ for (OutputStream ostream : streams) {
+ ostream.write(b);
+ }
+ }
+
+ /**
+ * Writes the provided byte array to each stream, with the provided offset
+ * and length.
+ * @param b The byte array.
+ * @param offset The offset.
+ * @param length The length.
+ * @throws IOException An IO exception.
+ */
+ @Override
+ public void write(byte[] b, int offset, int length) throws IOException {
+ for (OutputStream ostream : streams) {
+ ostream.write(b, offset, length);
+ }
+ }
+
+ /**
+ * Flushes every stream.
+ * @throws IOException An IO exception.
+ */
+ @Override
+ public void flush() throws IOException {
+ for (OutputStream ostream : streams) {
+ ostream.flush();
+ }
+ }
+
+ /**
+ * Closes every stream silently.
+ */
+ @Override
+ public void close() {
+ for (OutputStream ostream : streams) {
+ IOUtils.closeQuietly(ostream);
+ }
+ }
+
+}
diff --git a/support/arara/source/src/main/java/com/github/cereda/arara/utils/UnsafeUtils.java b/support/arara/source/src/main/java/com/github/cereda/arara/utils/UnsafeUtils.java
new file mode 100644
index 0000000000..b989e074c8
--- /dev/null
+++ b/support/arara/source/src/main/java/com/github/cereda/arara/utils/UnsafeUtils.java
@@ -0,0 +1,87 @@
+/**
+ * Arara, the cool TeX automation tool
+ * Copyright (c) 2012 -- 2018, Paulo Roberto Massa Cereda
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * 3. Neither the name of the project's author nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+ * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
+ * COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+ * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY
+ * WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+package com.github.cereda.arara.utils;
+
+import com.github.cereda.arara.model.Command;
+import com.github.cereda.arara.model.Pair;
+import org.zeroturnaround.exec.ProcessExecutor;
+import org.zeroturnaround.exec.ProcessResult;
+
+/**
+ * Implements unsafe utilitary methods.
+ * @author Paulo Roberto Massa Cereda
+ * @version 4.0
+ * @since 4.0
+ */
+public class UnsafeUtils {
+
+ /**
+ * Executes a system command from the underlying operating system and
+ * returns a pair containing the exit status and the command output as a
+ * string.
+ * @param command The system command to be executed.
+ * @return A pair containing the exit status and the system command output
+ * as a string.
+ */
+ public static Pair<Integer, String> executeSystemCommand(Command command) {
+
+ try {
+
+ // create a process result with the provided
+ // command, capturing the output
+ ProcessResult result = new ProcessExecutor(
+ command.getElements()
+ ).readOutput(true).execute();
+
+ // return the pair containing the exit status
+ // and the output string as UTF-8
+ return new Pair<Integer, String>(
+ result.getExitValue(),
+ result.outputUTF8()
+ );
+
+ } catch (Exception exception) {
+
+ // quack, quack, do nothing, just
+ // return a default error code
+
+ // if something goes wrong, the default
+ // error branch returns an exit status of
+ // -99 and an empty string
+ return new Pair<Integer, String>(-99, "");
+
+ }
+ }
+
+}
diff --git a/support/arara/source/src/main/java/com/github/cereda/arara/utils/VelocityUtils.java b/support/arara/source/src/main/java/com/github/cereda/arara/utils/VelocityUtils.java
new file mode 100644
index 0000000000..2040b671b7
--- /dev/null
+++ b/support/arara/source/src/main/java/com/github/cereda/arara/utils/VelocityUtils.java
@@ -0,0 +1,157 @@
+/**
+ * Arara, the cool TeX automation tool
+ * Copyright (c) 2012 -- 2018, Paulo Roberto Massa Cereda
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * 3. Neither the name of the project's author nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+ * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
+ * COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+ * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY
+ * WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+package com.github.cereda.arara.utils;
+
+import com.github.cereda.arara.controller.LanguageController;
+import com.github.cereda.arara.model.AraraException;
+import com.github.cereda.arara.model.Messages;
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.OutputStreamWriter;
+import java.io.Writer;
+import java.util.Map;
+import org.apache.velocity.Template;
+import org.apache.velocity.VelocityContext;
+import org.apache.velocity.app.VelocityEngine;
+import org.apache.velocity.exception.MethodInvocationException;
+import org.apache.velocity.exception.ParseErrorException;
+import org.apache.velocity.exception.ResourceNotFoundException;
+import org.apache.velocity.runtime.RuntimeConstants;
+
+/**
+ * Implements the template merging from Apache Velocity.
+ * @author Paulo Roberto Massa Cereda
+ * @version 4.0
+ * @since 4.0
+ */
+public class VelocityUtils {
+
+ // the language controller
+ private static final LanguageController messages =
+ LanguageController.getInstance();
+
+ /**
+ * Merges the provided template with the context map and writes the result
+ * in an output file. The operation relies on the Apache Velocity project.
+ * @param input The input file.
+ * @param output The output file.
+ * @param map The context map.
+ * @throws AraraException Something terribly wrong happened, to be caught
+ * in the higher levels.
+ */
+ public static void mergeVelocityTemplate(File input, File output,
+ Map<String, Object> map) throws AraraException {
+
+ // let us try
+ try {
+
+ // create the template engine instance
+ VelocityEngine engine = new VelocityEngine();
+
+ // use the resource path trick: set the default
+ // location to the input file's parent directory,
+ // so our file is easily located
+ engine.setProperty(RuntimeConstants.FILE_RESOURCE_LOADER_PATH,
+ input.getCanonicalFile().getParent());
+
+ // set the logging feature of Apache Velocity to
+ // register the occurrences in a null provider
+ // (we do not want unnecessary verbose output)
+ engine.setProperty(RuntimeConstants.RUNTIME_LOG_LOGSYSTEM_CLASS,
+ "org.apache.velocity.runtime.log.NullLogSystem");
+
+ // init the engine with the
+ // provided settings
+ engine.init();
+
+ // create a context for Apache Velocity,
+ // based on the provided map
+ VelocityContext context = new VelocityContext(map);
+
+ // get the template from the engine and
+ // read it as an UTF-8 file
+ Template template = engine.getTemplate(input.getName(), "UTF-8");
+
+ // create an output stream from
+ // the file output reference
+ FileOutputStream stream = new FileOutputStream(output);
+
+ // create a writer based on the
+ // previously created stream
+ Writer writer = new OutputStreamWriter(stream, "UTF-8");
+
+ // merge the context map with the
+ // template file and write the result
+ // to the output stream writer
+ template.merge(context, writer);
+
+ // close both writer
+ // and output stream
+ writer.close();
+ stream.close();
+
+ } catch(ResourceNotFoundException rnfexception) {
+ throw new AraraException(
+ messages.getMessage(
+ Messages.ERROR_VELOCITY_FILE_NOT_FOUND
+ ),
+ rnfexception
+ );
+ } catch(ParseErrorException peexception) {
+ throw new AraraException(
+ messages.getMessage(
+ Messages.ERROR_VELOCITY_PARSE_EXCEPTION
+ ),
+ peexception
+ );
+ } catch(MethodInvocationException miexception) {
+ throw new AraraException(
+ messages.getMessage(
+ Messages.ERROR_VELOCITY_METHOD_INVOCATION_EXCEPTION
+ ),
+ miexception
+ );
+ } catch (IOException ioexception) {
+ throw new AraraException(
+ messages.getMessage(
+ Messages.ERROR_VELOCITY_FILE_NOT_FOUND
+ ),
+ ioexception
+ );
+ }
+
+ }
+
+}