org.bndtools.templating.engine.st.StringTemplateEngine Maven / Gradle / Ivy
The newest version!
package org.bndtools.templating.engine.st;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.io.Reader;
import java.io.StringReader;
import java.io.StringWriter;
import java.io.UnsupportedEncodingException;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Properties;
import org.bndtools.templating.Resource;
import org.bndtools.templating.ResourceMap;
import org.bndtools.templating.ResourceType;
import org.bndtools.templating.StringResource;
import org.bndtools.templating.TemplateEngine;
import org.eclipse.core.runtime.IProgressMonitor;
import org.osgi.service.component.annotations.Component;
import org.stringtemplate.v4.AutoIndentWriter;
import org.stringtemplate.v4.InstanceScope;
import org.stringtemplate.v4.Interpreter;
import org.stringtemplate.v4.ST;
import org.stringtemplate.v4.STGroup;
import org.stringtemplate.v4.compiler.CompiledST;
import org.stringtemplate.v4.compiler.Compiler;
import org.stringtemplate.v4.compiler.STLexer;
import org.stringtemplate.v4.misc.ErrorBuffer;
import aQute.bnd.osgi.Constants;
import aQute.bnd.osgi.Instructions;
import st4hidden.org.antlr.runtime.ANTLRInputStream;
import st4hidden.org.antlr.runtime.CommonToken;
@Component(name = "org.bndtools.templating.engine.st", property = {
"name=stringtemplate", "version=4.0.8"
})
public class StringTemplateEngine implements TemplateEngine {
private static final String TEMPLATE_PROPERTIES = "_template.properties";
private static final String TEMPLATE_DEFS_PREFIX = "_defs/";
private static final String TEMPLATE_FILE_SUFFIX = ".st";
private static class TemplateSettings {
char leftDelim = '$';
char rightDelim = '$';
Instructions preprocessMatch = new Instructions("*");
Instructions ignore = null;
private TemplateSettings() {}
static TemplateSettings readFrom(Properties props) {
TemplateSettings settings = new TemplateSettings();
if (props != null) {
settings.leftDelim = readSingleChar(props, "leftDelim", settings.leftDelim);
settings.rightDelim = readSingleChar(props, "rightDelim", settings.rightDelim);
String process = props.getProperty("process", Constants.DEFAULT_PREPROCESSS_MATCHERS);
String processBefore = props.getProperty("process.before", null);
if (processBefore != null)
process = processBefore + ", " + process;
String processAfter = props.getProperty("process.after", null);
if (processAfter != null)
process = process + ", " + processAfter;
settings.preprocessMatch = new Instructions(process);
String ignore = props.getProperty("ignore", null);
settings.ignore = ignore != null ? new Instructions(ignore) : null;
}
return settings;
}
private static char readSingleChar(Properties props, String propName, char dflt) {
String str = props.getProperty(propName, String.valueOf(dflt))
.trim();
if (str.length() != 1)
throw new IllegalArgumentException("Setting value for " + propName + "must be a single character");
return str.charAt(0);
}
}
@Override
public Map getTemplateParameters(ResourceMap inputs, IProgressMonitor monitor) throws Exception {
Map params = new HashMap<>();
// Initialise the engine
TemplateSettings settings = readSettings(inputs);
STGroup stg = new STGroup(settings.leftDelim, settings.rightDelim);
// Assemble a mapping properties file of outputPath=sourcePath
String mappingTemplate = loadMappingTemplate(inputs, settings, stg);
extractAttrs(compile(stg, "_mapping", new StringResource(mappingTemplate)), params);
// Iterate the entries
Properties contentProps = new Properties();
contentProps.load(new StringReader(mappingTemplate));
@SuppressWarnings("unchecked")
Enumeration contentEnum = (Enumeration) contentProps.propertyNames();
while (contentEnum.hasMoreElements()) {
String outputPath = contentEnum.nextElement()
.trim();
String sourcePath = contentProps.getProperty(outputPath);
Resource source = inputs.get(sourcePath);
if (source == null)
throw new RuntimeException(
String.format("Internal error in template engine: could not find input resource '%s'", sourcePath));
if (settings.ignore == null || !settings.ignore.matches(sourcePath)) {
if (source.getType() == ResourceType.File) {
if (settings.preprocessMatch.matches(sourcePath)) {
extractAttrs(compile(stg, sourcePath, source), params);
}
}
}
}
return params;
}
@Override
public ResourceMap generateOutputs(ResourceMap inputs, Map> parameters,
IProgressMonitor monitor) throws Exception {
TemplateSettings settings = readSettings(inputs);
STGroup stg = new STGroup(settings.leftDelim, settings.rightDelim);
// Assemble a mapping properties file of outputPath=sourcePath
String mappingTemplate = loadMappingTemplate(inputs, settings, stg);
String renderedMapping = render(compile(stg, "_mapping", new StringResource(mappingTemplate)), parameters);
Properties contentProps = new Properties();
contentProps.load(new StringReader(renderedMapping));
// Iterate the content entries
ResourceMap outputs = new ResourceMap();
@SuppressWarnings("unchecked")
Enumeration contentEnum = (Enumeration) contentProps.propertyNames();
while (contentEnum.hasMoreElements()) {
String outputName = contentEnum.nextElement()
.trim();
String sourceName = contentProps.getProperty(outputName, "")
.trim();
Resource source = inputs.get(sourceName);
if (source == null)
throw new RuntimeException(
String.format("Internal error in template engine: could not find input resource '%s'", sourceName));
Resource output;
if (settings.ignore == null || !settings.ignore.matches(sourceName)) {
if (source.getType() == ResourceType.Folder) {
output = source;
} else if (settings.preprocessMatch.matches(sourceName)) {
// This file is a candidate for preprocessing with ST
String rendered = render(compile(stg, sourceName, source), parameters);
output = new StringResource(rendered);
} else {
// This file should be directly copied
output = source;
}
outputs.put(outputName, output);
}
}
return outputs;
}
private String loadMappingTemplate(ResourceMap inputs, TemplateSettings settings, STGroup stg) throws IOException {
StringWriter buf = new StringWriter();
try (PrintWriter bufPrint = new PrintWriter(buf)) {
for (String inputPath : inputs.getPaths()) {
if (inputPath.startsWith(TEMPLATE_DEFS_PREFIX)) {
if (inputPath.endsWith(TEMPLATE_FILE_SUFFIX)) {
// Definition... load into StringTemplate group and
// don't generate output
String inputPathRelative = inputPath.substring(TEMPLATE_DEFS_PREFIX.length());
Resource resource = inputs.get(inputPath);
if (resource != null && resource.getType() == ResourceType.File)
loadTemplate(stg, inputPathRelative, resource.getContent(), resource.getTextEncoding());
}
} else {
// Mapping to output file
String outputPath = inputPath;
String escapedSourcePath = escapeDelimiters(inputPath, settings);
bufPrint.printf("%s=%s%n", outputPath, escapedSourcePath);
}
}
}
String mappingTemplate = buf.toString();
return mappingTemplate;
}
private TemplateSettings readSettings(ResourceMap inputs) throws IOException, UnsupportedEncodingException {
Properties settingsProp = new Properties();
Resource settingsResource = inputs.remove(TEMPLATE_PROPERTIES);
if (settingsResource != null) {
if (settingsResource.getType() != ResourceType.File)
throw new IllegalArgumentException(
String.format("Template settings resource %s must be a file; found resource type %s.",
TEMPLATE_PROPERTIES, settingsResource.getType()));
try (Reader reader = new InputStreamReader(settingsResource.getContent(),
settingsResource.getTextEncoding())) {
settingsProp.load(reader);
}
}
TemplateSettings settings = TemplateSettings.readFrom(settingsProp);
return settings;
}
private void loadTemplate(STGroup stg, String fileName, InputStream is, String encoding) throws IOException {
ANTLRInputStream charStream = new ANTLRInputStream(is, encoding);
charStream.name = fileName;
try {
stg.loadTemplateFile("/", fileName, charStream);
} catch (NullPointerException e) {
throw new IOException(String.format(
"Error loading template file: %s. Ensure the file contains a template definition matching the file name.",
fileName), e);
}
}
private CompiledST loadRawTemplate(STGroup stg, String name, Resource resource) throws IOException {
if (resource.getType() != ResourceType.File)
throw new IllegalArgumentException(
String.format("Cannot build resource from resource of type %s (name='%s').", resource.getType(), name));
try (InputStream is = resource.getContent()) {
ANTLRInputStream templateStream = new ANTLRInputStream(is, resource.getTextEncoding());
String template = templateStream.substring(0, templateStream.size() - 1);
CompiledST impl = new Compiler(stg).compile(name, template);
CommonToken nameT = new CommonToken(STLexer.SEMI);
nameT.setInputStream(templateStream);
stg.rawDefineTemplate("/" + name, impl, nameT);
impl.defineImplicitlyDefinedTemplates(stg);
return impl;
}
}
private void extractAttrs(ST st, final Map attrs) throws Exception {
Interpreter interpreter = new Interpreter(st.groupThatCreatedThisInstance, Locale.getDefault(), true) {
@Override
public Object getAttribute(InstanceScope scope, String name) {
attrs.put(name, null);
return "X";
}
};
StringWriter writer = new StringWriter();
interpreter.exec(new AutoIndentWriter(writer), new InstanceScope(null, st));
}
private String render(ST st, Map> params) throws Exception {
for (Entry> entry : params.entrySet()) {
for (Object value : entry.getValue()) {
st.add(entry.getKey(), value);
}
}
return st.render();
}
private ST compile(STGroup group, String name, Resource resource) throws Exception {
ErrorBuffer errors = new ErrorBuffer();
group.setListener(errors);
ST st;
try {
loadRawTemplate(group, name, resource);
st = group.getInstanceOf(name);
} catch (Exception e) {
// Wrap the ST exception, which gives us no detail in its message
throw new IllegalArgumentException(
String.format("Failed to compile template '%s': %s", name, errors.toString()));
}
if (st == null)
throw new Exception("Template name not loaded: " + name);
return st;
}
static String escapeDelimiters(String string, TemplateSettings settings) {
StringBuilder builder = new StringBuilder();
for (int i = 0; i < string.length(); i++) {
char c = string.charAt(i);
if (settings.leftDelim == c || settings.rightDelim == c) {
builder.append('\\');
}
builder.append(c);
}
return builder.toString();
}
}
© 2015 - 2024 Weber Informatics LLC | Privacy Policy