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 27fd68c  FREEMARKER-173 [freemarker-generator] Allow to pass arbitrary 
key/value pairs to DataSource when using NamedURIs (#29)
27fd68c is described below

commit 27fd68cb7534f4677aee416e3d542a929e5fa5cd
Author: Siegfried Goeschl <[email protected]>
AuthorDate: Tue Feb 9 00:34:51 2021 +0100

    FREEMARKER-173 [freemarker-generator] Allow to pass arbitrary key/value 
pairs to DataSource when using NamedURIs (#29)
---
 .../generator/base/datasource/DataSource.java      | 29 ++++++-
 .../base/datasource/DataSourceFactory.java         | 68 ++++++++++-----
 .../base/datasource/DataSourceLoader.java          | 15 +---
 .../base/datasource/DataSourcesSupplier.java       | 20 +++--
 .../datasource/loader/DefaultDataSourceLoader.java |  7 --
 .../loader/EnvironmentDataSourceLoader.java        | 12 +--
 .../datasource/loader/FileDataSourceLoader.java    | 20 ++---
 .../datasource/loader/HttpDataSourceLoader.java    | 16 ++--
 .../freemarker/generator/base/uri/NamedUri.java    | 87 ++++++++++++-------
 .../datasource/DataSourceFactoryTest.java          |  5 +-
 .../generator/datasource/DataSourceLoaderTest.java | 14 ++--
 .../generator/datasource/DataSourceTest.java       | 23 ++---
 .../datasource/DataSourcesSupplierTest.java        | 24 +++++-
 .../generator/datasource/DataSourcesTest.java      |  4 +-
 .../TemplateTransformationsBuilderTest.java        |  2 +
 .../generator/uri/NamedUriStringParserTest.java    |  7 +-
 .../generator/util/PropertiesTransformerTest.java  |  4 -
 freemarker-generator-cli/CHANGELOG.md              |  2 +
 .../examples/templates/datasources.ftl}            | 44 +++++++---
 .../src/app/scripts/run-examples.bat               |  9 +-
 .../src/app/scripts/run-examples.sh                |  9 +-
 .../freemarker-generator/lib/commons-csv.ftl       | 18 ++++
 .../cli/config/OutputGeneratorsSupplier.java       |  3 +-
 .../src/site/markdown/cli/concepts/data-sources.md | 36 +++++++-
 .../src/site/markdown/cli/concepts/named-uris.md   | 98 +++++++++++++++++++---
 .../freemarker/generator/cli/ExamplesTest.java     |  5 ++
 .../freemarker/generator/cli/ManualTest.java       |  2 +-
 .../src/test/templates/manual.ftl                  | 26 ++++--
 28 files changed, 433 insertions(+), 176 deletions(-)

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 0bb4b57..5d5d42d 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
@@ -35,6 +35,7 @@ import java.io.StringWriter;
 import java.net.URI;
 import java.nio.charset.Charset;
 import java.util.Arrays;
+import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
 import java.util.stream.Collectors;
@@ -90,28 +91,44 @@ public class DataSource implements Closeable, 
javax.activation.DataSource {
     /** The underlying "javax.activation.DataSource" */
     private final javax.activation.DataSource dataSource;
 
-    /** Content type of data source either provided by the user or fetched 
directly from the data source */
+    /** Content type of data source either provided by the caller or fetched 
directly from the data source */
     private final String contentType;
 
     /** Charset for directly accessing text-based content */
     private final Charset charset;
 
+    /** Additional properties as name/value pairs */
+    private final Map<String, String> properties;
+
     /** Collect all closeables handed out to the caller to be closed when the 
data source is closed itself */
     private final CloseableReaper closeables;
 
+    /**
+     * Constructor.
+     *
+     * @param name        Human-readable name of the data source
+     * @param group       Optional group of data source
+     * @param uri         source URI of the data source
+     * @param dataSource  JAF data source being wrapped
+     * @param contentType content type of data source either provided by the 
caller or fetched directly from the data source
+     * @param charset     option charset for directly accessing text-based 
content
+     * @param properties  optional name/value pairs
+     */
     public DataSource(
             String name,
             String group,
             URI uri,
             javax.activation.DataSource dataSource,
             String contentType,
-            Charset charset) {
-        this.name = requireNonNull(name);
+            Charset charset,
+            Map<String, String> properties) {
+        this.name = requireNonNull(name).trim();
         this.group = StringUtils.emptyToNull(group);
         this.uri = requireNonNull(uri);
         this.dataSource = requireNonNull(dataSource);
         this.contentType = contentType;
         this.charset = charset;
+        this.properties = properties != null ? new HashMap<>(properties) : new 
HashMap<>();
         this.closeables = new CloseableReaper();
     }
 
@@ -206,6 +223,10 @@ public class DataSource implements Closeable, 
javax.activation.DataSource {
         return uri;
     }
 
+    public Map<String, String> getProperties() {
+        return properties;
+    }
+
     /**
      * Try to get the length lazily, efficient and without consuming the input 
stream.
      *
@@ -339,7 +360,7 @@ public class DataSource implements Closeable, 
javax.activation.DataSource {
             case METADATA_MIME_TYPE:
                 return getMimeType();
             default:
-                throw new IllegalArgumentException("Unknown key: " + key);
+                throw new IllegalArgumentException("Unknown metatdata key: " + 
key);
         }
     }
 
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 c9467a8..6930e91 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
@@ -36,6 +36,8 @@ import java.net.MalformedURLException;
 import java.net.URI;
 import java.net.URL;
 import java.nio.charset.Charset;
+import java.util.HashMap;
+import java.util.Map;
 import java.util.Properties;
 import java.util.UUID;
 
@@ -57,18 +59,39 @@ public abstract class DataSourceFactory {
             String group,
             URI uri,
             javax.activation.DataSource dataSource,
+            Map<String, String> properties) {
+        return new DataSource(name, group, uri, dataSource, null, null, 
properties);
+    }
+
+    public static DataSource create(
+            String name,
+            String group,
+            URI uri,
+            javax.activation.DataSource dataSource,
             String contentType,
-            Charset charset
-    ) {
-        return new DataSource(name, group, uri, dataSource, contentType, 
charset);
+            Charset charset,
+            Map<String, String> properties) {
+        return new DataSource(name, group, uri, dataSource, contentType, 
charset, properties);
     }
 
     // == URL ===============================================================
 
-    public static DataSource fromUrl(String name, String group, URL url, 
String contentType, Charset charset) {
+    public static DataSource fromUrl(String name, String group, URL url) {
+        final URLDataSource dataSource = new CachingUrlDataSource(url);
+        final URI uri = UriUtils.toUri(url);
+        return create(name, group, uri, dataSource, noProperties());
+    }
+
+    public static DataSource fromUrl(
+            String name,
+            String group,
+            URL url,
+            String contentType,
+            Charset charset,
+            Map<String, String> properties) {
         final URLDataSource dataSource = new CachingUrlDataSource(url);
         final URI uri = UriUtils.toUri(url);
-        return create(name, group, uri, dataSource, contentType, charset);
+        return create(name, group, uri, dataSource, contentType, charset, 
properties);
     }
 
     // == String ============================================================
@@ -76,22 +99,28 @@ public abstract class DataSourceFactory {
     public static DataSource fromString(String name, String group, String 
content, String contentType) {
         final StringDataSource dataSource = new StringDataSource(name, 
content, contentType, UTF_8);
         final URI uri = UriUtils.toUri(Location.STRING, 
UUID.randomUUID().toString());
-        return create(name, group, uri, dataSource, contentType, UTF_8);
+        return create(name, group, uri, dataSource, contentType, UTF_8, 
noProperties());
     }
 
     // == File ==============================================================
 
     public static DataSource fromFile(File file, Charset charset) {
-        return fromFile(file.getName(), DEFAULT_GROUP, file, charset);
+        return fromFile(file.getName(), DEFAULT_GROUP, file, charset, 
noProperties());
     }
 
-    public static DataSource fromFile(String name, String group, File file, 
Charset charset) {
+    public static DataSource fromFile(
+            String name,
+            String group,
+            File file,
+            Charset charset,
+            Map<String, String> properties) {
         Validate.isTrue(file.exists(), "File not found: " + file);
 
         final FileDataSource dataSource = new FileDataSource(file);
+        // content type is determined from file extension
         dataSource.setFileTypeMap(MimetypesFileTypeMapFactory.create());
         final String contentType = dataSource.getContentType();
-        return create(name, group, file.toURI(), dataSource, contentType, 
charset);
+        return create(name, group, file.toURI(), dataSource, contentType, 
charset, properties);
     }
 
     // == Bytes ============================================================
@@ -99,26 +128,21 @@ public abstract class DataSourceFactory {
     public static DataSource fromBytes(String name, String group, byte[] 
content, String contentType) {
         final ByteArrayDataSource dataSource = new ByteArrayDataSource(name, 
content);
         final URI uri = UriUtils.toUri(Location.BYTES + ":///");
-        return create(name, group, uri, dataSource, contentType, UTF_8);
+        return create(name, group, uri, dataSource, contentType, UTF_8, 
noProperties());
     }
 
     // == InputStream =======================================================
 
-    public static DataSource fromInputStream(String name, String group, 
InputStream is, String contentType, Charset charset) {
-        final URI uri = UriUtils.toUri(Location.INPUTSTREAM + ":///");
-        return fromInputStream(name, group, uri, is, contentType, charset);
-    }
-
     public static DataSource fromInputStream(
             String name,
             String group,
             URI uri,
             InputStream is,
             String contentType,
-            Charset charset
-    ) {
+            Charset charset,
+            Map<String, String> properties) {
         final InputStreamDataSource dataSource = new 
InputStreamDataSource(name, is);
-        return create(name, group, uri, dataSource, contentType, charset);
+        return create(name, group, uri, dataSource, contentType, charset, 
properties);
     }
 
     // == Environment =======================================================
@@ -130,7 +154,7 @@ public abstract class DataSourceFactory {
             properties.store(writer, null);
             final StringDataSource dataSource = new StringDataSource(name, 
writer.toString(), contentType, UTF_8);
             final URI uri = UriUtils.toUri(Location.ENVIRONMENT, "");
-            return create(name, group, uri, dataSource, contentType, UTF_8);
+            return create(name, group, uri, dataSource, contentType, UTF_8, 
noProperties());
         } catch (IOException e) {
             throw new RuntimeException("Unable to load environment variables", 
e);
         }
@@ -141,7 +165,7 @@ public abstract class DataSourceFactory {
 
         final StringDataSource dataSource = new StringDataSource(name, 
System.getenv(key), contentType, UTF_8);
         final URI uri = UriUtils.toUri(Location.ENVIRONMENT, key);
-        return create(name, group, uri, dataSource, contentType, UTF_8);
+        return create(name, group, uri, dataSource, contentType, UTF_8, 
noProperties());
     }
 
     public static URL toUrl(String url) {
@@ -151,4 +175,8 @@ public abstract class DataSourceFactory {
             throw new IllegalArgumentException(url, e);
         }
     }
+
+    private static Map<String, String> noProperties() {
+        return new HashMap<>();
+    }
 }
diff --git 
a/freemarker-generator-base/src/main/java/org/apache/freemarker/generator/base/datasource/DataSourceLoader.java
 
b/freemarker-generator-base/src/main/java/org/apache/freemarker/generator/base/datasource/DataSourceLoader.java
index 423b648..4a22137 100644
--- 
a/freemarker-generator-base/src/main/java/org/apache/freemarker/generator/base/datasource/DataSourceLoader.java
+++ 
b/freemarker-generator-base/src/main/java/org/apache/freemarker/generator/base/datasource/DataSourceLoader.java
@@ -16,14 +16,12 @@
  */
 package org.apache.freemarker.generator.base.datasource;
 
-import java.nio.charset.Charset;
-
 public interface DataSourceLoader {
 
     /**
-     * Check if the source would be accepted
+     * Check if the data source can be loaded by this instance.
      *
-     * @param source source
+     * @param source source to be loaded from
      * @return true if the instance wold be able to load a data source
      */
     boolean accept(String source);
@@ -35,13 +33,4 @@ public interface DataSourceLoader {
      * @return DataSource
      */
     DataSource load(String source);
-
-    /**
-     * Load a DataSource using the given charset.
-     *
-     * @param source source of the data source
-     * @param charset charset to use
-     * @return DataSource
-     */
-    DataSource load(String source, Charset charset);
 }
diff --git 
a/freemarker-generator-base/src/main/java/org/apache/freemarker/generator/base/datasource/DataSourcesSupplier.java
 
b/freemarker-generator-base/src/main/java/org/apache/freemarker/generator/base/datasource/DataSourcesSupplier.java
index c2604ba..cc919fc 100644
--- 
a/freemarker-generator-base/src/main/java/org/apache/freemarker/generator/base/datasource/DataSourcesSupplier.java
+++ 
b/freemarker-generator-base/src/main/java/org/apache/freemarker/generator/base/datasource/DataSourcesSupplier.java
@@ -27,16 +27,17 @@ import java.nio.charset.Charset;
 import java.util.ArrayList;
 import java.util.Collection;
 import java.util.List;
+import java.util.Map;
 import java.util.function.Supplier;
 
 import static java.util.Collections.singletonList;
 import static java.util.Objects.requireNonNull;
 import static java.util.stream.Collectors.toList;
 import static 
org.apache.freemarker.generator.base.FreeMarkerConstants.DEFAULT_GROUP;
+import static 
org.apache.freemarker.generator.base.datasource.DataSourceFactory.fromFile;
 
 /**
- * Create a list of <code>DataSource</code> based on a list of sources 
consisting of
- * URIs, directories and files.
+ * Create a list of <code>DataSource</code> based on a list URIs, directories 
and files.
  */
 public class DataSourcesSupplier implements Supplier<List<DataSource>> {
 
@@ -57,14 +58,14 @@ public class DataSourcesSupplier implements 
Supplier<List<DataSource>> {
     /**
      * Constructor.
      *
-     * @param sources List of source files and/or directories
+     * @param sources List of source files and/or directories supporting 
<code>NamedUri</code> syntax
      * @param include Optional include pattern for resolving source files or 
directory
      * @param exclude Optional exclude pattern for resolving source files or 
directory
      * @param charset The charset for loading text files
      */
     public DataSourcesSupplier(Collection<String> sources, String include, 
String exclude, Charset charset) {
         this.dataSourceLoader = DataSourceLoaderFactory.create();
-        this.sources = new ArrayList<>(sources);
+        this.sources = new ArrayList<>(requireNonNull(sources));
         this.include = include;
         this.exclude = exclude;
         this.charset = requireNonNull(charset);
@@ -111,10 +112,11 @@ public class DataSourcesSupplier implements 
Supplier<List<DataSource>> {
     private static List<DataSource> resolveFileOrDirectory(String source, 
String include, String exclude, Charset charset) {
         final NamedUri namedUri = NamedUriStringParser.parse(source);
         final String path = namedUri.getFile().getPath();
-        final String group = namedUri.getGroupOrElse(DEFAULT_GROUP);
-        final Charset currCharset = getCharsetOrElse(namedUri, charset);
+        final String group = namedUri.getGroupOrDefault(DEFAULT_GROUP);
+        final Charset currCharset = getCharsetOrDefault(namedUri, charset);
+        final Map<String, String> parameters = namedUri.getParameters();
         return fileSupplier(path, include, exclude).get().stream()
-                .map(file -> 
DataSourceFactory.fromFile(getDataSourceName(namedUri, file), group, file, 
currCharset))
+                .map(file -> fromFile(getDataSourceName(namedUri, file), 
group, file, currCharset, parameters))
                 .collect(toList());
     }
 
@@ -122,8 +124,8 @@ public class DataSourcesSupplier implements 
Supplier<List<DataSource>> {
         return new RecursiveFileSupplier(singletonList(source), 
singletonList(include), singletonList(exclude));
     }
 
-    private static Charset getCharsetOrElse(NamedUri namedUri, Charset def) {
-        return Charset.forName(namedUri.getParameter(NamedUri.CHARSET, 
def.name()));
+    private static Charset getCharsetOrDefault(NamedUri namedUri, Charset def) 
{
+        return namedUri.getCharsetOrDefault(def);
     }
 
     private static boolean isHttpUri(String value) {
diff --git 
a/freemarker-generator-base/src/main/java/org/apache/freemarker/generator/base/datasource/loader/DefaultDataSourceLoader.java
 
b/freemarker-generator-base/src/main/java/org/apache/freemarker/generator/base/datasource/loader/DefaultDataSourceLoader.java
index 467531c..2067aa2 100644
--- 
a/freemarker-generator-base/src/main/java/org/apache/freemarker/generator/base/datasource/loader/DefaultDataSourceLoader.java
+++ 
b/freemarker-generator-base/src/main/java/org/apache/freemarker/generator/base/datasource/loader/DefaultDataSourceLoader.java
@@ -19,7 +19,6 @@ package 
org.apache.freemarker.generator.base.datasource.loader;
 import org.apache.freemarker.generator.base.datasource.DataSource;
 import org.apache.freemarker.generator.base.datasource.DataSourceLoader;
 
-import java.nio.charset.Charset;
 import java.util.ArrayList;
 import java.util.List;
 
@@ -47,16 +46,10 @@ public class DefaultDataSourceLoader implements 
DataSourceLoader {
         return get(source).load(source);
     }
 
-    @Override
-    public DataSource load(String source, Charset charset) {
-        return get(source).load(source, charset);
-    }
-
     private DataSourceLoader get(String source) {
         return dataSourceLoaders.stream()
                 .filter(loader -> loader.accept(source))
                 .findFirst()
                 .orElseThrow(() -> new IllegalArgumentException("Don't know 
how to load: " + source));
     }
-
 }
diff --git 
a/freemarker-generator-base/src/main/java/org/apache/freemarker/generator/base/datasource/loader/EnvironmentDataSourceLoader.java
 
b/freemarker-generator-base/src/main/java/org/apache/freemarker/generator/base/datasource/loader/EnvironmentDataSourceLoader.java
index 41eeeef..8d194ff 100644
--- 
a/freemarker-generator-base/src/main/java/org/apache/freemarker/generator/base/datasource/loader/EnvironmentDataSourceLoader.java
+++ 
b/freemarker-generator-base/src/main/java/org/apache/freemarker/generator/base/datasource/loader/EnvironmentDataSourceLoader.java
@@ -25,8 +25,6 @@ import org.apache.freemarker.generator.base.uri.NamedUri;
 import org.apache.freemarker.generator.base.uri.NamedUriStringParser;
 import org.apache.freemarker.generator.base.util.StringUtils;
 
-import java.nio.charset.Charset;
-
 import static 
org.apache.freemarker.generator.base.FreeMarkerConstants.DEFAULT_GROUP;
 import static 
org.apache.freemarker.generator.base.util.StringUtils.firstNonEmpty;
 import static org.apache.freemarker.generator.base.util.StringUtils.isNotEmpty;
@@ -47,9 +45,9 @@ public class EnvironmentDataSourceLoader implements 
DataSourceLoader {
     public DataSource load(String source) {
         final NamedUri namedUri = NamedUriStringParser.parse(source);
         final String key = stripRootDir(namedUri.getUri().getPath());
-        final String contentType = 
namedUri.getMimeTypeOrElse(Mimetypes.MIME_TEXT_PLAIN);
+        final String contentType = 
namedUri.getMimeTypeOrDefault(Mimetypes.MIME_TEXT_PLAIN);
         final String name = firstNonEmpty(namedUri.getName(), key, 
Location.ENVIRONMENT);
-        final String group = namedUri.getGroupOrElse(DEFAULT_GROUP);
+        final String group = namedUri.getGroupOrDefault(DEFAULT_GROUP);
         if (StringUtils.isEmpty(key)) {
             return DataSourceFactory.fromEnvironment(name, group, contentType);
         } else {
@@ -57,12 +55,6 @@ public class EnvironmentDataSourceLoader implements 
DataSourceLoader {
         }
     }
 
-    @Override
-    public DataSource load(String source, Charset charset) {
-        // We already habe internal strings so we can ignore the charset
-        return load(source);
-    }
-
     /**
      * Environment variables come with a leading "/" to be removed.
      */
diff --git 
a/freemarker-generator-base/src/main/java/org/apache/freemarker/generator/base/datasource/loader/FileDataSourceLoader.java
 
b/freemarker-generator-base/src/main/java/org/apache/freemarker/generator/base/datasource/loader/FileDataSourceLoader.java
index 1ee8b66..2d890f3 100644
--- 
a/freemarker-generator-base/src/main/java/org/apache/freemarker/generator/base/datasource/loader/FileDataSourceLoader.java
+++ 
b/freemarker-generator-base/src/main/java/org/apache/freemarker/generator/base/datasource/loader/FileDataSourceLoader.java
@@ -26,6 +26,7 @@ import org.apache.freemarker.generator.base.util.UriUtils;
 
 import java.io.File;
 import java.nio.charset.Charset;
+import java.util.Map;
 
 import static java.nio.charset.StandardCharsets.UTF_8;
 import static org.apache.freemarker.generator.base.util.StringUtils.isNotEmpty;
@@ -40,20 +41,11 @@ public class FileDataSourceLoader implements 
DataSourceLoader {
     @Override
     public DataSource load(String source) {
         final NamedUri namedUri = NamedUriStringParser.parse(source);
-        final String group = 
namedUri.getGroupOrElse(FreeMarkerConstants.DEFAULT_GROUP);
-        final Charset charset = namedUri.getCharsetOrElse(UTF_8);
+        final String group = 
namedUri.getGroupOrDefault(FreeMarkerConstants.DEFAULT_GROUP);
+        final Charset charset = namedUri.getCharsetOrDefault(UTF_8);
         final File file = namedUri.getFile();
-        final String name = namedUri.getNameOrElse(file.getName());
-        return DataSourceFactory.fromFile(name, group, file, charset);
+        final String name = namedUri.getNameOrDefault(file.getName());
+        final Map<String, String> parameters = namedUri.getParameters();
+        return DataSourceFactory.fromFile(name, group, file, charset, 
parameters);
     }
-
-    @Override
-    public DataSource load(String source, Charset charset) {
-        final NamedUri namedUri = NamedUriStringParser.parse(source);
-        final String group = 
namedUri.getGroupOrElse(FreeMarkerConstants.DEFAULT_GROUP);
-        final File file = namedUri.getFile();
-        final String name = 
namedUri.getNameOrElse(UriUtils.toStringWithoutFragment(file.toURI()));
-        return DataSourceFactory.fromFile(name, group, file, charset);
-    }
-
 }
diff --git 
a/freemarker-generator-base/src/main/java/org/apache/freemarker/generator/base/datasource/loader/HttpDataSourceLoader.java
 
b/freemarker-generator-base/src/main/java/org/apache/freemarker/generator/base/datasource/loader/HttpDataSourceLoader.java
index 10755f9..aa3d8ad 100644
--- 
a/freemarker-generator-base/src/main/java/org/apache/freemarker/generator/base/datasource/loader/HttpDataSourceLoader.java
+++ 
b/freemarker-generator-base/src/main/java/org/apache/freemarker/generator/base/datasource/loader/HttpDataSourceLoader.java
@@ -27,6 +27,7 @@ import java.net.MalformedURLException;
 import java.net.URI;
 import java.net.URL;
 import java.nio.charset.Charset;
+import java.util.Map;
 
 import static 
org.apache.freemarker.generator.base.FreeMarkerConstants.DEFAULT_GROUP;
 import static org.apache.freemarker.generator.base.util.StringUtils.isNotEmpty;
@@ -42,18 +43,13 @@ public class HttpDataSourceLoader implements 
DataSourceLoader {
     public DataSource load(String source) {
         final NamedUri namedUri = NamedUriStringParser.parse(source);
         final URI uri = namedUri.getUri();
-        final String group = namedUri.getGroupOrElse(DEFAULT_GROUP);
-        final Charset charset = namedUri.getCharsetOrElse(null);
+        final String group = namedUri.getGroupOrDefault(DEFAULT_GROUP);
+        final Charset charset = namedUri.getCharset();
         final String mimeType = namedUri.getMimeType();
         final URL url = toUrl(uri);
-        final String name = 
namedUri.getNameOrElse(UriUtils.toStringWithoutFragment(uri));
-        return DataSourceFactory.fromUrl(name, group, url, mimeType, charset);
-    }
-
-    @Override
-    public DataSource load(String source, Charset charset) {
-        // We should pick up the charset from the HTTP server
-        return load(source);
+        final String name = 
namedUri.getNameOrDefault(UriUtils.toStringWithoutFragment(uri));
+        final Map<String, String> parameters = namedUri.getParameters();
+        return DataSourceFactory.fromUrl(name, group, url, mimeType, charset, 
parameters);
     }
 
     private static URL toUrl(URI uri) {
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 6003d3c..ad496a8 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
@@ -17,23 +17,29 @@
 package org.apache.freemarker.generator.base.uri;
 
 import org.apache.freemarker.generator.base.util.StringUtils;
+import org.apache.freemarker.generator.base.util.Validate;
 
 import java.io.File;
 import java.net.URI;
 import java.nio.charset.Charset;
+import java.util.HashMap;
 import java.util.Map;
 
-import static java.util.Objects.requireNonNull;
-import static 
org.apache.freemarker.generator.base.util.StringUtils.emptyToNull;
-import static org.apache.freemarker.generator.base.util.StringUtils.isEmpty;
-
 /**
  * Captures the information of a user-supplied "named URI".
+ * <p>
+ * <ul>
+ *     <li><code>name</code> is optional</li>
+ *     <li><code>group</code> is optional</li>
+ * </ul>
  */
 public class NamedUri {
 
-    public static final String CHARSET = "charset";
-    public static final String MIMETYPE = "mimeType";
+    // Pre-defined parameter names
+    private static final String NAME_KEY = "name";
+    private static final String GROUP_KEY = "group";
+    private static final String CHARSET_KEY = "charset";
+    private static final String MIMETYPE_KEY = "mimeType";
 
     /** User-supplied name */
     private final String name;
@@ -47,34 +53,59 @@ public class NamedUri {
     /** Name/value pairs parsed from URI fragment */
     private final Map<String, String> parameters;
 
+    /**
+     * Constructor.
+     * <p>
+     * The <code>name</code> and <code>group</code> a read from 
<code>parameters</code>.
+     *
+     * @param uri        URI
+     * @param parameters map of parameters
+     */
     public NamedUri(URI uri, Map<String, String> parameters) {
-        this.name = null;
-        this.group = null;
-        this.uri = requireNonNull(uri);
-        this.parameters = requireNonNull(parameters);
-    }
+        Validate.notNull(uri, "uri is null");
+        Validate.notNull(parameters, "parameters are null");
+
+        this.uri = uri;
+        this.name = StringUtils.emptyToNull(parameters.get(NAME_KEY));
+        this.group = StringUtils.emptyToNull(parameters.get(GROUP_KEY));
+        this.parameters = new HashMap<>(parameters);
+    }
+
+    /**
+     * Constructor.
+     * <p>
+     * For empty <code>name</code> and <code>group</code> a fallback to 
<code>parameters</code> is provided.
+     *
+     * @param name       optional name of the named URI
+     * @param group      optional group of the named URI
+     * @param uri        URI
+     * @param parameters map of parameters
+     */
 
     public NamedUri(String name, String group, URI uri, Map<String, String> 
parameters) {
-        this.name = emptyToNull(name);
-        this.group = emptyToNull(group);
-        this.uri = requireNonNull(uri);
-        this.parameters = requireNonNull(parameters);
+        Validate.notNull(uri, "uri is null");
+        Validate.notNull(parameters, "parameters are null");
+
+        this.uri = uri;
+        this.name = StringUtils.firstNonEmpty(name, parameters.get(NAME_KEY));
+        this.group = StringUtils.firstNonEmpty(group, 
parameters.get(GROUP_KEY));
+        this.parameters = new HashMap<>(parameters);
     }
 
     public String getName() {
         return name;
     }
 
-    public String getNameOrElse(String def) {
-        return isEmpty(name) ? def : name;
+    public String getNameOrDefault(String def) {
+        return StringUtils.isEmpty(name) ? def : name;
     }
 
     public String getGroup() {
         return group;
     }
 
-    public String getGroupOrElse(String def) {
-        return isEmpty(group) ? def : group;
+    public String getGroupOrDefault(String def) {
+        return StringUtils.isEmpty(group) ? def : group;
     }
 
     public URI getUri() {
@@ -89,16 +120,16 @@ public class NamedUri {
         return parameters.get(key);
     }
 
-    public String getParameter(String key, String defaultValue) {
+    public String getParameterOrDefault(String key, String defaultValue) {
         return parameters.getOrDefault(key, defaultValue);
     }
 
     public boolean hasName() {
-        return !isEmpty(this.name);
+        return !StringUtils.isEmpty(this.name);
     }
 
     public boolean hasGroup() {
-        return !isEmpty(this.group);
+        return !StringUtils.isEmpty(this.group);
     }
 
     public File getFile() {
@@ -106,20 +137,20 @@ public class NamedUri {
     }
 
     public String getMimeType() {
-        return getParameter(NamedUri.MIMETYPE);
+        return getParameter(NamedUri.MIMETYPE_KEY);
     }
 
-    public String getMimeTypeOrElse(String def) {
-        return getParameter(NamedUri.MIMETYPE, def);
+    public String getMimeTypeOrDefault(String def) {
+        return getParameterOrDefault(NamedUri.MIMETYPE_KEY, def);
     }
 
     public Charset getCharset() {
-        final String charsetName = getParameter(NamedUri.CHARSET);
+        final String charsetName = getParameter(NamedUri.CHARSET_KEY);
         return Charset.forName(charsetName);
     }
 
-    public Charset getCharsetOrElse(Charset def) {
-        final String charsetName = getParameter(NamedUri.CHARSET);
+    public Charset getCharsetOrDefault(Charset def) {
+        final String charsetName = getParameter(NamedUri.CHARSET_KEY);
         return StringUtils.isEmpty(charsetName) ? def : 
Charset.forName(charsetName);
     }
 
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 37d1f92..20733c7 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
@@ -30,6 +30,7 @@ import java.io.InputStream;
 import java.net.URI;
 import java.net.URL;
 import java.nio.charset.Charset;
+import java.util.HashMap;
 
 import static java.nio.charset.StandardCharsets.UTF_8;
 import static junit.framework.TestCase.assertFalse;
@@ -85,7 +86,7 @@ public class DataSourceFactoryTest {
     public void shouldCreateDataSourceFromInputStream() {
         final URI uri = UriUtils.toUri(Location.INPUTSTREAM + ":///");
         final InputStream is = new 
ByteArrayInputStream(ANY_TEXT.getBytes(UTF_8));
-        final DataSource dataSource = 
DataSourceFactory.fromInputStream("test.txt", "default", uri, is, "text/plain", 
UTF_8);
+        final DataSource dataSource = 
DataSourceFactory.fromInputStream("test.txt", "default", uri, is, "text/plain", 
UTF_8, new HashMap<>());
 
         assertEquals("test.txt", dataSource.getName());
         assertEquals(UTF_8, dataSource.getCharset());
@@ -98,7 +99,7 @@ public class DataSourceFactoryTest {
     @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);
+        final DataSource dataSource = 
DataSourceFactory.fromUrl("jsonplaceholder.typicode.com", "default", url);
 
         assertEquals("jsonplaceholder.typicode.com", dataSource.getName());
         assertEquals("jsonplaceholder.typicode.com", dataSource.getFileName());
diff --git 
a/freemarker-generator-base/src/test/java/org/apache/freemarker/generator/datasource/DataSourceLoaderTest.java
 
b/freemarker-generator-base/src/test/java/org/apache/freemarker/generator/datasource/DataSourceLoaderTest.java
index 1567699..2bc284e 100644
--- 
a/freemarker-generator-base/src/test/java/org/apache/freemarker/generator/datasource/DataSourceLoaderTest.java
+++ 
b/freemarker-generator-base/src/test/java/org/apache/freemarker/generator/datasource/DataSourceLoaderTest.java
@@ -26,7 +26,6 @@ import org.junit.Test;
 import java.io.File;
 
 import static java.lang.String.format;
-import static java.nio.charset.StandardCharsets.UTF_16;
 import static java.nio.charset.StandardCharsets.UTF_8;
 import static junit.framework.TestCase.assertFalse;
 import static 
org.apache.freemarker.generator.base.FreeMarkerConstants.DEFAULT_GROUP;
@@ -174,11 +173,14 @@ public class DataSourceLoaderTest {
 
     @Test
     public void shouldLoadDataSourceWithCharset() {
-        final DataSource utf8DataSource = 
dataSourceLoader().load("./src/test/data/txt/utf8.txt", UTF_8);
-        final DataSource utf16DataSource = 
dataSourceLoader().load("./src/test/data/txt/utf16.txt", UTF_16);
-
-        // skip the first line before comparing
-        assertEquals(utf8DataSource.getLines().subList(1, 5), 
utf16DataSource.getLines().subList(1, 5));
+        final String utf8Uri = "./src/test/data/txt/utf8.txt#charset=UTF-8";
+        final String utf16Uri = "./src/test/data/txt/utf16.txt#charset=UTF-16";
+        try (DataSource utf8DataSource = dataSourceLoader().load(utf8Uri)) {
+            try (DataSource utf16DataSource = 
dataSourceLoader().load(utf16Uri)) {
+                // skip the first line before comparing
+                assertEquals(utf8DataSource.getLines().subList(1, 5), 
utf16DataSource.getLines().subList(1, 5));
+            }
+        }
     }
 
     private DataSourceLoader dataSourceLoader() {
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 8961359..e1a8f80 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
@@ -28,6 +28,7 @@ import java.io.File;
 import java.io.IOException;
 import java.nio.charset.Charset;
 import java.util.Iterator;
+import java.util.Map;
 
 import static java.nio.charset.StandardCharsets.UTF_8;
 import static 
org.apache.freemarker.generator.base.FreeMarkerConstants.DEFAULT_GROUP;
@@ -84,7 +85,7 @@ public class DataSourceTest {
     @Test
     @Ignore("Requires internet access")
     public void shouldSupportUrlDataSource() {
-        try (DataSource dataSource = 
DataSourceFactory.fromUrl("www.google.com", DEFAULT_GROUP, 
toUrl("https://www.google.com/?foo=bar";), null, null)) {
+        try (DataSource dataSource = 
DataSourceFactory.fromUrl("www.google.com", DEFAULT_GROUP, 
toUrl("https://www.google.com/?foo=bar";), null, null, null)) {
             assertEquals("www.google.com", dataSource.getName());
             assertEquals(DEFAULT_GROUP, dataSource.getGroup());
             assertEquals("", dataSource.getBaseName());
@@ -125,15 +126,17 @@ public class DataSourceTest {
     @Test
     public void shouldGetMetadata() {
         try (DataSource dataSource = stringDataSource()) {
-            assertEquals(8, dataSource.getMetadata().size());
-            assertEquals("", dataSource.getMetadata().get("basename"));
-            assertEquals("", dataSource.getMetadata().get("extension"));
-            assertEquals("", dataSource.getMetadata().get("filename"));
-            assertEquals("/", dataSource.getMetadata().get("filepath"));
-            assertEquals("default", dataSource.getMetadata().get("group"));
-            assertEquals("stdin", dataSource.getMetadata().get("name"));
-            
assertTrue(dataSource.getMetadata().get("uri").startsWith("string://"));
-            assertEquals("text/plain", 
dataSource.getMetadata().get("mimetype"));
+            final Map<String, String> metadata = dataSource.getMetadata();
+
+            assertEquals(8, metadata.size());
+            assertEquals("", metadata.get("basename"));
+            assertEquals("", metadata.get("extension"));
+            assertEquals("", metadata.get("filename"));
+            assertEquals("/", metadata.get("filepath"));
+            assertEquals("default", metadata.get("group"));
+            assertEquals("stdin", metadata.get("name"));
+            assertTrue(metadata.get("uri").startsWith("string://"));
+            assertEquals("text/plain", metadata.get("mimetype"));
         }
     }
 
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 779485c..1fb5fe7 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
@@ -35,6 +35,8 @@ public class DataSourcesSupplierTest {
 
     private static final String NO_EXCLUDE = null;
     private static final String DATA_DIRECTORY = "./src/test/data";
+    private static final String DATA_DIRECTORY_WITH_FRAGMENT = 
"./src/test/data#scope=test";
+    private static final String DATA_DIRECTORY_WITH_GROUP = 
":data=src/test/data";
     private static final String PWD = FilenameUtils.separatorsToUnix(new 
File("").getAbsolutePath());
 
     @Test
@@ -106,6 +108,26 @@ public class DataSourcesSupplierTest {
     }
 
     @Test
+    public void shouldUseFragmentForDataSourceWhenResolvingDirectory() {
+        final List<DataSource> dataSources = 
supplier(DATA_DIRECTORY_WITH_FRAGMENT, "*.*", NO_EXCLUDE).get();
+
+        for (DataSource dataSource : dataSources) {
+            assertEquals(1, dataSource.getProperties().size());
+            assertEquals("test", dataSource.getProperties().get("scope"));
+        }
+    }
+
+    @Test
+    public void shouldUseGroupNameForDataSourceWhenResolvingDirectory() {
+        final List<DataSource> dataSources = 
supplier(DATA_DIRECTORY_WITH_GROUP, "*.*", NO_EXCLUDE).get();
+
+        for (DataSource dataSource : dataSources) {
+            assertEquals(0, dataSource.getProperties().size());
+            assertEquals("data", dataSource.getGroup());
+        }
+    }
+
+    @Test
     public void shouldResolveEnvironmentVariable() {
         assertEquals(1, supplier("env:///PATH", "*", NO_EXCLUDE).get().size());
         assertEquals(1, supplier("path=env:///PATH", "*", 
NO_EXCLUDE).get().size());
@@ -118,7 +140,7 @@ public class DataSourcesSupplierTest {
     }
 
     @Test(expected = RuntimeException.class)
-    public void shouldThrowExceptionForNonexistingSourceDirectory() {
+    public void shouldThrowRuntimeExceptionForNonexistingSourceDirectory() {
         assertEquals(0, supplier("/does-not-exist", "*", null).get().size());
     }
 
diff --git 
a/freemarker-generator-base/src/test/java/org/apache/freemarker/generator/datasource/DataSourcesTest.java
 
b/freemarker-generator-base/src/test/java/org/apache/freemarker/generator/datasource/DataSourcesTest.java
index 3591e38..030ea62 100644
--- 
a/freemarker-generator-base/src/test/java/org/apache/freemarker/generator/datasource/DataSourcesTest.java
+++ 
b/freemarker-generator-base/src/test/java/org/apache/freemarker/generator/datasource/DataSourcesTest.java
@@ -24,6 +24,7 @@ import org.junit.Test;
 import java.io.File;
 import java.net.MalformedURLException;
 import java.net.URL;
+import java.util.HashMap;
 
 import static java.nio.charset.StandardCharsets.UTF_8;
 import static java.util.Arrays.asList;
@@ -32,7 +33,6 @@ import static 
org.apache.freemarker.generator.base.FreeMarkerConstants.DEFAULT_G
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertNotNull;
-import static org.junit.Assert.assertTrue;
 
 public class DataSourcesTest {
 
@@ -133,7 +133,7 @@ public class DataSourcesTest {
     }
 
     private static DataSource urlDataSource() {
-        return DataSourceFactory.fromUrl("server.invalid?foo=bar", "default", 
toUrl(ANY_URL), "plain/text", UTF_8);
+        return DataSourceFactory.fromUrl("server.invalid?foo=bar", "default", 
toUrl(ANY_URL), "plain/text", UTF_8, new HashMap<>());
     }
 
     private static URL toUrl(String value) {
diff --git 
a/freemarker-generator-base/src/test/java/org/apache/freemarker/generator/template/TemplateTransformationsBuilderTest.java
 
b/freemarker-generator-base/src/test/java/org/apache/freemarker/generator/template/TemplateTransformationsBuilderTest.java
index 822b66a..7170cd7 100644
--- 
a/freemarker-generator-base/src/test/java/org/apache/freemarker/generator/template/TemplateTransformationsBuilderTest.java
+++ 
b/freemarker-generator-base/src/test/java/org/apache/freemarker/generator/template/TemplateTransformationsBuilderTest.java
@@ -23,6 +23,7 @@ import 
org.apache.freemarker.generator.base.template.TemplateSource.Origin;
 import org.apache.freemarker.generator.base.template.TemplateTransformation;
 import 
org.apache.freemarker.generator.base.template.TemplateTransformationsBuilder;
 import org.apache.freemarker.generator.base.util.NonClosableWriterWrapper;
+import org.junit.Ignore;
 import org.junit.Test;
 
 import java.io.BufferedWriter;
@@ -181,6 +182,7 @@ public class TemplateTransformationsBuilderTest {
     // === Template URL ===============================================
 
     @Test
+    @Ignore("Requires internet access")
     public void shouldCreateFromTemplateUrl() {
         final List<TemplateTransformation> transformations = builder()
                 .setTemplateSource(ANY_TEMPLATE_URL)
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 ebcb84e..01e68bb 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
@@ -240,13 +240,14 @@ public class NamedUriStringParserTest {
 
     @Test
     public void shouldParseNamedGroupUrlWithQueryAndFragment() {
-        final NamedUri namedURI = 
parse("google:web=http://google.com?foo=bar#charset=UTF-16";);
+        final NamedUri namedURI = 
parse("google:web=http://google.com?foo=bar#charset=UTF-16&name=value";);
 
         assertEquals("google", namedURI.getName());
         assertEquals("web", namedURI.getGroup());
-        assertEquals("http://google.com?foo=bar#charset=UTF-16";, 
namedURI.getUri().toString());
-        assertEquals(1, namedURI.getParameters().size());
+        assertEquals("http://google.com?foo=bar#charset=UTF-16&name=value";, 
namedURI.getUri().toString());
+        assertEquals(2, namedURI.getParameters().size());
         assertEquals("UTF-16", namedURI.getParameters().get("charset"));
+        assertEquals("value", namedURI.getParameters().get("name"));
     }
 
     @Test(expected = RuntimeException.class)
diff --git 
a/freemarker-generator-base/src/test/java/org/apache/freemarker/generator/util/PropertiesTransformerTest.java
 
b/freemarker-generator-base/src/test/java/org/apache/freemarker/generator/util/PropertiesTransformerTest.java
index de0c483..5a116e0 100644
--- 
a/freemarker-generator-base/src/test/java/org/apache/freemarker/generator/util/PropertiesTransformerTest.java
+++ 
b/freemarker-generator-base/src/test/java/org/apache/freemarker/generator/util/PropertiesTransformerTest.java
@@ -18,8 +18,6 @@ package org.apache.freemarker.generator.util;
 
 import org.junit.Test;
 
-import java.util.HashMap;
-import java.util.Map;
 import java.util.Properties;
 
 import static 
org.apache.freemarker.generator.base.FreeMarkerConstants.Configuration.TOOLS_PREFIX;
@@ -29,8 +27,6 @@ import static org.junit.Assert.assertEquals;
 
 public class PropertiesTransformerTest {
 
-    private final Map<String, Object> settings = new HashMap<>();
-
     @Test
     public void shouldFilterKeyPrefix() {
         final Properties properties = filterKeyPrefix(properties(), 
TOOLS_PREFIX);
diff --git a/freemarker-generator-cli/CHANGELOG.md 
b/freemarker-generator-cli/CHANGELOG.md
index c6a5cce..7dbdd16 100644
--- a/freemarker-generator-cli/CHANGELOG.md
+++ b/freemarker-generator-cli/CHANGELOG.md
@@ -20,6 +20,7 @@ All notable changes to this project will be documented in 
this file. We try to a
 * [FREEMARKER-129] Provide a `toString()` method for all tools
 
 ### Changed
+* [FREEMARKER-173] Allow to pass arbitrary key/value pairs to DataSource when 
using NamedURIs
 * [FREEMARKER-172] Use lower-case keys for DataSource metadata map
 * [FREEMARKER-148] Make usage of "DataSources" more "Freemarker" like
 * [FREEMARKER-155] Migrate the FTL code to terser dotter form
@@ -70,3 +71,4 @@ All notable changes to this project will be documented in 
this file. We try to a
 [FREEMARKER-164]: https://issues.apache.org/jira/browse/FREEMARKER-164
 [FREEMARKER-168]: https://issues.apache.org/jira/browse/FREEMARKER-168
 [FREEMARKER-172]: https://issues.apache.org/jira/browse/FREEMARKER-172
+[FREEMARKER-173]: https://issues.apache.org/jira/browse/FREEMARKER-175
diff --git a/freemarker-generator-cli/src/test/templates/manual.ftl 
b/freemarker-generator-cli/src/app/examples/templates/datasources.ftl
similarity index 71%
copy from freemarker-generator-cli/src/test/templates/manual.ftl
copy to freemarker-generator-cli/src/app/examples/templates/datasources.ftl
index 3ad42c6..364a7ba 100644
--- a/freemarker-generator-cli/src/test/templates/manual.ftl
+++ b/freemarker-generator-cli/src/app/examples/templates/datasources.ftl
@@ -1,4 +1,4 @@
-<#ftl output_format="plainText" >
+<#ftl output_format="plainText">
 <#--
   Licensed to the Apache Software Foundation (ASF) under one
   or more contributor license agreements.  See the NOTICE file
@@ -16,43 +16,63 @@
   under the License.
 -->
 Support FreeMarker Directives
----------------------------------------------------------------------------
-Has Content: ${dataSources?has_content?c}
-Nr. of Documents: ${dataSources?size}
+==============================================================================
+has_content: ${dataSources?has_content?c}
+size: ${dataSources?size}
 
 Use FTL Array-style Access
----------------------------------------------------------------------------
-${dataSources?values[0].toString()}
-${dataSources?values?first.toString()}
+==============================================================================
+${dataSources?values[0].name}
+${dataSources?values?first.name}
 
 Get Document Names As Keys
----------------------------------------------------------------------------
+==============================================================================
 <#list dataSources?keys as name>
     ${name}<#lt>
 </#list>
 
 Iterate Over Names & DataSources
----------------------------------------------------------------------------
+==============================================================================
 <#list dataSources as name, dataSource>
     ${name} => ${dataSource.uri}<#lt>
 </#list>
 
+<#if dataSources?has_content>
+    <#list dataSources?values as dataSource>
+        <@writeDataSource dataSource/>
+    </#list>
+<#else>
+    No data sources found ...
+</#if>
+
+<#macro writeDataSource dataSource>
+
+${dataSource.name}
+==============================================================================
+
 Invoke Arbitrary Methods On DataSource
 ---------------------------------------------------------------------------
-<#if dataSources?has_content>
 <#assign dataSource=dataSources?values?first>
 Name            : ${dataSource.name}
+Group           : ${dataSource.group}
 Nr of lines     : ${dataSource.lines?size}
 Content Type    : ${dataSource.contentType}
 Charset         : ${dataSource.charset}
 Extension       : ${dataSource.extension}
 Nr of chars     : ${dataSource.text?length}
 Nr of bytes     : ${dataSource.bytes?size}
-File name       : ${dataSource.metadata["filename"]}
+File name       : ${dataSource.fileName}
+URI schema      : ${dataSource.uri.scheme}
 
 Iterating Over Metadata Of A Datasource
 ---------------------------------------------------------------------------
 <#list dataSource.metadata as name, value>
 ${name?right_pad(15)} : ${value}
 </#list>
-</#if>
+
+Iterating Over Properties Of A Datasource
+---------------------------------------------------------------------------
+<#list dataSource.properties as name, value>
+${name?right_pad(15)} : ${value}
+</#list>
+</#macro>
diff --git a/freemarker-generator-cli/src/app/scripts/run-examples.bat 
b/freemarker-generator-cli/src/app/scripts/run-examples.bat
index 6ef6f9a..5fc0e1c 100644
--- a/freemarker-generator-cli/src/app/scripts/run-examples.bat
+++ b/freemarker-generator-cli/src/app/scripts/run-examples.bat
@@ -37,6 +37,13 @@ REM 
=========================================================================
 echo "examples\templates\demo.ftl"
 %FREEMARKER_CMD% -t examples\templates\demo.ftl README.md --output-encoding 
CP1252 > target\out\demo.txt
 
+#############################################################################
+# DataSources
+#############################################################################
+
+echo "examples\templates\datasources.ftl"
+$FREEMARKER_CMD -t examples\templates\datasources.ftl -s :data=examples/data > 
target\out\datasources.txt
+
 REM =========================================================================
 REM Interactive Mode
 REM =========================================================================
@@ -177,4 +184,4 @@ echo 
"templates\freemarker-generator\yaml\json\transform.ftl"
 %FREEMARKER_CMD% -t freemarker-generator\yaml\json\transform.ftl 
examples\data\yaml\swagger-spec.yaml > target\out\swagger-spec.json
 
 echo "Created the following sample files in .\target\out"
-dir .\target\out
\ No newline at end of file
+dir .\target\out
diff --git a/freemarker-generator-cli/src/app/scripts/run-examples.sh 
b/freemarker-generator-cli/src/app/scripts/run-examples.sh
index 034774f..d0f42b7 100755
--- a/freemarker-generator-cli/src/app/scripts/run-examples.sh
+++ b/freemarker-generator-cli/src/app/scripts/run-examples.sh
@@ -43,6 +43,13 @@ echo "examples/templates/demo.ftl"
 $FREEMARKER_CMD -t examples/templates/demo.ftl README.md > target/out/demo.txt 
|| { echo >&2 "Test failed.  Aborting."; exit 1; }
 
 #############################################################################
+# DataSources
+#############################################################################
+
+echo "examples/templates/datasources.ftl"
+$FREEMARKER_CMD -t examples/templates/datasources.ftl -s :data=examples/data > 
target/out/datasources.txt || { echo >&2 "Test failed.  Aborting."; exit 1; }
+
+#############################################################################
 # Interactive Mode
 #############################################################################
 
@@ -198,4 +205,4 @@ echo 
"templates/freemarker-generator/yaml/json/transform.ftl"
 $FREEMARKER_CMD -t freemarker-generator/yaml/json/transform.ftl 
examples/data/yaml/swagger-spec.yaml > target/out/swagger-spec.json || { echo 
>&2 "Test failed.  Aborting."; exit 1; }
 
 echo "Created the following sample files in ./target/out"
-ls -l ./target/out
\ No newline at end of file
+ls -l ./target/out
diff --git 
a/freemarker-generator-cli/src/app/templates/freemarker-generator/lib/commons-csv.ftl
 
b/freemarker-generator-cli/src/app/templates/freemarker-generator/lib/commons-csv.ftl
index e8b0538..881a2f2 100644
--- 
a/freemarker-generator-cli/src/app/templates/freemarker-generator/lib/commons-csv.ftl
+++ 
b/freemarker-generator-cli/src/app/templates/freemarker-generator/lib/commons-csv.ftl
@@ -17,6 +17,24 @@
 -->
 
 <#---
+    Detemine the CSV format for reading a CSV files from properties of the 
data source.
+
+    * format - see 
https://commons.apache.org/proper/commons-csv/apidocs/org/apache/commons/csv/CSVFormat.html
+    * delimiter - symbolic name of delimiter, e.g. "COLON" or "SEMICOLON"
+    * header - whether the first rows are headers
+-->
+<#function dataSourceFormat dataSource>
+    <#assign format = 
tools.csv.formats[dataSource.properties.format!"DEFAULT"]>
+    <#assign delimiter = 
tools.csv.toDelimiter(dataSource.properties.delimiter!format.getDelimiter())>
+    <#assign withHeader = dataSource.properties.header!"true">
+    <#assign format = format.withDelimiter(delimiter)>
+    <#if withHeader?boolean>
+        <#assign format = format.withFirstRecordAsHeader()>
+    </#if>
+    <#return format>
+</#function>
+
+<#---
     Detemine the CSV format for reading a CSV files using user-supplied
     parameters from the data model.
 
diff --git 
a/freemarker-generator-cli/src/main/java/org/apache/freemarker/generator/cli/config/OutputGeneratorsSupplier.java
 
b/freemarker-generator-cli/src/main/java/org/apache/freemarker/generator/cli/config/OutputGeneratorsSupplier.java
index 085ad60..059a9ab 100644
--- 
a/freemarker-generator-cli/src/main/java/org/apache/freemarker/generator/cli/config/OutputGeneratorsSupplier.java
+++ 
b/freemarker-generator-cli/src/main/java/org/apache/freemarker/generator/cli/config/OutputGeneratorsSupplier.java
@@ -31,6 +31,7 @@ import 
org.apache.freemarker.generator.cli.picocli.TemplateSourceDefinition;
 import java.net.URI;
 import java.util.ArrayList;
 import java.util.Collection;
+import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
 import java.util.function.Supplier;
@@ -136,6 +137,6 @@ public class OutputGeneratorsSupplier implements 
Supplier<List<OutputGenerator>>
 
     private static DataSource stdinDataSource() {
         final URI uri = UriUtils.toUri(Location.SYSTEM, STDIN);
-        return DataSourceFactory.fromInputStream(STDIN, DEFAULT_GROUP, uri, 
System.in, MIME_TEXT_PLAIN, UTF_8);
+        return DataSourceFactory.fromInputStream(STDIN, DEFAULT_GROUP, uri, 
System.in, MIME_TEXT_PLAIN, UTF_8, new HashMap<>());
     }
 }
diff --git 
a/freemarker-generator-cli/src/site/markdown/cli/concepts/data-sources.md 
b/freemarker-generator-cli/src/site/markdown/cli/concepts/data-sources.md
index b4205e3..7ad0daf 100644
--- a/freemarker-generator-cli/src/site/markdown/cli/concepts/data-sources.md
+++ b/freemarker-generator-cli/src/site/markdown/cli/concepts/data-sources.md
@@ -171,7 +171,7 @@ Charset         : ${dataSource.charset}
 Extension       : ${dataSource.extension}
 Nr of chars     : ${dataSource.text?length}
 Nr of bytes     : ${dataSource.bytes?size}
-File name       : ${dataSource.metadata["filename"]}
+File name       : ${dataSource.fileName}
 
 Iterating Over Metadata Of A Datasource
 ---------------------------------------------------------------------------
@@ -207,4 +207,38 @@ uri             : 
file:/Users/sgoeschl/work/github/apache/freemarker-generator/f
 group           : default
 ```
 
+### Inspecting A DataSource
+
+```
+> freemarker-generator -t examples/templates/datasources.ftl 
user:csv=examples/data/csv/transactions.csv#delimiter=TAB
+
+Invoke Arbitrary Methods On DataSource
+---------------------------------------------------------------------------
+Name            : user
+Group           : csv
+Nr of lines     : 101
+Content Type    : text/csv
+Charset         : UTF-8
+Extension       : csv
+Nr of chars     : 12,643
+Nr of bytes     : 12,643
+File name       : transactions.csv
+URI schema      : file
+
+Iterating Over Metadata Of A Datasource
+---------------------------------------------------------------------------
+extension       : csv
+basename        : transactions
+filename        : transactions.csv
+filepath        : 
/Users/sgoeschl/work/github/apache/freemarker-generator/freemarker-generator-cli/target/appassembler/examples/data/csv
+name            : user
+mimetype        : text/csv
+uri             : 
file:/Users/sgoeschl/work/github/apache/freemarker-generator/freemarker-generator-cli/target/appassembler/examples/data/csv/transactions.csv
+group           : csv
+
+Iterating Over Properties Of A Datasource
+---------------------------------------------------------------------------
+delimiter       : TAB
+```
+
 
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 55bf420..f6a80a3 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
@@ -9,7 +9,7 @@ A Named URI consists of
 
 As a refresher, a URI is made up of the following components (inspired by 
https://docs.gomplate.ca/datasources/)
 
-```
+```text
   foo://[email protected]:8042/over/there?name=ferret#nose
   \_/   \_______________________/\_________/ \_________/ \__/
    |           |                    |            |        |
@@ -20,15 +20,17 @@ For our purposes, the scheme and the path components are 
especially important, t
 
 | Component | Purpose                                                          
                                         |
 
|-----------|-----------------------------------------------------------------------------------------------------------|
-| scheme       | All data sources require a scheme (except for file when using 
relative paths)                              |
-| authority    | Used only by remote data sources, and can be omitted in some 
of those cases.                               |
+| scheme       | All data sources require a scheme (except for file when using 
relative paths)                             |
+| authority    | Used only by remote data sources, 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` 
+### Using Named URIs For A File
 
-```
+The following Named URI loads a "user.csv" and the data source is available as 
`my_users`
+
+```text
 freemarker-generator -t freemarker-generator/info.ftl 
my_users=examples/data/csv/user.csv
 
 FreeMarker Generator DataSources
@@ -39,7 +41,7 @@ URI : 
file:/Users/sgoeschl/work/github/apache/freemarker-generator/freemarker-ge
 
 A Named URI allows to pass additional information as part of the fragment, 
e.g. the charset of the text file 
 
-```
+```text
 freemarker-generator -t freemarker-generator/info.ftl 
my_users=examples/data/csv/user.csv#charset=UTF-16
 
 FreeMarker Generator DataSources
@@ -50,8 +52,8 @@ URI : 
file:/Users/sgoeschl/work/github/apache/freemarker-generator/freemarker-ge
 
 In addition to the simplified file syntax full URIs can be used
 
-```
-freemarker-generator -t freemarker-generator/info.ftl http://google.com?foo=bar
+```text
+freemarker-generator -t freemarker-generator/info.ftl 
'http://google.com?foo=bar'
 
 FreeMarker Generator DataSources
 ------------------------------------------------------------------------------
@@ -61,11 +63,87 @@ URI : http://google.com?foo=bar
 
 and also combined with a name
 
-```
-freemarker-generator -t freemarker-generator/info.ftl 
page=http://google.com\?foo\=bar
+```text
+freemarker-generator -t freemarker-generator/info.ftl 
'page=http://google.com?foo=bar'
 
 FreeMarker Generator DataSources
 ------------------------------------------------------------------------------
 [#1]: name=page, group=default, fileName=page mimeType=text/html, 
charset=ISO-8859-1, length=-1 Bytes
 URI : http://google.com?foo=bar
 ```
+
+### Using Named URIs For Directories
+
+A Name URI can be also combined with file directories.
+
+Load all CVS files of a directory using the group "csv"
+
+```text
+freemarker-generator -t freemarker-generator/info.ftl :csv=examples/data/csv
+
+FreeMarker Generator DataSources
+------------------------------------------------------------------------------
+[#1]: 
name=file:/Users/sgoeschl/work/github/apache/freemarker-generator/freemarker-generator-cli/target/appassembler/examples/data/csv/contract.csv,
 group=csv, fileName=contract.csv, mimeType=text/csv, charset=UTF-8, 
length=6,328 Bytes
+URI : 
file:/Users/sgoeschl/work/github/apache/freemarker-generator/freemarker-generator-cli/target/appassembler/examples/data/csv/contract.csv
+...
+[#7]: 
name=file:/Users/sgoeschl/work/github/apache/freemarker-generator/freemarker-generator-cli/target/appassembler/examples/data/csv/user.csv,
 group=csv, fileName=user.csv, mimeType=text/csv, charset=UTF-8, length=376 
Bytes
+URI : 
file:/Users/sgoeschl/work/github/apache/freemarker-generator/freemarker-generator-cli/target/appassembler/examples/data/csv/user.csv
+```
+
+or use a charset for all files of a directory
+
+```text
+freemarker-generator -t freemarker-generator/info.ftl 
'examples/data/csv#charset=UTF-16&mimetype=text/plain'
+
+FreeMarker Generator DataSources
+------------------------------------------------------------------------------
+[#1]: 
name=file:/Users/sgoeschl/work/github/apache/freemarker-generator/freemarker-generator-cli/target/appassembler/examples/data/csv/contract.csv,
 group=default, fileName=contract.csv, mimeType=text/csv, charset=UTF-16, 
length=6,328 Bytes
+URI : 
file:/Users/sgoeschl/work/github/apache/freemarker-generator/freemarker-generator-cli/target/appassembler/examples/data/csv/contract.csv
+...
+[#7]: 
name=file:/Users/sgoeschl/work/github/apache/freemarker-generator/freemarker-generator-cli/target/appassembler/examples/data/csv/user.csv,
 group=default, fileName=user.csv, mimeType=text/csv, charset=UTF-16, 
length=376 Bytes
+URI : 
file:/Users/sgoeschl/work/github/apache/freemarker-generator/freemarker-generator-cli/target/appassembler/examples/data/csv/user.csv
+```
+
+It is also possible to provide data source properties to all files being loaded
+
+```text
+freemarker-generator -t examples/templates/datasources.ftl 
'examples/data/csv#format=DEFAULT'
+
+file:/Users/sgoeschl/work/github/apache/freemarker-generator/freemarker-generator-cli/target/appassembler/examples/data/csv/user.csv
+==============================================================================
+
+Invoke Arbitrary Methods On DataSource
+---------------------------------------------------------------------------
+Name            : 
file:/Users/sgoeschl/work/github/apache/freemarker-generator/freemarker-generator-cli/target/appassembler/examples/data/csv/user.csv
+Group           : default
+Nr of lines     : 5
+Content Type    : text/csv
+Charset         : UTF-8
+Extension       : csv
+Nr of chars     : 376
+Nr of bytes     : 376
+File name       : user.csv
+URI schema      : file
+
+Iterating Over Metadata Of A Datasource
+---------------------------------------------------------------------------
+extension       : csv
+basename        : user
+filename        : user.csv
+filepath        : 
/Users/sgoeschl/work/github/apache/freemarker-generator/freemarker-generator-cli/target/appassembler/examples/data/csv
+name            : 
file:/Users/sgoeschl/work/github/apache/freemarker-generator/freemarker-generator-cli/target/appassembler/examples/data/csv/user.csv
+mimetype        : text/csv
+uri             : 
file:/Users/sgoeschl/work/github/apache/freemarker-generator/freemarker-generator-cli/target/appassembler/examples/data/csv/user.csv
+group           : default
+
+Iterating Over Properties Of A Datasource
+---------------------------------------------------------------------------
+format          : DEFAULT
+
+```
+
+
+
+
+
+
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 5c5ebd5..88067b2 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
@@ -48,6 +48,11 @@ public class ExamplesTest extends AbstractMainTest {
     }
 
     @Test
+    public void shouldRunDataSourceExamples() throws IOException {
+        assertValid(execute("-t src/app/examples/templates/datasources.ftl -s 
:csv=src/app/examples/data/csv"));
+    }
+
+    @Test
     public void shouldRunCsvExamples() throws IOException {
         assertValid(execute("-t freemarker-generator/csv/html/transform.ftl 
src/app/examples/data/csv/contract.csv"));
         assertValid(execute("-t freemarker-generator/csv/md/transform.ftl 
src/app/examples/data/csv/contract.csv"));
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 54be597..1e6600a 100644
--- 
a/freemarker-generator-cli/src/test/java/org/apache/freemarker/generator/cli/ManualTest.java
+++ 
b/freemarker-generator-cli/src/test/java/org/apache/freemarker/generator/cli/ManualTest.java
@@ -24,7 +24,7 @@ import java.io.IOException;
 public class ManualTest extends AbstractMainTest {
 
     // private static final String CMD = "-V";
-    private static final String CMD = "-t src/test/templates/manual.ftl -s 
src/app/examples/data/csv";
+    private static final String CMD = "-t 
src/app/examples/templates/datasources.ftl -s 
:csv-data=src/app/examples/data/csv#separator=COLON 
https://xkcd.com/info.0.json envvars=env:///";
 
     @Override
     public String execute(String commandLine) throws IOException {
diff --git a/freemarker-generator-cli/src/test/templates/manual.ftl 
b/freemarker-generator-cli/src/test/templates/manual.ftl
index 3ad42c6..c1112be 100644
--- a/freemarker-generator-cli/src/test/templates/manual.ftl
+++ b/freemarker-generator-cli/src/test/templates/manual.ftl
@@ -1,4 +1,4 @@
-<#ftl output_format="plainText" >
+<#ftl output_format="plainText" strip_whitespace=true>
 <#--
   Licensed to the Apache Software Foundation (ASF) under one
   or more contributor license agreements.  See the NOTICE file
@@ -16,30 +16,34 @@
   under the License.
 -->
 Support FreeMarker Directives
----------------------------------------------------------------------------
+==============================================================================
 Has Content: ${dataSources?has_content?c}
 Nr. of Documents: ${dataSources?size}
 
 Use FTL Array-style Access
----------------------------------------------------------------------------
+==============================================================================
 ${dataSources?values[0].toString()}
 ${dataSources?values?first.toString()}
 
 Get Document Names As Keys
----------------------------------------------------------------------------
+==============================================================================
 <#list dataSources?keys as name>
     ${name}<#lt>
 </#list>
 
 Iterate Over Names & DataSources
----------------------------------------------------------------------------
+==============================================================================
 <#list dataSources as name, dataSource>
     ${name} => ${dataSource.uri}<#lt>
 </#list>
 
+<#if dataSources?has_content>
+    <#list dataSources?values as dataSource>
+[#${dataSource?counter}] - ${dataSource.name}
+==============================================================================
+
 Invoke Arbitrary Methods On DataSource
 ---------------------------------------------------------------------------
-<#if dataSources?has_content>
 <#assign dataSource=dataSources?values?first>
 Name            : ${dataSource.name}
 Nr of lines     : ${dataSource.lines?size}
@@ -55,4 +59,14 @@ Iterating Over Metadata Of A Datasource
 <#list dataSource.metadata as name, value>
 ${name?right_pad(15)} : ${value}
 </#list>
+
+Iterating Over Properties Of A Datasource
+---------------------------------------------------------------------------
+<#list dataSource.properties as name, value>
+${name?right_pad(15)} : ${value}
+</#list>
+
+    </#list>
+<#else>
+No data sources found ...
 </#if>

Reply via email to