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

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

commit c670c73ab9df38d2d2bc13901a131fdbb3b4f33b
Author: Siegfried Goeschl <[email protected]>
AuthorDate: Tue Jan 5 00:01:58 2021 +0100

    FREEMARKER-161 [freemarker-generator] Allow multiple transformations on the 
CLI
---
 .../generator/base/output/OutputGenerator.java     |  79 +++++++
 .../base/output/OutputGeneratorBuilder.java        |  75 ++++++
 .../template/TemplateTransformationsBuilder.java   |  20 +-
 .../freemarker/generator/base/util/MapBuilder.java |   8 +
 .../TemplateTransformationsBuilderTest.java        |  18 +-
 freemarker-generator-cli/CHANGELOG.md              |   2 +
 .../org/apache/freemarker/generator/cli/Main.java  | 135 ++++-------
 .../cli/config/OutputGeneratorsSupplier.java       | 107 +++++++++
 .../freemarker/generator/cli/config/Settings.java  | 254 ++++++---------------
 .../freemarker/generator/cli/config/Suppliers.java |  31 +--
 .../generator/cli/picocli/DataModelDefinition.java |  30 +++
 .../cli/picocli/DataSourceDefinition.java          |  28 +++
 .../cli/picocli/DataSourceFilterDefinition.java    |  30 +++
 .../cli/picocli/OutputGeneratorDefinition.java     | 117 ++++++++++
 .../cli/picocli/TemplateOutputDefinition.java      |  31 +++
 .../cli/picocli/TemplateSourceDefinition.java      |  32 +++
 .../picocli/TemplateSourceFilterDefinition.java    |  31 +++
 .../generator/cli/task/FreeMarkerTask.java         | 126 +++-------
 .../freemarker/generator/cli/PicocliTest.java      |  95 ++++++--
 .../generator/cli/config/CompositeGroupDemo.java   |  22 +-
 .../cli/config/ConfigurationSupplierTest.java      |   3 +-
 .../generator/cli/config/SettingsTest.java         |  81 +++----
 22 files changed, 871 insertions(+), 484 deletions(-)

diff --git 
a/freemarker-generator-base/src/main/java/org/apache/freemarker/generator/base/output/OutputGenerator.java
 
b/freemarker-generator-base/src/main/java/org/apache/freemarker/generator/base/output/OutputGenerator.java
new file mode 100644
index 0000000..a9cfc37
--- /dev/null
+++ 
b/freemarker-generator-base/src/main/java/org/apache/freemarker/generator/base/output/OutputGenerator.java
@@ -0,0 +1,79 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.freemarker.generator.base.output;
+
+import org.apache.freemarker.generator.base.datasource.DataSource;
+import org.apache.freemarker.generator.base.template.TemplateOutput;
+import org.apache.freemarker.generator.base.template.TemplateSource;
+
+import java.util.List;
+import java.util.Map;
+
+import static java.util.Objects.requireNonNull;
+
+/**
+ * Information about loading templates and writing their output.
+ */
+public class OutputGenerator {
+
+    /** Source of template */
+    private final TemplateSource templateSource;
+
+    /** Output of template */
+    private final TemplateOutput templateOutput;
+
+    /** Data sources used for the transformation */
+    private final List<DataSource> dataSources;
+
+    /** Data sources used for the transformation */
+    private final Map<String, Object> variables;
+
+    public OutputGenerator(
+            TemplateSource templateSource,
+            TemplateOutput templateOutput,
+            List<DataSource> dataSources,
+            Map<String, Object> variables) {
+        this.templateSource = requireNonNull(templateSource);
+        this.templateOutput = requireNonNull(templateOutput);
+        this.dataSources = requireNonNull(dataSources);
+        this.variables = requireNonNull(variables);
+    }
+
+    public TemplateSource getTemplateSource() {
+        return templateSource;
+    }
+
+    public TemplateOutput getTemplateOutput() {
+        return templateOutput;
+    }
+
+    public List<DataSource> getDataSources() {
+        return dataSources;
+    }
+
+    public Map<String, Object> getVariables() {
+        return variables;
+    }
+
+    @Override
+    public String toString() {
+        return "OutputGenerator{" +
+                "templateSource=" + templateSource +
+                ", templateOutput=" + templateOutput +
+                '}';
+    }
+}
diff --git 
a/freemarker-generator-base/src/main/java/org/apache/freemarker/generator/base/output/OutputGeneratorBuilder.java
 
b/freemarker-generator-base/src/main/java/org/apache/freemarker/generator/base/output/OutputGeneratorBuilder.java
new file mode 100644
index 0000000..5f272c7
--- /dev/null
+++ 
b/freemarker-generator-base/src/main/java/org/apache/freemarker/generator/base/output/OutputGeneratorBuilder.java
@@ -0,0 +1,75 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.freemarker.generator.base.output;
+
+import org.apache.freemarker.generator.base.datasource.DataSource;
+import org.apache.freemarker.generator.base.template.TemplateSource;
+
+import java.io.Writer;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Provide the logic to define multiple transformations from the user input.
+ */
+public class OutputGeneratorBuilder {
+
+    /** Interactive template */
+    private TemplateSource interactiveTemplate;
+
+    /** List of templates and/or template directories to be rendered */
+    private String templateName;
+
+    /** Optional include patterns for resolving source templates or template 
directories */
+    private final List<String> includes;
+
+    /** Optional exclude patterns for resolving source templates or template 
directories */
+    private final List<String> excludes;
+
+    /** Optional output file or directory */
+    private String output;
+
+    /** Optional user-supplied writer */
+    private Writer writer;
+
+    /** Data sources used for the transformation */
+    private List<DataSource> dataSources;
+
+    /** Data sources used for the transformation */
+    private Map<String, Object> variables;
+
+    private OutputGeneratorBuilder() {
+        this.templateName = null;
+        this.includes = new ArrayList<>();
+        this.excludes = new ArrayList<>();
+        this.output = null;
+        this.writer = null;
+        this.dataSources = new ArrayList<>();
+        this.variables = new HashMap<>();
+    }
+
+    public static OutputGeneratorBuilder builder() {
+        return new OutputGeneratorBuilder();
+    }
+
+    public List<OutputGenerator> build() {
+        final List<OutputGenerator> result = new ArrayList<>();
+        return result;
+    }
+}
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 e352173..5561cb7 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
@@ -23,13 +23,16 @@ import 
org.apache.freemarker.generator.base.file.RecursiveFileSupplier;
 import org.apache.freemarker.generator.base.util.StringUtils;
 import org.apache.freemarker.generator.base.util.Validate;
 
+import java.io.BufferedWriter;
 import java.io.File;
+import java.io.OutputStreamWriter;
 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;
 
 /**
@@ -54,14 +57,14 @@ public class TemplateTransformationsBuilder {
     private final List<String> outputs;
 
     /** Optional user-supplied writer */
-    private Writer writer;
+    private Writer userSuppliedWriter;
 
     private TemplateTransformationsBuilder() {
         this.templateSources = new ArrayList<>();
         this.includes = new ArrayList<>();
         this.excludes = new ArrayList<>();
         this.outputs = new ArrayList<>();
-        this.writer = null;
+        this.userSuppliedWriter = null;
     }
 
     public static TemplateTransformationsBuilder builder() {
@@ -134,13 +137,13 @@ public class TemplateTransformationsBuilder {
         return this;
     }
 
-    public TemplateTransformationsBuilder setWriter(Writer writer) {
-        this.writer = writer;
+    public TemplateTransformationsBuilder setUserSuppliedWriter(Writer 
userSuppliedWriter) {
+        this.userSuppliedWriter = userSuppliedWriter;
         return this;
     }
 
     private void validate() {
-        Validate.isTrue(interactiveTemplate != null || 
!templateSources.isEmpty(), "Interactive template does not support multiple 
sources");
+        // Validate.isTrue(interactiveTemplate != null || 
!templateSources.isEmpty(), "Interactive template does not support multiple 
sources");
         Validate.isTrue(interactiveTemplate == null || 
templateSources.isEmpty(), "No template was provided");
     }
 
@@ -212,10 +215,13 @@ public class TemplateTransformationsBuilder {
     }
 
     private TemplateOutput templateOutput(File templateOutputFile) {
-        if (writer == null && templateOutputFile != null) {
+        if (userSuppliedWriter != null) {
+            return TemplateOutput.fromWriter(userSuppliedWriter);
+        } else if (templateOutputFile != null) {
             return TemplateOutput.fromFile(templateOutputFile);
         } else {
-            return TemplateOutput.fromWriter(writer);
+            // @TODO FREEMARKER-161 sgoeschl Shall we close the writer or use 
"userSuppliedWriter"?
+            return TemplateOutput.fromWriter(new BufferedWriter(new 
OutputStreamWriter(System.out, UTF_8)));
         }
     }
 
diff --git 
a/freemarker-generator-base/src/main/java/org/apache/freemarker/generator/base/util/MapBuilder.java
 
b/freemarker-generator-base/src/main/java/org/apache/freemarker/generator/base/util/MapBuilder.java
index 60daa80..9b71d22 100644
--- 
a/freemarker-generator-base/src/main/java/org/apache/freemarker/generator/base/util/MapBuilder.java
+++ 
b/freemarker-generator-base/src/main/java/org/apache/freemarker/generator/base/util/MapBuilder.java
@@ -18,6 +18,7 @@ package org.apache.freemarker.generator.base.util;
 
 import java.util.HashMap;
 import java.util.LinkedHashMap;
+import java.util.List;
 import java.util.Map;
 
 public class MapBuilder {
@@ -50,4 +51,11 @@ public class MapBuilder {
 
         return map;
     }
+
+    public static Map<String, Object> merge(List<Map<String, Object>> maps) {
+        final Map<String, Object> result = new HashMap<>();
+        maps.forEach(result::putAll);
+        return result;
+    }
+
 }
diff --git 
a/freemarker-generator-base/src/test/java/org/apache/freemarker/generator/template/TemplateTransformationsBuilderTest.java
 
b/freemarker-generator-base/src/test/java/org/apache/freemarker/generator/template/TemplateTransformationsBuilderTest.java
index 66d3c9a..42e933e 100644
--- 
a/freemarker-generator-base/src/test/java/org/apache/freemarker/generator/template/TemplateTransformationsBuilderTest.java
+++ 
b/freemarker-generator-base/src/test/java/org/apache/freemarker/generator/template/TemplateTransformationsBuilderTest.java
@@ -54,7 +54,7 @@ public class TemplateTransformationsBuilderTest {
     public void shouldCreateFromInteractiveTemplate() {
         final List<TemplateTransformation> transformations = builder()
                 .setInteractiveTemplate("Hello World")
-                .setWriter(stdoutWriter())
+                .setUserSuppliedWriter(stdoutWriter())
                 .build();
 
         assertEquals(1, transformations.size());
@@ -77,7 +77,7 @@ public class TemplateTransformationsBuilderTest {
         builder()
                 .setInteractiveTemplate("Hello World")
                 .addTemplateSource(ANY_TEMPLATE_FILE_NAME)
-                .setWriter(stdoutWriter())
+                .setUserSuppliedWriter(stdoutWriter())
                 .build();
     }
 
@@ -87,7 +87,7 @@ public class TemplateTransformationsBuilderTest {
     public void shouldCreateFromTemplateFile() {
         final List<TemplateTransformation> transformations = builder()
                 .addTemplateSource(ANY_TEMPLATE_FILE_NAME)
-                .setWriter(stdoutWriter())
+                .setUserSuppliedWriter(stdoutWriter())
                 .build();
 
         assertEquals(1, transformations.size());
@@ -125,7 +125,7 @@ public class TemplateTransformationsBuilderTest {
     public void shouldCreateFromTemplatePath() {
         final List<TemplateTransformation> transformations = builder()
                 .addTemplateSource(ANY_TEMPLATE_PATH)
-                .setWriter(stdoutWriter())
+                .setUserSuppliedWriter(stdoutWriter())
                 .build();
 
         assertEquals(1, transformations.size());
@@ -149,7 +149,7 @@ public class TemplateTransformationsBuilderTest {
     public void shouldCreateFromTemplateDirectory() {
         final List<TemplateTransformation> transformations = builder()
                 .addTemplateSource(ANY_TEMPLATE_DIRECTORY_NAME)
-                .setWriter(stdoutWriter())
+                .setUserSuppliedWriter(stdoutWriter())
                 .build();
 
         assertEquals(2, transformations.size());
@@ -174,7 +174,7 @@ public class TemplateTransformationsBuilderTest {
         final List<TemplateTransformation> transformations = builder()
                 .addTemplateSource(ANY_TEMPLATE_DIRECTORY_NAME)
                 .addInclude("*.properties")
-                .setWriter(stdoutWriter())
+                .setUserSuppliedWriter(stdoutWriter())
                 .build();
 
         assertEquals(1, transformations.size());
@@ -186,7 +186,7 @@ public class TemplateTransformationsBuilderTest {
         final List<TemplateTransformation> transformations = builder()
                 .addTemplateSource(ANY_TEMPLATE_DIRECTORY_NAME)
                 .addExclude("*.ftl")
-                .setWriter(stdoutWriter())
+                .setUserSuppliedWriter(stdoutWriter())
                 .build();
 
         assertEquals(1, transformations.size());
@@ -199,7 +199,7 @@ public class TemplateTransformationsBuilderTest {
     public void shouldCreateFromTemplateUrl() {
         final List<TemplateTransformation> transformations = builder()
                 .addTemplateSource(ANY_TEMPLATE_URL)
-                .setWriter(stdoutWriter())
+                .setUserSuppliedWriter(stdoutWriter())
                 .build();
 
         final TemplateSource templateSource = 
transformations.get(0).getTemplateSource();
@@ -218,7 +218,7 @@ public class TemplateTransformationsBuilderTest {
     public void shouldCreateFromTemplateEnvironmentVariable() {
         final List<TemplateTransformation> transformations = builder()
                 .addTemplateSource(ANY_ENV_URI)
-                .setWriter(stdoutWriter())
+                .setUserSuppliedWriter(stdoutWriter())
                 .build();
 
         final TemplateSource templateSource = 
transformations.get(0).getTemplateSource();
diff --git a/freemarker-generator-cli/CHANGELOG.md 
b/freemarker-generator-cli/CHANGELOG.md
index c6abcd7..6565367 100644
--- a/freemarker-generator-cli/CHANGELOG.md
+++ b/freemarker-generator-cli/CHANGELOG.md
@@ -19,6 +19,7 @@ All notable changes to this project will be documented in 
this file. We try to a
 * [FREEMARKER-129] Migrate `freemarker-cli` into `freemarker-generator` 
project (see 
[https://github.com/sgoeschl/freemarker-cli](https://github.com/sgoeschl/freemarker-cli))
 
 ### Changed
+* [FREEMARKER-161] Allow multiple transformations on the CLI
 * [FREEMARKER-155] Migrate the FTL code to terser dotter form 
 * [FREEMARKER-153] Packaged templates are now prefixed with 
`freemarker-generator`, e.g. `freemarker-generator/info.ftl`
 * [FREEMARKER-153] Renamed `--basedir` command line option to `--template-dir`
@@ -61,6 +62,7 @@ All notable changes to this project will be documented in 
this file. We try to a
 [FREEMARKER-151]: https://issues.apache.org/jira/browse/FREEMARKER-151
 [FREEMARKER-153]: https://issues.apache.org/jira/browse/FREEMARKER-153
 [FREEMARKER-155]: https://issues.apache.org/jira/browse/FREEMARKER-155
+[FREEMARKER-161]: https://issues.apache.org/jira/browse/FREEMARKER-161
 [FREEMARKER-163]: https://issues.apache.org/jira/browse/FREEMARKER-163
 [FREEMARKER-164]: https://issues.apache.org/jira/browse/FREEMARKER-164
 [FREEMARKER-168]: https://issues.apache.org/jira/browse/FREEMARKER-168
\ 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 4869909..b737357 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
@@ -20,16 +20,16 @@ import 
org.apache.freemarker.generator.base.FreeMarkerConstants.Configuration;
 import 
org.apache.freemarker.generator.base.FreeMarkerConstants.SystemProperties;
 import org.apache.freemarker.generator.base.parameter.ParameterModelSupplier;
 import org.apache.freemarker.generator.base.util.ClosableUtils;
-import org.apache.freemarker.generator.base.util.ListUtils;
+import org.apache.freemarker.generator.base.util.MapBuilder;
 import org.apache.freemarker.generator.cli.config.Settings;
 import org.apache.freemarker.generator.cli.picocli.GitVersionProvider;
+import org.apache.freemarker.generator.cli.picocli.OutputGeneratorDefinition;
 import org.apache.freemarker.generator.cli.task.FreeMarkerTask;
 import picocli.CommandLine;
 import picocli.CommandLine.ArgGroup;
 import picocli.CommandLine.Command;
 import picocli.CommandLine.Model.CommandSpec;
 import picocli.CommandLine.Option;
-import picocli.CommandLine.ParameterException;
 import picocli.CommandLine.Parameters;
 import picocli.CommandLine.Spec;
 
@@ -38,35 +38,35 @@ import java.io.File;
 import java.io.IOException;
 import java.io.OutputStreamWriter;
 import java.io.Writer;
-import java.util.Collection;
+import java.util.ArrayList;
+import java.util.Collections;
 import java.util.List;
 import java.util.Map;
-import java.util.Objects;
 import java.util.Properties;
 import java.util.concurrent.Callable;
-import java.util.stream.Collectors;
 import java.util.stream.IntStream;
-import java.util.stream.Stream;
 
+import static java.util.Arrays.asList;
 import static java.util.Objects.requireNonNull;
 import static org.apache.freemarker.generator.base.util.StringUtils.isEmpty;
 import static org.apache.freemarker.generator.base.util.StringUtils.isNotEmpty;
+import static 
org.apache.freemarker.generator.cli.config.Suppliers.configurationSupplier;
+import static 
org.apache.freemarker.generator.cli.config.Suppliers.outputGeneratorsSupplier;
 import static 
org.apache.freemarker.generator.cli.config.Suppliers.propertiesSupplier;
 import static 
org.apache.freemarker.generator.cli.config.Suppliers.templateDirectorySupplier;
+import static 
org.apache.freemarker.generator.cli.config.Suppliers.toolsSupplier;
 
 @Command(description = "Apache FreeMarker Generator", name = 
"freemarker-generator", mixinStandardHelpOptions = true, versionProvider = 
GitVersionProvider.class)
 public class Main implements Callable<Integer> {
 
-    @ArgGroup(multiplicity = "1")
-    TemplateSourceOptions templateSourceOptions;
+    @ArgGroup(exclusive = false, multiplicity = "1..*")
+    List<OutputGeneratorDefinition> outputGeneratorDefinitions;
 
-    public static final class TemplateSourceOptions {
-        @Option(names = { "-t", "--template" }, description = "templates to 
process")
-        public List<String> templates;
+    @Option(names = { "--data-source-include" }, description = "data source 
include pattern")
+    public String dataSourceIncludePattern;
 
-        @Option(names = { "-i", "--interactive" }, description = "interactive 
template to process")
-        public String interactiveTemplate;
-    }
+    @Option(names = { "--data-source-exclude" }, description = "data source 
exclude pattern")
+    public String dataSourceExcludePattern;
 
     @Option(names = { "-D", "--system-property" }, description = "set system 
property")
     Properties systemProperties;
@@ -77,27 +77,12 @@ public class Main implements Callable<Integer> {
     @Option(names = { "-l", "--locale" }, description = "locale being used for 
the output, e.g. 'en_US'")
     String locale;
 
-    @Option(names = { "-m", "--data-model" }, description = "data model used 
for rendering")
-    List<String> dataModels;
-
-    @Option(names = { "-o", "--output" }, description = "output files or 
directories")
-    List<String> outputs;
-
     @Option(names = { "-P", "--param" }, description = "set parameter")
     Map<String, String> parameters;
 
-    @Option(names = { "-s", "--data-source" }, description = "data source used 
for rendering")
-    List<String> dataSources;
-
     @Option(names = { "--config" }, description = "FreeMarker Generator 
configuration file")
     String configFile;
 
-    @Option(names = { "--data-source-include" }, description = "file include 
pattern for data sources")
-    String dataSourceIncludePattern;
-
-    @Option(names = { "--data-source-exclude" }, description = "file exclude 
pattern for data sources")
-    String dataSourceExcludePattern;
-
     @Option(names = { "--output-encoding" }, description = "encoding of 
output, e.g. UTF-8", defaultValue = "UTF-8")
     String outputEncoding;
 
@@ -117,7 +102,7 @@ public class Main implements Callable<Integer> {
     final String[] args;
 
     /** User-supplied writer (used mainly for unit testing) */
-    Writer userSuppliedWriter;
+    Writer callerSuppliedWriter;
 
     /** Injected by Picocli */
     @Spec private CommandSpec spec;
@@ -126,13 +111,13 @@ public class Main implements Callable<Integer> {
         this.args = new String[0];
     }
 
-    private Main(String[] args) {
+    Main(String[] args) {
         this.args = requireNonNull(args);
     }
 
-    private Main(String[] args, Writer userSuppliedWriter) {
+    private Main(String[] args, Writer callerSuppliedWriter) {
         this.args = requireNonNull(args);
-        this.userSuppliedWriter = requireNonNull(userSuppliedWriter);
+        this.callerSuppliedWriter = requireNonNull(callerSuppliedWriter);
     }
 
     public static void main(String[] args) {
@@ -171,77 +156,46 @@ public class Main implements Callable<Integer> {
         final String currentConfigFile = isNotEmpty(configFile) ? configFile : 
getDefaultConfigFileName();
         final Properties configuration = 
loadFreeMarkerCliConfiguration(currentConfigFile);
         final List<File> templateDirectories = 
getTemplateDirectories(templateDir);
-        final Settings settings = settings(configuration, templateDirectories);
+        final Settings settings = settings(configuration, templateDirectories, 
outputGeneratorDefinitions);
 
         try {
-            final FreeMarkerTask freeMarkerTask = new FreeMarkerTask(settings);
+            final FreeMarkerTask freeMarkerTask = new FreeMarkerTask(
+                    configurationSupplier(settings),
+                    outputGeneratorsSupplier(settings),
+                    () -> 
MapBuilder.merge(asList(toolsSupplier(settings).get(), 
settings.getUserParameters()))
+            );
             return freeMarkerTask.call();
         } finally {
-            if (settings.hasOutputs()) {
-                ClosableUtils.closeQuietly(settings.getWriter());
-            }
+            ClosableUtils.closeQuietly(settings.getCallerSuppliedWriter());
         }
     }
 
     void validate() {
-        // "-d" or "--data-source" parameter shall not contain wildcard 
characters
-        if (dataSources != null) {
-            for (String source : dataSources) {
-                if (isFileSource(source) && (source.contains("*") || 
source.contains("?"))) {
-                    throw new ParameterException(spec.commandLine(), "No 
wildcards supported for data source: " + source);
-                }
-            }
-        }
-
-        // 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");
-            }
-        }
+        outputGeneratorDefinitions.forEach(t -> 
t.validate(spec.commandLine()));
     }
 
-    private Settings settings(Properties configuration, List<File> 
templateDirectories) {
+    private Settings settings(Properties configuration, List<File> 
templateDirectories, List<OutputGeneratorDefinition> 
outputGeneratorDefinitions) {
         final ParameterModelSupplier parameterModelSupplier = new 
ParameterModelSupplier(parameters);
 
         return Settings.builder()
                 .isReadFromStdin(readFromStdin)
-                .setArgs(args)
+                .setCommandLineArgs(args)
                 .setConfiguration(configuration)
-                .setDataModels(dataModels)
-                .setDataSources(getCombinedDataSources())
-                .setDataSourceIncludePattern(dataSourceIncludePattern)
-                .setDataSourceExcludePattern(dataSourceExcludePattern)
+                .setTemplateDirectories(templateDirectories)
+                .setOutputGeneratorDefinitions(outputGeneratorDefinitions)
+                .setSources(getDataSources())
+                .setSourceIncludePattern(dataSourceIncludePattern)
+                .setSourceExcludePattern(dataSourceExcludePattern)
                 .setInputEncoding(inputEncoding)
-                
.setInteractiveTemplate(templateSourceOptions.interactiveTemplate)
                 .setLocale(locale)
                 .setOutputEncoding(outputEncoding)
-                .setOutputs(outputs)
                 .setParameters(parameterModelSupplier.get())
                 .setSystemProperties(systemProperties != null ? 
systemProperties : new Properties())
                 .setTemplateDirectories(templateDirectories)
-                .setTemplateNames(templateSourceOptions.templates)
-                .setWriter(writer(outputs, outputEncoding))
+                .setCallerSuppliedWriter(callerSuppliedWriter)
                 .build();
     }
 
-    private Writer writer(List<String> outputFiles, String outputEncoding) {
-        try {
-            if (userSuppliedWriter != null) {
-                return userSuppliedWriter;
-            } else if (ListUtils.isNullOrEmpty(outputFiles)) {
-                return new BufferedWriter(new OutputStreamWriter(System.out, 
outputEncoding));
-            } else {
-                return null;
-            }
-        } catch (IOException e) {
-            throw new RuntimeException("Unable to create writer", e);
-        }
-    }
-
     private void updateSystemProperties() {
         if (systemProperties != null && !systemProperties.isEmpty()) {
             System.getProperties().putAll(systemProperties);
@@ -254,11 +208,12 @@ public class Main implements Callable<Integer> {
      *
      * @return List of data sources
      */
-    private List<String> getCombinedDataSources() {
-        return Stream.of(dataSources, sources)
-                .filter(Objects::nonNull)
-                .flatMap(Collection::stream)
-                .collect(Collectors.toList());
+    private List<String> getDataSources() {
+        if (sources != null) {
+            return new ArrayList<>(sources);
+        } else {
+            return Collections.emptyList();
+        }
     }
 
     private static List<File> getTemplateDirectories(String 
additionalTemplateDir) {
@@ -288,14 +243,4 @@ public class Main implements Callable<Integer> {
             return new Properties();
         }
     }
-
-    private static boolean isFileSource(String source) {
-        if (source.contains("file://")) {
-            return true;
-        } else if (source.contains("://")) {
-            return false;
-        } else {
-            return true;
-        }
-    }
 }
diff --git 
a/freemarker-generator-cli/src/main/java/org/apache/freemarker/generator/cli/config/OutputGeneratorsSupplier.java
 
b/freemarker-generator-cli/src/main/java/org/apache/freemarker/generator/cli/config/OutputGeneratorsSupplier.java
new file mode 100644
index 0000000..b49dce0
--- /dev/null
+++ 
b/freemarker-generator-cli/src/main/java/org/apache/freemarker/generator/cli/config/OutputGeneratorsSupplier.java
@@ -0,0 +1,107 @@
+package org.apache.freemarker.generator.cli.config;
+
+import org.apache.freemarker.generator.base.datasource.DataSource;
+import org.apache.freemarker.generator.base.datasource.DataSourcesSupplier;
+import org.apache.freemarker.generator.base.output.OutputGenerator;
+import org.apache.freemarker.generator.base.template.TemplateTransformation;
+import 
org.apache.freemarker.generator.base.template.TemplateTransformationsBuilder;
+import org.apache.freemarker.generator.cli.picocli.OutputGeneratorDefinition;
+import org.apache.freemarker.generator.cli.picocli.TemplateOutputDefinition;
+import org.apache.freemarker.generator.cli.picocli.TemplateSourceDefinition;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+import java.util.Map;
+import java.util.function.Supplier;
+import java.util.stream.Collectors;
+
+public class OutputGeneratorsSupplier implements 
Supplier<List<OutputGenerator>> {
+
+    private final Settings settings;
+
+    public OutputGeneratorsSupplier(Settings settings) {
+        this.settings = settings;
+    }
+
+    @Override
+    public List<OutputGenerator> get() {
+        return settings.getOutputGeneratorDefinitions().stream()
+                .map(this::outputGenerator)
+                .flatMap(Collection::stream)
+                .collect(Collectors.toList());
+    }
+
+    private List<OutputGenerator> outputGenerator(OutputGeneratorDefinition 
definition) {
+        final List<OutputGenerator> result = new ArrayList<>();
+        final TemplateSourceDefinition templateSourceDefinition = 
definition.templateSourceDefinition;
+        final TemplateOutputDefinition templateOutputDefinition = 
definition.templateOutputDefinition;
+        final TemplateTransformationsBuilder builder = 
TemplateTransformationsBuilder.builder();
+
+        // set the template
+        if (templateSourceDefinition.isInteractiveTemplate()) {
+            
builder.setInteractiveTemplate(templateSourceDefinition.interactiveTemplate);
+        } else {
+            builder.addTemplateSource(templateSourceDefinition.template);
+        }
+
+        // set the writer
+        builder.setUserSuppliedWriter(settings.getCallerSuppliedWriter());
+
+        // set template output
+        if (templateOutputDefinition != null) {
+            builder.addOutputs(templateOutputDefinition.outputs);
+        }
+
+        // set template filter
+        if (definition.hasTemplateSourceIncludes()) {
+            
builder.addInclude(definition.getTemplateSourceFilterDefinition().templateIncludePatterns.get(0));
+        }
+
+        if (definition.hasTemplateSourceExcludes()) {
+            
builder.addExclude(definition.getTemplateSourceFilterDefinition().templateExcludePatterns.get(0));
+        }
+
+        final List<TemplateTransformation> templateTransformations = 
builder.build();
+
+        for (TemplateTransformation templateTransformation : 
templateTransformations) {
+            final OutputGenerator outputGenerator = new OutputGenerator(
+                    templateTransformation.getTemplateSource(),
+                    templateTransformation.getTemplateOutput(),
+                    dataSources(definition),
+                    dataModels(definition)
+            );
+            result.add(outputGenerator);
+        }
+
+        return result;
+    }
+
+    private List<DataSource> dataSources(OutputGeneratorDefinition 
outputGeneratorDefinition) {
+        final ArrayList<DataSource> result = new ArrayList<>();
+
+        final DataSourcesSupplier sharedDataSourcesSupplier = new 
DataSourcesSupplier(
+                settings.getSources(),
+                settings.getSourceIncludePattern(),
+                settings.getSourceExcludePattern(),
+                settings.getInputEncoding()
+        );
+
+        result.addAll(sharedDataSourcesSupplier.get());
+
+        final DataSourcesSupplier outputGeneratorDataSourcesSupplier = new 
DataSourcesSupplier(
+                outputGeneratorDefinition.getDataSources(),
+                settings.getSourceIncludePattern(),
+                settings.getSourceExcludePattern(),
+                settings.getInputEncoding()
+        );
+
+        result.addAll(outputGeneratorDataSourcesSupplier.get());
+
+        return result;
+    }
+
+    private Map<String, Object> dataModels(OutputGeneratorDefinition 
outputGeneratorDefinition) {
+        return new 
DataModelSupplier(outputGeneratorDefinition.getDataModels()).get();
+    }
+}
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 427fd93..8959a3f 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,9 +17,9 @@
 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;
+import org.apache.freemarker.generator.cli.picocli.OutputGeneratorDefinition;
 
 import java.io.File;
 import java.io.Writer;
@@ -38,7 +38,6 @@ import static java.util.Objects.requireNonNull;
 import static 
org.apache.freemarker.generator.base.FreeMarkerConstants.Configuration.LOCALE_KEY;
 import static 
org.apache.freemarker.generator.base.FreeMarkerConstants.DEFAULT_CHARSET;
 import static 
org.apache.freemarker.generator.base.FreeMarkerConstants.DEFAULT_LOCALE;
-import static org.apache.freemarker.generator.base.util.StringUtils.isEmpty;
 
 /**
  * Capture all the settings required for rendering a FreeMarker template.
@@ -49,22 +48,22 @@ public class Settings {
     private final Properties configuration;
 
     /** Command line arguments */
-    private final List<String> args;
+    private final List<String> commandLineArgs;
 
     /** List of FreeMarker template directories to be passed to FreeMarker 
<code>TemplateLoader</code> */
     private final List<File> templateDirectories;
 
-    /** List of template to be loaded and rendered */
-    private final List<String> templates;
+    /** User-provided output generators (transformations) */
+    private final List<OutputGeneratorDefinition> outputGeneratorDefinitions;
 
-    /** Template provided by the user interactively */
-    private final String interactiveTemplate;
+    /** List of additional sources provided by positional parameters */
+    private final List<String> sources;
 
-    /** Optional include pattern for recursive directly search of template 
files */
-    private final String templateFileIncludePattern;
+    /** Include pattern for sources */
+    private final String sourceIncludePattern;
 
-    /** Optional exclude pattern for recursive directly search of data source 
files */
-    private final String templateFileExcludePattern;
+    /** Exclude pattern for sources */
+    private final String sourceExcludePattern;
 
     /** Encoding of input files */
     private final Charset inputEncoding;
@@ -75,81 +74,53 @@ public class Settings {
     /** Enable verbose mode (currently not used) **/
     private final boolean verbose;
 
-    /** 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;
-
-    /** Optional exclude pattern for recursive directly search of data source 
files */
-    private final String dataSourceExcludePattern;
-
     /** The locale used for rendering the template */
     private final Locale locale;
 
     /** Read from stdin? */
     private final boolean isReadFromStdin;
 
-    /** User-supplied list of data sources or directories */
-    private final List<String> dataSources;
-
-    /** User-supplied list of data sources directly exposed in the data model 
*/
-    private final List<String> dataModels;
-
     /** User-supplied parameters */
     private final Map<String, Object> userParameters;
 
     /** User-supplied system properties */
     private final Properties userSystemProperties;
 
-    /** The writer used for rendering templates, e.g. stdout or a file writer 
*/
-    private final Writer writer;
+    /** Caller-supplied writer */
+    private final Writer callerSuppliedWriter;
 
     private Settings(
             Properties configuration,
-            List<String> args,
+            List<String> commandLineArgs,
             List<File> templateDirectories,
-            List<String> templates,
-            String interactiveTemplate,
-            String templateFileIncludePattern,
-            String templateFileExcludePattern,
+            List<OutputGeneratorDefinition> outputGeneratorDefinitions,
+            List<String> sources,
+            String sourceIncludePattern,
+            String sourceExcludePattern,
             Charset inputEncoding,
             Charset outputEncoding,
             boolean verbose,
-            List<String> outputs,
-            String dataSourceIncludePattern,
-            String dataSourceExcludePattern,
             Locale locale,
             boolean isReadFromStdin,
-            List<String> dataSources,
-            List<String> dataModels,
             Map<String, Object> userParameters,
             Properties userSystemProperties,
-            Writer writer) {
-        if ((templates == null || templates.isEmpty()) && 
isEmpty(interactiveTemplate)) {
-            throw new IllegalArgumentException("Either 'template' or 
'interactiveTemplate' must be provided");
-        }
+            Writer callerSuppliedWriter) {
 
-        this.args = requireNonNull(args);
+        this.commandLineArgs = requireNonNull(commandLineArgs);
         this.templateDirectories = requireNonNull(templateDirectories);
-        this.templates = requireNonNull(templates);
-        this.interactiveTemplate = interactiveTemplate;
-        this.templateFileIncludePattern = templateFileIncludePattern;
-        this.templateFileExcludePattern = templateFileExcludePattern;
+        this.outputGeneratorDefinitions = 
requireNonNull(outputGeneratorDefinitions);
+        this.sources = requireNonNull(sources);
+        this.sourceIncludePattern = sourceIncludePattern;
+        this.sourceExcludePattern = sourceExcludePattern;
         this.inputEncoding = inputEncoding;
         this.outputEncoding = outputEncoding;
         this.verbose = verbose;
-        this.outputs = outputs;
-        this.dataSourceIncludePattern = dataSourceIncludePattern;
-        this.dataSourceExcludePattern = dataSourceExcludePattern;
         this.locale = requireNonNull(locale);
         this.isReadFromStdin = isReadFromStdin;
-        this.dataSources = requireNonNull(dataSources);
-        this.dataModels = requireNonNull(dataModels);
         this.userParameters = requireNonNull(userParameters);
         this.userSystemProperties = requireNonNull(userSystemProperties);
         this.configuration = requireNonNull(configuration);
-        this.writer = writer != null ? new NonClosableWriterWrapper(writer) : 
null;
+        this.callerSuppliedWriter = callerSuppliedWriter != null ? new 
NonClosableWriterWrapper(callerSuppliedWriter) : null;
     }
 
     public static SettingsBuilder builder() {
@@ -160,28 +131,28 @@ public class Settings {
         return configuration;
     }
 
-    public List<String> getArgs() {
-        return args;
+    public List<String> getCommandLineArgs() {
+        return commandLineArgs;
     }
 
     public List<File> getTemplateDirectories() {
         return templateDirectories;
     }
 
-    public List<String> getTemplates() {
-        return templates;
+    public List<OutputGeneratorDefinition> getOutputGeneratorDefinitions() {
+        return outputGeneratorDefinitions;
     }
 
-    public String getInteractiveTemplate() {
-        return interactiveTemplate;
+    public List<String> getSources() {
+        return sources;
     }
 
-    public String getTemplateFileIncludePattern() {
-        return templateFileIncludePattern;
+    public String getSourceIncludePattern() {
+        return sourceIncludePattern;
     }
 
-    public String getTemplateFileExcludePattern() {
-        return templateFileExcludePattern;
+    public String getSourceExcludePattern() {
+        return sourceExcludePattern;
     }
 
     public Charset getInputEncoding() {
@@ -200,18 +171,6 @@ public class Settings {
         return verbose;
     }
 
-    public List<String> getOutputs() {
-        return outputs;
-    }
-
-    public String getDataSourceIncludePattern() {
-        return dataSourceIncludePattern;
-    }
-
-    public String getDataSourceExcludePattern() {
-        return dataSourceExcludePattern;
-    }
-
     public Locale getLocale() {
         return locale;
     }
@@ -220,14 +179,6 @@ public class Settings {
         return isReadFromStdin;
     }
 
-    public List<String> getDataSources() {
-        return dataSources;
-    }
-
-    public List<String> getDataModels() {
-        return dataModels;
-    }
-
     public Map<String, Object> getUserParameters() {
         return userParameters;
     }
@@ -236,12 +187,8 @@ public class Settings {
         return userSystemProperties;
     }
 
-    public boolean hasOutputs() {
-        return ListUtils.isNotEmpty(outputs);
-    }
-
-    public Writer getWriter() {
-        return writer;
+    public Writer getCallerSuppliedWriter() {
+        return callerSuppliedWriter;
     }
 
     /**
@@ -252,84 +199,73 @@ public class Settings {
      */
     public Map<String, Object> toMap() {
         final Map<String, Object> result = new HashMap<>();
-        result.put(Model.FREEMARKER_CLI_ARGS, getArgs());
+        result.put(Model.FREEMARKER_CLI_ARGS, getCommandLineArgs());
         result.put(Model.FREEMARKER_LOCALE, getLocale());
         result.put(Model.FREEMARKER_TEMPLATE_DIRECTORIES, 
getTemplateDirectories());
         result.put(Model.FREEMARKER_USER_PARAMETERS, getUserParameters());
         result.put(Model.FREEMARKER_USER_SYSTEM_PROPERTIES, 
getUserSystemProperties());
-        result.put(Model.FREEMARKER_WRITER, getWriter());
+        result.put(Model.FREEMARKER_WRITER, getCallerSuppliedWriter());
         return result;
     }
 
-    public boolean isInteractiveTemplate() {
-        return interactiveTemplate != null;
-    }
-
     @Override
     public String toString() {
         return "Settings{" +
                 "configuration=" + configuration +
-                ", args=" + args +
+                ", commandLineArgs=" + commandLineArgs +
                 ", templateDirectories=" + templateDirectories +
-                ", templateName=s'" + templates + '\'' +
-                ", interactiveTemplate='" + interactiveTemplate + '\'' +
-                ", templateFileIncludePattern='" + templateFileIncludePattern 
+ '\'' +
-                ", templateFileExcludePattern='" + templateFileExcludePattern 
+ '\'' +
+                ", outputGeneratorDefinitions=" + outputGeneratorDefinitions +
                 ", inputEncoding=" + inputEncoding +
                 ", outputEncoding=" + outputEncoding +
                 ", verbose=" + verbose +
-                ", outputs=" + outputs +
-                ", include='" + dataSourceIncludePattern + '\'' +
-                ", exclude='" + dataSourceExcludePattern + '\'' +
                 ", locale=" + locale +
                 ", isReadFromStdin=" + isReadFromStdin +
-                ", dataSources=" + dataSources +
                 ", userParameters=" + userParameters +
                 ", userSystemProperties=" + userSystemProperties +
+                ", writer=" + callerSuppliedWriter +
+                ", templateEncoding=" + getTemplateEncoding() +
+                ", readFromStdin=" + isReadFromStdin() +
+                ", toMap=" + toMap() +
                 '}';
     }
 
     public static class SettingsBuilder {
-        private List<String> args;
+        private List<String> commandLineArgs;
         private List<File> templateDirectories;
-        private List<String> templateNames;
-        private String interactiveTemplate;
-        private String templateFileIncludePattern;
-        private String templateFileExcludePattern;
+        private List<OutputGeneratorDefinition> outputGeneratorDefinitions;
+        private List<String> sources;
+        private String sourceIncludePattern;
+        private String sourceExcludePattern;
         private String inputEncoding;
         private String outputEncoding;
         private boolean verbose;
-        private List<String> outputs;
-        private String dataSourceIncludePattern;
-        private String dataSourceExcludePattern;
         private String locale;
         private boolean isReadFromStdin;
-        private List<String> dataSources;
-        private List<String> dataModels;
         private Map<String, Object> parameters;
         private Properties systemProperties;
         private Properties configuration;
-        private Writer writer;
+        private Writer callerSuppliedWriter;
 
         private SettingsBuilder() {
-            this.args = emptyList();
+            this.commandLineArgs = emptyList();
+            this.templateDirectories = emptyList();
+            this.outputGeneratorDefinitions = emptyList();
+            this.sources = emptyList();
+            this.sourceIncludePattern = null;
+            this.sourceExcludePattern = null;
             this.configuration = new Properties();
             this.locale = DEFAULT_LOCALE.toString();
             this.parameters = new HashMap<>();
             this.systemProperties = new Properties();
             this.setInputEncoding(DEFAULT_CHARSET.name());
             this.setOutputEncoding(DEFAULT_CHARSET.name());
-            this.templateNames = new ArrayList<>();
-            this.dataSources = emptyList();
-            this.dataModels = emptyList();
-            this.templateDirectories = emptyList();
         }
 
-        public SettingsBuilder setArgs(String[] args) {
-            if (args == null) {
-                this.args = emptyList();
+        public SettingsBuilder setCommandLineArgs(String[] commandLineArgs) {
+            if (commandLineArgs == null) {
+                this.commandLineArgs = emptyList();
             } else {
-                this.args = Arrays.asList(args);
+                this.commandLineArgs = Arrays.asList(commandLineArgs);
             }
 
             return this;
@@ -340,25 +276,23 @@ public class Settings {
             return this;
         }
 
-        public SettingsBuilder setTemplateNames(List<String> templateNames) {
-            if (templateNames != null) {
-                this.templateNames = templateNames;
-            }
+        public SettingsBuilder 
setOutputGeneratorDefinitions(List<OutputGeneratorDefinition> 
outputGeneratorDefinitions) {
+            this.outputGeneratorDefinitions = new 
ArrayList<>(outputGeneratorDefinitions);
             return this;
         }
 
-        public SettingsBuilder setInteractiveTemplate(String 
interactiveTemplate) {
-            this.interactiveTemplate = interactiveTemplate;
+        public SettingsBuilder setSources(List<String> sources) {
+            this.sources = new ArrayList<>(sources);
             return this;
         }
 
-        public SettingsBuilder setTemplateFileIncludePattern(String 
templateFileIncludePattern) {
-            this.templateFileIncludePattern = templateFileIncludePattern;
+        public SettingsBuilder setSourceIncludePattern(String 
sourceIncludePattern) {
+            this.sourceIncludePattern = sourceIncludePattern;
             return this;
         }
 
-        public SettingsBuilder setTemplateFileExcludePattern(String 
templateFileExcludePattern) {
-            this.templateFileExcludePattern = templateFileExcludePattern;
+        public SettingsBuilder setSourceExcludePattern(String 
sourceExcludePattern) {
+            this.sourceExcludePattern = sourceExcludePattern;
             return this;
         }
 
@@ -381,23 +315,6 @@ public class Settings {
             return this;
         }
 
-        public SettingsBuilder setOutputs(List<String> outputs) {
-            if (outputs != null) {
-                this.outputs = outputs;
-            }
-            return this;
-        }
-
-        public SettingsBuilder setDataSourceIncludePattern(String 
dataSourceIncludePattern) {
-            this.dataSourceIncludePattern = dataSourceIncludePattern;
-            return this;
-        }
-
-        public SettingsBuilder setDataSourceExcludePattern(String 
dataSourceExcludePattern) {
-            this.dataSourceExcludePattern = dataSourceExcludePattern;
-            return this;
-        }
-
         public SettingsBuilder setLocale(String locale) {
             this.locale = locale;
             return this;
@@ -408,20 +325,6 @@ public class Settings {
             return this;
         }
 
-        public SettingsBuilder setDataSources(List<String> dataSources) {
-            if (dataSources != null) {
-                this.dataSources = dataSources;
-            }
-            return this;
-        }
-
-        public SettingsBuilder setDataModels(List<String> dataModels) {
-            if (dataModels != null) {
-                this.dataModels = dataModels;
-            }
-            return this;
-        }
-
         public SettingsBuilder setParameters(Map<String, Object> parameters) {
             if (parameters != null) {
                 this.parameters = parameters;
@@ -443,8 +346,8 @@ public class Settings {
             return this;
         }
 
-        public SettingsBuilder setWriter(Writer writer) {
-            this.writer = writer;
+        public SettingsBuilder setCallerSuppliedWriter(Writer 
callerSuppliedWriter) {
+            this.callerSuppliedWriter = callerSuppliedWriter;
             return this;
         }
 
@@ -455,25 +358,20 @@ public class Settings {
 
             return new Settings(
                     configuration,
-                    args,
+                    commandLineArgs,
                     templateDirectories,
-                    templateNames,
-                    interactiveTemplate,
-                    templateFileIncludePattern,
-                    templateFileExcludePattern,
+                    outputGeneratorDefinitions,
+                    sources,
+                    sourceIncludePattern,
+                    sourceExcludePattern,
                     inputEncoding,
                     outputEncoding,
                     verbose,
-                    outputs,
-                    dataSourceIncludePattern,
-                    dataSourceExcludePattern,
                     LocaleUtils.parseLocale(currLocale),
                     isReadFromStdin,
-                    dataSources,
-                    dataModels,
                     parameters,
                     systemProperties,
-                    writer
+                    callerSuppliedWriter
             );
         }
 
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 a7bc21d..793318e 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
@@ -17,15 +17,10 @@
 package org.apache.freemarker.generator.cli.config;
 
 import freemarker.cache.TemplateLoader;
-import org.apache.freemarker.generator.base.datasource.DataSourcesSupplier;
 import org.apache.freemarker.generator.base.file.PropertiesClassPathSupplier;
 import org.apache.freemarker.generator.base.file.PropertiesFileSystemSupplier;
 import org.apache.freemarker.generator.base.file.PropertiesSupplier;
-import org.apache.freemarker.generator.base.template.TemplateTransformation;
-import 
org.apache.freemarker.generator.base.template.TemplateTransformationsBuilder;
 
-import java.util.List;
-import java.util.Map;
 import java.util.function.Supplier;
 
 /**
@@ -53,30 +48,8 @@ public class Suppliers {
         return new ToolsSupplier(settings.getConfiguration(), 
settings.toMap());
     }
 
-    public static DataSourcesSupplier dataSourcesSupplier(Settings settings) {
-        return new DataSourcesSupplier(settings.getDataSources(),
-                settings.getDataSourceIncludePattern(),
-                settings.getDataSourceExcludePattern(),
-                settings.getInputEncoding());
-    }
-
-    public static DataModelSupplier dataModelSupplier(Settings settings) {
-        return new DataModelSupplier(settings.getDataModels());
-    }
-
-    public static Supplier<Map<String, Object>> parameterSupplier(Settings 
settings) {
-        return settings::getUserParameters;
-    }
-
-    public static Supplier<List<TemplateTransformation>> 
templateTransformationsSupplier(Settings settings) {
-        return () -> TemplateTransformationsBuilder.builder()
-                .setInteractiveTemplate(settings.getInteractiveTemplate())
-                .addTemplateSources(settings.getTemplates())
-                .addInclude(settings.getTemplateFileIncludePattern())
-                .addExclude(settings.getTemplateFileExcludePattern())
-                .addOutputs(settings.getOutputs())
-                .setWriter(settings.getWriter())
-                .build();
+    public static OutputGeneratorsSupplier outputGeneratorsSupplier(Settings 
settings) {
+        return new OutputGeneratorsSupplier(settings);
     }
 
     public static PropertiesSupplier propertiesSupplier(String fileName) {
diff --git 
a/freemarker-generator-cli/src/main/java/org/apache/freemarker/generator/cli/picocli/DataModelDefinition.java
 
b/freemarker-generator-cli/src/main/java/org/apache/freemarker/generator/cli/picocli/DataModelDefinition.java
new file mode 100644
index 0000000..0c4ffcf
--- /dev/null
+++ 
b/freemarker-generator-cli/src/main/java/org/apache/freemarker/generator/cli/picocli/DataModelDefinition.java
@@ -0,0 +1,30 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.freemarker.generator.cli.picocli;
+
+import picocli.CommandLine.Option;
+
+import java.util.List;
+
+/**
+ * User-supplied list of data sources directly exposed in the data model
+ */
+public class DataModelDefinition {
+
+    @Option(names = { "-m", "--data-model" }, description = "data model used 
for rendering")
+    public List<String> dataModels;
+}
diff --git 
a/freemarker-generator-cli/src/main/java/org/apache/freemarker/generator/cli/picocli/DataSourceDefinition.java
 
b/freemarker-generator-cli/src/main/java/org/apache/freemarker/generator/cli/picocli/DataSourceDefinition.java
new file mode 100644
index 0000000..3be4f08
--- /dev/null
+++ 
b/freemarker-generator-cli/src/main/java/org/apache/freemarker/generator/cli/picocli/DataSourceDefinition.java
@@ -0,0 +1,28 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.freemarker.generator.cli.picocli;
+
+import picocli.CommandLine.Option;
+
+import java.util.List;
+
+/** User-supplied list of data sources or directories */
+public class DataSourceDefinition {
+
+    @Option(names = { "-s", "--data-source" }, description = "data source used 
for rendering")
+    public List<String> dataSources;
+}
diff --git 
a/freemarker-generator-cli/src/main/java/org/apache/freemarker/generator/cli/picocli/DataSourceFilterDefinition.java
 
b/freemarker-generator-cli/src/main/java/org/apache/freemarker/generator/cli/picocli/DataSourceFilterDefinition.java
new file mode 100644
index 0000000..d1c3c44
--- /dev/null
+++ 
b/freemarker-generator-cli/src/main/java/org/apache/freemarker/generator/cli/picocli/DataSourceFilterDefinition.java
@@ -0,0 +1,30 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.freemarker.generator.cli.picocli;
+
+import picocli.CommandLine.Option;
+
+import java.util.List;
+
+public class DataSourceFilterDefinition {
+
+    @Option(names = { "--data-source-include" }, description = "data source 
include pattern")
+    public List<String> dataSourceIncludePatterns;
+
+    @Option(names = { "--data-source-exclude" }, description = "data source 
exclude pattern")
+    public List<String> dataSourceExcludePatterns;
+}
diff --git 
a/freemarker-generator-cli/src/main/java/org/apache/freemarker/generator/cli/picocli/OutputGeneratorDefinition.java
 
b/freemarker-generator-cli/src/main/java/org/apache/freemarker/generator/cli/picocli/OutputGeneratorDefinition.java
new file mode 100644
index 0000000..d35c277
--- /dev/null
+++ 
b/freemarker-generator-cli/src/main/java/org/apache/freemarker/generator/cli/picocli/OutputGeneratorDefinition.java
@@ -0,0 +1,117 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.freemarker.generator.cli.picocli;
+
+import picocli.CommandLine;
+import picocli.CommandLine.ArgGroup;
+import picocli.CommandLine.ParameterException;
+
+import java.util.List;
+
+import static java.util.Collections.emptyList;
+
+public class OutputGeneratorDefinition {
+
+    @ArgGroup(multiplicity = "1")
+    public TemplateSourceDefinition templateSourceDefinition;
+
+    @ArgGroup(exclusive = false)
+    public TemplateSourceFilterDefinition templateSourceFilterDefinition;
+
+    @ArgGroup(exclusive = false)
+    public TemplateOutputDefinition templateOutputDefinition;
+
+    @ArgGroup(exclusive = false)
+    public DataSourceDefinition dataSourceDefinition;
+
+    @ArgGroup(exclusive = false)
+    public DataModelDefinition dataModelDefinition;
+
+    public void validate(CommandLine commandLine) {
+        if (templateOutputDefinition != null && 
templateOutputDefinition.outputs.size() > 1) {
+            throw new ParameterException(commandLine, "More than one output 
defined for a template");
+        }
+
+        if (dataSourceDefinition != null && dataSourceDefinition.dataSources 
!= null) {
+            for (String source : dataSourceDefinition.dataSources) {
+                if (isFileSource(source) && (source.contains("*") || 
source.contains("?"))) {
+                    throw new ParameterException(commandLine, "No wildcards 
supported for data source: " + source);
+                }
+            }
+
+        }
+    }
+
+    public List<String> getDataSources() {
+        if (dataSourceDefinition != null && dataSourceDefinition.dataSources 
!= null) {
+            return dataSourceDefinition.dataSources;
+        } else {
+            return emptyList();
+        }
+    }
+
+    public List<String> getDataModels() {
+        if (dataModelDefinition != null && dataModelDefinition.dataModels != 
null) {
+            return dataModelDefinition.dataModels;
+        } else {
+            return emptyList();
+        }
+
+    }
+
+    public TemplateSourceDefinition getTemplateSourceDefinition() {
+        return templateSourceDefinition;
+    }
+
+    public TemplateSourceFilterDefinition getTemplateSourceFilterDefinition() {
+        return templateSourceFilterDefinition;
+    }
+
+    public TemplateOutputDefinition getTemplateOutputDefinition() {
+        return templateOutputDefinition;
+    }
+
+    public DataSourceDefinition getDataSourceDefinition() {
+        return dataSourceDefinition;
+    }
+
+    public DataModelDefinition getDataModelDefinition() {
+        return dataModelDefinition;
+    }
+
+    public boolean hasTemplateSourceIncludes() {
+        return getTemplateSourceFilterDefinition() != null &&
+                getTemplateSourceFilterDefinition().templateIncludePatterns != 
null &&
+                
!getTemplateSourceFilterDefinition().templateIncludePatterns.isEmpty();
+    }
+
+    public boolean hasTemplateSourceExcludes() {
+        return getTemplateSourceFilterDefinition() != null &&
+                getTemplateSourceFilterDefinition().templateExcludePatterns != 
null &&
+                
!getTemplateSourceFilterDefinition().templateExcludePatterns.isEmpty();
+    }
+
+    private static boolean isFileSource(String source) {
+        if (source.contains("file://")) {
+            return true;
+        } else if (source.contains("://")) {
+            return false;
+        } else {
+            return true;
+        }
+    }
+}
diff --git 
a/freemarker-generator-cli/src/main/java/org/apache/freemarker/generator/cli/picocli/TemplateOutputDefinition.java
 
b/freemarker-generator-cli/src/main/java/org/apache/freemarker/generator/cli/picocli/TemplateOutputDefinition.java
new file mode 100644
index 0000000..59627e3
--- /dev/null
+++ 
b/freemarker-generator-cli/src/main/java/org/apache/freemarker/generator/cli/picocli/TemplateOutputDefinition.java
@@ -0,0 +1,31 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.freemarker.generator.cli.picocli;
+
+import picocli.CommandLine.Option;
+
+import java.util.List;
+
+public class TemplateOutputDefinition {
+
+    @Option(names = { "-o", "--output" }, description = "output files or 
directories")
+    public List<String> outputs;
+
+    public boolean hasOutput() {
+        return outputs != null && !outputs.isEmpty();
+    }
+}
diff --git 
a/freemarker-generator-cli/src/main/java/org/apache/freemarker/generator/cli/picocli/TemplateSourceDefinition.java
 
b/freemarker-generator-cli/src/main/java/org/apache/freemarker/generator/cli/picocli/TemplateSourceDefinition.java
new file mode 100644
index 0000000..6193c7b
--- /dev/null
+++ 
b/freemarker-generator-cli/src/main/java/org/apache/freemarker/generator/cli/picocli/TemplateSourceDefinition.java
@@ -0,0 +1,32 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.freemarker.generator.cli.picocli;
+
+import picocli.CommandLine.Option;
+
+public class TemplateSourceDefinition {
+
+    @Option(names = { "-t", "--template" }, description = "templates to 
process")
+    public String template;
+
+    @Option(names = { "-i", "--interactive" }, description = "interactive 
template to process")
+    public String interactiveTemplate;
+
+    public boolean isInteractiveTemplate() {
+        return interactiveTemplate != null && !interactiveTemplate.isEmpty();
+    }
+}
diff --git 
a/freemarker-generator-cli/src/main/java/org/apache/freemarker/generator/cli/picocli/TemplateSourceFilterDefinition.java
 
b/freemarker-generator-cli/src/main/java/org/apache/freemarker/generator/cli/picocli/TemplateSourceFilterDefinition.java
new file mode 100644
index 0000000..68cce4e
--- /dev/null
+++ 
b/freemarker-generator-cli/src/main/java/org/apache/freemarker/generator/cli/picocli/TemplateSourceFilterDefinition.java
@@ -0,0 +1,31 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.freemarker.generator.cli.picocli;
+
+import picocli.CommandLine.Option;
+
+import java.util.List;
+
+/** Include/exclude pattern when processing template directories */
+public class TemplateSourceFilterDefinition {
+
+    @Option(names = { "--template-include" }, description = "template include 
pattern")
+    public List<String> templateIncludePatterns;
+
+    @Option(names = { "--template-exclude" }, description = "template exclude 
pattern")
+    public List<String> templateExcludePatterns;
+}
diff --git 
a/freemarker-generator-cli/src/main/java/org/apache/freemarker/generator/cli/task/FreeMarkerTask.java
 
b/freemarker-generator-cli/src/main/java/org/apache/freemarker/generator/cli/task/FreeMarkerTask.java
index cc20d76..19a4634 100644
--- 
a/freemarker-generator-cli/src/main/java/org/apache/freemarker/generator/cli/task/FreeMarkerTask.java
+++ 
b/freemarker-generator-cli/src/main/java/org/apache/freemarker/generator/cli/task/FreeMarkerTask.java
@@ -20,104 +20,70 @@ import freemarker.template.Configuration;
 import freemarker.template.Template;
 import freemarker.template.TemplateException;
 import org.apache.commons.io.FileUtils;
-import org.apache.freemarker.generator.base.FreeMarkerConstants.Location;
-import org.apache.freemarker.generator.base.datasource.DataSource;
-import org.apache.freemarker.generator.base.datasource.DataSourceFactory;
 import org.apache.freemarker.generator.base.datasource.DataSources;
+import org.apache.freemarker.generator.base.output.OutputGenerator;
 import org.apache.freemarker.generator.base.template.TemplateOutput;
 import org.apache.freemarker.generator.base.template.TemplateSource;
-import org.apache.freemarker.generator.base.template.TemplateTransformation;
-import org.apache.freemarker.generator.base.util.UriUtils;
-import org.apache.freemarker.generator.cli.config.Settings;
 
 import java.io.BufferedWriter;
 import java.io.File;
 import java.io.FileWriter;
 import java.io.IOException;
 import java.io.Writer;
-import java.net.URI;
-import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
 import java.util.concurrent.Callable;
 import java.util.function.Supplier;
 
-import static java.nio.charset.StandardCharsets.UTF_8;
 import static java.util.Objects.requireNonNull;
-import static 
org.apache.freemarker.generator.base.FreeMarkerConstants.DEFAULT_GROUP;
-import static 
org.apache.freemarker.generator.base.FreeMarkerConstants.Location.STDIN;
 import static 
org.apache.freemarker.generator.base.FreeMarkerConstants.Model.DATASOURCES;
-import static 
org.apache.freemarker.generator.base.mime.Mimetypes.MIME_TEXT_PLAIN;
-import static 
org.apache.freemarker.generator.cli.config.Suppliers.configurationSupplier;
-import static 
org.apache.freemarker.generator.cli.config.Suppliers.dataModelSupplier;
-import static 
org.apache.freemarker.generator.cli.config.Suppliers.dataSourcesSupplier;
-import static 
org.apache.freemarker.generator.cli.config.Suppliers.parameterSupplier;
-import static 
org.apache.freemarker.generator.cli.config.Suppliers.templateTransformationsSupplier;
-import static 
org.apache.freemarker.generator.cli.config.Suppliers.toolsSupplier;
 
 /**
  * Renders a FreeMarker template.
+ * <p>
+ * Implementation notes
+ * <ul>
+ *     <li>configurationSupplier provides the tools</li>
+ *     <li>outputGeneratorsSupplier handles STDIN already</li>
+ * </ul>
  */
 public class FreeMarkerTask implements Callable<Integer> {
 
     private static final int SUCCESS = 0;
 
-    private final Settings settings;
-    private final Supplier<Map<String, Object>> toolsSupplier;
-    private final Supplier<List<DataSource>> dataSourcesSupplier;
-    private final Supplier<Map<String, Object>> dataModelsSupplier;
-    private final Supplier<Map<String, Object>> parameterModelSupplier;
     private final Supplier<Configuration> configurationSupplier;
-    private final Supplier<List<TemplateTransformation>> 
templateTransformationsSupplier;
-
-
-    public FreeMarkerTask(Settings settings) {
-        this(settings,
-                configurationSupplier(settings),
-                templateTransformationsSupplier(settings),
-                dataSourcesSupplier(settings),
-                dataModelSupplier(settings),
-                parameterSupplier(settings),
-                toolsSupplier(settings)
-        );
-    }
-
-    public FreeMarkerTask(Settings settings,
-                          Supplier<Configuration> configurationSupplier,
-                          Supplier<List<TemplateTransformation>> 
templateTransformationsSupplier,
-                          Supplier<List<DataSource>> dataSourcesSupplier,
-                          Supplier<Map<String, Object>> dataModelsSupplier,
-                          Supplier<Map<String, Object>> parameterModelSupplier,
-                          Supplier<Map<String, Object>> toolsSupplier) {
-        this.settings = requireNonNull(settings);
-        this.toolsSupplier = requireNonNull(toolsSupplier);
-        this.dataSourcesSupplier = requireNonNull(dataSourcesSupplier);
-        this.dataModelsSupplier = requireNonNull(dataModelsSupplier);
-        this.parameterModelSupplier = requireNonNull(parameterModelSupplier);
-        this.configurationSupplier = requireNonNull(configurationSupplier);
-        this.templateTransformationsSupplier = 
requireNonNull(templateTransformationsSupplier);
+    private final Supplier<List<OutputGenerator>> outputGeneratorsSupplier;
+    private final Supplier<Map<String, Object>> sharedDataModelSupplier;
+
+    public FreeMarkerTask(Supplier<Configuration> configurationSupplier,
+                          Supplier<List<OutputGenerator>> 
outputGeneratorsSupplier,
+                          Supplier<Map<String, Object>> 
sharedDataModelSupplier) {
+        this.configurationSupplier = requireNonNull(configurationSupplier, 
"configurationSupplier");
+        this.outputGeneratorsSupplier = 
requireNonNull(outputGeneratorsSupplier, "outputGeneratorsSupplier");
+        this.sharedDataModelSupplier = requireNonNull(sharedDataModelSupplier, 
"sharedDataModelSupplier");
     }
 
     @Override
     public Integer call() {
-        try {
-            final Configuration configuration = configurationSupplier.get();
-            final List<TemplateTransformation> templateTransformations = 
templateTransformationsSupplier.get();
-            final DataSources dataSources = dataSources(settings, 
dataSourcesSupplier);
-            final Map<String, Object> dataModel = dataModel(dataSources, 
parameterModelSupplier, dataModelsSupplier, toolsSupplier);
-            templateTransformations.forEach(t -> process(configuration, t, 
dataModel));
-            return SUCCESS;
-        } catch (RuntimeException e) {
-            throw new RuntimeException("Failed to process templates", e);
-        }
+        final Configuration configuration = configurationSupplier.get();
+        final List<OutputGenerator> outputGenerators = 
outputGeneratorsSupplier.get();
+        final Map<String, Object> sharedDataModel = 
sharedDataModelSupplier.get();
+        outputGenerators.forEach(outputGenerator -> process(configuration, 
outputGenerator, sharedDataModel));
+        return SUCCESS;
     }
 
     private void process(Configuration configuration,
-                         TemplateTransformation templateTransformation,
-                         Map<String, Object> dataModel) {
-        final TemplateSource templateSource = 
templateTransformation.getTemplateSource();
-        final TemplateOutput templateOutput = 
templateTransformation.getTemplateOutput();
+                         OutputGenerator outputGenerator,
+                         Map<String, Object> sharedDataModelMap) {
+
+        final TemplateSource templateSource = 
outputGenerator.getTemplateSource();
+        final TemplateOutput templateOutput = 
outputGenerator.getTemplateOutput();
+        final DataSources dataSources = new 
DataSources(outputGenerator.getDataSources());
+        final Map<String, Object> dataModelMap = 
outputGenerator.getVariables();
+        final Map<String, Object> dataModel = toDataModel(dataSources, 
dataModelMap, sharedDataModelMap);
+
         try (Writer writer = writer(templateOutput)) {
             final Template template = template(configuration, templateSource);
             template.process(dataModel, writer);
@@ -126,34 +92,14 @@ public class FreeMarkerTask implements Callable<Integer> {
         }
     }
 
-    private static DataSources dataSources(Settings settings, 
Supplier<List<DataSource>> dataSourcesSupplier) {
-        final List<DataSource> dataSources = new 
ArrayList<>(dataSourcesSupplier.get());
-
-        // Add optional data source from STDIN at the start of the list since
-        // this allows easy sequence slicing in FreeMarker.
-        if (settings.isReadFromStdin()) {
-            final URI uri = UriUtils.toUri(Location.SYSTEM, "in");
-            dataSources.add(0, DataSourceFactory.fromInputStream(STDIN, 
DEFAULT_GROUP, uri, System.in, MIME_TEXT_PLAIN, UTF_8));
-        }
-
-        return new DataSources(dataSources);
-    }
-
-    private static Map<String, Object> dataModel(
-            DataSources dataSources,
-            Supplier<Map<String, Object>> parameterModelSupplier,
-            Supplier<Map<String, Object>> dataModelsSupplier,
-            Supplier<Map<String, Object>> tools) {
+    @SafeVarargs
+    private static Map<String, Object> toDataModel(DataSources dataSources, 
Map<String, Object>... maps) {
         final Map<String, Object> result = new HashMap<>();
-        result.putAll(dataModelsSupplier.get());
+        Arrays.stream(maps).forEach(result::putAll);
         result.put(DATASOURCES, dataSources);
-        result.putAll(parameterModelSupplier.get());
-        result.putAll(tools.get());
         return result;
     }
 
-    // ==============================================================
-
     private static Writer writer(TemplateOutput templateOutput) throws 
IOException {
         if (templateOutput.getWriter() != null) {
             return templateOutput.getWriter();
@@ -177,7 +123,7 @@ public class FreeMarkerTask implements Callable<Integer> {
     private static Template template(Configuration configuration, 
TemplateSource templateSource) {
         switch (templateSource.getOrigin()) {
             case TEMPLATE_LOADER:
-                return fromTemplateLoader(configuration, templateSource);
+                return fromTemplatePath(configuration, templateSource);
             case TEMPLATE_CODE:
                 return fromTemplateCode(configuration, templateSource);
             default:
@@ -185,7 +131,7 @@ public class FreeMarkerTask implements Callable<Integer> {
         }
     }
 
-    private static Template fromTemplateLoader(Configuration configuration, 
TemplateSource templateSource) {
+    private static Template fromTemplatePath(Configuration configuration, 
TemplateSource templateSource) {
         final String path = templateSource.getPath();
         try {
             return configuration.getTemplate(path);
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 34077d5..4cf5be3 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
@@ -16,12 +16,15 @@
  */
 package org.apache.freemarker.generator.cli;
 
+import org.apache.freemarker.generator.cli.picocli.OutputGeneratorDefinition;
 import org.junit.Test;
 import picocli.CommandLine;
-import picocli.CommandLine.ParameterException;
+
+import java.util.List;
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
 
 public class PicocliTest {
 
@@ -35,8 +38,10 @@ public class PicocliTest {
 
     @Test
     public void shouldParseSinglePositionalParameter() {
-        assertEquals(ANY_FILE_URI, parse("-t", ANY_TEMPLATE, 
ANY_FILE_URI).sources.get(0));
-        assertNull(ANY_FILE, parse("-t", ANY_TEMPLATE, 
ANY_FILE_URI).dataSources);
+        final Main main = parse("-t", ANY_TEMPLATE, ANY_FILE_URI);
+
+        assertEquals(1, main.outputGeneratorDefinitions.size());
+        assertEquals(ANY_FILE_URI, main.sources.get(0));
     }
 
     @Test
@@ -53,33 +58,43 @@ public class PicocliTest {
 
     @Test
     public void shouldParseSingleNamedDataSource() {
-        assertEquals(ANY_FILE, parse("-t", ANY_TEMPLATE, 
ANY_FILE).sources.get(0));
-        assertEquals(ANY_FILE, parse("-t", ANY_TEMPLATE, "-s", 
ANY_FILE).dataSources.get(0));
-        assertEquals(ANY_FILE, parse("-t", ANY_TEMPLATE, "--data-source", 
ANY_FILE).dataSources.get(0));
-        assertEquals(ANY_FILE_URI, parse("-t", ANY_TEMPLATE, "--data-source", 
ANY_FILE_URI).dataSources.get(0));
+        assertEquals(ANY_FILE, parse("-t", ANY_TEMPLATE, "-s", 
ANY_FILE).outputGeneratorDefinitions.get(0)
+                .getDataSources()
+                .get(0));
+        assertEquals(ANY_FILE, parse("-t", ANY_TEMPLATE, "--data-source", 
ANY_FILE).outputGeneratorDefinitions.get(0)
+                .getDataSources()
+                .get(0));
+        assertEquals(ANY_FILE_URI, parse("-t", ANY_TEMPLATE, "--data-source", 
ANY_FILE_URI).outputGeneratorDefinitions.get(0)
+                .getDataSources()
+                .get(0));
     }
 
     @Test
-    public void shouldParseMultipleNamedDataSource() {
+    public void shouldParseMultipleNamedDataSources() {
         final Main main = parse("-t", ANY_TEMPLATE, "-s", ANY_FILE, 
"--data-source", OTHER_FILE_URI);
 
-        assertEquals(ANY_FILE, main.dataSources.get(0));
-        assertEquals(OTHER_FILE_URI, main.dataSources.get(1));
+        assertEquals(ANY_FILE, 
main.outputGeneratorDefinitions.get(0).getDataSources().get(0));
+        assertEquals(OTHER_FILE_URI, 
main.outputGeneratorDefinitions.get(0).getDataSources().get(1));
         assertNull(main.sources);
     }
 
     @Test
     public void shouldParseSingleDataModel() {
-        assertEquals(ANY_FILE, parse("-t", ANY_TEMPLATE, "-m", 
ANY_FILE).dataModels.get(0));
-        assertEquals(ANY_FILE, parse("-t", ANY_TEMPLATE, "--data-model", 
ANY_FILE).dataModels.get(0));
+        assertEquals(ANY_FILE, parse("-t", ANY_TEMPLATE, "-m", 
ANY_FILE).outputGeneratorDefinitions.get(0)
+                .getDataModels()
+                .get(0));
+        assertEquals(ANY_FILE, parse("-t", ANY_TEMPLATE, "--data-model", 
ANY_FILE).outputGeneratorDefinitions.get(0)
+                .getDataModels()
+                .get(0));
     }
 
     @Test
     public void shouldParseMultipleDataModels() {
         final Main main = parse("-t", ANY_TEMPLATE, "-m", ANY_FILE, 
"--data-model", OTHER_FILE_URI);
+        final OutputGeneratorDefinition outputGeneratorDefinition = 
main.outputGeneratorDefinitions.get(0);
 
-        assertEquals(ANY_FILE, main.dataModels.get(0));
-        assertEquals(OTHER_FILE_URI, main.dataModels.get(1));
+        assertEquals(ANY_FILE, 
outputGeneratorDefinition.getDataModels().get(0));
+        assertEquals(OTHER_FILE_URI, 
outputGeneratorDefinition.getDataModels().get(1));
         assertNull(main.sources);
     }
 
@@ -102,25 +117,65 @@ public class PicocliTest {
     public void shouldParseSingleTemplate() {
         final Main main = parse("-t", ANY_TEMPLATE);
 
-        assertEquals(ANY_TEMPLATE, 
main.templateSourceOptions.templates.get(0));
+        assertEquals(ANY_TEMPLATE, 
main.outputGeneratorDefinitions.get(0).templateSourceDefinition.template);
     }
 
     @Test
     public void shouldParseInteractiveTemplate() {
         final Main main = parse("-i", INTERACTIVE_TEMPLATE);
 
-        assertEquals(INTERACTIVE_TEMPLATE, 
main.templateSourceOptions.interactiveTemplate);
+        assertEquals(INTERACTIVE_TEMPLATE, 
main.outputGeneratorDefinitions.get(0).templateSourceDefinition.interactiveTemplate);
     }
 
-    @Test(expected = ParameterException.class)
-    public void shouldThrowParameterExceptionForMismatchedTemplateOutput() {
-        final Main main = parse("-t", "foo.ftl", "-t", "bar.ftl", "-o", 
"foo.out");
+    @Test
+    public void shouldParseMultipleTemplates() {
+        final Main main = parse("-t", ANY_TEMPLATE, "--template", 
ANY_TEMPLATE);
+
+        assertEquals(2, main.outputGeneratorDefinitions.size());
+    }
+
+    @Test
+    public void shouldParseStdin() {
+        final Main main = parse("-t", ANY_TEMPLATE, "--stdin");
+
+        assertTrue(main.readFromStdin);
+    }
+
+    @Test
+    public void shouldParseComplexCommandLine01() {
+        final Main main = parse(
+                "--template", "template01.ftl", "--data-source", 
"datasource10.csv",
+                "-t", "template02.ftl", "-s", "datasource20.csv", "-s", 
"datasource21.csv",
+                "-i", "some-interactive-template01", "-s", "datasource30.csv", 
"-o", "out.txt",
+                "-i", "some-interactive-template02");
 
         main.validate();
+
+        final List<OutputGeneratorDefinition> defs = 
main.outputGeneratorDefinitions;
+        assertEquals(4, defs.size());
+
+        
assertTrue(defs.get(0).templateSourceDefinition.template.equals("template01.ftl"));
+        assertTrue(defs.get(0).dataSourceDefinition.dataSources.size() == 1);
+        
assertTrue(defs.get(0).dataSourceDefinition.dataSources.get(0).equals("datasource10.csv"));
+        assertTrue(defs.get(0).templateOutputDefinition == null);
+
+        
assertTrue(defs.get(1).templateSourceDefinition.template.equals("template02.ftl"));
+        assertTrue(defs.get(1).dataSourceDefinition.dataSources.size() == 2);
+        
assertTrue(defs.get(1).dataSourceDefinition.dataSources.get(0).equals("datasource20.csv"));
+        
assertTrue(defs.get(1).dataSourceDefinition.dataSources.get(1).equals("datasource21.csv"));
+        assertTrue(defs.get(0).templateOutputDefinition == null);
+
+        
assertTrue(defs.get(2).templateSourceDefinition.interactiveTemplate.equals("some-interactive-template01"));
+        assertTrue(defs.get(2).dataSourceDefinition.dataSources.size() == 1);
+        
assertTrue(defs.get(2).dataSourceDefinition.dataSources.get(0).equals("datasource30.csv"));
+        
assertTrue(defs.get(2).templateOutputDefinition.outputs.get(0).equals("out.txt"));
+
+        
assertTrue(defs.get(3).templateSourceDefinition.interactiveTemplate.equals("some-interactive-template02"));
+
     }
 
     private static Main parse(String... args) {
-        final Main main = new Main();
+        final Main main = new Main(args);
         new CommandLine(main).parseArgs(args);
         return main;
     }
diff --git 
a/freemarker-generator-cli/src/test/java/org/apache/freemarker/generator/cli/config/CompositeGroupDemo.java
 
b/freemarker-generator-cli/src/test/java/org/apache/freemarker/generator/cli/config/CompositeGroupDemo.java
index 45bad76..8b7f80a 100644
--- 
a/freemarker-generator-cli/src/test/java/org/apache/freemarker/generator/cli/config/CompositeGroupDemo.java
+++ 
b/freemarker-generator-cli/src/test/java/org/apache/freemarker/generator/cli/config/CompositeGroupDemo.java
@@ -14,7 +14,7 @@ public class CompositeGroupDemo {
     @ArgGroup(exclusive = false, multiplicity = "1..*")
     List<OutputGenerator> outputGenerators;
 
-    static class OutputDefinition {
+    static class TemplateOutputDefinition {
         @Option(names = { "-o", "--output" }, description = "output files or 
directories") List<String> outputs;
     }
 
@@ -22,20 +22,20 @@ public class CompositeGroupDemo {
         @Option(names = { "-s", "--data-source" }, description = "data source 
used for rendering") List<String> dataSources;
     }
 
-    static class TemplateDefinition {
+    static class TemplateSourceDefinition {
         @Option(names = { "-t", "--template" }, description = "templates to 
process") String template;
         @Option(names = { "-i", "--interactive" }, description = "interactive 
template to process") public String interactiveTemplate;
     }
 
     static class OutputGenerator {
         @ArgGroup(multiplicity = "1")
-        TemplateDefinition templateDefinition;
+        TemplateSourceDefinition templateSourceDefinition;
 
         @ArgGroup(exclusive = false)
         DataSourceDefinition dataSourceDefinition;
 
         @ArgGroup(exclusive = false)
-        OutputDefinition outputDefinition;
+        TemplateOutputDefinition templateOutputDefinition;
     }
 
     public static void main(String[] args) {
@@ -54,23 +54,23 @@ public class CompositeGroupDemo {
         Validate.notNull(outputGenerators);
         Validate.isTrue(outputGenerators.size() == 4);
 
-        
Validate.isTrue(outputGenerators.get(0).templateDefinition.template.equals("template01.ftl"));
+        
Validate.isTrue(outputGenerators.get(0).templateSourceDefinition.template.equals("template01.ftl"));
         
Validate.isTrue(outputGenerators.get(0).dataSourceDefinition.dataSources.size() 
== 1);
         
Validate.isTrue(outputGenerators.get(0).dataSourceDefinition.dataSources.get(0).equals("datasource10.csv"));
-        Validate.isTrue(outputGenerators.get(0).outputDefinition == null);
+        Validate.isTrue(outputGenerators.get(0).templateOutputDefinition == 
null);
 
-        
Validate.isTrue(outputGenerators.get(1).templateDefinition.template.equals("template02.ftl"));
+        
Validate.isTrue(outputGenerators.get(1).templateSourceDefinition.template.equals("template02.ftl"));
         
Validate.isTrue(outputGenerators.get(1).dataSourceDefinition.dataSources.size() 
== 2);
         
Validate.isTrue(outputGenerators.get(1).dataSourceDefinition.dataSources.get(0).equals("datasource20.csv"));
         
Validate.isTrue(outputGenerators.get(1).dataSourceDefinition.dataSources.get(1).equals("datasource21.csv"));
-        Validate.isTrue(outputGenerators.get(0).outputDefinition == null);
+        Validate.isTrue(outputGenerators.get(0).templateOutputDefinition == 
null);
 
-        
Validate.isTrue(outputGenerators.get(2).templateDefinition.interactiveTemplate.equals("some-interactive-template01"));
+        
Validate.isTrue(outputGenerators.get(2).templateSourceDefinition.interactiveTemplate.equals("some-interactive-template01"));
         
Validate.isTrue(outputGenerators.get(2).dataSourceDefinition.dataSources.size() 
== 1);
         
Validate.isTrue(outputGenerators.get(2).dataSourceDefinition.dataSources.get(0).equals("datasource30.csv"));
-        
Validate.isTrue(outputGenerators.get(2).outputDefinition.outputs.get(0).equals("out.txt"));
+        
Validate.isTrue(outputGenerators.get(2).templateOutputDefinition.outputs.get(0).equals("out.txt"));
 
-        
Validate.isTrue(outputGenerators.get(3).templateDefinition.interactiveTemplate.equals("some-interactive-template02"));
+        
Validate.isTrue(outputGenerators.get(3).templateSourceDefinition.interactiveTemplate.equals("some-interactive-template02"));
 
         return;
     }
diff --git 
a/freemarker-generator-cli/src/test/java/org/apache/freemarker/generator/cli/config/ConfigurationSupplierTest.java
 
b/freemarker-generator-cli/src/test/java/org/apache/freemarker/generator/cli/config/ConfigurationSupplierTest.java
index 93ddfa2..ac817f6 100644
--- 
a/freemarker-generator-cli/src/test/java/org/apache/freemarker/generator/cli/config/ConfigurationSupplierTest.java
+++ 
b/freemarker-generator-cli/src/test/java/org/apache/freemarker/generator/cli/config/ConfigurationSupplierTest.java
@@ -60,7 +60,6 @@ public class ConfigurationSupplierTest {
 
     private SettingsBuilder settingsBuilder() {
         return Settings.builder()
-                .setTemplateNames(singletonList(ANY_TEMPLATE_NAME))
-                .setWriter(new StringWriter());
+                .setCallerSuppliedWriter(new StringWriter());
     }
 }
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 cbd838a..7a7d647 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
@@ -16,20 +16,12 @@
  */
 package org.apache.freemarker.generator.cli.config;
 
-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;
 import java.util.Properties;
 
 import static java.util.Collections.singletonList;
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertNotNull;
-import static org.junit.Assert.assertTrue;
 
 public class SettingsTest {
 
@@ -46,41 +38,44 @@ public class SettingsTest {
     private static final Map<String, Object> ANY_USER_PARAMETERS = new 
HashMap<>();
     private static final Properties ANY_SYSTEM_PROPERTIES = new Properties();
 
-    @Test
-    public void shouldProvideAllExpectedSettings() {
-        final Settings settings = allSettingsBuilder().build();
+    // @TODO FREEMARKER-161 sgoeschl Update the tests
+    /**
+     @Test public void shouldProvideAllExpectedSettings() {
+     final Settings settings = allSettingsBuilder().build();
+
+     assertEquals(1, settings.getCommandLineArgs().size());
+     assertNotNull(settings.getConfiguration());
+     assertEquals(ANY_INCLUDE, settings.getDataSourceIncludePattern());
+     assertEquals(ANY_INPUT_ENCODING, settings.getInputEncoding().name());
+     assertEquals(ANY_OUTPUT_ENCODING, settings.getOutputEncoding().name());
+     assertEquals(ANY_OUTPUT_FILE, settings.getOutputs().get(0));
+     assertEquals(ANY_TEMPLATE_NAME, settings.getTemplates().get(0));
+     assertNotNull(settings.getDataSources());
+     assertNotNull(settings.getUserParameters());
+     assertNotNull(settings.getUserSystemProperties());
+     assertTrue(settings.isReadFromStdin());
+     assertTrue(settings.isInteractiveTemplate());
+     assertTrue(settings.isVerbose());
+     }
 
-        assertEquals(1, settings.getArgs().size());
-        assertNotNull(settings.getConfiguration());
-        assertEquals(ANY_INCLUDE, settings.getDataSourceIncludePattern());
-        assertEquals(ANY_INPUT_ENCODING, settings.getInputEncoding().name());
-        assertEquals(ANY_OUTPUT_ENCODING, settings.getOutputEncoding().name());
-        assertEquals(ANY_OUTPUT_FILE, settings.getOutputs().get(0));
-        assertEquals(ANY_TEMPLATE_NAME, settings.getTemplates().get(0));
-        assertNotNull(settings.getDataSources());
-        assertNotNull(settings.getUserParameters());
-        assertNotNull(settings.getUserSystemProperties());
-        assertTrue(settings.isReadFromStdin());
-        assertTrue(settings.isInteractiveTemplate());
-        assertTrue(settings.isVerbose());
-    }
+     private SettingsBuilder allSettingsBuilder() {
+     return Settings.builder()
+     .isReadFromStdin(true)
+     .setCommandLineArgs(ANY_ARGS)
+     .setConfiguration(ANY_CONFIGURATION)
+     .setDataSourceIncludePattern(ANY_INCLUDE)
+     .setInputEncoding(ANY_INPUT_ENCODING)
+     .setInteractiveTemplate(ANY_INTERACTIVE_TEMPLATE)
+     .setLocale(ANY_LOCALE)
+     .setOutputEncoding(ANY_OUTPUT_ENCODING)
+     .setOutputs(Collections.singletonList(ANY_OUTPUT_FILE))
+     .setParameters(ANY_USER_PARAMETERS)
+     .setDataSources(ANY_SOURCES)
+     .setSystemProperties(ANY_SYSTEM_PROPERTIES)
+     .setTemplateNames(singletonList(ANY_TEMPLATE_NAME))
+     .setWriter(new StringWriter())
+     .setVerbose(true);
+     }
 
-    private SettingsBuilder allSettingsBuilder() {
-        return Settings.builder()
-                .isReadFromStdin(true)
-                .setArgs(ANY_ARGS)
-                .setConfiguration(ANY_CONFIGURATION)
-                .setDataSourceIncludePattern(ANY_INCLUDE)
-                .setInputEncoding(ANY_INPUT_ENCODING)
-                .setInteractiveTemplate(ANY_INTERACTIVE_TEMPLATE)
-                .setLocale(ANY_LOCALE)
-                .setOutputEncoding(ANY_OUTPUT_ENCODING)
-                .setOutputs(Collections.singletonList(ANY_OUTPUT_FILE))
-                .setParameters(ANY_USER_PARAMETERS)
-                .setDataSources(ANY_SOURCES)
-                .setSystemProperties(ANY_SYSTEM_PROPERTIES)
-                .setTemplateNames(singletonList(ANY_TEMPLATE_NAME))
-                .setWriter(new StringWriter())
-                .setVerbose(true);
-    }
+     */
 }

Reply via email to