| /* |
| * 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.apache.freemarker.generator.base.FreeMarkerConstants.Configuration; |
| import org.apache.freemarker.generator.base.FreeMarkerConstants.SystemProperties; |
| import org.apache.freemarker.generator.base.parameter.ParameterModelSupplier; |
| import org.apache.freemarker.generator.base.util.ClosableUtils; |
| import org.apache.freemarker.generator.base.util.ListUtils; |
| import org.apache.freemarker.generator.cli.config.Settings; |
| import org.apache.freemarker.generator.cli.picocli.GitVersionProvider; |
| import org.apache.freemarker.generator.cli.task.FreeMarkerTask; |
| import picocli.CommandLine; |
| import picocli.CommandLine.ArgGroup; |
| import picocli.CommandLine.Command; |
| import picocli.CommandLine.Model.CommandSpec; |
| import picocli.CommandLine.Option; |
| import picocli.CommandLine.ParameterException; |
| import picocli.CommandLine.Parameters; |
| import picocli.CommandLine.Spec; |
| |
| import java.io.BufferedWriter; |
| import java.io.File; |
| import java.io.IOException; |
| import java.io.OutputStreamWriter; |
| import java.io.Writer; |
| import java.util.Collection; |
| import java.util.List; |
| import java.util.Map; |
| import java.util.Objects; |
| import java.util.Properties; |
| import java.util.concurrent.Callable; |
| import java.util.stream.Collectors; |
| import java.util.stream.IntStream; |
| import java.util.stream.Stream; |
| |
| import static java.util.Objects.requireNonNull; |
| import static org.apache.freemarker.generator.base.util.StringUtils.isEmpty; |
| import static org.apache.freemarker.generator.base.util.StringUtils.isNotEmpty; |
| import static org.apache.freemarker.generator.cli.config.Suppliers.propertiesSupplier; |
| import static org.apache.freemarker.generator.cli.config.Suppliers.templateDirectorySupplier; |
| |
| @Command(description = "Apache FreeMarker Generator", name = "freemarker-generator", mixinStandardHelpOptions = true, versionProvider = GitVersionProvider.class) |
| public class Main implements Callable<Integer> { |
| |
| @ArgGroup(multiplicity = "1") |
| TemplateSourceOptions templateSourceOptions; |
| |
| public static final class TemplateSourceOptions { |
| @Option(names = { "-t", "--template" }, description = "templates to process") |
| public List<String> templates; |
| |
| @Option(names = { "-i", "--interactive" }, description = "interactive template to process") |
| public String interactiveTemplate; |
| } |
| |
| @Option(names = { "-D", "--system-property" }, description = "set system property") |
| Properties systemProperties; |
| |
| @Option(names = { "-e", "--input-encoding" }, description = "encoding of data source", defaultValue = "UTF-8") |
| String inputEncoding; |
| |
| @Option(names = { "-l", "--locale" }, description = "locale being used for the output, e.g. 'en_US'") |
| String locale; |
| |
| @Option(names = { "-m", "--data-model" }, description = "data model used for rendering") |
| List<String> dataModels; |
| |
| @Option(names = { "-o", "--output" }, description = "output files or directories") |
| List<String> outputs; |
| |
| @Option(names = { "-P", "--param" }, description = "set parameter") |
| Map<String, String> parameters; |
| |
| @Option(names = { "-s", "--data-source" }, description = "data source used for rendering") |
| List<String> dataSources; |
| |
| @Option(names = { "--config" }, description = "FreeMarker Generator configuration file") |
| String configFile; |
| |
| @Option(names = { "--data-source-include" }, description = "file include pattern for data sources") |
| String dataSourceIncludePattern; |
| |
| @Option(names = { "--data-source-exclude" }, description = "file exclude pattern for data sources") |
| String dataSourceExcludePattern; |
| |
| @Option(names = { "--output-encoding" }, description = "encoding of output, e.g. UTF-8", defaultValue = "UTF-8") |
| String outputEncoding; |
| |
| @Option(names = { "--stdin" }, description = "read data source from stdin") |
| boolean readFromStdin; |
| |
| @Option(names = { "--template-dir" }, description = "additional template directory") |
| String templateDir; |
| |
| @Option(names = { "--times" }, defaultValue = "1", description = "re-run X times for profiling") |
| int times; |
| |
| @Parameters(description = "data source files and/or directories") |
| List<String> sources; |
| |
| /** User-supplied command line parameters */ |
| final String[] args; |
| |
| /** User-supplied writer (used mainly for unit testing) */ |
| Writer userSuppliedWriter; |
| |
| /** Injected by Picocli */ |
| @Spec private CommandSpec spec; |
| |
| Main() { |
| this.args = new String[0]; |
| } |
| |
| private Main(String[] args) { |
| this.args = requireNonNull(args); |
| } |
| |
| private Main(String[] args, Writer userSuppliedWriter) { |
| this.args = requireNonNull(args); |
| this.userSuppliedWriter = requireNonNull(userSuppliedWriter); |
| } |
| |
| public static void main(String[] args) { |
| try { |
| System.exit(execute(args)); |
| } catch (RuntimeException e) { |
| System.err.println(e.getMessage()); |
| System.exit(1); |
| } |
| } |
| |
| public static int execute(String[] args) { |
| return new CommandLine(new Main(args)).execute(args); |
| } |
| |
| /** |
| * Used for testing to inject a writer. |
| * |
| * @param args command line parameters |
| * @param writer writer used for template rendering |
| * @return exit code |
| */ |
| public static int execute(String[] args, Writer writer) { |
| return new CommandLine(new Main(args, writer)).execute(args); |
| } |
| |
| @Override |
| public Integer call() { |
| validate(); |
| return IntStream.range(0, times).map(i -> onCall()).max().orElse(0); |
| } |
| |
| private Integer onCall() { |
| updateSystemProperties(); |
| |
| final String currentConfigFile = isNotEmpty(configFile) ? configFile : getDefaultConfigFileName(); |
| final Properties configuration = loadFreeMarkerCliConfiguration(currentConfigFile); |
| final List<File> templateDirectories = getTemplateDirectories(templateDir); |
| final Settings settings = settings(configuration, templateDirectories); |
| |
| try { |
| final FreeMarkerTask freeMarkerTask = new FreeMarkerTask(settings); |
| return freeMarkerTask.call(); |
| } finally { |
| if (settings.hasOutputs()) { |
| ClosableUtils.closeQuietly(settings.getWriter()); |
| } |
| } |
| } |
| |
| void validate() { |
| // "-d" or "--data-source" parameter shall not contain wildcard characters |
| if (dataSources != null) { |
| for (String source : dataSources) { |
| if (isFileSource(source) && (source.contains("*") || source.contains("?"))) { |
| throw new ParameterException(spec.commandLine(), "No wildcards supported for data source: " + source); |
| } |
| } |
| } |
| |
| // does the templates match the expected outputs?! |
| // -) no output means it goes to stdout |
| // -) for each template there should be an output |
| final List<String> templates = templateSourceOptions.templates; |
| if (templates != null && templates.size() > 1) { |
| if (outputs != null && outputs.size() != templates.size()) { |
| throw new ParameterException(spec.commandLine(), "Template output does not match specified templates"); |
| } |
| } |
| } |
| |
| private Settings settings(Properties configuration, List<File> templateDirectories) { |
| final ParameterModelSupplier parameterModelSupplier = new ParameterModelSupplier(parameters); |
| |
| return Settings.builder() |
| .isReadFromStdin(readFromStdin) |
| .setArgs(args) |
| .setConfiguration(configuration) |
| .setDataModels(dataModels) |
| .setDataSources(getCombinedDataSources()) |
| .setDataSourceIncludePattern(dataSourceIncludePattern) |
| .setDataSourceExcludePattern(dataSourceExcludePattern) |
| .setInputEncoding(inputEncoding) |
| .setInteractiveTemplate(templateSourceOptions.interactiveTemplate) |
| .setLocale(locale) |
| .setOutputEncoding(outputEncoding) |
| .setOutputs(outputs) |
| .setParameters(parameterModelSupplier.get()) |
| .setSystemProperties(systemProperties != null ? systemProperties : new Properties()) |
| .setTemplateDirectories(templateDirectories) |
| .setTemplateNames(templateSourceOptions.templates) |
| .setWriter(writer(outputs, outputEncoding)) |
| .build(); |
| } |
| |
| private Writer writer(List<String> outputFiles, String outputEncoding) { |
| try { |
| if (userSuppliedWriter != null) { |
| return userSuppliedWriter; |
| } else if (ListUtils.isNullOrEmpty(outputFiles)) { |
| return new BufferedWriter(new OutputStreamWriter(System.out, outputEncoding)); |
| } else { |
| return null; |
| } |
| } catch (IOException e) { |
| throw new RuntimeException("Unable to create writer", e); |
| } |
| } |
| |
| private void updateSystemProperties() { |
| if (systemProperties != null && !systemProperties.isEmpty()) { |
| System.getProperties().putAll(systemProperties); |
| } |
| } |
| |
| /** |
| * Data sources can be passed via command line option and/or |
| * positional parameter so we need to merge them. |
| * |
| * @return List of data sources |
| */ |
| private List<String> getCombinedDataSources() { |
| return Stream.of(dataSources, sources) |
| .filter(Objects::nonNull) |
| .flatMap(Collection::stream) |
| .collect(Collectors.toList()); |
| } |
| |
| private static List<File> getTemplateDirectories(String additionalTemplateDir) { |
| return templateDirectorySupplier(additionalTemplateDir).get(); |
| } |
| |
| /** |
| * Get the default configuration file based on the "app.home" system property |
| * provided by the shell wrapper. |
| * |
| * @return default configuration file name or null |
| */ |
| private static String getDefaultConfigFileName() { |
| final String appHome = System.getProperty(SystemProperties.APP_HOME); |
| return isNotEmpty(appHome) ? new File(appHome, Configuration.CONFIG_FILE_NAME).getAbsolutePath() : null; |
| } |
| |
| private static Properties loadFreeMarkerCliConfiguration(String fileName) { |
| if (isEmpty(fileName)) { |
| return new Properties(); |
| } |
| |
| final Properties properties = propertiesSupplier(fileName).get(); |
| if (properties != null) { |
| return properties; |
| } else { |
| return new Properties(); |
| } |
| } |
| |
| private static boolean isFileSource(String source) { |
| if (source.contains("file://")) { |
| return true; |
| } else if (source.contains("://")) { |
| return false; |
| } else { |
| return true; |
| } |
| } |
| } |