Skip to content

Commit

Permalink
Merge pull request #89 from nolaviz/nolaviz-patch1
Browse files Browse the repository at this point in the history
Add a simple mechanism for runtime debugging of templates.
  • Loading branch information
ddekany authored Dec 18, 2023
2 parents 6965a7e + 66857d9 commit d5c8c76
Show file tree
Hide file tree
Showing 4 changed files with 162 additions and 3 deletions.
20 changes: 20 additions & 0 deletions src/main/java/freemarker/core/Environment.java
Original file line number Diff line number Diff line change
Expand Up @@ -188,6 +188,8 @@ public final class Environment extends Configurable {

private boolean fastInvalidReferenceExceptions;

private TemplateProcessingTracer currentTracer;

/**
* Retrieves the environment object associated with the current thread, or {@code null} if there's no template
* processing going on in this thread. Data model implementations that need access to the environment can call this
Expand Down Expand Up @@ -2879,6 +2881,13 @@ public TemplateModel get(String key) throws TemplateModelException {
};
}

/**
* Sets the tracer to use for this environment.
*/
public void setTracer(TemplateProcessingTracer tracer) {
currentTracer = tracer;
}

private void pushElement(TemplateElement element) {
final int newSize = ++instructionStackSize;
TemplateElement[] instructionStack = this.instructionStack;
Expand All @@ -2891,9 +2900,20 @@ private void pushElement(TemplateElement element) {
this.instructionStack = instructionStack;
}
instructionStack[newSize - 1] = element;
if (currentTracer != null) {
currentTracer.enterElement(element.getTemplate(),
element.getBeginColumn(), element.getBeginLine(),
element.getEndColumn(), element.getEndLine(), element.isLeaf());
}
}

private void popElement() {
if (currentTracer != null) {
TemplateElement element = instructionStack[instructionStackSize - 1];
currentTracer.exitElement(element.getTemplate(),
element.getBeginColumn(), element.getBeginLine(),
element.getEndColumn(), element.getEndLine());
}
instructionStackSize--;
}

Expand Down
6 changes: 3 additions & 3 deletions src/main/java/freemarker/core/ListElseContainer.java
Original file line number Diff line number Diff line change
Expand Up @@ -37,10 +37,10 @@ public ListElseContainer(IteratorBlock listPart, ElseOfList elsePart) {

@Override
TemplateElement[] accept(Environment env) throws TemplateException, IOException {
if (!listPart.acceptWithResult(env)) {
return elsePart.accept(env);
if (listPart.acceptWithResult(env)) {
return null;
}
return null;
return new TemplateElement[] { elsePart };
}

@Override
Expand Down
56 changes: 56 additions & 0 deletions src/main/java/freemarker/core/TemplateProcessingTracer.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
/*
* 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 freemarker.core;

import freemarker.ext.util.IdentityHashMap;
import freemarker.template.Configuration;
import freemarker.template.Template;
import freemarker.template.TemplateDirectiveModel;
import freemarker.template.TemplateTransformModel;
import freemarker.template.utility.ObjectFactory;

/**
* Run-time tracer plug-in. This may be * used to implement profiling, coverage analytis, execution tracing,
* and other on-the-fly debugging mechanisms.
* <p>
* Use {@link Environment#setTracer(TemplateProcessingTracer)} to configure a tracer for the current environment.
*
* @since 2.3.33
*/
public interface TemplateProcessingTracer {

/**
* Invoked by {@link Environment} whenever it starts processing a new template element. {@code
* isLeafElement} indicates whether this element is a leaf, or whether the tracer should expect
* to receive lower-level elements within the context of this one.
*
* @since 2.3.23
*/
void enterElement(Template template, int beginColumn, int beginLine, int endColumn, int endLine,
boolean isLeafElement);

/**
* Invoked by {@link Environment} whenever it completes processing a new template element.
*
* @since 2.3.23
*/
void exitElement(Template template, int beginColumn, int beginLine, int endColumn, int endLine);

}
83 changes: 83 additions & 0 deletions src/test/java/freemarker/core/TemplateProcessingTracerTest.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
/*
* 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 freemarker.core;

import static org.junit.Assert.*;

import java.io.StringWriter;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

import org.junit.Test;

import freemarker.template.Configuration;
import freemarker.template.Template;

public class TemplateProcessingTracerTest {

private static final String TEMPLATE_TEXT =
"<#if 0 == 1>Nope.\n</#if>" +
"<#if 1 == 1>Yup.\n</#if>" +
"Always.\n" +
"<#list [1, 2, 3] as item>\n" +
"${item}<#else>\n" +
"Nope.\n" +
"</#list>\n" +
"<#list [] as item>\n" +
"${item}<#else>" +
"Yup.\n" +
"</#list>\n";

@Test
public void test() throws Exception {
Configuration cfg = new Configuration(Configuration.VERSION_2_3_32);
Template t = new Template("test.ftl", TEMPLATE_TEXT, cfg);
StringWriter sw = new StringWriter();
Tracer tracer = new Tracer(TEMPLATE_TEXT);
Environment env = t.createProcessingEnvironment(null, sw);
env.setTracer(tracer);
env.process();

List<String> expected = Arrays.asList("Yup.", "Always.", "${item}", "${item}", "${item}", "Yup.");
assertEquals(expected, tracer.elementsVisited);
}

private static class Tracer implements TemplateProcessingTracer {
final ArrayList<String> elementsVisited;
final String[] templateLines;

Tracer(String template) {
elementsVisited = new ArrayList<>();
templateLines = template.split("\\n");
}

public void enterElement(Template template, int beginColumn, int beginLine, int endColumn, int endLine,
boolean isLeafElement) {
if (isLeafElement) {
String line = templateLines[beginLine - 1];
String elementText = line.substring(beginColumn - 1,
endLine == beginLine ? Math.min(endColumn, line.length()) : line.length());
elementsVisited.add(elementText);
}
}

public void exitElement(Template template, int beginColumn, int beginLine, int endColumn, int endLine) {}
}
}

0 comments on commit d5c8c76

Please sign in to comment.