blob: 5158668f577fd942c785f43e0d5149239a5187df [file] [log] [blame]
/*
* 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.cli.config.Settings;
import org.apache.freemarker.generator.cli.config.Suppliers;
import org.apache.freemarker.generator.cli.picocli.GitVersionProvider;
import org.apache.freemarker.generator.cli.picocli.OutputGeneratorDefinition;
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.Parameters;
import picocli.CommandLine.Spec;
import java.io.File;
import java.io.Writer;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.concurrent.Callable;
import java.util.stream.IntStream;
import static java.util.Collections.emptyList;
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;
@Command(description = "Apache FreeMarker Generator", name = "freemarker-generator", mixinStandardHelpOptions = true, versionProvider = GitVersionProvider.class)
public class Main implements Callable<Integer> {
@ArgGroup(exclusive = false, multiplicity = "1..*")
List<OutputGeneratorDefinition> outputGeneratorDefinitions;
@Option(names = { "--data-source-include" }, description = "data source include pattern")
public String dataSourceIncludePattern;
@Option(names = { "--data-source-exclude" }, description = "data source exclude pattern")
public String dataSourceExcludePattern;
@Option(names = { "--shared-data-model" }, description = "shared data models used for rendering")
public List<String> sharedDataModels;
@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 = { "-P", "--param" }, description = "set parameter")
Map<String, String> parameters;
@Option(names = { "--config" }, description = "FreeMarker Generator configuration file")
String configFile;
@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 = { "--template-encoding" }, description = "template encoding", defaultValue = "UTF-8")
String templateEncoding;
@Option(names = { "--times" }, defaultValue = "1", description = "re-run X times for profiling")
int times;
@Parameters(description = "shared data source files and/or directories")
List<String> sharedDataSources;
/** User-supplied command line parameters */
final String[] args;
/** User-supplied writer (used mainly for unit testing) */
final Writer callerSuppliedWriter;
/** Injected by Picocli */
@Spec private CommandSpec spec;
Main(String[] args) {
this.args = requireNonNull(args);
this.callerSuppliedWriter = null;
}
private Main(String[] args, Writer callerSuppliedWriter) {
this.args = requireNonNull(args);
this.callerSuppliedWriter = requireNonNull(callerSuppliedWriter);
}
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, outputGeneratorDefinitions);
try {
final FreeMarkerTask freeMarkerTask = new FreeMarkerTask(
Suppliers.configurationSupplier(settings),
Suppliers.outputGeneratorsSupplier(settings),
Suppliers.sharedDataModelSupplier(settings),
Suppliers.sharedDataSourcesSupplier(settings),
settings::getUserParameters
);
return freeMarkerTask.call();
} finally {
ClosableUtils.closeQuietly(settings.getCallerSuppliedWriter());
}
}
void validate() {
outputGeneratorDefinitions.forEach(t -> t.validate(spec.commandLine()));
}
private Settings settings(Properties configuration, List<File> templateDirectories, List<OutputGeneratorDefinition> outputGeneratorDefinitions) {
final ParameterModelSupplier parameterModelSupplier = new ParameterModelSupplier(parameters);
return Settings.builder()
.isReadFromStdin(readFromStdin)
.setCommandLineArgs(args)
.setConfiguration(configuration)
.setTemplateDirectories(templateDirectories)
.setTemplateEncoding(templateEncoding)
.setOutputGeneratorDefinitions(outputGeneratorDefinitions)
.setSharedDataSources(getSharedDataSources())
.setSharedDataModels(sharedDataModels)
.setSourceIncludePattern(dataSourceIncludePattern)
.setSourceExcludePattern(dataSourceExcludePattern)
.setInputEncoding(inputEncoding)
.setLocale(locale)
.setOutputEncoding(outputEncoding)
.setParameters(parameterModelSupplier.get())
.setSystemProperties(systemProperties != null ? systemProperties : new Properties())
.setTemplateDirectories(templateDirectories)
.setCallerSuppliedWriter(callerSuppliedWriter)
.setVerbose(false)
.build();
}
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> getSharedDataSources() {
return sharedDataSources != null ? new ArrayList<>(sharedDataSources) : emptyList();
}
private static List<File> getTemplateDirectories(String additionalTemplateDir) {
return Suppliers.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 = Suppliers.propertiesSupplier(fileName).get();
if (properties != null) {
return properties;
} else {
return new Properties();
}
}
}