blob: 5b7ac9b74f63c5f1666bdd95b2d67bec1fb3e583 [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.util.ClosableUtils;
import org.apache.freemarker.generator.base.util.StringUtils;
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.Option;
import picocli.CommandLine.Parameters;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.io.OutputStreamWriter;
import java.io.Writer;
import java.util.ArrayList;
import java.util.HashMap;
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.Objects.requireNonNull;
import static org.apache.freemarker.generator.cli.config.Suppliers.propertiesSupplier;
import static org.apache.freemarker.generator.cli.config.Suppliers.templateDirectorySupplier;
@Command(description = "Apache FreeMarker CLI", name = "freemarker-cli", mixinStandardHelpOptions = true, versionProvider = GitVersionProvider.class)
public class Main implements Callable<Integer> {
private static final String FREEMARKER_CLI_PROPERTY_FILE = "freemarker-cli.properties";
@ArgGroup(multiplicity = "1")
private TemplateSourceOptions templateSourceOptions;
private static final class TemplateSourceOptions {
@Option(names = { "-t", "--template" }, description = "FreeMarker template to render")
private String template;
@Option(names = { "-i", "--interactive" }, description = "Interactive FreeMarker template")
private String interactiveTemplate;
}
@Option(names = { "-b", "--basedir" }, description = "Optional template base directory")
private String baseDir;
@Option(names = { "-D", "--system-property" }, description = "Set system property")
private Properties systemProperties;
@Option(names = { "-e", "--input-encoding" }, description = "Encoding of input documents", defaultValue = "UTF-8")
private String inputEncoding;
@Option(names = { "-E", "--expose-env" }, description = "Expose environment variables and user-supplied properties globally")
private boolean isEnvironmentExposed;
@Option(names = { "-l", "--locale" }, description = "Locale being used for the output, e.g. 'en_US'")
private String locale;
@Option(names = { "-o", "--output" }, description = "Output file")
private String outputFile;
@Option(names = { "-P", "--param" }, description = "Set parameter")
private Map<String, String> parameters;
@Option(names = { "--config" }, defaultValue = FREEMARKER_CLI_PROPERTY_FILE, description = "FreeMarker CLI configuration file")
private String configFile;
@Option(names = { "--include" }, description = "File pattern for document input directory")
private String include;
@Option(names = { "--exclude" }, description = "File pattern for document input directory")
private String exclude;
@Option(names = { "--output-encoding" }, description = "Encoding of output, e.g. UTF-8", defaultValue = "UTF-8")
private String outputEncoding;
@Option(names = { "--stdin" }, description = "Read input document from stdin")
private boolean readFromStdin;
@Option(names = { "--times" }, defaultValue = "1", description = "Re-run X times for profiling")
private int times;
@Parameters(description = "List of input files and/or input directories")
private List<String> sources;
/** User-supplied command line parameters */
private final String[] args;
/** User-supplied writer (used mainly for unit testing) */
private Writer userSuppliedWriter;
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() {
return IntStream.range(0, times).map(i -> onCall()).max().orElse(0);
}
private Integer onCall() {
updateSystemProperties();
final Properties configuration = loadFreeMarkerCliConfiguration(configFile);
final List<File> templateDirectories = getTemplateDirectories(baseDir);
final Settings settings = settings(configuration, templateDirectories);
try {
final FreeMarkerTask freeMarkerTask = new FreeMarkerTask(settings);
return freeMarkerTask.call();
} finally {
if (settings.hasOutputFile()) {
ClosableUtils.closeQuietly(settings.getWriter());
}
}
}
private Settings settings(Properties configuration, List<File> templateDirectories) {
return Settings.builder()
.isEnvironmentExposed(isEnvironmentExposed)
.isReadFromStdin(readFromStdin)
.setArgs(args)
.setConfiguration(configuration)
.setInclude(include)
.setExclude(exclude)
.setInputEncoding(inputEncoding)
.setInteractiveTemplate(templateSourceOptions.interactiveTemplate)
.setLocale(locale)
.setOutputEncoding(outputEncoding)
.setOutputFile(outputFile)
.setParameters(parameters != null ? parameters : new HashMap<>())
.setSources(sources != null ? sources : new ArrayList<>())
.setSystemProperties(systemProperties != null ? systemProperties : new Properties())
.setTemplateDirectories(templateDirectories)
.setTemplateName(templateSourceOptions.template)
.setWriter(writer(outputFile, outputEncoding))
.build();
}
private Writer writer(String outputFile, String ouputEncoding) {
try {
if (userSuppliedWriter != null) {
return userSuppliedWriter;
} else if (!StringUtils.isEmpty(outputFile)) {
return new BufferedWriter(new FileWriter(outputFile));
} else {
return new BufferedWriter(new OutputStreamWriter(System.out, ouputEncoding));
}
} catch (IOException e) {
throw new RuntimeException("Unable to create writer", e);
}
}
private void updateSystemProperties() {
if (systemProperties != null && !systemProperties.isEmpty()) {
System.getProperties().putAll(systemProperties);
}
}
private static List<File> getTemplateDirectories(String baseDir) {
return templateDirectorySupplier(baseDir).get();
}
private static Properties loadFreeMarkerCliConfiguration(String fileName) {
final Properties properties = propertiesSupplier(fileName).get();
if (properties != null) {
return properties;
} else {
throw new RuntimeException("FreeMarker CLI configuration file not found: " + fileName);
}
}
}