You are viewing a plain text version of this content. The canonical link for it is here.
Posted to notifications@freemarker.apache.org by sg...@apache.org on 2020/07/01 05:49:27 UTC

[freemarker-generator] 01/02: FREEMARKER-149 Support multiple template transformation on the command line

This is an automated email from the ASF dual-hosted git repository.

sgoeschl pushed a commit to branch FREEMARKER-149
in repository https://gitbox.apache.org/repos/asf/freemarker-generator.git

commit bc57b85bc0654186137fb9525c1925c504db901f
Author: Siegfried Goeschl <si...@gmail.com>
AuthorDate: Tue Jun 30 23:40:23 2020 +0200

    FREEMARKER-149 Support multiple template transformation on the command line
---
 .../template/TemplateTransformationsBuilder.java   | 28 +++++++++++++---
 .../freemarker/generator/base/util/ListUtils.java  |  4 +++
 freemarker-generator-cli/CHANGELOG.md              |  6 ++--
 .../org/apache/freemarker/generator/cli/Main.java  | 38 ++++++++++++++--------
 .../freemarker/generator/cli/config/Settings.java  | 30 +++++++++--------
 .../freemarker/generator/cli/config/Suppliers.java |  2 +-
 .../site/markdown/cli/concepts/template-loading.md |  2 +-
 .../site/markdown/cli/concepts/transformation.md   | 35 +++++++++++++++++++-
 .../freemarker/generator/cli/ExamplesTest.java     | 17 ++++++++--
 .../freemarker/generator/cli/ManualTest.java       |  3 +-
 .../freemarker/generator/cli/PicocliTest.java      |  8 +++++
 .../generator/cli/config/SettingsTest.java         |  5 +--
 12 files changed, 135 insertions(+), 43 deletions(-)

diff --git a/freemarker-generator-base/src/main/java/org/apache/freemarker/generator/base/template/TemplateTransformationsBuilder.java b/freemarker-generator-base/src/main/java/org/apache/freemarker/generator/base/template/TemplateTransformationsBuilder.java
index fe62017..75bb47b 100644
--- a/freemarker-generator-base/src/main/java/org/apache/freemarker/generator/base/template/TemplateTransformationsBuilder.java
+++ b/freemarker-generator-base/src/main/java/org/apache/freemarker/generator/base/template/TemplateTransformationsBuilder.java
@@ -28,6 +28,7 @@ import java.io.Writer;
 import java.util.ArrayList;
 import java.util.Collection;
 import java.util.List;
+import java.util.Optional;
 
 import static java.nio.charset.StandardCharsets.UTF_8;
 import static java.util.Collections.singletonList;
@@ -50,7 +51,7 @@ public class TemplateTransformationsBuilder {
     private final List<String> excludes;
 
     /** Optional output file or directory */
-    private final List<File> outputs;
+    private final List<String> outputs;
 
     /** Optional user-supplied writer */
     private Writer writer;
@@ -73,12 +74,12 @@ public class TemplateTransformationsBuilder {
         final List<TemplateTransformation> result = new ArrayList<>();
 
         if (hasInteractiveTemplate()) {
-            final File outputFile = outputs.isEmpty() ? null : outputs.get(0);
+            final File outputFile = getOutputFile(0).orElse(null);
             result.add(resolveInteractiveTemplate(outputFile));
         } else {
             for (int i = 0; i < sources.size(); i++) {
                 final String source = sources.get(i);
-                final File output = i < outputs.size() ? outputs.get(i) : null;
+                final File output = getOutputFile(i).orElse(null);
                 result.addAll(resolve(source, output));
             }
         }
@@ -133,16 +134,23 @@ public class TemplateTransformationsBuilder {
         return this;
     }
 
+    public TemplateTransformationsBuilder addOutputs(List<String> outputs) {
+        if (outputs != null && !outputs.isEmpty()) {
+            this.outputs.addAll(outputs);
+        }
+        return this;
+    }
+
     public TemplateTransformationsBuilder addOutput(String output) {
         if (StringUtils.isNotEmpty(output)) {
-            this.outputs.add(new File(output));
+            this.outputs.add(output);
         }
         return this;
     }
 
     public TemplateTransformationsBuilder addOutput(File output) {
         if (output != null) {
-            this.outputs.add(output);
+            this.outputs.add(output.getAbsolutePath());
         }
         return this;
     }
@@ -252,6 +260,16 @@ public class TemplateTransformationsBuilder {
         return template != null;
     }
 
+    private Optional<File> getOutputFile(int i) {
+        if (outputs.isEmpty()) {
+            return Optional.empty();
+        } else if (i < outputs.size()) {
+            return Optional.of(new File(outputs.get(i)));
+        } else {
+            return Optional.of(new File(outputs.get(0)));
+        }
+    }
+
     private static File getTemplateOutputFile(File templateDirectory, File templateFile, File outputDirectory) {
         final String relativePath = relativePath(templateDirectory, templateFile);
         final String relativeOutputFileName = mapExtension(relativePath);
diff --git a/freemarker-generator-base/src/main/java/org/apache/freemarker/generator/base/util/ListUtils.java b/freemarker-generator-base/src/main/java/org/apache/freemarker/generator/base/util/ListUtils.java
index 369bf90..613b3e1 100644
--- a/freemarker-generator-base/src/main/java/org/apache/freemarker/generator/base/util/ListUtils.java
+++ b/freemarker-generator-base/src/main/java/org/apache/freemarker/generator/base/util/ListUtils.java
@@ -26,6 +26,10 @@ public class ListUtils {
         return list == null || list.isEmpty();
     }
 
+    public static <T> boolean isNotEmpty(final List<T> list) {
+        return !isNullOrEmpty(list);
+    }
+
     /**
      * Transposes the given tabular data, swapping rows with columns.
      *
diff --git a/freemarker-generator-cli/CHANGELOG.md b/freemarker-generator-cli/CHANGELOG.md
index 32ab747..c73e622 100644
--- a/freemarker-generator-cli/CHANGELOG.md
+++ b/freemarker-generator-cli/CHANGELOG.md
@@ -5,6 +5,7 @@ All notable changes to this project will be documented in this file. We try to a
 ## 0.1.0-SNAPSHOT
 
 ### Added
+* [FREEMARKER-149] Support multiple template transformations on the command line
 * [FREEMARKER-144] Proof Of Concept for providing DataFrames
 * [FREEMARKER-142] Support Transformation Of Directories
 * [FREEMARKER-139] freemarker-cli: Provide GsonTool to align with Maven plugin
@@ -25,7 +26,7 @@ All notable changes to this project will be documented in this file. We try to a
 * [FREEMARKER-129] Use version "0.X.Y" to cater for API changes according to [Semantic Versioning](https://semver.org)
 
 ### Fixed 
-* [FREEMARKER-147] Complete Maven site documenation
+* [FREEMARKER-147] Complete Maven site documentation
 * [FREEMARKER-127] Site build fails with missing "org/apache/maven/doxia/siterenderer/DocumentContent"
 
 [FREEMARKER-127]: https://issues.apache.org/jira/browse/FREEMARKER-127
@@ -39,4 +40,5 @@ All notable changes to this project will be documented in this file. We try to a
 [FREEMARKER-142]: https://issues.apache.org/jira/browse/FREEMARKER-142
 [FREEMARKER-144]: https://issues.apache.org/jira/browse/FREEMARKER-144
 [FREEMARKER-146]: https://issues.apache.org/jira/browse/FREEMARKER-146
-[FREEMARKER-147]: https://issues.apache.org/jira/browse/FREEMARKER-147
\ No newline at end of file
+[FREEMARKER-147]: https://issues.apache.org/jira/browse/FREEMARKER-147
+[FREEMARKER-149]: https://issues.apache.org/jira/browse/FREEMARKER-149
\ No newline at end of file
diff --git a/freemarker-generator-cli/src/main/java/org/apache/freemarker/generator/cli/Main.java b/freemarker-generator-cli/src/main/java/org/apache/freemarker/generator/cli/Main.java
index 7e86f85..f9d135a 100644
--- a/freemarker-generator-cli/src/main/java/org/apache/freemarker/generator/cli/Main.java
+++ b/freemarker-generator-cli/src/main/java/org/apache/freemarker/generator/cli/Main.java
@@ -18,7 +18,7 @@ package org.apache.freemarker.generator.cli;
 
 import org.apache.freemarker.generator.base.parameter.ParameterModelSupplier;
 import org.apache.freemarker.generator.base.util.ClosableUtils;
-import org.apache.freemarker.generator.base.util.StringUtils;
+import org.apache.freemarker.generator.base.util.ListUtils;
 import org.apache.freemarker.generator.cli.config.Settings;
 import org.apache.freemarker.generator.cli.picocli.GitVersionProvider;
 import org.apache.freemarker.generator.cli.task.FreeMarkerTask;
@@ -59,7 +59,7 @@ public class Main implements Callable<Integer> {
     TemplateSourceOptions templateSourceOptions;
 
     public static final class TemplateSourceOptions {
-        @Option(names = { "-t", "--template" }, description = "template to process")
+        @Option(names = { "-t", "--template" }, description = "templates to process")
         public List<String> templates;
 
         @Option(names = { "-i", "--interactive" }, description = "interactive template to process")
@@ -81,8 +81,8 @@ public class Main implements Callable<Integer> {
     @Option(names = { "-m", "--data-model" }, description = "data model used for rendering")
     List<String> dataModels;
 
-    @Option(names = { "-o", "--output" }, description = "output file or directory")
-    String outputFile;
+    @Option(names = { "-o", "--output" }, description = "output files or directories")
+    List<String> outputs;
 
     @Option(names = { "-P", "--param" }, description = "set parameter")
     Map<String, String> parameters;
@@ -94,10 +94,10 @@ public class Main implements Callable<Integer> {
     String configFile;
 
     @Option(names = { "--data-source-include" }, description = "file include pattern for data sources")
-    String include;
+    String dataSourceIncludePattern;
 
     @Option(names = { "--data-source-exclude" }, description = "file exclude pattern for data sources")
-    String exclude;
+    String dataSourceExcludePattern;
 
     @Option(names = { "--output-encoding" }, description = "encoding of output, e.g. UTF-8", defaultValue = "UTF-8")
     String outputEncoding;
@@ -174,13 +174,13 @@ public class Main implements Callable<Integer> {
             final FreeMarkerTask freeMarkerTask = new FreeMarkerTask(settings);
             return freeMarkerTask.call();
         } finally {
-            if (settings.hasOutputFile()) {
+            if (settings.hasOutputs()) {
                 ClosableUtils.closeQuietly(settings.getWriter());
             }
         }
     }
 
-    private void validate() {
+    void validate() {
         // "-d" or "--data-source" parameter shall not contain wildcard characters
         if (dataSources != null) {
             for (String source : dataSources) {
@@ -189,6 +189,16 @@ public class Main implements Callable<Integer> {
                 }
             }
         }
+
+        // does the templates match the expected outputs?!
+        // -) no output means it goes to stdout
+        // -) for each template there should be an output
+        final List<String> templates = templateSourceOptions.templates;
+        if (templates != null && templates.size() > 1) {
+            if (outputs != null && outputs.size() != templates.size()) {
+                throw new ParameterException(spec.commandLine(), "Template output does not match specified templates");
+            }
+        }
     }
 
     private Settings settings(Properties configuration, List<File> templateDirectories) {
@@ -198,28 +208,28 @@ public class Main implements Callable<Integer> {
                 .isReadFromStdin(readFromStdin)
                 .setArgs(args)
                 .setConfiguration(configuration)
-                .setDataSourceIncludePattern(include)
-                .setDataSourceExcludePattern(exclude)
+                .setDataSourceIncludePattern(dataSourceIncludePattern)
+                .setDataSourceExcludePattern(dataSourceExcludePattern)
                 .setInputEncoding(inputEncoding)
                 .setInteractiveTemplate(templateSourceOptions.interactiveTemplate)
                 .setLocale(locale)
                 .setOutputEncoding(outputEncoding)
-                .setOutputFile(outputFile)
+                .setOutputs(outputs)
                 .setParameters(parameterModelSupplier.get())
                 .setDataSources(getCombinedDataSources())
                 .setDataModels(dataModels)
                 .setSystemProperties(systemProperties != null ? systemProperties : new Properties())
                 .setTemplateDirectories(templateDirectories)
                 .setTemplateNames(templateSourceOptions.templates)
-                .setWriter(writer(outputFile, outputEncoding))
+                .setWriter(writer(outputs, outputEncoding))
                 .build();
     }
 
-    private Writer writer(String outputFile, String outputEncoding) {
+    private Writer writer(List<String> outputFiles, String outputEncoding) {
         try {
             if (userSuppliedWriter != null) {
                 return userSuppliedWriter;
-            } else if (StringUtils.isEmpty(outputFile)) {
+            } else if (ListUtils.isNullOrEmpty(outputFiles)) {
                 return new BufferedWriter(new OutputStreamWriter(System.out, outputEncoding));
             } else {
                 return null;
diff --git a/freemarker-generator-cli/src/main/java/org/apache/freemarker/generator/cli/config/Settings.java b/freemarker-generator-cli/src/main/java/org/apache/freemarker/generator/cli/config/Settings.java
index 16dc76f..245a2a8 100644
--- a/freemarker-generator-cli/src/main/java/org/apache/freemarker/generator/cli/config/Settings.java
+++ b/freemarker-generator-cli/src/main/java/org/apache/freemarker/generator/cli/config/Settings.java
@@ -17,6 +17,7 @@
 package org.apache.freemarker.generator.cli.config;
 
 import org.apache.freemarker.generator.base.FreeMarkerConstants.Model;
+import org.apache.freemarker.generator.base.util.ListUtils;
 import org.apache.freemarker.generator.base.util.LocaleUtils;
 import org.apache.freemarker.generator.base.util.NonClosableWriterWrapper;
 
@@ -74,8 +75,8 @@ public class Settings {
     /** Enable verbose mode (currently not used) **/
     private final boolean verbose;
 
-    /** Optional output file or directory if not written to stdout */
-    private final File output;
+    /** Optional output files or directories if not written to stdout */
+    private final List<String> outputs;
 
     /** Optional include pattern for recursive directly search of data source files */
     private final String dataSourceIncludePattern;
@@ -115,7 +116,7 @@ public class Settings {
             Charset inputEncoding,
             Charset outputEncoding,
             boolean verbose,
-            File output,
+            List<String> outputs,
             String dataSourceIncludePattern,
             String dataSourceExcludePattern,
             Locale locale,
@@ -138,7 +139,7 @@ public class Settings {
         this.inputEncoding = inputEncoding;
         this.outputEncoding = outputEncoding;
         this.verbose = verbose;
-        this.output = output;
+        this.outputs = outputs;
         this.dataSourceIncludePattern = dataSourceIncludePattern;
         this.dataSourceExcludePattern = dataSourceExcludePattern;
         this.locale = requireNonNull(locale);
@@ -199,8 +200,8 @@ public class Settings {
         return verbose;
     }
 
-    public File getOutput() {
-        return output;
+    public List<String> getOutputs() {
+        return outputs;
     }
 
     public String getDataSourceIncludePattern() {
@@ -235,8 +236,8 @@ public class Settings {
         return userSystemProperties;
     }
 
-    public boolean hasOutputFile() {
-        return output != null;
+    public boolean hasOutputs() {
+        return ListUtils.isNotEmpty(outputs);
     }
 
     public Writer getWriter() {
@@ -277,7 +278,7 @@ public class Settings {
                 ", inputEncoding=" + inputEncoding +
                 ", outputEncoding=" + outputEncoding +
                 ", verbose=" + verbose +
-                ", outputFile=" + output +
+                ", outputs=" + outputs +
                 ", include='" + dataSourceIncludePattern + '\'' +
                 ", exclude='" + dataSourceExcludePattern + '\'' +
                 ", locale=" + locale +
@@ -298,7 +299,7 @@ public class Settings {
         private String inputEncoding;
         private String outputEncoding;
         private boolean verbose;
-        private String outputFile;
+        private List<String> outputs;
         private String dataSourceIncludePattern;
         private String dataSourceExcludePattern;
         private String locale;
@@ -380,8 +381,10 @@ public class Settings {
             return this;
         }
 
-        public SettingsBuilder setOutputFile(String outputFile) {
-            this.outputFile = outputFile;
+        public SettingsBuilder setOutputs(List<String> outputs) {
+            if (outputs != null) {
+                this.outputs = outputs;
+            }
             return this;
         }
 
@@ -449,7 +452,6 @@ public class Settings {
             final Charset inputEncoding = Charset.forName(this.inputEncoding);
             final Charset outputEncoding = Charset.forName(this.outputEncoding);
             final String currLocale = locale != null ? locale : getDefaultLocale();
-            final File currOutputFile = outputFile != null ? new File(outputFile) : null;
 
             return new Settings(
                     configuration,
@@ -462,7 +464,7 @@ public class Settings {
                     inputEncoding,
                     outputEncoding,
                     verbose,
-                    currOutputFile,
+                    outputs,
                     dataSourceIncludePattern,
                     dataSourceExcludePattern,
                     LocaleUtils.parseLocale(currLocale),
diff --git a/freemarker-generator-cli/src/main/java/org/apache/freemarker/generator/cli/config/Suppliers.java b/freemarker-generator-cli/src/main/java/org/apache/freemarker/generator/cli/config/Suppliers.java
index 54e0758..6412b4d 100644
--- a/freemarker-generator-cli/src/main/java/org/apache/freemarker/generator/cli/config/Suppliers.java
+++ b/freemarker-generator-cli/src/main/java/org/apache/freemarker/generator/cli/config/Suppliers.java
@@ -73,7 +73,7 @@ public class Suppliers {
                 .addSources(settings.getTemplates())
                 .addInclude(settings.getTemplateFileIncludePattern())
                 .addExclude(settings.getTemplateFileExcludePattern())
-                .addOutput(settings.getOutput())
+                .addOutputs(settings.getOutputs())
                 .setWriter(settings.getWriter())
                 .build();
     }
diff --git a/freemarker-generator-cli/src/site/markdown/cli/concepts/template-loading.md b/freemarker-generator-cli/src/site/markdown/cli/concepts/template-loading.md
index f54228e..c512d5c 100644
--- a/freemarker-generator-cli/src/site/markdown/cli/concepts/template-loading.md
+++ b/freemarker-generator-cli/src/site/markdown/cli/concepts/template-loading.md
@@ -40,7 +40,7 @@ and [Template Includes](https://freemarker.apache.org/docs/ref_directive_include
 
 ### Free-Style Template Loading
 
-The previosly described `Template Loaders` do not support absolute template files or arbitraRY URLS - this behaviour 
+The previosly described `Template Loaders` do not support absolute template files or arbitrary URLS - this behaviour 
 stems from security aspects when running `Apache FreeMarker` on the server side. For a command-line tool this is mostly
 irrelevant therefore any template file outside of the template loader directories can be loaded 
 
diff --git a/freemarker-generator-cli/src/site/markdown/cli/concepts/transformation.md b/freemarker-generator-cli/src/site/markdown/cli/concepts/transformation.md
index 388a681..f7788ee 100644
--- a/freemarker-generator-cli/src/site/markdown/cli/concepts/transformation.md
+++ b/freemarker-generator-cli/src/site/markdown/cli/concepts/transformation.md
@@ -11,4 +11,37 @@ The `freemarker-cli` generates text output based on FreeMarker templates and dat
     * an output directory
 * When the output is written to a directory
     * the structure of the input directory is preserved
-    * a `ftl` file externsion is removed
+    * a `ftl` file extension is removed
+
+Transforming a single template and data source to `stdout`
+
+```
+freemarker-cli \
+-t templates/csv/md/transform.ftl examples/data/csv/contract.csv
+```
+
+Transforming multiple templates to multiple output files
+
+```
+freemarker-cli \
+-t templates/csv/md/transform.ftl -o target/contract.md \
+-t templates/csv/html/transform.ftl -o target/contract.html \
+examples/data/csv/contract.csv
+```
+
+Transforming single template directory to single output directory
+
+```
+freemarker-cli \ 
+-P NGINX_HOSTNAME=localhost \
+-t examples/data/template -o target/out/template1
+```
+
+Transforming multiple template directories to multiple output directories
+
+```
+freemarker-cli \ 
+-P NGINX_HOSTNAME=localhost \
+-t examples/data/template -o target/out/template1 \
+-t examples/data/template -o target/out/template2 
+```
diff --git a/freemarker-generator-cli/src/test/java/org/apache/freemarker/generator/cli/ExamplesTest.java b/freemarker-generator-cli/src/test/java/org/apache/freemarker/generator/cli/ExamplesTest.java
index 0c3820c..e66d457 100644
--- a/freemarker-generator-cli/src/test/java/org/apache/freemarker/generator/cli/ExamplesTest.java
+++ b/freemarker-generator-cli/src/test/java/org/apache/freemarker/generator/cli/ExamplesTest.java
@@ -121,12 +121,24 @@ public class ExamplesTest extends AbstractMainTest {
     }
 
     @Test
-    public void shouldTransformTemplateDirectory() throws IOException {
+    public void shouldTransformSingleTemplateDirectory() throws IOException {
         assertTrue(execute("-t examples/data/template").contains("server.name=127.0.0.1"));
         assertTrue(execute("-t examples/data/template -PNGINX_HOSTNAME=my.domain.com").contains("server.name=my.domain.com"));
     }
 
     @Test
+    public void shouldTransformMultipleTemplateDirectories() throws IOException {
+        assertValid(execute("-t examples/data/template -t examples/data/template"));
+        assertValid(execute("-t examples/data/template -o target/out/template1 -t examples/data/template -o target/out/template2"));
+    }
+
+    @Test
+    public void shouldTransformMultipleTemplates() throws IOException {
+        assertValid(execute("-t templates/csv/md/transform.ftl -t templates/csv/html/transform.ftl examples/data/csv/contract.csv"));
+        assertValid(execute("-t templates/csv/md/transform.ftl -o target/contract.md -t templates/csv/html/transform.ftl -o target/contract.html examples/data/csv/contract.csv"));
+    }
+
+    @Test
     @Ignore("Manual test to check memory consumption and resource handling")
     public void shouldCloseAllResources() throws IOException {
         for (int i = 0; i < 500; i++) {
@@ -141,7 +153,8 @@ public class ExamplesTest extends AbstractMainTest {
             shouldRunXmlExamples();
             shouldRunGrokExamples();
             shouldRunInteractiveTemplateExamples();
-            shouldTransformTemplateDirectory();
+            shouldTransformSingleTemplateDirectory();
+            shouldTransformMultipleTemplates();
             shouldRunWithExposedEnvironmentVariableExamples();
         }
     }
diff --git a/freemarker-generator-cli/src/test/java/org/apache/freemarker/generator/cli/ManualTest.java b/freemarker-generator-cli/src/test/java/org/apache/freemarker/generator/cli/ManualTest.java
index 2b7f456..ab10101 100644
--- a/freemarker-generator-cli/src/test/java/org/apache/freemarker/generator/cli/ManualTest.java
+++ b/freemarker-generator-cli/src/test/java/org/apache/freemarker/generator/cli/ManualTest.java
@@ -24,10 +24,11 @@ import java.util.Arrays;
 public class ManualTest {
 
     private static final String SPACE = " ";
-    private static final String CMD = "-V";
+    // private static final String CMD = "-V";
     // private static final String CMD = "-PCSV_SOURCE_FORMAT=DATAFRAME -t examples/templates/dataframe/example.ftl https://raw.githubusercontent.com/nRo/DataFrame/master/src/test/resources/users.csv";
     // private static final String CMD = "-PCSV_SOURCE_WITH_HEADER=false -PCSV_SOURCE_FORMAT=DEFAULT -PCSV_TARGET_FORMAT=EXCEL -PCSV_TARGET_WITH_HEADER=true -t templates/csv/csv/transform.ftl examples/data/csv/contract.csv";
     // private static final String CMD = "-t examples/templates/json/dataframe/github-users.ftl examples/data/json/github-users.json";
+    private static final String CMD = "-t templates/csv/md/transform.ftl -o target/contract.md -t templates/csv/html/transform.ftl examples/data/csv/contract.csv";
 
 
     public static void main(String[] args) {
diff --git a/freemarker-generator-cli/src/test/java/org/apache/freemarker/generator/cli/PicocliTest.java b/freemarker-generator-cli/src/test/java/org/apache/freemarker/generator/cli/PicocliTest.java
index 800e8be..34077d5 100644
--- a/freemarker-generator-cli/src/test/java/org/apache/freemarker/generator/cli/PicocliTest.java
+++ b/freemarker-generator-cli/src/test/java/org/apache/freemarker/generator/cli/PicocliTest.java
@@ -18,6 +18,7 @@ package org.apache.freemarker.generator.cli;
 
 import org.junit.Test;
 import picocli.CommandLine;
+import picocli.CommandLine.ParameterException;
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNull;
@@ -111,6 +112,13 @@ public class PicocliTest {
         assertEquals(INTERACTIVE_TEMPLATE, main.templateSourceOptions.interactiveTemplate);
     }
 
+    @Test(expected = ParameterException.class)
+    public void shouldThrowParameterExceptionForMismatchedTemplateOutput() {
+        final Main main = parse("-t", "foo.ftl", "-t", "bar.ftl", "-o", "foo.out");
+
+        main.validate();
+    }
+
     private static Main parse(String... args) {
         final Main main = new Main();
         new CommandLine(main).parseArgs(args);
diff --git a/freemarker-generator-cli/src/test/java/org/apache/freemarker/generator/cli/config/SettingsTest.java b/freemarker-generator-cli/src/test/java/org/apache/freemarker/generator/cli/config/SettingsTest.java
index 90c7696..cbd838a 100644
--- a/freemarker-generator-cli/src/test/java/org/apache/freemarker/generator/cli/config/SettingsTest.java
+++ b/freemarker-generator-cli/src/test/java/org/apache/freemarker/generator/cli/config/SettingsTest.java
@@ -20,6 +20,7 @@ import org.apache.freemarker.generator.cli.config.Settings.SettingsBuilder;
 import org.junit.Test;
 
 import java.io.StringWriter;
+import java.util.Collections;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
@@ -54,7 +55,7 @@ public class SettingsTest {
         assertEquals(ANY_INCLUDE, settings.getDataSourceIncludePattern());
         assertEquals(ANY_INPUT_ENCODING, settings.getInputEncoding().name());
         assertEquals(ANY_OUTPUT_ENCODING, settings.getOutputEncoding().name());
-        assertEquals(ANY_OUTPUT_FILE, settings.getOutput().getName());
+        assertEquals(ANY_OUTPUT_FILE, settings.getOutputs().get(0));
         assertEquals(ANY_TEMPLATE_NAME, settings.getTemplates().get(0));
         assertNotNull(settings.getDataSources());
         assertNotNull(settings.getUserParameters());
@@ -74,7 +75,7 @@ public class SettingsTest {
                 .setInteractiveTemplate(ANY_INTERACTIVE_TEMPLATE)
                 .setLocale(ANY_LOCALE)
                 .setOutputEncoding(ANY_OUTPUT_ENCODING)
-                .setOutputFile(ANY_OUTPUT_FILE)
+                .setOutputs(Collections.singletonList(ANY_OUTPUT_FILE))
                 .setParameters(ANY_USER_PARAMETERS)
                 .setDataSources(ANY_SOURCES)
                 .setSystemProperties(ANY_SYSTEM_PROPERTIES)