You are viewing a plain text version of this content. The canonical link for it is here.
Posted to notifications@freemarker.apache.org by sg...@apache.org on 2020/04/19 14:56:28 UTC

[freemarker-generator] branch master updated: FREEMARKER-141 Expose user-supplied parameters in the data model (#12)

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

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


The following commit(s) were added to refs/heads/master by this push:
     new a3603ca  FREEMARKER-141 Expose user-supplied parameters in the data model (#12)
a3603ca is described below

commit a3603ca2d2f7e5c841ac3ae22712ae287741adf5
Author: Siegfried Goeschl <si...@gmail.com>
AuthorDate: Sun Apr 19 16:56:22 2020 +0200

    FREEMARKER-141 Expose user-supplied parameters in the data model (#12)
---
 .../base/activation/ByteArrayDataSource.java       |  2 +-
 .../base/activation/CachingUrlDataSource.java      | 16 ++++
 .../base/activation/InputStreamDataSource.java     |  2 +-
 .../base/activation/StringDataSource.java          |  2 +-
 .../generator/base/datasource/DataSource.java      | 29 ++-----
 .../base/datasource/DataSourceFactory.java         | 16 +++-
 .../generator/base/mime/MimetypeParser.java        | 58 ++++++++++++++
 .../base/{activation => mime}/Mimetypes.java       | 18 ++++-
 .../MimetypesFileTypeMapFactory.java               | 28 +++----
 .../generator/base/parameter/Parameter.java        | 73 +++++++++++++++++
 .../base/parameter/ParameterModelSupplier.java     | 66 ++++++++++++++++
 .../generator/base/parameter/ParameterParser.java  | 66 ++++++++++++++++
 .../freemarker/generator/base/uri/NamedUri.java    |  8 +-
 .../generator/base/uri/NamedUriStringParser.java   | 12 +--
 .../generator/base/util/StringUtils.java           | 11 +++
 .../datasource/DataSourceFactoryTest.java          | 11 ++-
 .../generator/datasource/DataSourceTest.java       |  4 +-
 .../datasource/DataSourcesSupplierTest.java        | 12 +--
 .../generator/mime/MimetypeParserTest.java         | 59 ++++++++++++++
 .../parameter/ParameterModelSupplierTest.java      | 92 ++++++++++++++++++++++
 .../generator/uri/NamedUriStringParserTest.java    | 39 ++++++++-
 freemarker-generator-cli/README.md                 | 52 ++++++------
 .../org/apache/freemarker/generator/cli/Main.java  | 10 +--
 .../generator/cli/config/DataModelSupplier.java    |  6 +-
 .../freemarker/generator/cli/config/Settings.java  | 35 ++------
 .../freemarker/generator/cli/config/Suppliers.java |  5 ++
 .../generator/cli/task/FreeMarkerTask.java         | 26 +++---
 .../src/site/markdown/cli/concepts/named-uris.md   | 14 ++--
 .../site/markdown/cli/concepts/user-parameters.md  | 37 +++++++++
 .../src/site/markdown/index.md                     |  1 +
 .../freemarker/generator/cli/ExamplesTest.java     |  2 +-
 .../freemarker/generator/cli/ManualTest.java       |  4 +-
 .../freemarker/generator/cli/PicocliTest.java      | 15 ++++
 .../cli/config/DataModelSupplierTest.java          |  8 +-
 .../generator/cli/config/SettingsTest.java         |  4 +-
 .../templates/csv/fo/transform.ftl                 |  2 +-
 .../templates/csv/transform.ftl                    |  8 +-
 .../templates/excel/csv/custom.ftl                 |  2 +-
 .../templates/excel/csv/transform.ftl              |  2 +-
 freemarker-generator-cli/templates/info.ftl        | 39 +++++----
 .../generator/tools/system/SystemTool.java         |  8 +-
 .../tools/snakeyaml/SnakeYamlToolTest.java         |  3 +-
 .../generator/tools/xml/XmlToolTest.java           |  2 +-
 43 files changed, 709 insertions(+), 200 deletions(-)

diff --git a/freemarker-generator-base/src/main/java/org/apache/freemarker/generator/base/activation/ByteArrayDataSource.java b/freemarker-generator-base/src/main/java/org/apache/freemarker/generator/base/activation/ByteArrayDataSource.java
index 8933ea6..196b254 100644
--- a/freemarker-generator-base/src/main/java/org/apache/freemarker/generator/base/activation/ByteArrayDataSource.java
+++ b/freemarker-generator-base/src/main/java/org/apache/freemarker/generator/base/activation/ByteArrayDataSource.java
@@ -23,7 +23,7 @@ import java.io.InputStream;
 import java.io.OutputStream;
 
 import static java.util.Objects.requireNonNull;
-import static org.apache.freemarker.generator.base.activation.Mimetypes.MIME_APPLICATION_OCTET_STREAM;
+import static org.apache.freemarker.generator.base.mime.Mimetypes.MIME_APPLICATION_OCTET_STREAM;
 
 public class ByteArrayDataSource implements DataSource {
 
diff --git a/freemarker-generator-base/src/main/java/org/apache/freemarker/generator/base/activation/CachingUrlDataSource.java b/freemarker-generator-base/src/main/java/org/apache/freemarker/generator/base/activation/CachingUrlDataSource.java
index 24e02ac..6616598 100644
--- a/freemarker-generator-base/src/main/java/org/apache/freemarker/generator/base/activation/CachingUrlDataSource.java
+++ b/freemarker-generator-base/src/main/java/org/apache/freemarker/generator/base/activation/CachingUrlDataSource.java
@@ -1,3 +1,19 @@
+/*
+ * 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.activation;
 
 import javax.activation.URLDataSource;
diff --git a/freemarker-generator-base/src/main/java/org/apache/freemarker/generator/base/activation/InputStreamDataSource.java b/freemarker-generator-base/src/main/java/org/apache/freemarker/generator/base/activation/InputStreamDataSource.java
index 11b54b1..01f8fa6 100644
--- a/freemarker-generator-base/src/main/java/org/apache/freemarker/generator/base/activation/InputStreamDataSource.java
+++ b/freemarker-generator-base/src/main/java/org/apache/freemarker/generator/base/activation/InputStreamDataSource.java
@@ -21,7 +21,7 @@ import java.io.InputStream;
 import java.io.OutputStream;
 
 import static java.util.Objects.requireNonNull;
-import static org.apache.freemarker.generator.base.activation.Mimetypes.MIME_APPLICATION_OCTET_STREAM;
+import static org.apache.freemarker.generator.base.mime.Mimetypes.MIME_APPLICATION_OCTET_STREAM;
 
 /**
  * Wraps an InputStream into a DataSource. Please note that the input stream
diff --git a/freemarker-generator-base/src/main/java/org/apache/freemarker/generator/base/activation/StringDataSource.java b/freemarker-generator-base/src/main/java/org/apache/freemarker/generator/base/activation/StringDataSource.java
index fd60cae..323aded 100644
--- a/freemarker-generator-base/src/main/java/org/apache/freemarker/generator/base/activation/StringDataSource.java
+++ b/freemarker-generator-base/src/main/java/org/apache/freemarker/generator/base/activation/StringDataSource.java
@@ -24,7 +24,7 @@ import java.nio.charset.Charset;
 
 import static java.nio.charset.StandardCharsets.UTF_8;
 import static java.util.Objects.requireNonNull;
-import static org.apache.freemarker.generator.base.activation.Mimetypes.MIME_TEXT_PLAIN;
+import static org.apache.freemarker.generator.base.mime.Mimetypes.MIME_TEXT_PLAIN;
 
 public class StringDataSource implements DataSource {
 
diff --git a/freemarker-generator-base/src/main/java/org/apache/freemarker/generator/base/datasource/DataSource.java b/freemarker-generator-base/src/main/java/org/apache/freemarker/generator/base/datasource/DataSource.java
index 4864dc6..3a94c7b 100644
--- a/freemarker-generator-base/src/main/java/org/apache/freemarker/generator/base/datasource/DataSource.java
+++ b/freemarker-generator-base/src/main/java/org/apache/freemarker/generator/base/datasource/DataSource.java
@@ -21,6 +21,7 @@ import org.apache.commons.io.IOUtils;
 import org.apache.commons.io.LineIterator;
 import org.apache.freemarker.generator.base.activation.ByteArrayDataSource;
 import org.apache.freemarker.generator.base.activation.StringDataSource;
+import org.apache.freemarker.generator.base.mime.MimetypeParser;
 import org.apache.freemarker.generator.base.util.CloseableReaper;
 
 import javax.activation.FileDataSource;
@@ -31,10 +32,7 @@ import java.io.StringWriter;
 import java.net.URI;
 import java.nio.charset.Charset;
 import java.util.List;
-import java.util.regex.Matcher;
-import java.util.regex.Pattern;
 
-import static java.nio.charset.Charset.forName;
 import static java.nio.charset.StandardCharsets.UTF_8;
 import static java.util.Objects.requireNonNull;
 import static org.apache.commons.io.IOUtils.lineIterator;
@@ -53,9 +51,6 @@ import static org.apache.freemarker.generator.base.util.StringUtils.isNotEmpty;
  */
 public class DataSource implements Closeable {
 
-    /** Parse something like "application/json; charset=utf-8" */
-    private static final Pattern CHARSET_PATTERN = Pattern.compile("(?i)\\bcharset=\\s*\"?([^\\s;\"]*)");
-
     /** Human-readable name of the data source */
     private final String name;
 
@@ -110,7 +105,7 @@ public class DataSource implements Closeable {
     }
 
     public Charset getCharset() {
-        return charset != null ? charset : getCharsetFromContentType(contentType(), UTF_8);
+        return charset != null ? charset : MimetypeParser.getCharset(contentType(), UTF_8);
     }
 
     /**
@@ -119,7 +114,7 @@ public class DataSource implements Closeable {
      * @return content type
      */
     public String getContentType() {
-        return stripExtraParameterFronContentType(contentType());
+        return MimetypeParser.getMimetype(contentType());
     }
 
     public URI getUri() {
@@ -172,7 +167,7 @@ public class DataSource implements Closeable {
     public String getText(String charsetName) {
         final StringWriter writer = new StringWriter();
         try (InputStream is = getUnsafeInputStream()) {
-            IOUtils.copy(is, writer, forName(charsetName));
+            IOUtils.copy(is, writer, Charset.forName(charsetName));
             return writer.toString();
         } catch (IOException e) {
             throw new RuntimeException("Failed to get text: " + toString(), e);
@@ -224,7 +219,7 @@ public class DataSource implements Closeable {
      */
     public LineIterator getLineIterator(String charsetName) {
         try {
-            return closables.add(lineIterator(getUnsafeInputStream(), forName(charsetName)));
+            return closables.add(lineIterator(getUnsafeInputStream(), Charset.forName(charsetName)));
         } catch (IOException e) {
             throw new RuntimeException("Failed to create line iterator: " + toString(), e);
         }
@@ -265,21 +260,7 @@ public class DataSource implements Closeable {
                 '}';
     }
 
-    private Charset getCharsetFromContentType(String contentType, Charset def) {
-        final Matcher matcher = CHARSET_PATTERN.matcher(contentType);
-        if (matcher.find()) {
-            final String name = matcher.group(1).trim().toUpperCase();
-            return Charset.forName(name);
-        }
-        return def;
-    }
-
     private String contentType() {
         return isNotEmpty(contentType) ? contentType : dataSource.getContentType();
     }
-
-    private String stripExtraParameterFronContentType(String contentType) {
-        final int end = contentType.indexOf(";");
-        return end > 0 ? contentType.substring(0, end).trim() : contentType;
-    }
 }
diff --git a/freemarker-generator-base/src/main/java/org/apache/freemarker/generator/base/datasource/DataSourceFactory.java b/freemarker-generator-base/src/main/java/org/apache/freemarker/generator/base/datasource/DataSourceFactory.java
index 76f5142..016a394 100644
--- a/freemarker-generator-base/src/main/java/org/apache/freemarker/generator/base/datasource/DataSourceFactory.java
+++ b/freemarker-generator-base/src/main/java/org/apache/freemarker/generator/base/datasource/DataSourceFactory.java
@@ -20,8 +20,8 @@ import org.apache.freemarker.generator.base.FreeMarkerConstants.Location;
 import org.apache.freemarker.generator.base.activation.ByteArrayDataSource;
 import org.apache.freemarker.generator.base.activation.CachingUrlDataSource;
 import org.apache.freemarker.generator.base.activation.InputStreamDataSource;
-import org.apache.freemarker.generator.base.activation.MimetypesFileTypeMapFactory;
 import org.apache.freemarker.generator.base.activation.StringDataSource;
+import org.apache.freemarker.generator.base.mime.MimetypesFileTypeMapFactory;
 import org.apache.freemarker.generator.base.uri.NamedUri;
 import org.apache.freemarker.generator.base.uri.NamedUriStringParser;
 import org.apache.freemarker.generator.base.util.PropertiesFactory;
@@ -43,7 +43,7 @@ import java.util.Properties;
 
 import static java.nio.charset.StandardCharsets.UTF_8;
 import static org.apache.freemarker.generator.base.FreeMarkerConstants.DEFAULT_GROUP;
-import static org.apache.freemarker.generator.base.activation.Mimetypes.MIME_TEXT_PLAIN;
+import static org.apache.freemarker.generator.base.mime.Mimetypes.MIME_TEXT_PLAIN;
 import static org.apache.freemarker.generator.base.util.StringUtils.firstNonEmpty;
 
 /**
@@ -53,6 +53,7 @@ public class DataSourceFactory {
 
     private static final String NO_MIME_TYPE = null;
     private static final Charset NO_CHARSET = null;
+    private static final String ROOT_DIR = "/";
 
     private DataSourceFactory() {
     }
@@ -82,7 +83,8 @@ public class DataSourceFactory {
             final String name = namedUri.getNameOrElse(file.getName());
             return fromFile(name, group, file, charset);
         } else if (UriUtils.isEnvUri(uri)) {
-            final String key = uri.getPath().substring(1);
+            // environment variables come with a leading "/" to be removed
+            final String key = stripRootDir(uri.getPath());
             final String contentType = getMimeTypeOrElse(namedUri, MIME_TEXT_PLAIN);
             final String name = firstNonEmpty(namedUri.getName(), key, Location.ENVIRONMENT);
             if (StringUtils.isEmpty(key)) {
@@ -218,4 +220,12 @@ public class DataSourceFactory {
             throw new IllegalArgumentException(uri.toString(), e);
         }
     }
+
+    private static String stripRootDir(String str) {
+        if (str.startsWith(ROOT_DIR)) {
+            return str.substring(ROOT_DIR.length());
+        } else {
+            return str;
+        }
+    }
 }
diff --git a/freemarker-generator-base/src/main/java/org/apache/freemarker/generator/base/mime/MimetypeParser.java b/freemarker-generator-base/src/main/java/org/apache/freemarker/generator/base/mime/MimetypeParser.java
new file mode 100644
index 0000000..46b82a6
--- /dev/null
+++ b/freemarker-generator-base/src/main/java/org/apache/freemarker/generator/base/mime/MimetypeParser.java
@@ -0,0 +1,58 @@
+/*
+ * 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.mime;
+
+import java.nio.charset.Charset;
+
+import static org.apache.freemarker.generator.base.util.StringUtils.isEmpty;
+
+/**
+ * Parse a mimetype.
+ * <p>
+ * Some examples
+ * <ul>
+ *     <li>text/html;charset=utf-8</li>
+ *     <li>text/html;charset=windows-1252</li>
+ * </ul>
+ */
+public class MimetypeParser {
+
+    public static String getMimetype(String raw) {
+        if (isEmpty(raw)) {
+            return null;
+        }
+
+        final int pos = raw.indexOf(';');
+        return pos > 0 ? raw.substring(0, pos).toLowerCase() : raw;
+    }
+
+    public static Charset getCharset(String raw) {
+        if (isEmpty(raw) || !raw.contains(";") || !raw.toLowerCase().contains("charset")) {
+            return null;
+        }
+
+        final int pos = raw.indexOf(';');
+        final String name = raw.substring(pos).split("=")[1].trim();
+
+        return Charset.forName(name);
+    }
+
+    public static Charset getCharset(String raw, Charset def) {
+        final Charset charset = getCharset(raw);
+        return charset != null ? charset : def;
+    }
+}
\ No newline at end of file
diff --git a/freemarker-generator-base/src/main/java/org/apache/freemarker/generator/base/activation/Mimetypes.java b/freemarker-generator-base/src/main/java/org/apache/freemarker/generator/base/mime/Mimetypes.java
similarity index 54%
rename from freemarker-generator-base/src/main/java/org/apache/freemarker/generator/base/activation/Mimetypes.java
rename to freemarker-generator-base/src/main/java/org/apache/freemarker/generator/base/mime/Mimetypes.java
index 5bb4ebe..c8bb76c 100644
--- a/freemarker-generator-base/src/main/java/org/apache/freemarker/generator/base/activation/Mimetypes.java
+++ b/freemarker-generator-base/src/main/java/org/apache/freemarker/generator/base/mime/Mimetypes.java
@@ -1,4 +1,20 @@
-package org.apache.freemarker.generator.base.activation;
+/*
+ * 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.mime;
 
 public class Mimetypes {
 
diff --git a/freemarker-generator-base/src/main/java/org/apache/freemarker/generator/base/activation/MimetypesFileTypeMapFactory.java b/freemarker-generator-base/src/main/java/org/apache/freemarker/generator/base/mime/MimetypesFileTypeMapFactory.java
similarity index 62%
rename from freemarker-generator-base/src/main/java/org/apache/freemarker/generator/base/activation/MimetypesFileTypeMapFactory.java
rename to freemarker-generator-base/src/main/java/org/apache/freemarker/generator/base/mime/MimetypesFileTypeMapFactory.java
index 4fff085..b91d534 100644
--- a/freemarker-generator-base/src/main/java/org/apache/freemarker/generator/base/activation/MimetypesFileTypeMapFactory.java
+++ b/freemarker-generator-base/src/main/java/org/apache/freemarker/generator/base/mime/MimetypesFileTypeMapFactory.java
@@ -14,23 +14,23 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package org.apache.freemarker.generator.base.activation;
+package org.apache.freemarker.generator.base.mime;
 
 import javax.activation.MimetypesFileTypeMap;
 
-import static org.apache.freemarker.generator.base.activation.Mimetypes.MIME_APPLICATION_JSON;
-import static org.apache.freemarker.generator.base.activation.Mimetypes.MIME_APPLICATION_OCTET_STREAM;
-import static org.apache.freemarker.generator.base.activation.Mimetypes.MIME_APPLICATION_XHTML;
-import static org.apache.freemarker.generator.base.activation.Mimetypes.MIME_APPLICATION_XML;
-import static org.apache.freemarker.generator.base.activation.Mimetypes.MIME_TEXT_CSV;
-import static org.apache.freemarker.generator.base.activation.Mimetypes.MIME_TEXT_HTML;
-import static org.apache.freemarker.generator.base.activation.Mimetypes.MIME_TEXT_MARKDOWM;
-import static org.apache.freemarker.generator.base.activation.Mimetypes.MIME_TEXT_PLAIN;
-import static org.apache.freemarker.generator.base.activation.Mimetypes.MIME_TEXT_RTF;
-import static org.apache.freemarker.generator.base.activation.Mimetypes.MIME_TEXT_TSV;
-import static org.apache.freemarker.generator.base.activation.Mimetypes.MIME_TEXT_YAML;
-import static org.apache.freemarker.generator.base.activation.Mimetypes.MIME_VENDOR_MS_EXCEL;
-import static org.apache.freemarker.generator.base.activation.Mimetypes.MIME_VENDOR_OPEN_XML_SPREADSHEET;
+import static org.apache.freemarker.generator.base.mime.Mimetypes.MIME_APPLICATION_JSON;
+import static org.apache.freemarker.generator.base.mime.Mimetypes.MIME_APPLICATION_OCTET_STREAM;
+import static org.apache.freemarker.generator.base.mime.Mimetypes.MIME_APPLICATION_XHTML;
+import static org.apache.freemarker.generator.base.mime.Mimetypes.MIME_APPLICATION_XML;
+import static org.apache.freemarker.generator.base.mime.Mimetypes.MIME_TEXT_CSV;
+import static org.apache.freemarker.generator.base.mime.Mimetypes.MIME_TEXT_HTML;
+import static org.apache.freemarker.generator.base.mime.Mimetypes.MIME_TEXT_MARKDOWM;
+import static org.apache.freemarker.generator.base.mime.Mimetypes.MIME_TEXT_PLAIN;
+import static org.apache.freemarker.generator.base.mime.Mimetypes.MIME_TEXT_RTF;
+import static org.apache.freemarker.generator.base.mime.Mimetypes.MIME_TEXT_TSV;
+import static org.apache.freemarker.generator.base.mime.Mimetypes.MIME_TEXT_YAML;
+import static org.apache.freemarker.generator.base.mime.Mimetypes.MIME_VENDOR_MS_EXCEL;
+import static org.apache.freemarker.generator.base.mime.Mimetypes.MIME_VENDOR_OPEN_XML_SPREADSHEET;
 
 public class MimetypesFileTypeMapFactory {
 
diff --git a/freemarker-generator-base/src/main/java/org/apache/freemarker/generator/base/parameter/Parameter.java b/freemarker-generator-base/src/main/java/org/apache/freemarker/generator/base/parameter/Parameter.java
new file mode 100644
index 0000000..707a038
--- /dev/null
+++ b/freemarker-generator-base/src/main/java/org/apache/freemarker/generator/base/parameter/Parameter.java
@@ -0,0 +1,73 @@
+/*
+ * 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.parameter;
+
+import static java.lang.String.format;
+import static java.util.Objects.requireNonNull;
+import static org.apache.freemarker.generator.base.util.StringUtils.isNotEmpty;
+
+/**
+ * Caputeres the information of a user-supplied parameter.
+ */
+public class Parameter {
+
+    /** User-supplied name */
+    private final String name;
+
+    /** User-supplied group */
+    private final String group;
+
+    /** User-supplied value */
+    private final String value;
+
+    public Parameter(String name, String value) {
+        this.name = requireNonNull(name);
+        this.group = null;
+        this.value = requireNonNull(value);
+    }
+
+    public Parameter(String name, String group, String value) {
+        this.name = requireNonNull(name);
+        this.group = requireNonNull(group);
+        this.value = requireNonNull(value);
+    }
+
+    public String getName() {
+        return name;
+    }
+
+    public String getGroup() {
+        return group;
+    }
+
+    public String getValue() {
+        return value;
+    }
+
+    public boolean hasGroup() {
+        return isNotEmpty(group);
+    }
+
+    public String getKey() {
+        return hasGroup() ? format("%s:%s", name, group) : format("%s", name);
+    }
+
+    @Override
+    public String toString() {
+        return format("%s=%s", getKey(), value);
+    }
+}
diff --git a/freemarker-generator-base/src/main/java/org/apache/freemarker/generator/base/parameter/ParameterModelSupplier.java b/freemarker-generator-base/src/main/java/org/apache/freemarker/generator/base/parameter/ParameterModelSupplier.java
new file mode 100644
index 0000000..ac74090
--- /dev/null
+++ b/freemarker-generator-base/src/main/java/org/apache/freemarker/generator/base/parameter/ParameterModelSupplier.java
@@ -0,0 +1,66 @@
+/*
+ * 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.parameter;
+
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.function.Supplier;
+
+/**
+ * Converts a map of parameters to a map. The resulting map contains
+ * either (key=String) or (key=Map&lt;String, Object&gt;).
+ */
+public class ParameterModelSupplier implements Supplier<Map<String, Object>> {
+
+    private final Map<String, String> parameters;
+
+    public ParameterModelSupplier(Map<String, String> parameters) {
+        this.parameters = parameters != null ? parameters : Collections.emptyMap();
+    }
+
+    @Override
+    public Map<String, Object> get() {
+        final Map<String, Object> map = new HashMap<>();
+
+        for (Map.Entry<String, String> entry : parameters.entrySet()) {
+            final Parameter parameter = ParameterParser.parse(entry.getKey(), entry.getValue());
+            final String name = parameter.getName();
+            final String value = parameter.getValue();
+
+            if (parameter.hasGroup()) {
+                final String group = parameter.getGroup();
+                getOrCreateGroupMap(map, group).put(name, value);
+            } else {
+                map.put(parameter.getName(), value);
+            }
+        }
+
+        return map;
+    }
+
+    @SuppressWarnings("unchecked")
+    private static Map<String, Object> getOrCreateGroupMap(Map<String, Object> map, String group) {
+        if (map.containsKey(group)) {
+            return (Map<String, Object>) map.get(group);
+        } else {
+            final Map<String, Object> groupMap = new HashMap<>();
+            map.put(group, groupMap);
+            return groupMap;
+        }
+    }
+}
diff --git a/freemarker-generator-base/src/main/java/org/apache/freemarker/generator/base/parameter/ParameterParser.java b/freemarker-generator-base/src/main/java/org/apache/freemarker/generator/base/parameter/ParameterParser.java
new file mode 100644
index 0000000..4bf7b75
--- /dev/null
+++ b/freemarker-generator-base/src/main/java/org/apache/freemarker/generator/base/parameter/ParameterParser.java
@@ -0,0 +1,66 @@
+/*
+ * 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.parameter;
+
+import org.apache.freemarker.generator.base.util.Validate;
+
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import static java.lang.String.format;
+import static java.util.regex.Pattern.compile;
+import static org.apache.freemarker.generator.base.util.StringUtils.count;
+import static org.apache.freemarker.generator.base.util.StringUtils.isEmpty;
+
+/**
+ * Parses a parameter provided by the user
+ * <ul>
+ *     <li>name=value</li>
+ *     <li>name:group=value</li>
+ * </ul>
+ */
+public class ParameterParser {
+
+    private static final String NAME = "name";
+    private static final String GROUP = "group";
+    private static final Pattern NAME_GROUP_REGEXP = compile("^(?<name>[a-zA-Z0-9-._]*):?(?<group>[a-zA-Z0-9-._]*)");
+
+    public static Parameter parse(String str) {
+        Validate.notEmpty(str, "parameter is empty");
+        Validate.isTrue(count(str, '=') == 1, "invalid parameter");
+
+        final String[] parts = str.split("=");
+        return parse(parts[0], parts[1]);
+    }
+
+    public static Parameter parse(String key, String value) {
+        Validate.notEmpty(key, "key is empty");
+        Validate.notEmpty(value, "value is empty");
+
+        final Matcher matcher = NAME_GROUP_REGEXP.matcher(key);
+
+        if (matcher.matches()) {
+            return parameter(matcher.group(NAME), matcher.group(GROUP), value);
+        } else {
+            throw new IllegalArgumentException(format("Unable to parse parameter: %s=%s", key, value));
+        }
+    }
+
+    private static Parameter parameter(String name, String group, String value) {
+        return isEmpty(group) ? new Parameter(name, value) : new Parameter(name, group, value);
+    }
+}
\ No newline at end of file
diff --git a/freemarker-generator-base/src/main/java/org/apache/freemarker/generator/base/uri/NamedUri.java b/freemarker-generator-base/src/main/java/org/apache/freemarker/generator/base/uri/NamedUri.java
index 77cd1fa..45fb595 100644
--- a/freemarker-generator-base/src/main/java/org/apache/freemarker/generator/base/uri/NamedUri.java
+++ b/freemarker-generator-base/src/main/java/org/apache/freemarker/generator/base/uri/NamedUri.java
@@ -16,8 +16,6 @@
  */
 package org.apache.freemarker.generator.base.uri;
 
-import org.apache.freemarker.generator.base.util.UriUtils;
-
 import java.io.File;
 import java.net.URI;
 import java.util.Map;
@@ -101,11 +99,7 @@ public class NamedUri {
     }
 
     public File getFile() {
-        if (UriUtils.isFileUri(uri)) {
-            return new File(uri.getPath().substring(1));
-        } else {
-            return new File(uri.getPath());
-        }
+        return new File(uri.getPath());
     }
 
     @Override
diff --git a/freemarker-generator-base/src/main/java/org/apache/freemarker/generator/base/uri/NamedUriStringParser.java b/freemarker-generator-base/src/main/java/org/apache/freemarker/generator/base/uri/NamedUriStringParser.java
index 76d961d..6aabbd0 100644
--- a/freemarker-generator-base/src/main/java/org/apache/freemarker/generator/base/uri/NamedUriStringParser.java
+++ b/freemarker-generator-base/src/main/java/org/apache/freemarker/generator/base/uri/NamedUriStringParser.java
@@ -42,7 +42,7 @@ public class NamedUriStringParser {
     private static final String GROUP = "group";
     private static final String URI = "uri";
 
-    private static final Pattern NAMED_URI_REGEXP = compile("^(?<name>[a-zA-Z0-9-_]*):?(?<group>[a-zA-Z0-9-_]*)=(?<uri>.*)");
+    private static final Pattern NAMED_URI_REGEXP = compile("^(?<name>[a-zA-Z0-9-_$@]*):?(?<group>[a-zA-Z0-9-_$@]*)=(?<uri>.*)");
 
     public static NamedUri parse(String value) {
         Validate.notEmpty(value, "Named URI is empty");
@@ -52,7 +52,7 @@ public class NamedUriStringParser {
         if (matcher.matches()) {
             final String name = matcher.group(NAME);
             final String group = matcher.group(GROUP);
-            final URI uri = uri(matcher.group(URI));
+            final URI uri = UriUtils.toURI(matcher.group(URI));
             return new NamedUri(name, group, uri, parameters(uri));
         } else {
             final URI uri = UriUtils.toURI(value);
@@ -63,12 +63,4 @@ public class NamedUriStringParser {
     private static Map<String, String> parameters(URI uri) {
         return UriFragmentParser.parse(uri.getFragment());
     }
-
-    private static URI uri(String str) {
-        if (!str.contains("://")) {
-            return UriUtils.toURI("file:///" + str);
-        } else {
-            return UriUtils.toURI(str);
-        }
-    }
 }
diff --git a/freemarker-generator-base/src/main/java/org/apache/freemarker/generator/base/util/StringUtils.java b/freemarker-generator-base/src/main/java/org/apache/freemarker/generator/base/util/StringUtils.java
index 24b81af..a36bf1e 100644
--- a/freemarker-generator-base/src/main/java/org/apache/freemarker/generator/base/util/StringUtils.java
+++ b/freemarker-generator-base/src/main/java/org/apache/freemarker/generator/base/util/StringUtils.java
@@ -45,4 +45,15 @@ public class StringUtils {
         return null;
     }
 
+    public static int count(final String s, final char c) {
+        final char[] chars = s.toCharArray();
+        int count = 0;
+        for (final char aChar : chars) {
+            if (aChar == c) {
+                count++;
+            }
+        }
+        return count;
+    }
+
 }
diff --git a/freemarker-generator-base/src/test/java/org/apache/freemarker/generator/datasource/DataSourceFactoryTest.java b/freemarker-generator-base/src/test/java/org/apache/freemarker/generator/datasource/DataSourceFactoryTest.java
index 28062be..bf96829 100644
--- a/freemarker-generator-base/src/test/java/org/apache/freemarker/generator/datasource/DataSourceFactoryTest.java
+++ b/freemarker-generator-base/src/test/java/org/apache/freemarker/generator/datasource/DataSourceFactoryTest.java
@@ -20,6 +20,7 @@ import org.apache.freemarker.generator.base.datasource.DataSource;
 import org.apache.freemarker.generator.base.datasource.DataSourceFactory;
 import org.apache.freemarker.generator.base.uri.NamedUri;
 import org.apache.freemarker.generator.base.uri.NamedUriStringParser;
+import org.junit.Ignore;
 import org.junit.Test;
 
 import java.io.ByteArrayInputStream;
@@ -28,18 +29,21 @@ import java.io.IOException;
 import java.io.InputStream;
 import java.net.URL;
 import java.nio.charset.Charset;
+import java.nio.file.Paths;
 
+import static java.lang.String.format;
 import static java.nio.charset.StandardCharsets.UTF_8;
 import static junit.framework.TestCase.assertFalse;
 import static junit.framework.TestCase.assertTrue;
-import static org.apache.freemarker.generator.base.activation.Mimetypes.MIME_APPLICATION_XML;
+import static org.apache.freemarker.generator.base.mime.Mimetypes.MIME_APPLICATION_XML;
 import static org.junit.Assert.assertEquals;
 
 public class DataSourceFactoryTest {
 
+    private static final String PWD = Paths.get(".").toAbsolutePath().normalize().toString();
     private static final String ANY_TEXT = "Hello World";
     private static final String ANY_FILE_NAME = "pom.xml";
-    private static final String ANY_FILE_URI = "file:///pom.xml";
+    private static final String ANY_FILE_URI = format("file:///%s/pom.xml", PWD);
     private static final Charset ANY_CHAR_SET = UTF_8;
     private static final File ANY_FILE = new File(ANY_FILE_NAME);
     private static final String ANY_NAMED_URL_STRING = "content:www=https://www.google.com?foo=bar#contenttype=application/json";
@@ -105,6 +109,7 @@ public class DataSourceFactoryTest {
     }
 
     @Test
+    @Ignore
     public void shouldCreateDataSourceFromURL() throws IOException {
         final URL url = new URL("https://jsonplaceholder.typicode.com/posts/2");
         final DataSource dataSource = DataSourceFactory.fromUrl("jsonplaceholder.typicode.com", "default", url, null, null);
@@ -115,6 +120,7 @@ public class DataSourceFactoryTest {
     }
 
     @Test
+    @Ignore
     public void shouldCreateDataSourceFromNamedURL() {
         final NamedUri namedUri = NamedUriStringParser.parse(ANY_NAMED_URL_STRING);
         final DataSource dataSource = DataSourceFactory.fromNamedUri(namedUri);
@@ -160,5 +166,4 @@ public class DataSourceFactoryTest {
         assertEquals("env:///PWD", dataSource.getUri().toString());
         assertEquals("text/plain", dataSource.getContentType());
     }
-
 }
diff --git a/freemarker-generator-base/src/test/java/org/apache/freemarker/generator/datasource/DataSourceTest.java b/freemarker-generator-base/src/test/java/org/apache/freemarker/generator/datasource/DataSourceTest.java
index 040658e..bf2753c 100644
--- a/freemarker-generator-base/src/test/java/org/apache/freemarker/generator/datasource/DataSourceTest.java
+++ b/freemarker-generator-base/src/test/java/org/apache/freemarker/generator/datasource/DataSourceTest.java
@@ -17,9 +17,9 @@
 package org.apache.freemarker.generator.datasource;
 
 import org.apache.commons.io.LineIterator;
-import org.apache.freemarker.generator.base.activation.Mimetypes;
 import org.apache.freemarker.generator.base.datasource.DataSource;
 import org.apache.freemarker.generator.base.datasource.DataSourceFactory;
+import org.apache.freemarker.generator.base.mime.Mimetypes;
 import org.junit.Ignore;
 import org.junit.Test;
 
@@ -31,7 +31,7 @@ import java.util.Iterator;
 
 import static java.nio.charset.StandardCharsets.UTF_8;
 import static org.apache.freemarker.generator.base.FreeMarkerConstants.DEFAULT_GROUP;
-import static org.apache.freemarker.generator.base.activation.Mimetypes.MIME_APPLICATION_OCTET_STREAM;
+import static org.apache.freemarker.generator.base.mime.Mimetypes.MIME_APPLICATION_OCTET_STREAM;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertTrue;
diff --git a/freemarker-generator-base/src/test/java/org/apache/freemarker/generator/datasource/DataSourcesSupplierTest.java b/freemarker-generator-base/src/test/java/org/apache/freemarker/generator/datasource/DataSourcesSupplierTest.java
index 68e6662..8089c48 100644
--- a/freemarker-generator-base/src/test/java/org/apache/freemarker/generator/datasource/DataSourcesSupplierTest.java
+++ b/freemarker-generator-base/src/test/java/org/apache/freemarker/generator/datasource/DataSourcesSupplierTest.java
@@ -21,6 +21,7 @@ import org.apache.freemarker.generator.base.datasource.DataSourcesSupplier;
 import org.junit.Test;
 
 import java.nio.charset.Charset;
+import java.nio.file.Paths;
 import java.util.Arrays;
 import java.util.List;
 
@@ -33,16 +34,17 @@ public class DataSourcesSupplierTest {
 
     private static final String NO_EXCLUDE = null;
     private static final String DATA_DIRECTORY = "./src/test/data";
+    private static final String PWD = Paths.get(".").toAbsolutePath().normalize().toString();
 
     @Test
     public void shouldResolveSingleFile() {
         assertEquals(1, supplier("pom.xml", "*", NO_EXCLUDE).get().size());
         assertEquals(1, supplier("./pom.xml", "*", NO_EXCLUDE).get().size());
-        assertEquals(1, supplier("file://./pom.xml", "*", NO_EXCLUDE).get().size());
         assertEquals(1, supplier("pom=pom.xml", "*", NO_EXCLUDE).get().size());
         assertEquals(1, supplier("pom=./pom.xml", "*", NO_EXCLUDE).get().size());
-        assertEquals(1, supplier("pom=file://./pom.xml", "*", NO_EXCLUDE).get().size());
-        assertEquals(1, supplier("pom=file://./pom.xml?mimetype=application/xml", "*", NO_EXCLUDE).get().size());
+        assertEquals(1, supplier("pom=./pom.xml#mimetype=application/xml", "*", NO_EXCLUDE).get().size());
+        assertEquals(1, supplier("pom=" + PWD + "/pom.xml", "*", NO_EXCLUDE).get().size());
+        assertEquals(1, supplier("pom=file:///" + PWD + "/pom.xml#mimetype=application/xml", "*", NO_EXCLUDE).get().size());
     }
 
     @Test
@@ -127,9 +129,7 @@ public class DataSourcesSupplierTest {
     public void shouldNormalizeDataSourceNameBasedOnFilePath() {
         assertEquals("pom.xml", supplier("pom.xml", "*", NO_EXCLUDE).get().get(0).getName());
         assertEquals("pom.xml", supplier("./pom.xml", "*", NO_EXCLUDE).get().get(0).getName());
-        assertEquals("pom.xml", supplier("file://./pom.xml", "*", NO_EXCLUDE).get().get(0).getName());
-        assertEquals("pom.xml", supplier("file:///pom.xml", "*", NO_EXCLUDE).get().get(0).getName());
-        assertEquals("pom.xml", supplier("file://tmp/pom.xml", "*", NO_EXCLUDE).get().get(0).getName());
+        assertEquals("pom.xml", supplier("file:///" + PWD + "/pom.xml", "*", NO_EXCLUDE).get().get(0).getName());
     }
 
     private static DataSourcesSupplier supplier(String directory, String include, String exclude) {
diff --git a/freemarker-generator-base/src/test/java/org/apache/freemarker/generator/mime/MimetypeParserTest.java b/freemarker-generator-base/src/test/java/org/apache/freemarker/generator/mime/MimetypeParserTest.java
new file mode 100644
index 0000000..f682e13
--- /dev/null
+++ b/freemarker-generator-base/src/test/java/org/apache/freemarker/generator/mime/MimetypeParserTest.java
@@ -0,0 +1,59 @@
+/*
+ * 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.mime;
+
+import org.apache.freemarker.generator.base.mime.MimetypeParser;
+import org.junit.Test;
+
+import java.nio.charset.StandardCharsets;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNull;
+
+public class MimetypeParserTest {
+
+    @Test
+    public void shouldHandleMissingMimeType() {
+        assertNull(MimetypeParser.getMimetype(null));
+        assertNull(MimetypeParser.getMimetype(""));
+        assertNull(MimetypeParser.getMimetype(" "));
+    }
+
+    @Test
+    public void shouldGetMimetype() {
+        assertEquals("text/html", MimetypeParser.getMimetype("text/html"));
+        assertEquals("text/html", MimetypeParser.getMimetype("text/html;charset=utf-8"));
+    }
+
+    @Test
+    public void shouldHandleMissingContentType() {
+        assertNull(MimetypeParser.getCharset(null));
+        assertNull(MimetypeParser.getCharset(""));
+        assertNull(MimetypeParser.getCharset(" "));
+        assertNull(MimetypeParser.getCharset("text/html"));
+        assertNull(MimetypeParser.getCharset("text/html;something=utf-8"));
+    }
+
+    @Test
+    public void shouldGetCharset() {
+        assertEquals(StandardCharsets.UTF_8, MimetypeParser.getCharset("text/html;charset=utf-8"));
+        assertEquals(StandardCharsets.UTF_8, MimetypeParser.getCharset("text/html;charset=UTF-8"));
+        assertEquals(StandardCharsets.UTF_8, MimetypeParser.getCharset("text/html; charset=utf-8"));
+        assertEquals(StandardCharsets.UTF_8, MimetypeParser.getCharset("text/html; charset=UTF-8"));
+        assertEquals(StandardCharsets.UTF_8, MimetypeParser.getCharset("text/html;Charset=UTF-8"));
+    }
+}
diff --git a/freemarker-generator-base/src/test/java/org/apache/freemarker/generator/parameter/ParameterModelSupplierTest.java b/freemarker-generator-base/src/test/java/org/apache/freemarker/generator/parameter/ParameterModelSupplierTest.java
new file mode 100644
index 0000000..3ad7bc7
--- /dev/null
+++ b/freemarker-generator-base/src/test/java/org/apache/freemarker/generator/parameter/ParameterModelSupplierTest.java
@@ -0,0 +1,92 @@
+/*
+ * 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.parameter;
+
+import org.apache.freemarker.generator.base.parameter.Parameter;
+import org.apache.freemarker.generator.base.parameter.ParameterModelSupplier;
+import org.apache.freemarker.generator.base.parameter.ParameterParser;
+import org.junit.Test;
+
+import java.util.Arrays;
+import java.util.Map;
+import java.util.stream.Collectors;
+
+import static org.junit.Assert.assertEquals;
+
+public class ParameterModelSupplierTest {
+
+    @Test
+    public void shouldConvertMissingParametersToMap() {
+        assertEquals(0, supplier(new String[0]).get().size());
+    }
+
+    @Test
+    public void shouldConverNameValueToMap() {
+        final Map<String, Object> map = supplier("name=value").get();
+
+        assertEquals(1, map.size());
+        assertEquals("value", map.get("name"));
+    }
+
+    @Test
+    public void shouldConverNameValueWithSpacesToMap() {
+        final Map<String, Object> map = supplier("name=value with spaces").get();
+
+        assertEquals(1, map.size());
+        assertEquals("value with spaces", map.get("name"));
+    }
+
+    @Test
+    public void shouldConvertGroupedNameValueToMap() {
+        final Map<String, Object> map = supplier("name1:group=value1").get();
+
+        assertEquals(1, map.size());
+        assertEquals("value1", toMap(map, "group").get("name1"));
+    }
+
+    @Test
+    public void shouldConvertGroupedNameValuesToMap() {
+        final Map<String, Object> map = supplier("name1:group=value1", "name2:group=value2").get();
+
+        assertEquals(1, map.size());
+        assertEquals("value1", toMap(map, "group").get("name1"));
+        assertEquals("value2", toMap(map, "group").get("name2"));
+    }
+
+    @Test
+    public void shouldConvertMultipleGroupedNameValuesToMap() {
+        final Map<String, Object> map = supplier("name1:group1=value1", "name1:group2=value1", "name2:group2=value2").get();
+
+        assertEquals(2, map.size());
+        assertEquals("value1", toMap(map, "group1").get("name1"));
+        assertEquals("value1", toMap(map, "group2").get("name1"));
+        assertEquals("value2", toMap(map, "group2").get("name2"));
+    }
+
+    private static ParameterModelSupplier supplier(String... values) {
+        final Map<String, String> parameters = Arrays.stream(values)
+                .map(ParameterParser::parse)
+                .collect(Collectors.toMap(Parameter::getKey, Parameter::getValue));
+
+        return new ParameterModelSupplier(parameters);
+    }
+
+    @SuppressWarnings("unchecked")
+    private static Map<String, Object> toMap(Map<String, Object> model, String key) {
+        return (Map<String, Object>) model.get(key);
+    }
+}
diff --git a/freemarker-generator-base/src/test/java/org/apache/freemarker/generator/uri/NamedUriStringParserTest.java b/freemarker-generator-base/src/test/java/org/apache/freemarker/generator/uri/NamedUriStringParserTest.java
index f007064..a4da79e 100644
--- a/freemarker-generator-base/src/test/java/org/apache/freemarker/generator/uri/NamedUriStringParserTest.java
+++ b/freemarker-generator-base/src/test/java/org/apache/freemarker/generator/uri/NamedUriStringParserTest.java
@@ -28,7 +28,7 @@ import static org.junit.Assert.assertTrue;
 public class NamedUriStringParserTest {
 
     @Test
-    public void shouldParseRelativeFileName() {
+    public void shouldParseFileName() {
         final NamedUri namedURI = parse("users.csv");
 
         assertNull(namedURI.getName());
@@ -39,6 +39,30 @@ public class NamedUriStringParserTest {
     }
 
     @Test
+    public void shouldParseFileNameWithFragment() {
+        final NamedUri namedURI = parse("users.csv#foo=bar");
+
+        assertNull(namedURI.getName());
+        assertNull(namedURI.getGroup());
+        assertEquals("users.csv#foo=bar", namedURI.getUri().toString());
+        assertEquals("users.csv", namedURI.getFile().getName());
+        assertEquals("foo=bar", namedURI.getUri().getFragment());
+        assertEquals(1, namedURI.getParameters().size());
+        assertEquals("bar", namedURI.getParameters().get("foo"));
+    }
+
+    @Test
+    public void shouldParseRelativeFileName() {
+        final NamedUri namedURI = parse("./users.csv");
+
+        assertNull(namedURI.getName());
+        assertNull(namedURI.getGroup());
+        assertEquals("./users.csv", namedURI.getUri().toString());
+        assertEquals("users.csv", namedURI.getFile().getName());
+        assertEquals(0, namedURI.getParameters().size());
+    }
+
+    @Test
     public void shouldParseAbsoluteFileName() {
         final NamedUri namedURI = parse("/data/users.csv");
 
@@ -77,7 +101,7 @@ public class NamedUriStringParserTest {
 
         assertEquals("users", namedURI.getName());
         assertNull(namedURI.getGroup());
-        assertEquals("file:///users.csv", namedURI.getUri().toString());
+        assertEquals("users.csv", namedURI.getUri().toString());
         assertEquals("users.csv", namedURI.getFile().getName());
         assertEquals(0, namedURI.getParameters().size());
         assertTrue(namedURI.hasName());
@@ -85,6 +109,17 @@ public class NamedUriStringParserTest {
     }
 
     @Test
+    public void shouldParseNamedGroupFileName() {
+        final NamedUri namedURI = parse("name:group=users.csv");
+
+        assertEquals("name", namedURI.getName());
+        assertEquals("group", namedURI.getGroup());
+        assertEquals("users.csv", namedURI.getUri().toString());
+        assertEquals("users.csv", namedURI.getFile().getName());
+        assertEquals(0, namedURI.getParameters().size());
+    }
+
+    @Test
     public void shouldParseNamedFileUri() {
         final NamedUri namedURI = parse("users=file:///users.csv");
 
diff --git a/freemarker-generator-cli/README.md b/freemarker-generator-cli/README.md
index 3fde98b..bd9ec0e 100644
--- a/freemarker-generator-cli/README.md
+++ b/freemarker-generator-cli/README.md
@@ -78,11 +78,11 @@ You can test the installation by executing
 > ./bin/freemarker-cli -t templates/info.ftl 
 FreeMarker CLI Information
 ------------------------------------------------------------------------------
-FreeMarker version     : 2.3.29
+FreeMarker version     : 2.3.30
 Template name          : templates/info.ftl
 Language               : en
 Locale                 : en_US
-Timestamp              : Apr 4, 2020 12:39:28 PM
+Timestamp              : Apr 14, 2020 11:34:13 PM
 Output encoding        : UTF-8
 Output format          : plainText
 
@@ -122,7 +122,7 @@ Command line         : -t, templates/info.ftl
 Host Name            : W0GL5179.local
 Java Home            : /Library/Java/JavaVirtualMachines/jdk1.8.0_192.jdk/Contents/Home
 User Name            : sgoeschl
-Timestamp            : 1,585,996,768,896
+Timestamp            : 1,586,900,053,355
 Writer               : org.apache.freemarker.generator.base.util.NonClosableWriterWrapper
 ```
 
@@ -195,13 +195,14 @@ Please note that generated PDF files are very likely not found since they requir
 
 ```text
 > ./bin/freemarker-cli  -h
-Usage: freemarker-cli (-t=<template> | -i=<interactiveTemplate>) [-EhV]
+Usage: freemarker-cli (-t=<template> | -i=<interactiveTemplate>) [-hV]
                       [--stdin] [-b=<baseDir>] [--config=<configFile>]
                       [-e=<inputEncoding>] [--exclude=<exclude>]
-                      [--include=<include>] [-l=<locale>] [-o=<outputFile>]
-                      [--output-encoding=<outputEncoding>] [--times=<times>]
-                      [-D=<String=String>]... [-P=<String=String>]...
-                      [<sources>...]
+                      [--include=<include>] [-l=<locale>] [--mode=<mode>]
+                      [-o=<outputFile>] [--output-encoding=<outputEncoding>]
+                      [--times=<times>] [-D=<String=String>]...
+                      [-m=<dataModels>]... [-P=<String=String>]...
+                      [-s=<dataSources>]... [<sources>...]
 Apache FreeMarker CLI
       [<sources>...]        List of input files and/or input directories
   -b, --basedir=<baseDir>   Optional template base directory
@@ -210,24 +211,26 @@ Apache FreeMarker CLI
                             Set system property
   -e, --input-encoding=<inputEncoding>
                             Encoding of data source
-  -E, --expose-env          Expose environment variables and user-supplied
-                              properties globally
       --exclude=<exclude>   File pattern for data source input directory
   -h, --help                Show this help message and exit.
   -i, --interactive=<interactiveTemplate>
                             Interactive FreeMarker template
       --include=<include>   File pattern for data source input directory
   -l, --locale=<locale>     Locale being used for the output, e.g. 'en_US'
+  -m, --data-model=<dataModels>
+                            Data model used for rendering
+      --mode=<mode>         [template|datasource]
   -o, --output=<outputFile> Output file
       --output-encoding=<outputEncoding>
                             Encoding of output, e.g. UTF-8
   -P, --param=<String=String>
                             Set parameter
-      --stdin               Read data source from stdin
+  -s, --data-source=<dataSources>
+                            Data source used for rendering
+      --stdin               Read data  source from stdin
   -t, --template=<template> FreeMarker template to render
       --times=<times>       Re-run X times for profiling
   -V, --version             Print version information and exit.
-
 ```
 
 # 6. Examples
@@ -876,7 +879,7 @@ While this looks small and tidy there are some nifty features
 
 Sometimes you have a CSV file which is not quite right - you need to change the format. Lets have a look how `freemarker-cli` can help
 
-> bin/freemarker-cli -Pcsv.in.delimiter=COMMA -Pcsv.out.delimiter=PIPE -t templates/csv/transform.ftl ./site/sample/csv/contract.csv 
+> bin/freemarker-cli -PCVS_IN_DELIMITER=COMMA -PCSV_OUT_DELIMITER=PIPE -t templates/csv/transform.ftl ./site/sample/csv/contract.csv 
 
 renders the following template
 
@@ -892,15 +895,15 @@ renders the following template
 </#compress>
 
 <#function createCsvParser dataSource>
-    <#assign initialCvsInFormat = CSVTool.formats[SystemTool.getParameter("csv.in.format", "DEFAULT")]>
-    <#assign csvInDelimiter = CSVTool.toDelimiter(SystemTool.getParameter("csv.in.delimiter", initialCvsInFormat.getDelimiter()))>
+    <#assign initialCvsInFormat = CSVTool.formats[CSV_IN_FORMAT!"DEFAULT"]>
+    <#assign csvInDelimiter = CSVTool.toDelimiter(CSV_IN_DELIMITER!initialCvsInFormat.getDelimiter())>
     <#assign cvsInFormat = initialCvsInFormat.withDelimiter(csvInDelimiter)>
     <#return CSVTool.parse(dataSource, cvsInFormat)>
 </#function>
 
 <#function createCsvPrinter>
-    <#assign initialCvsOutFormat = CSVTool.formats[SystemTool.getParameter("csv.out.format", "DEFAULT")]>
-    <#assign csvOutDelimiter = CSVTool.toDelimiter(SystemTool.getParameter("csv.out.delimiter", initialCvsOutFormat.getDelimiter()))>
+    <#assign initialCvsOutFormat = CSVTool.formats[CSV_OUT_FORMAT!"DEFAULT"]>
+    <#assign csvOutDelimiter = CSVTool.toDelimiter(CSV_OUT_DELIMITER!initialCvsOutFormat.getDelimiter())>
     <#assign cvsOutFormat = initialCvsOutFormat.withDelimiter(csvOutDelimiter)>
     <#return CSVTool.printer(cvsOutFormat, SystemTool.writer)>
 </#function>
@@ -1118,8 +1121,6 @@ Sometimes we simply need to transform a JSON into an equivalent YAML or the othe
 > ./bin/freemarker-cli -i '${YamlTool.toYaml(GsonTool.parse(DataSources.get(0)))}' site/sample/json/swagger-spec.json
 ```
 
-
-
 ## 6.16 Using Advanced FreeMarker Features
 
 There is a `demo.ftl` which shows some advanced FreeMarker functionality
@@ -1139,13 +1140,12 @@ gives you
 ```text
 1) FreeMarker Special Variables
 ---------------------------------------------------------------------------
-FreeMarker version     : 2.3.29
+FreeMarker version     : 2.3.30
 Template name          : templates/demo.ftl
 Language               : en
 Locale                 : en_US
-Timestamp              : Feb 22, 2020 4:54:19 PM
+Timestamp              : Apr 14, 2020 11:40:26 PM
 Output encoding        : UTF-8
-Output format          : plainText
 
 2) Invoke a constructor of a Java class
 ---------------------------------------------------------------------------
@@ -1264,13 +1264,13 @@ German Special Characters: äöüßÄÖÜ
 ---------------------------------------------------------------------------
 Small Number :  1.23
 Large Number :  12,345,678.90
-Date         :  Feb 22, 2020
-Time         :  4:54:20 PM
+Date         :  Apr 14, 2020
+Time         :  11:40:26 PM
 
 17) Execute a program
 ---------------------------------------------------------------------------
 > date
-Sat Feb 22 16:54:20 CET 2020
+Tue Apr 14 23:40:26 CEST 2020
 ```
 
 # 7. Design Considerations
@@ -1298,9 +1298,9 @@ Within the script a FreeMarker data model is set up and passed to the template -
 | JsoupTool             | Processing HTML files using [Jsoup](https://jsoup.org)                                                    |
 | PropertiesTool        | Process JDK properties files                                                                              |
 | SystemTool            | System-related utility methods                                                                            |
+| UUIDTool              | Create UUIDs                                                                                              |
 | XmlTool               | Process XML files using [Apache FreeMarker](https://freemarker.apache.org/docs/xgui.html)                 |
 | YamlTool              | Process YAML files using [SnakeYAML](https://bitbucket.org/asomov/snakeyaml/wiki/Home)                    |
-| UUIDTool              | Create UUIDs                                                                                              |
 
 
 # 8. Tips & Tricks
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 e3b17b4..fa2cd12 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
@@ -17,6 +17,7 @@
 package org.apache.freemarker.generator.cli;
 
 import org.apache.freemarker.generator.base.FreeMarkerConstants.GeneratorMode;
+import org.apache.freemarker.generator.base.parameter.ParameterModelSupplier;
 import org.apache.freemarker.generator.base.util.ClosableUtils;
 import org.apache.freemarker.generator.base.util.StringUtils;
 import org.apache.freemarker.generator.cli.config.Settings;
@@ -38,7 +39,6 @@ import java.io.IOException;
 import java.io.OutputStreamWriter;
 import java.io.Writer;
 import java.util.Collection;
-import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
 import java.util.Objects;
@@ -77,9 +77,6 @@ public class Main implements Callable<Integer> {
     @Option(names = { "-e", "--input-encoding" }, description = "Encoding of data source", defaultValue = "UTF-8")
     String inputEncoding;
 
-    @Option(names = { "-E", "--expose-env" }, description = "Expose environment variables and user-supplied properties globally")
-    boolean isEnvironmentExposed;
-
     @Option(names = { "-l", "--locale" }, description = "Locale being used for the output, e.g. 'en_US'")
     String locale;
 
@@ -208,8 +205,9 @@ public class Main implements Callable<Integer> {
     }
 
     private Settings settings(Properties configuration, List<File> templateDirectories) {
+        final ParameterModelSupplier parameterModelSupplier = new ParameterModelSupplier(parameters);
+
         return Settings.builder()
-                .isEnvironmentExposed(isEnvironmentExposed)
                 .isReadFromStdin(readFromStdin)
                 .setArgs(args)
                 .setConfiguration(configuration)
@@ -220,7 +218,7 @@ public class Main implements Callable<Integer> {
                 .setLocale(locale)
                 .setOutputEncoding(outputEncoding)
                 .setOutputFile(outputFile)
-                .setParameters(parameters != null ? parameters : new HashMap<>())
+                .setParameters(parameterModelSupplier.get())
                 .setDataSources(getCombindedDataSources())
                 .setDataModels(dataModels)
                 .setSystemProperties(systemProperties != null ? systemProperties : new Properties())
diff --git a/freemarker-generator-cli/src/main/java/org/apache/freemarker/generator/cli/config/DataModelSupplier.java b/freemarker-generator-cli/src/main/java/org/apache/freemarker/generator/cli/config/DataModelSupplier.java
index 62ae775..d8e3491 100644
--- a/freemarker-generator-cli/src/main/java/org/apache/freemarker/generator/cli/config/DataModelSupplier.java
+++ b/freemarker-generator-cli/src/main/java/org/apache/freemarker/generator/cli/config/DataModelSupplier.java
@@ -37,9 +37,9 @@ import java.util.function.Supplier;
 
 import static java.util.Objects.requireNonNull;
 import static java.util.stream.Collectors.toMap;
-import static org.apache.freemarker.generator.base.activation.Mimetypes.MIME_APPLICATION_JSON;
-import static org.apache.freemarker.generator.base.activation.Mimetypes.MIME_TEXT_PLAIN;
-import static org.apache.freemarker.generator.base.activation.Mimetypes.MIME_TEXT_YAML;
+import static org.apache.freemarker.generator.base.mime.Mimetypes.MIME_APPLICATION_JSON;
+import static org.apache.freemarker.generator.base.mime.Mimetypes.MIME_TEXT_PLAIN;
+import static org.apache.freemarker.generator.base.mime.Mimetypes.MIME_TEXT_YAML;
 
 /**
  * Create a map representing a data model based on a list of sources consisting of
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 51bbae8..5163aed 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
@@ -82,17 +82,14 @@ public class Settings {
     /** Read from stdin? */
     private final boolean isReadFromStdin;
 
-    /** Expose environment variables globally in the data model? */
-    private final boolean isEnvironmentExposed;
-
     /** 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 List<String> dataModels;
+    private final List<String> dataModels;
 
     /** User-supplied parameters */
-    private final Map<String, String> parameters;
+    private final Map<String, Object> parameters;
 
     /** User-supplied system properties */
     private final Properties sytemProperties;
@@ -114,10 +111,9 @@ public class Settings {
             String exclude,
             Locale locale,
             boolean isReadFromStdin,
-            boolean isEnvironmentExposed,
             List<String> dataSources,
             List<String> dataModels,
-            Map<String, String> parameters,
+            Map<String, Object> parameters,
             Properties sytemProperties,
             Writer writer) {
         if (isEmpty(template) && isEmpty(interactiveTemplate)) {
@@ -136,7 +132,6 @@ public class Settings {
         this.exclude = exclude;
         this.locale = requireNonNull(locale);
         this.isReadFromStdin = isReadFromStdin;
-        this.isEnvironmentExposed = isEnvironmentExposed;
         this.dataSources = requireNonNull(dataSources);
         this.dataModels = requireNonNull(dataModels);
         this.parameters = requireNonNull(parameters);
@@ -205,10 +200,6 @@ public class Settings {
         return isReadFromStdin;
     }
 
-    public boolean isEnvironmentExposed() {
-        return isEnvironmentExposed;
-    }
-
     public List<String> getDataSources() {
         return dataSources;
     }
@@ -217,7 +208,7 @@ public class Settings {
         return dataModels;
     }
 
-    public Map<String, String> getParameters() {
+    public Map<String, Object> getParameters() {
         return parameters;
     }
 
@@ -270,16 +261,9 @@ public class Settings {
                 ", exclude='" + include + '\'' +
                 ", locale=" + locale +
                 ", isReadFromStdin=" + isReadFromStdin +
-                ", isEnvironmentExposed=" + isEnvironmentExposed +
                 ", dataSources=" + dataSources +
                 ", properties=" + parameters +
                 ", sytemProperties=" + sytemProperties +
-                ", writer=" + writer +
-                ", templateEncoding=" + getTemplateEncoding() +
-                ", readFromStdin=" + isReadFromStdin() +
-                ", environmentExposed=" + isEnvironmentExposed() +
-                ", hasOutputFile=" + hasOutputFile() +
-                ", toMap=" + toMap() +
                 '}';
     }
 
@@ -296,10 +280,9 @@ public class Settings {
         private String exclude;
         private String locale;
         private boolean isReadFromStdin;
-        private boolean isEnvironmentExposed;
         private List<String> dataSources;
         private List<String> dataModels;
-        private Map<String, String> parameters;
+        private Map<String, Object> parameters;
         private Properties systemProperties;
         private Properties configuration;
         private Writer writer;
@@ -386,11 +369,6 @@ public class Settings {
             return this;
         }
 
-        public SettingsBuilder isEnvironmentExposed(boolean isEnvironmentExposed) {
-            this.isEnvironmentExposed = isEnvironmentExposed;
-            return this;
-        }
-
         public SettingsBuilder setDataSources(List<String> dataSources) {
             if (dataSources != null) {
                 this.dataSources = dataSources;
@@ -405,7 +383,7 @@ public class Settings {
             return this;
         }
 
-        public SettingsBuilder setParameters(Map<String, String> parameters) {
+        public SettingsBuilder setParameters(Map<String, Object> parameters) {
             if (parameters != null) {
                 this.parameters = parameters;
             }
@@ -451,7 +429,6 @@ public class Settings {
                     exclude,
                     LocaleUtils.parseLocale(currLocale),
                     isReadFromStdin,
-                    isEnvironmentExposed,
                     dataSources,
                     dataModels,
                     parameters,
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 79f6e0a..6a57e59 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
@@ -22,6 +22,7 @@ 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 java.util.Map;
 import java.util.function.Supplier;
 
 /**
@@ -60,6 +61,10 @@ public class Suppliers {
         return new DataModelSupplier(settings.getDataModels());
     }
 
+    public static Supplier<Map<String, Object>> parameterSupplier(Settings settings) {
+        return settings::getParameters;
+    }
+
     public static PropertiesSupplier propertiesSupplier(String fileName) {
         return new PropertiesSupplier(
                 new PropertiesFileSystemSupplier(fileName),
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 a89bc33..803b9c2 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
@@ -45,6 +45,7 @@ import static org.apache.freemarker.generator.base.FreeMarkerConstants.Model.DAT
 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.toolsSupplier;
 
 /**
@@ -58,21 +59,29 @@ public class FreeMarkerTask implements Callable<Integer> {
     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;
 
     public FreeMarkerTask(Settings settings) {
-        this(settings, toolsSupplier(settings), dataSourcesSupplier(settings), dataModelSupplier(settings), configurationSupplier(settings));
+        this(settings,
+                toolsSupplier(settings),
+                dataSourcesSupplier(settings),
+                dataModelSupplier(settings),
+                parameterSupplier(settings),
+                configurationSupplier(settings));
     }
 
     public FreeMarkerTask(Settings settings,
                           Supplier<Map<String, Object>> toolsSupplier,
                           Supplier<List<DataSource>> dataSourcesSupplier,
                           Supplier<Map<String, Object>> dataModelsSupplier,
+                          Supplier<Map<String, Object>> parameterModelSupplier,
                           Supplier<Configuration> configurationSupplier) {
         this.settings = requireNonNull(settings);
         this.toolsSupplier = requireNonNull(toolsSupplier);
         this.dataSourcesSupplier = requireNonNull(dataSourcesSupplier);
         this.dataModelsSupplier = requireNonNull(dataModelsSupplier);
+        this.parameterModelSupplier = requireNonNull(parameterModelSupplier);
         this.configurationSupplier = requireNonNull(configurationSupplier);
     }
 
@@ -80,7 +89,7 @@ public class FreeMarkerTask implements Callable<Integer> {
     public Integer call() {
         final Template template = template(settings, configurationSupplier);
         try (Writer writer = settings.getWriter(); DataSources dataSources = dataSources(settings, dataSourcesSupplier)) {
-            final Map<String, Object> dataModel = dataModel(settings, dataSources, dataModelsSupplier, toolsSupplier);
+            final Map<String, Object> dataModel = dataModel(dataSources, parameterModelSupplier, dataModelsSupplier, toolsSupplier);
             template.process(dataModel, writer);
             return SUCCESS;
         } catch (RuntimeException e) {
@@ -131,24 +140,15 @@ public class FreeMarkerTask implements Callable<Integer> {
     }
 
     private static Map<String, Object> dataModel(
-            Settings settings,
             DataSources dataSources,
+            Supplier<Map<String, Object>> parameterModelSupplier,
             Supplier<Map<String, Object>> dataModelsSupplier,
             Supplier<Map<String, Object>> tools) {
         final Map<String, Object> result = new HashMap<>();
-
         result.putAll(dataModelsSupplier.get());
         result.put(DATASOURCES, dataSources);
-
-        // TODO rework  based on FREEMARKER-140
-        if (settings.isEnvironmentExposed()) {
-            // add all system & user-supplied properties as top-level entries
-            result.putAll(System.getenv());
-            result.putAll(settings.getParameters());
-        }
-
+        result.putAll(parameterModelSupplier.get());
         result.putAll(tools.get());
-
         return result;
     }
 
diff --git a/freemarker-generator-cli/src/site/markdown/cli/concepts/named-uris.md b/freemarker-generator-cli/src/site/markdown/cli/concepts/named-uris.md
index 2a2469e..7ab78e8 100644
--- a/freemarker-generator-cli/src/site/markdown/cli/concepts/named-uris.md
+++ b/freemarker-generator-cli/src/site/markdown/cli/concepts/named-uris.md
@@ -18,13 +18,13 @@ scheme     authority               path        query   fragment
 
 For our purposes, the scheme and the path components are especially important, though the other components are used by certain datasources for particular purposes.
 
-| Component | Purpose |
-|-----------|---------|
-| scheme	| All datasources require a scheme (except for file when using relative paths) |
-| authority	| Used only by remote datasources, and can be omitted in some of those cases. Consists of userinfo (user:pass), host, and port. |
-| path	    | Can be omitted, but usually used as the basis of the locator for the datasource. |
-| query	    | Used mainly for HTTP and HTTPS URLs |
-| fragment	| Used rarely for providing additional attributes, e.g. `mimetype` of `charset` |
+| Component | Purpose                                                                                                   |
+|-----------|-----------------------------------------------------------------------------------------------------------|
+| scheme	| All datasources require a scheme (except for file when using relative paths)                              |
+| authority	| Used only by remote datasources, and can be omitted in some of those cases.                               |
+| path	    | Can be omitted, but usually used as the basis of the locator for the datasource.                          |
+| query	    | Used mainly for HTTP and HTTPS URLs                                                                       |
+| fragment	| Used rarely for providing additional attributes, e.g. `mimetype` of `charset`                             |
 
 The following Named URI loads a "user.csv" and the data source is available as `my_users` 
 
diff --git a/freemarker-generator-cli/src/site/markdown/cli/concepts/user-parameters.md b/freemarker-generator-cli/src/site/markdown/cli/concepts/user-parameters.md
new file mode 100644
index 0000000..46f2d2e
--- /dev/null
+++ b/freemarker-generator-cli/src/site/markdown/cli/concepts/user-parameters.md
@@ -0,0 +1,37 @@
+# User-Supplied Parameters
+
+User-supplied parameters allow to pass additional information to an Apache FreeMarker template 
+
+* `-Pname=value` to define a key/value pair
+* `-Pname:group=value` to define a map containing key/value pairs, i.e nested map 
+
+Pass a simple name/value pair on the command line 
+
+```
+> bin/freemarker-cli -t templates/info.ftl -P key=value
+
+User Supplied Parameters
+------------------------------------------------------------------------------
+- key ==> value
+```
+
+By providing a `group` you can create nested maps
+
+```
+> bin/freemarker-cli -t templates/info.ftl -P foo1:group=bar1 -P foo2:group=bar2
+
+User Supplied Parameters
+------------------------------------------------------------------------------
+- group ==> { foo1=bar1 foo2=bar2 }
+```
+
+It is also possible to mix and match the two approaches
+
+```
+> bin/freemarker-cli -t templates/info.ftl -P foo1:group=bar1 -P foo2:group=bar2 -P key=value
+
+User Supplied Parameters
+------------------------------------------------------------------------------
+- key ==> value
+- group ==> { foo1=bar1 foo2=bar2 }
+```
\ No newline at end of file
diff --git a/freemarker-generator-cli/src/site/markdown/index.md b/freemarker-generator-cli/src/site/markdown/index.md
index c5807bc..791ab12 100644
--- a/freemarker-generator-cli/src/site/markdown/index.md
+++ b/freemarker-generator-cli/src/site/markdown/index.md
@@ -2,5 +2,6 @@
 
 ### Concepts
 
+* [User-Supplied Parameters](cli/concepts/user-parameters.html)
 * [Named URIs](cli/concepts/named-uris.html)
 * [Data Models](cli/concepts/data-models.html)
\ No newline at end of file
diff --git a/freemarker-generator-cli/src/test/java/org/apache/freemarker/generator/cli/ExamplesTest.java b/freemarker-generator-cli/src/test/java/org/apache/freemarker/generator/cli/ExamplesTest.java
index 295b2e7..147a2d7 100644
--- a/freemarker-generator-cli/src/test/java/org/apache/freemarker/generator/cli/ExamplesTest.java
+++ b/freemarker-generator-cli/src/test/java/org/apache/freemarker/generator/cli/ExamplesTest.java
@@ -99,7 +99,7 @@ public class ExamplesTest extends AbstractMainTest {
 
     @Test
     public void shouldRunWithExposedEnvironmentVariableExamples() throws IOException {
-        assertValid(execute("-b ./src/test -E -t templates/environment.ftl"));
+        assertValid(execute("-b ./src/test -m env:/// -t templates/environment.ftl"));
     }
 
     @Test
diff --git a/freemarker-generator-cli/src/test/java/org/apache/freemarker/generator/cli/ManualTest.java b/freemarker-generator-cli/src/test/java/org/apache/freemarker/generator/cli/ManualTest.java
index 0dac268..210f32f 100644
--- a/freemarker-generator-cli/src/test/java/org/apache/freemarker/generator/cli/ManualTest.java
+++ b/freemarker-generator-cli/src/test/java/org/apache/freemarker/generator/cli/ManualTest.java
@@ -28,7 +28,7 @@ public class ManualTest {
     // private static final String CMD = "-b ./src/test -l de_AT -DFOO=foo -DBAR=bar -t templates/info.ftl site/sample/csv/transactions.csv";
     // private static final String CMD = "-b ./src/test -DFOO=foo -PBAR=bar -l de -t templates/demo.ftl site/sample/csv/transactions.csv";
     // private static final String CMD = "-b ./src/test -DFOO=foo -PBAR=bar -t templates/demo.ftl site/sample/csv/transactions.csv";
-    // private static final String CMD = "-b ./src/test -Dcsv.out.format=TDF -t templates/csv/transform.ftl site/sample/csv/contract.csv";
+    // private static final String CMD = "-b ./src/test -PCSV_OUT_FORMAT=TDF -t templates/csv/transform.ftl site/sample/csv/contract.csv";
     // private static final String CMD = "-t templates/excel/csv/transform.ftl -l de_AT site/sample/excel/test.xlsx";
     // private static final String CMD = "-i ${JsonPathTool.parse(DataSources.first).read('$.info.title')} site/sample/json/swagger-spec.json";
     // private static final String CMD = "-i ${XmlTool.parse(DataSources.first)['recipients/person[1]/name']} site/sample/xml/recipients.xml";
@@ -48,7 +48,7 @@ public class ManualTest {
     // private static final String CMD = "-b ./src/test -t templates/demo.ftl -m env=./site/sample/properties/user_0001/user.properties";
     // private static final String CMD = "-b ./src/test -t templates/demo.ftl -m ./site/sample/properties/user_0001/user.properties";
     // private static final String CMD = "-b ./src/test --data-model post=https://jsonplaceholder.typicode.com/posts/2 -t templates/info.ftl";
-    private static final String CMD = "-b ./src/test -t templates/info.ftl google=https://www.google.com";
+    private static final String CMD = "-b ./src/test -t templates/info.ftl -P name=value";
 
 
     public static void main(String[] args) {
diff --git a/freemarker-generator-cli/src/test/java/org/apache/freemarker/generator/cli/PicocliTest.java b/freemarker-generator-cli/src/test/java/org/apache/freemarker/generator/cli/PicocliTest.java
index d5e15d6..d465fd0 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
@@ -81,6 +81,21 @@ public class PicocliTest {
         assertNull(main.sources);
     }
 
+    @Test
+    public void testSingleParameter() {
+        final Main main = parse("-t", TEMPLATE, "-P", "name:group=value");
+
+        assertEquals("value", main.parameters.get("name:group"));
+    }
+
+    @Test
+    public void testMultipleParameters() {
+        final Main main = parse("-t", TEMPLATE, "-P", "name1:group=value1", "-P", "name2:group=value2");
+
+        assertEquals("value1", main.parameters.get("name1:group"));
+        assertEquals("value2", main.parameters.get("name2:group"));
+    }
+
     private static Main parse(String... args) {
         final Main main = new Main();
         new CommandLine(main).parseArgs(args);
diff --git a/freemarker-generator-cli/src/test/java/org/apache/freemarker/generator/cli/config/DataModelSupplierTest.java b/freemarker-generator-cli/src/test/java/org/apache/freemarker/generator/cli/config/DataModelSupplierTest.java
index 0dc0d78..def6a86 100644
--- a/freemarker-generator-cli/src/test/java/org/apache/freemarker/generator/cli/config/DataModelSupplierTest.java
+++ b/freemarker-generator-cli/src/test/java/org/apache/freemarker/generator/cli/config/DataModelSupplierTest.java
@@ -16,8 +16,10 @@
  */
 package org.apache.freemarker.generator.cli.config;
 
+import org.junit.Ignore;
 import org.junit.Test;
 
+import java.nio.file.Paths;
 import java.util.Map;
 
 import static java.util.Collections.singletonList;
@@ -27,6 +29,7 @@ import static org.junit.Assert.assertTrue;
 
 public class DataModelSupplierTest {
 
+    private static final String PWD = Paths.get(".").toAbsolutePath().normalize().toString();
     private static final String PWD_VALUE = System.getenv("PWD");
     private static final int NR_OF_ALL_ENV_VARIABLES = System.getenv().size();
 
@@ -104,7 +107,7 @@ public class DataModelSupplierTest {
 
     @Test
     public void shouldResolvePropertiesUriToDataModelVariable() {
-        final DataModelSupplier supplier = supplier("props=file://./src/test/data/properties/test.properties");
+        final DataModelSupplier supplier = supplier("props=file:///" + PWD + "/src/test/data/properties/test.properties");
 
         final Map<String, Object> model = supplier.get();
 
@@ -140,6 +143,7 @@ public class DataModelSupplierTest {
     // == URL ===
 
     @Test
+    @Ignore
     public void shouldResolveUrlToTopLevelDataModel() {
         final DataModelSupplier supplier = supplier("post=https://jsonplaceholder.typicode.com/posts/2");
 
@@ -150,6 +154,7 @@ public class DataModelSupplierTest {
     }
 
     @Test
+    @Ignore
     public void shouldResolveUrlToDataModelVariable() {
         final DataModelSupplier supplier = supplier("https://jsonplaceholder.typicode.com/posts/2");
 
@@ -159,6 +164,7 @@ public class DataModelSupplierTest {
     }
 
     @Test(expected = RuntimeException.class)
+    @Ignore
     public void shouldResolveUrlToDataModelVariables() {
         supplier("https://jsonplaceholder.typicode.com/posts/does-not-exist").get();
     }
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 3c71276..16032e4 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
@@ -42,7 +42,7 @@ public class SettingsTest {
     private static final String ANY_OUTPUT_FILE = "outputFile";
     private static final List<String> ANY_SOURCES = singletonList("sources");
     private static final String ANY_TEMPLATE_NAME = "templateName";
-    private static final Map<String, String> ANY_USER_PARAMETERS = new HashMap<>();
+    private static final Map<String, Object> ANY_USER_PARAMETERS = new HashMap<>();
     private static final Properties ANY_SYSTEM_PROPERTIES = new Properties();
 
     @Test
@@ -60,14 +60,12 @@ public class SettingsTest {
         assertNotNull(settings.getParameters());
         assertNotNull(settings.getSytemProperties());
         assertTrue(settings.isReadFromStdin());
-        assertTrue(settings.isEnvironmentExposed());
         assertTrue(settings.isInteractiveTemplate());
         assertTrue(settings.isVerbose());
     }
 
     private SettingsBuilder allSettingsBuilder() {
         return Settings.builder()
-                .isEnvironmentExposed(true)
                 .isReadFromStdin(true)
                 .setArgs(ANY_ARGS)
                 .setConfiguration(ANY_CONFIGURATION)
diff --git a/freemarker-generator-cli/templates/csv/fo/transform.ftl b/freemarker-generator-cli/templates/csv/fo/transform.ftl
index 7ce74c8..4efa596 100644
--- a/freemarker-generator-cli/templates/csv/fo/transform.ftl
+++ b/freemarker-generator-cli/templates/csv/fo/transform.ftl
@@ -15,7 +15,7 @@
   specific language governing permissions and limitations
   under the License.
 -->
-<#assign csvFormatName = SystemTool.parameters["csv.format"]!"DEFAULT">
+<#assign csvFormatName = CVS_IN_FORMAT!"DEFAULT">
 <#assign cvsFormat = CSVTool.formats[csvFormatName].withHeader()>
 <#assign csvParser = CSVTool.parse(DataSources.get(0), cvsFormat)>
 <#assign csvHeaders = csvParser.getHeaderMap()?keys>
diff --git a/freemarker-generator-cli/templates/csv/transform.ftl b/freemarker-generator-cli/templates/csv/transform.ftl
index b4e21ef..af091ec 100644
--- a/freemarker-generator-cli/templates/csv/transform.ftl
+++ b/freemarker-generator-cli/templates/csv/transform.ftl
@@ -25,15 +25,15 @@
 </#compress>
 
 <#function createCsvParser dataSource>
-    <#assign initialCvsInFormat = CSVTool.formats[SystemTool.getParameter("csv.in.format", "DEFAULT")]>
-    <#assign csvInDelimiter = CSVTool.toDelimiter(SystemTool.getParameter("csv.in.delimiter", initialCvsInFormat.getDelimiter()))>
+    <#assign initialCvsInFormat = CSVTool.formats[CSV_IN_FORMAT!"DEFAULT"]>
+    <#assign csvInDelimiter = CSVTool.toDelimiter(CSV_IN_DELIMITER!initialCvsInFormat.getDelimiter())>
     <#assign cvsInFormat = initialCvsInFormat.withDelimiter(csvInDelimiter)>
     <#return CSVTool.parse(dataSource, cvsInFormat)>
 </#function>
 
 <#function createCsvPrinter>
-    <#assign initialCvsOutFormat = CSVTool.formats[SystemTool.getParameter("csv.out.format", "DEFAULT")]>
-    <#assign csvOutDelimiter = CSVTool.toDelimiter(SystemTool.getParameter("csv.out.delimiter", initialCvsOutFormat.getDelimiter()))>
+    <#assign initialCvsOutFormat = CSVTool.formats[CSV_OUT_FORMAT!"DEFAULT"]>
+    <#assign csvOutDelimiter = CSVTool.toDelimiter(CSV_OUT_DELIMITER!initialCvsOutFormat.getDelimiter())>
     <#assign cvsOutFormat = initialCvsOutFormat.withDelimiter(csvOutDelimiter)>
     <#return CSVTool.printer(cvsOutFormat, SystemTool.writer)>
 </#function>
\ No newline at end of file
diff --git a/freemarker-generator-cli/templates/excel/csv/custom.ftl b/freemarker-generator-cli/templates/excel/csv/custom.ftl
index 542d92b..a045bd3 100644
--- a/freemarker-generator-cli/templates/excel/csv/custom.ftl
+++ b/freemarker-generator-cli/templates/excel/csv/custom.ftl
@@ -15,7 +15,7 @@
   specific language governing permissions and limitations
   under the License.
 -->
-<#assign format = SystemTool.parameters["csv.format"]!"DEFAULT">
+<#assign format = CVS_FORMAT!"DEFAULT">
 <#assign salt = SystemTool.parameters["salt"]!"salt">
 <#-- Parse the first data source & sheet of the Excel document -->
 <#assign workbook = ExcelTool.parse(DataSources.get(0))>
diff --git a/freemarker-generator-cli/templates/excel/csv/transform.ftl b/freemarker-generator-cli/templates/excel/csv/transform.ftl
index d60421c..2455c49 100644
--- a/freemarker-generator-cli/templates/excel/csv/transform.ftl
+++ b/freemarker-generator-cli/templates/excel/csv/transform.ftl
@@ -15,7 +15,7 @@
   specific language governing permissions and limitations
   under the License.
 -->
-<#assign format = SystemTool.parameters["csv.format"]!"DEFAULT">
+<#assign format = CVS_IN_FORMAT!"DEFAULT">
 <#-- Parse the first data source & sheet of the Excel document -->
 <#assign workbook = ExcelTool.parse(DataSources.get(0))>
 <#assign sheet = ExcelTool.getSheets(workbook)[0]>
diff --git a/freemarker-generator-cli/templates/info.ftl b/freemarker-generator-cli/templates/info.ftl
index 01384c3..565e2ec 100644
--- a/freemarker-generator-cli/templates/info.ftl
+++ b/freemarker-generator-cli/templates/info.ftl
@@ -39,30 +39,29 @@ FreeMarker CLI Tools
 </#if>
 </#list>
 
-FreeMarker CLI DataSources
-------------------------------------------------------------------------------
-<#list DataSources.list as dataSource>
-[#${dataSource?counter}], name=${dataSource.name}, group=${dataSource.group}, contentType=${dataSource.contentType}, charset=${dataSource.charset}, length=${dataSource.length} Bytes
-URI : ${dataSource.uri}
+FreeMarker CLI Data Model
+---------------------------------------------------------------------------
+<#list .data_model?keys?sort as key>
+- ${key}<#lt>
 </#list>
 
-User Supplied Parameters
+<#if DataSources.list?has_content>
+FreeMarker CLI DataSources
 ------------------------------------------------------------------------------
-<#list SystemTool.parameters as name,value>
-- ${name} ==> ${value}
+<#list DataSources.list as dataSource>
+    [#${dataSource?counter}], name=${dataSource.name}, group=${dataSource.group}, contentType=${dataSource.contentType}, charset=${dataSource.charset}, length=${dataSource.length} Bytes
+    URI : ${dataSource.uri}
 </#list>
+</#if>
 
-User Supplied System Properties
+<#if SystemTool.parameters?has_content>
+FreeMarker CLI Parameters
 ------------------------------------------------------------------------------
-<#list SystemTool.userSystemProperties as name,value>
-- ${name} ==> ${value}
+<#list SystemTool.parameters as key,value>
+<#if value?is_hash>
+- ${key} ==> { <#list value as name,value>${name}=${value} </#list>}
+<#else>
+- ${key} ==> ${value}
+</#if>
 </#list>
-
-SystemTool
-------------------------------------------------------------------------------
-Command line         : ${SystemTool.getCommandLineArgs()?join(", ")}
-Host Name            : ${SystemTool.getHostName()}
-Java Home            : ${SystemTool.getEnv("JAVA_HOME", "N.A.")}
-User Name            : ${SystemTool.getSystemProperty("user.name", "N.A.")}
-Timestamp            : ${SystemTool.currentTimeMillis}
-Writer               : ${SystemTool.writer.class.name}
+</#if>
diff --git a/freemarker-generator-tools/src/main/java/org/apache/freemarker/generator/tools/system/SystemTool.java b/freemarker-generator-tools/src/main/java/org/apache/freemarker/generator/tools/system/SystemTool.java
index 2b50c99..183b9da 100644
--- a/freemarker-generator-tools/src/main/java/org/apache/freemarker/generator/tools/system/SystemTool.java
+++ b/freemarker-generator-tools/src/main/java/org/apache/freemarker/generator/tools/system/SystemTool.java
@@ -92,12 +92,12 @@ public class SystemTool {
         return System.getProperties();
     }
 
-    public String getParameter(String key) {
-        return parameters.get(key);
+    public String getParameter(String name) {
+        return parameters.get(name);
     }
 
-    public String getParameter(String key, String def) {
-        return parameters.getOrDefault(key, def);
+    public String getParameter(String name, String def) {
+        return parameters.getOrDefault(name, def);
     }
 
     public Map<String, String> getParameters() {
diff --git a/freemarker-generator-tools/src/test/java/org/apache/freemarker/generator/tools/snakeyaml/SnakeYamlToolTest.java b/freemarker-generator-tools/src/test/java/org/apache/freemarker/generator/tools/snakeyaml/SnakeYamlToolTest.java
index 6850b25..d588094 100644
--- a/freemarker-generator-tools/src/test/java/org/apache/freemarker/generator/tools/snakeyaml/SnakeYamlToolTest.java
+++ b/freemarker-generator-tools/src/test/java/org/apache/freemarker/generator/tools/snakeyaml/SnakeYamlToolTest.java
@@ -16,9 +16,9 @@
  */
 package org.apache.freemarker.generator.tools.snakeyaml;
 
-import org.apache.freemarker.generator.base.activation.Mimetypes;
 import org.apache.freemarker.generator.base.datasource.DataSource;
 import org.apache.freemarker.generator.base.datasource.DataSourceFactory;
+import org.apache.freemarker.generator.base.mime.Mimetypes;
 import org.junit.Test;
 
 import java.io.File;
@@ -27,7 +27,6 @@ import java.util.List;
 import java.util.Map;
 
 import static java.nio.charset.StandardCharsets.UTF_8;
-import static junit.framework.Assert.assertTrue;
 import static junit.framework.TestCase.assertEquals;
 import static org.apache.commons.io.FileUtils.readFileToString;
 
diff --git a/freemarker-generator-tools/src/test/java/org/apache/freemarker/generator/tools/xml/XmlToolTest.java b/freemarker-generator-tools/src/test/java/org/apache/freemarker/generator/tools/xml/XmlToolTest.java
index 475f8e9..f0f9676 100644
--- a/freemarker-generator-tools/src/test/java/org/apache/freemarker/generator/tools/xml/XmlToolTest.java
+++ b/freemarker-generator-tools/src/test/java/org/apache/freemarker/generator/tools/xml/XmlToolTest.java
@@ -23,7 +23,7 @@ import org.junit.Test;
 
 import static junit.framework.TestCase.assertEquals;
 import static junit.framework.TestCase.assertNotNull;
-import static org.apache.freemarker.generator.base.activation.Mimetypes.MIME_APPLICATION_XML;
+import static org.apache.freemarker.generator.base.mime.Mimetypes.MIME_APPLICATION_XML;
 
 public class XmlToolTest {