Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature/vtl cfg format #45

Open
wants to merge 8 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -552,6 +552,10 @@ private static VelocityEngine createDefaultVelocityEngine() {
engine.setProperty(RuntimeConstants.ENCODING_DEFAULT,
StandardCharsets.UTF_8.toString());

engine.setProperty("runtime.custom_directives",
"com.norconex.commons.lang.config.vlt.CustomIncludeDirective,"
+ "com.norconex.commons.lang.config.vlt.CustomParseDirective");

engine.setProperty("runtime.log", "");
return engine;
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
package com.norconex.commons.lang.config.vlt;

import com.norconex.commons.lang.config.ConfigurationException;
import org.apache.velocity.app.event.EventHandlerUtil;
import org.apache.velocity.context.InternalContextAdapter;
import org.apache.velocity.exception.MethodInvocationException;
import org.apache.velocity.exception.ResourceNotFoundException;
import org.apache.velocity.exception.VelocityException;
import org.apache.velocity.runtime.directive.Include;
import org.apache.velocity.runtime.parser.node.ASTDirective;
import org.apache.velocity.runtime.parser.node.Node;
import org.apache.velocity.runtime.parser.node.ParserTreeConstants;
import org.apache.velocity.runtime.resource.Resource;
import org.apache.velocity.util.StringUtils;

import java.io.IOException;
import java.io.Writer;
import java.lang.reflect.Method;

public class CustomIncludeDirective extends Include {
@Override
public boolean render(InternalContextAdapter context,
Writer writer, Node node)
throws IOException, MethodInvocationException,
ResourceNotFoundException {
try {
// Obtain the protected method 'outputErrorToStream' from the superclass 'Include'
Method outputErrorToStreamMethod = Include.class.getDeclaredMethod(
"outputErrorToStream",
Writer.class,
String.class);
outputErrorToStreamMethod.setAccessible(true); // Make the protected method accessible

int argCount = node.jjtGetNumChildren();

for (int i = 0; i < argCount; i++) {
Node n = node.jjtGetChild(i);

if (n.getType() == ParserTreeConstants.JJTSTRINGLITERAL ||
n.getType() == ParserTreeConstants.JJTREFERENCE) {
if (!renderOutput(n, context, writer))
outputErrorToStreamMethod.invoke(this, writer,
"error with arg " + i + " please see log.");
} else {
String msg = "invalid #include() argument '"
+ n.toString() + "' at "
+ StringUtils.formatFileString(this);
log.error(msg);
outputErrorToStreamMethod.invoke(this, writer,
"error with arg " + i + " please see log.");
throw new VelocityException(msg, null,
rsvc.getLogContext().getStackTrace());
}
}
} catch (Exception e) {
throw new ConfigurationException(e);
}
return true;
}

private boolean renderOutput(Node node, InternalContextAdapter context,
Writer writer)
throws IOException, MethodInvocationException,
ResourceNotFoundException {
if (node == null) {
log.error("#include() null argument");
return false;
}

Object value = node.value(context);
if (value == null) {
log.error("#include() null argument");
return false;
}

String sourcearg = value.toString();

String arg = EventHandlerUtil.includeEvent(rsvc, context, sourcearg,
context.getCurrentTemplateName(), getName());

boolean blockinput = false;
if (arg == null)
blockinput = true;

Resource resource = null;

try {
if (!blockinput)
resource = rsvc.getContent(arg, getInputEncoding(context));
} catch (ResourceNotFoundException rnfe) {
log.error("#include(): cannot find resource '{}', called at {}",
arg, StringUtils.formatFileString(this));
throw rnfe;
}

catch (RuntimeException e) {
log.error("#include(): arg = '{}', called at {}",
arg, StringUtils.formatFileString(this));
throw e;
} catch (Exception e) {
String msg = "#include(): arg = '" + arg +
"', called at " + StringUtils.formatFileString(this);
log.error(msg, e);
throw new VelocityException(msg, e,
rsvc.getLogContext().getStackTrace());
}

if (blockinput)
return true;

else if (resource == null)
return false;

String prefixSpace = ((ASTDirective) node.jjtGetParent()).getPrefix();
String indentedData =
((String) resource.getData()).replaceAll("(?m)^", prefixSpace);
writer.write(indentedData);

return true;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,157 @@
package com.norconex.commons.lang.config.vlt;

import org.apache.velocity.Template;
import org.apache.velocity.app.event.EventHandlerUtil;
import org.apache.velocity.context.InternalContextAdapter;
import org.apache.velocity.exception.MethodInvocationException;
import org.apache.velocity.exception.ParseErrorException;
import org.apache.velocity.exception.ResourceNotFoundException;
import org.apache.velocity.exception.VelocityException;
import org.apache.velocity.runtime.directive.Parse;
import org.apache.velocity.runtime.directive.StopCommand;
import org.apache.velocity.runtime.parser.node.ASTDirective;
import org.apache.velocity.runtime.parser.node.Node;
import org.apache.velocity.runtime.parser.node.SimpleNode;
import org.apache.velocity.util.StringUtils;

import java.io.IOException;
import java.io.StringWriter;
import java.io.Writer;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.List;

public class CustomParseDirective extends Parse {

@Override
public boolean render(InternalContextAdapter context,
Writer writer, Node node)
throws IOException, ResourceNotFoundException, ParseErrorException,
MethodInvocationException {

int maxDepth;
try {
Field maxDepthField = Parse.class.getDeclaredField("maxDepth");

maxDepthField.setAccessible(true);
maxDepth = (int) maxDepthField.get(this); // Cast to int

} catch (NoSuchFieldException | IllegalAccessException e) {
throw new RuntimeException(e);
}

if (node.jjtGetNumChildren() == 0) {
throw new VelocityException("#parse(): argument missing at " +
StringUtils.formatFileString(this), null,
rsvc.getLogContext().getStackTrace());
}

Object value = node.jjtGetChild(0).value(context);
if (value == null) {
log.debug("#parse(): null argument at {}",
StringUtils.formatFileString(this));
}

String sourcearg = value == null ? null : value.toString();

String arg = EventHandlerUtil.includeEvent(rsvc, context, sourcearg,
context.getCurrentTemplateName(), getName());

if (strictRef && value == null && arg == null) {
throw new VelocityException(
"The argument to #parse returned null at "
+ StringUtils.formatFileString(this),
null, rsvc.getLogContext().getStackTrace());
}

if (arg == null) {
return true;
}

if (maxDepth > 0) {
String[] templateStack = context.getTemplateNameStack();
if (templateStack.length >= maxDepth) {
StringBuilder path = new StringBuilder();
for (String aTemplateStack : templateStack) {
path.append(" > ").append(aTemplateStack);
}
log.error("Max recursion depth reached ({}). File stack: {}",
templateStack.length, path);

return false;
}
}

Template t = null;

try {
t = rsvc.getTemplate(arg, getInputEncoding(context));
} catch (ResourceNotFoundException rnfe) {
log.error("#parse(): cannot find template '{}', called at {}",
arg, StringUtils.formatFileString(this));
throw rnfe;
} catch (ParseErrorException pee) {
log.error(
"#parse(): syntax error in #parse()-ed template '{}', called at {}",
arg, StringUtils.formatFileString(this));
throw pee;
} catch (RuntimeException e) {
log.error("Exception rendering #parse({}) at {}",
arg, StringUtils.formatFileString(this));
throw e;
} catch (Exception e) {
String msg = "Exception rendering #parse(" + arg + ") at " +
StringUtils.formatFileString(this);
log.error(msg, e);
throw new VelocityException(msg, e,
rsvc.getLogContext().getStackTrace());
}
List<Template> macroLibraries = context.getMacroLibraries();

if (macroLibraries == null) {
macroLibraries = new ArrayList<>();
}

context.setMacroLibraries(macroLibraries);

macroLibraries.add(t);

try {
preRender(context);
context.pushCurrentTemplateName(arg);

//Mod start
Writer placeHolder = new StringWriter();
((SimpleNode) t.getData()).render(context, placeHolder);

String prefixSpace = ((ASTDirective) node).getPrefix();

String prefixedContent =
(placeHolder.toString()).replaceAll("(?m)^", prefixSpace);
writer.append(prefixedContent);
//Mod end

node.jjtGetChild(0).getColumn();


} catch (StopCommand stop) {
if (!stop.isFor(this)) {
throw stop;
}
} catch (RuntimeException e) {
log.error("Exception rendering #parse({}) at {}",
arg, StringUtils.formatFileString(this));
throw e;
} catch (Exception e) {
String msg = "Exception rendering #parse(" + arg + ") at " +
StringUtils.formatFileString(this);
log.error(msg, e);
throw new VelocityException(msg, e,
rsvc.getLogContext().getStackTrace());
} finally {
context.popCurrentTemplateName();
postRender(context);
}
return true;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,79 @@ void testLoadToString() throws Exception {
// invalid path
assertThat(loader.toString(cfgPath("doesntExist"))).isNull();
}
@Test
void testIndentToString() throws Exception {

var loader = ConfigurationLoader.builder()
.variablesFile(cfgPath("vlt_item7.vm"))
.build();
var str = SystemUtil.callWithProperty("date", "2024-08-20",
() -> loader.toString(cfgPath("vlt_indent.yaml")));
// "varB" should not be resolved as it comes from an #include
// directive (as opposed to parse)
assertThat(StringUtils.remove(str, '\r')).isEqualTo(
"""
title: $title
date: $date
title: template title
date: 2024-08-20
depth1_include_vlt_item2:
title: $title
date: $date
key: value
depth_vlt_item2:
title: $title
date: $date
depth1_parse_vlt_item2:
key: value
depth_vlt_item2:
title: template title
date: 2024-08-20
multi_depth_vlt_item2:
multi_depth_vlt_item2_1:
key: value
depth_vlt_item2:
title: $title
date: $date
key: value
depth_vlt_item2:
title: template title
date: 2024-08-20
recursive_depth_vlt_item3:
depth_vlt_item3:
key: value
title: $title
date: $date
title: template title
date: 2024-08-20
depth_vlt_item3_1:
tst_depth_3_1_1:
key: value
title: $title
date: $date
key: value
tst_depth_3_1_2:
title: template title
date: 2024-08-20
key: value
depth_vlt_item2:
title: template title
date: 2024-08-20
tst_depth_3_1_2-1
key: value
key: value
ifelse_loop_depth_vlt_item6:
Name: Alice
Age: 10
age: you are nothing.
Name: Bob
Age: 30
Feels: very old
Name: Charlie
Age: 50
Feels: very old
""");
}

private Path cfgPath(String path) {
return Path.of(CFG_BASE_PATH + path);
Expand Down
Loading
Loading