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 <[email protected]> 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; + } +}
