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/02/29 00:06:09 UTC
[freemarker-generator] 01/06: FREEMARKER-135 Support user-supplied
names for datasources
This is an automated email from the ASF dual-hosted git repository.
sgoeschl pushed a commit to branch FREEMARKER-135
in repository https://gitbox.apache.org/repos/asf/freemarker-generator.git
commit 8f7415e7e66eb83f5b261812c42847ba5e09e9b9
Author: Siegfried Goeschl <si...@gmail.com>
AuthorDate: Fri Feb 28 20:46:51 2020 +0100
FREEMARKER-135 Support user-supplied names for datasources
---
.../freemarker/generator/base/uri/NamedUri.java | 77 ++++++++++++++++++++
.../NamedUriFragmentParser.java} | 28 +++++---
.../generator/base/uri/NamedUriParser.java | 81 +++++++++++++++++++++
.../generator/base/util/CachingSupplier.java | 16 +++++
.../generator/base/util/LocaleUtils.java | 1 +
.../generator/uri/NamedUriParserTest.java | 83 ++++++++++++++++++++++
.../org/apache/freemarker/generator/cli/Main.java | 43 ++++++-----
.../freemarker/generator/cli/PicocliTest.java | 74 +++++++++++++++++++
8 files changed, 377 insertions(+), 26 deletions(-)
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
new file mode 100644
index 0000000..cc0b52c
--- /dev/null
+++ b/freemarker-generator-base/src/main/java/org/apache/freemarker/generator/base/uri/NamedUri.java
@@ -0,0 +1,77 @@
+/*
+ * 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.uri;
+
+import java.net.URI;
+import java.util.Map;
+
+import static java.util.Collections.emptyMap;
+import static java.util.Objects.requireNonNull;
+
+/**
+ * Caputeres the information of a user-supplied "named URI".
+ */
+public class NamedUri {
+
+ /** User-supplied name */
+ private final String name;
+
+ /** The URI */
+ private final URI uri;
+
+ /** Name/value pairs parsed from URI fragment */
+ private final Map<String, String> parameters;
+
+ public NamedUri(URI uri) {
+ this.name = null;
+ this.uri = requireNonNull(uri);
+ this.parameters = emptyMap();
+ }
+
+ public NamedUri(URI uri, Map<String, String> parameters) {
+ this.name = null;
+ this.uri = requireNonNull(uri);
+ this.parameters = requireNonNull(parameters);
+ }
+
+ public NamedUri(String name, URI uri, Map<String, String> parameters) {
+ this.name = requireNonNull(name);
+ this.uri = requireNonNull(uri);
+ this.parameters = requireNonNull(parameters);
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public URI getUri() {
+ return uri;
+ }
+
+ public Map<String, String> getParameters() {
+ return parameters;
+ }
+
+ @Override
+ public String toString() {
+ return "NamedUri{" +
+ "name='" + name + '\'' +
+ ", uri=" + uri +
+ ", parameters=" + parameters +
+ '}';
+ }
+}
diff --git a/freemarker-generator-base/src/main/java/org/apache/freemarker/generator/base/util/LocaleUtils.java b/freemarker-generator-base/src/main/java/org/apache/freemarker/generator/base/uri/NamedUriFragmentParser.java
similarity index 52%
copy from freemarker-generator-base/src/main/java/org/apache/freemarker/generator/base/util/LocaleUtils.java
copy to freemarker-generator-base/src/main/java/org/apache/freemarker/generator/base/uri/NamedUriFragmentParser.java
index 5edd405..a94d3da 100644
--- a/freemarker-generator-base/src/main/java/org/apache/freemarker/generator/base/util/LocaleUtils.java
+++ b/freemarker-generator-base/src/main/java/org/apache/freemarker/generator/base/uri/NamedUriFragmentParser.java
@@ -14,20 +14,32 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package org.apache.freemarker.generator.base.util;
+package org.apache.freemarker.generator.base.uri;
-import java.util.Locale;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.Map;
+import static java.util.stream.Collectors.toMap;
import static org.apache.freemarker.generator.base.util.StringUtils.isEmpty;
-public class LocaleUtils {
+/**
+ * Parses the URI fragment as list of name/value pairs seperated by an ampersand.
+ */
+public class NamedUriFragmentParser {
- public static Locale parseLocale(String value) {
- if (isEmpty(value) || value.equalsIgnoreCase("JVM default") || value.equalsIgnoreCase("default")) {
- return Locale.getDefault();
+ public static Map<String, String> parse(String fragment) {
+ if (isEmpty(fragment)) {
+ return Collections.emptyMap();
}
- final String[] parts = value.split("_");
- return parts.length == 1 ? new Locale(parts[0]) : new Locale(parts[0], parts[1]);
+ try {
+ final String[] nameValuePairs = fragment.split("&");
+ return Arrays.stream(nameValuePairs)
+ .map(s -> s.split("="))
+ .collect(toMap(parts -> parts[0], parts -> parts[1]));
+ } catch (RuntimeException e) {
+ throw new RuntimeException("Unable to parse URI fragment: " + fragment, e);
+ }
}
}
diff --git a/freemarker-generator-base/src/main/java/org/apache/freemarker/generator/base/uri/NamedUriParser.java b/freemarker-generator-base/src/main/java/org/apache/freemarker/generator/base/uri/NamedUriParser.java
new file mode 100644
index 0000000..a18d914
--- /dev/null
+++ b/freemarker-generator-base/src/main/java/org/apache/freemarker/generator/base/uri/NamedUriParser.java
@@ -0,0 +1,81 @@
+/*
+ * 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.uri;
+
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.util.Map;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import static java.util.Objects.requireNonNull;
+
+/**
+ * Parses a named URI provided by the caller.
+ * <ul>
+ * <li>users.csv</li>
+ * <li>file:///users.csv</li>
+ * <li>user=file:///users.csv</li>
+ * <li>users=file:///users.csv#charset=UTF-16&mimetype=text/csv</li>
+ * </ul>
+ */
+public class NamedUriParser {
+
+ private static final String NAME_GROUP = "name";
+ private static final String URI_GROUP = "uri";
+ private static final int NR_OF_NAMED_GROUPS = 2;
+ private static final Pattern NAMED_URI_REGEXP = Pattern.compile("^(?<name>[a-zA-Z0-9-_]*)=(?<uri>.*)");
+
+ public static NamedUri parse(String value) {
+ final String sanitzedUri = requireNonNull(value).trim();
+
+ if (sanitzedUri.isEmpty()) {
+ throw new IllegalArgumentException("Empty named URI");
+ }
+
+ try {
+ // avoid invoking the regexp if it can't match anyway
+ if (isSimpleUri(sanitzedUri)) {
+ return new NamedUri(new URI(sanitzedUri));
+ }
+
+ final Matcher matcher = NAMED_URI_REGEXP.matcher(sanitzedUri);
+
+ if (!matcher.matches() || matcher.groupCount() > NR_OF_NAMED_GROUPS) {
+ throw new IllegalArgumentException("Invalid named URI: " + value);
+ }
+
+ if (matcher.groupCount() == NR_OF_NAMED_GROUPS) {
+ final String name = matcher.group(NAME_GROUP);
+ final URI uri = new URI(matcher.group(URI_GROUP));
+ final Map<String, String> parameters = NamedUriFragmentParser.parse(uri.getFragment());
+ return new NamedUri(name, uri, parameters);
+ } else {
+ final URI uri = new URI(matcher.group(sanitzedUri));
+ final Map<String, String> parameters = NamedUriFragmentParser.parse(uri.getFragment());
+ return new NamedUri(uri, parameters);
+ }
+ } catch (URISyntaxException | RuntimeException e) {
+ throw new RuntimeException("Failed to parse named URI: " + value, e);
+ }
+ }
+
+ private static boolean isSimpleUri(String uri) {
+ return !uri.contains("=");
+ }
+
+}
diff --git a/freemarker-generator-base/src/main/java/org/apache/freemarker/generator/base/util/CachingSupplier.java b/freemarker-generator-base/src/main/java/org/apache/freemarker/generator/base/util/CachingSupplier.java
index fbada67..dab4334 100644
--- a/freemarker-generator-base/src/main/java/org/apache/freemarker/generator/base/util/CachingSupplier.java
+++ b/freemarker-generator-base/src/main/java/org/apache/freemarker/generator/base/util/CachingSupplier.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.util;
import java.util.function.Supplier;
diff --git a/freemarker-generator-base/src/main/java/org/apache/freemarker/generator/base/util/LocaleUtils.java b/freemarker-generator-base/src/main/java/org/apache/freemarker/generator/base/util/LocaleUtils.java
index 5edd405..95f6a7f 100644
--- a/freemarker-generator-base/src/main/java/org/apache/freemarker/generator/base/util/LocaleUtils.java
+++ b/freemarker-generator-base/src/main/java/org/apache/freemarker/generator/base/util/LocaleUtils.java
@@ -23,6 +23,7 @@ import static org.apache.freemarker.generator.base.util.StringUtils.isEmpty;
public class LocaleUtils {
public static Locale parseLocale(String value) {
+ // "JVM default" is a special value defined by FreeMarker
if (isEmpty(value) || value.equalsIgnoreCase("JVM default") || value.equalsIgnoreCase("default")) {
return Locale.getDefault();
}
diff --git a/freemarker-generator-base/src/test/java/org/apache/freemarker/generator/uri/NamedUriParserTest.java b/freemarker-generator-base/src/test/java/org/apache/freemarker/generator/uri/NamedUriParserTest.java
new file mode 100644
index 0000000..37819a3
--- /dev/null
+++ b/freemarker-generator-base/src/test/java/org/apache/freemarker/generator/uri/NamedUriParserTest.java
@@ -0,0 +1,83 @@
+/*
+ * 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.uri;
+
+import org.apache.freemarker.generator.base.uri.NamedUri;
+import org.apache.freemarker.generator.base.uri.NamedUriParser;
+import org.junit.Test;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNull;
+
+public class NamedUriParserTest {
+
+ @Test
+ public void shouldParseFileName() {
+ final NamedUri namedURI = parse("users.csv");
+
+ assertNull(namedURI.getName());
+ assertEquals("users.csv", namedURI.getUri().toString());
+ assertEquals(0, namedURI.getParameters().size());
+ }
+
+ @Test
+ public void shouldParseDirectoryName() {
+ final NamedUri namedURI = parse("users/");
+
+ assertNull(namedURI.getName());
+ assertEquals("users/", namedURI.getUri().toString());
+ assertEquals(0, namedURI.getParameters().size());
+ }
+
+ @Test
+ public void shouldParseSimpleFileUri() {
+ final NamedUri namedURI = parse("file:///users.csv");
+
+ assertNull(namedURI.getName());
+ assertEquals("file:///users.csv", namedURI.getUri().toString());
+ assertEquals(0, namedURI.getParameters().size());
+ }
+
+ @Test
+ public void shouldParseNamedFileUri() {
+ final NamedUri namedURI = parse("users=file:///users.csv");
+
+ assertEquals("users", namedURI.getName());
+ assertEquals("file:///users.csv", namedURI.getUri().toString());
+ assertEquals(0, namedURI.getParameters().size());
+ }
+
+ @Test
+ public void shouldParseNamedFileUriWithFragment() {
+ final NamedUri namedURI = parse("users=file:///users.csv#charset=UTF-16&mimetype=text/csv");
+
+ assertEquals("users", namedURI.getName());
+ assertEquals("file:///users.csv#charset=UTF-16&mimetype=text/csv", namedURI.getUri().toString());
+ assertEquals(2, namedURI.getParameters().size());
+ assertEquals("UTF-16", namedURI.getParameters().get("charset"));
+ assertEquals("text/csv", namedURI.getParameters().get("mimetype"));
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void shouldThrowIllegalArgumentExceptionForEmptyUri() {
+ parse("");
+ }
+
+ private static NamedUri parse(String value) {
+ return NamedUriParser.parse(value);
+ }
+}
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 82dbc6f..93ea256 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
@@ -51,9 +51,9 @@ public class Main implements Callable<Integer> {
private static final String FREEMARKER_CLI_PROPERTY_FILE = "freemarker-cli.properties";
@ArgGroup(multiplicity = "1")
- private TemplateSourceOptions templateSourceOptions;
+ TemplateSourceOptions templateSourceOptions;
- private static final class TemplateSourceOptions {
+ static final class TemplateSourceOptions {
@Option(names = { "-t", "--template" }, description = "FreeMarker template to render")
private String template;
@@ -62,52 +62,59 @@ public class Main implements Callable<Integer> {
}
@Option(names = { "-b", "--basedir" }, description = "Optional template base directory")
- private String baseDir;
+ String baseDir;
+
+ @Option(names = { "-d", "--datasource" }, description = "Datasource used for rendering")
+ List<String> datasources;
@Option(names = { "-D", "--system-property" }, description = "Set system property")
- private Properties systemProperties;
+ Properties systemProperties;
@Option(names = { "-e", "--input-encoding" }, description = "Encoding of datasource", defaultValue = "UTF-8")
- private String inputEncoding;
+ String inputEncoding;
@Option(names = { "-E", "--expose-env" }, description = "Expose environment variables and user-supplied properties globally")
- private boolean isEnvironmentExposed;
+ boolean isEnvironmentExposed;
@Option(names = { "-l", "--locale" }, description = "Locale being used for the output, e.g. 'en_US'")
- private String locale;
+ String locale;
@Option(names = { "-o", "--output" }, description = "Output file")
- private String outputFile;
+ String outputFile;
@Option(names = { "-P", "--param" }, description = "Set parameter")
- private Map<String, String> parameters;
+ Map<String, String> parameters;
@Option(names = { "--config" }, defaultValue = FREEMARKER_CLI_PROPERTY_FILE, description = "FreeMarker CLI configuration file")
- private String configFile;
+ String configFile;
@Option(names = { "--include" }, description = "File pattern for datasource input directory")
- private String include;
+ String include;
@Option(names = { "--exclude" }, description = "File pattern for datasource input directory")
- private String exclude;
+ String exclude;
@Option(names = { "--output-encoding" }, description = "Encoding of output, e.g. UTF-8", defaultValue = "UTF-8")
- private String outputEncoding;
+ String outputEncoding;
@Option(names = { "--stdin" }, description = "Read datasource from stdin")
- private boolean readFromStdin;
+ boolean readFromStdin;
@Option(names = { "--times" }, defaultValue = "1", description = "Re-run X times for profiling")
- private int times;
+ int times;
@Parameters(description = "List of input files and/or input directories")
- private List<String> sources;
+ List<String> sources;
/** User-supplied command line parameters */
- private final String[] args;
+ final String[] args;
/** User-supplied writer (used mainly for unit testing) */
- private Writer userSuppliedWriter;
+ Writer userSuppliedWriter;
+
+ Main() {
+ this.args = new String[0];
+ }
private Main(String[] args) {
this.args = requireNonNull(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
new file mode 100644
index 0000000..95662f0
--- /dev/null
+++ b/freemarker-generator-cli/src/test/java/org/apache/freemarker/generator/cli/PicocliTest.java
@@ -0,0 +1,74 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.freemarker.generator.cli;
+
+import org.junit.Test;
+import picocli.CommandLine;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNull;
+
+public class PicocliTest {
+
+ private static final String TEMPLATE = "template.ftl";
+ private static final String ANY_FILE = "users.csv";
+ private static final String ANY_NAMED_FILE = "users=users.csv";
+ private static final String OTHER_FILE = "transctions.csv";
+ private static final String ANY_FILE_URI = "file:///users.csv";
+ private static final String OTHER_FILE_URI = "file:///transctions.csv";
+
+ @Test
+ public void testSinglePositionalParameter() {
+ assertEquals(ANY_FILE_URI, parse("-t", TEMPLATE, ANY_FILE_URI).sources.get(0));
+ assertNull(ANY_FILE, parse("-t", TEMPLATE, ANY_FILE_URI).datasources);
+ }
+
+ @Test
+ public void testMultiplePositionalParameter() {
+ assertEquals(ANY_FILE, parse("-t", TEMPLATE, ANY_FILE, OTHER_FILE).sources.get(0));
+ assertEquals(OTHER_FILE, parse("-t", TEMPLATE, ANY_FILE, OTHER_FILE).sources.get(1));
+
+ assertEquals(ANY_FILE, parse("-t", TEMPLATE, ANY_FILE, OTHER_FILE_URI).sources.get(0));
+ assertEquals(OTHER_FILE_URI, parse("-t", TEMPLATE, ANY_FILE, OTHER_FILE_URI).sources.get(1));
+
+ assertEquals(ANY_FILE_URI, parse("-t", TEMPLATE, ANY_FILE_URI, OTHER_FILE_URI).sources.get(0));
+ assertEquals(OTHER_FILE_URI, parse("-t", TEMPLATE, ANY_FILE_URI, OTHER_FILE_URI).sources.get(1));
+ }
+
+ @Test
+ public void testSingleNamedDatasource() {
+ assertEquals(ANY_FILE, parse("-t", TEMPLATE, ANY_FILE).sources.get(0));
+ assertEquals(ANY_FILE, parse("-t", TEMPLATE, "-d", ANY_FILE).datasources.get(0));
+ assertEquals(ANY_FILE, parse("-t", TEMPLATE, "--datasource", ANY_FILE).datasources.get(0));
+ assertEquals(ANY_FILE_URI, parse("-t", TEMPLATE, "--datasource", ANY_FILE_URI).datasources.get(0));
+ }
+
+ @Test
+ public void testMultipleNamedDatasource() {
+ final Main main = parse("-t", TEMPLATE, "-d", ANY_FILE, "--datasource", OTHER_FILE_URI);
+
+ assertEquals(ANY_FILE, main.datasources.get(0));
+ assertEquals(OTHER_FILE_URI, main.datasources.get(1));
+ assertNull(main.sources);
+ }
+
+ private static Main parse(String... args) {
+ final Main main = new Main();
+ new CommandLine(main).parseArgs(args);
+ return main;
+ }
+}