freemarker-notifications mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From ddek...@apache.org
Subject [09/11] incubator-freemarker git commit: FREEMARKER-83: Added new special variable, macro_caller_template_name, which returns the name (path) of the template from which the current macro was called. It's mostly useful if you want to resolve paths relativ
Date Fri, 09 Mar 2018 21:50:15 GMT
FREEMARKER-83: Added new special variable, macro_caller_template_name, which returns the name
(path) of the template from which the current macro was called. It's mostly useful if you
want to resolve paths relative to the caller template.


Project: http://git-wip-us.apache.org/repos/asf/incubator-freemarker/repo
Commit: http://git-wip-us.apache.org/repos/asf/incubator-freemarker/commit/4a5eec42
Tree: http://git-wip-us.apache.org/repos/asf/incubator-freemarker/tree/4a5eec42
Diff: http://git-wip-us.apache.org/repos/asf/incubator-freemarker/diff/4a5eec42

Branch: refs/heads/2.3
Commit: 4a5eec42dcfed0058dcef03c930ad57ec3dfaa27
Parents: 846ef94
Author: ddekany <ddekany@apache.org>
Authored: Fri Mar 9 22:06:45 2018 +0100
Committer: ddekany <ddekany@apache.org>
Committed: Fri Mar 9 22:06:45 2018 +0100

----------------------------------------------------------------------
 .../java/freemarker/core/BuiltinVariable.java   |  14 ++
 src/main/java/freemarker/core/Environment.java  |  30 ++++
 .../java/freemarker/template/Configuration.java |   3 +-
 src/manual/en_US/book.xml                       |  31 ++++
 .../core/MacroCallerTemplateNameTest.java       | 155 +++++++++++++++++++
 5 files changed, 232 insertions(+), 1 deletion(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/4a5eec42/src/main/java/freemarker/core/BuiltinVariable.java
----------------------------------------------------------------------
diff --git a/src/main/java/freemarker/core/BuiltinVariable.java b/src/main/java/freemarker/core/BuiltinVariable.java
index 1b7617c..9b9b135 100644
--- a/src/main/java/freemarker/core/BuiltinVariable.java
+++ b/src/main/java/freemarker/core/BuiltinVariable.java
@@ -74,6 +74,8 @@ final class BuiltinVariable extends Expression {
     static final String NOW = "now";
     static final String GET_OPTIONAL_TEMPLATE = "get_optional_template";
     static final String GET_OPTIONAL_TEMPLATE_CC = "getOptionalTemplate";
+    static final String MACRO_CALLER_TEMPLATE_NAME = "macro_caller_template_name";
+    static final String MACRO_CALLER_TEMPLATE_NAME_CC = "macroCallerTemplateName";
     static final String[] SPEC_VAR_NAMES = new String[] {
         AUTO_ESC_CC,
         AUTO_ESC,
@@ -94,6 +96,8 @@ final class BuiltinVariable extends Expression {
         LOCALE_OBJECT_CC,
         LOCALE_OBJECT,
         LOCALS,
+        MACRO_CALLER_TEMPLATE_NAME_CC,
+        MACRO_CALLER_TEMPLATE_NAME,
         MAIN,
         MAIN_TEMPLATE_NAME_CC,
         MAIN_TEMPLATE_NAME,
@@ -248,6 +252,16 @@ final class BuiltinVariable extends Expression {
         if (name == GET_OPTIONAL_TEMPLATE_CC) {
             return GetOptionalTemplateMethod.INSTANCE_CC;
         }
+        if (name == MACRO_CALLER_TEMPLATE_NAME || name == MACRO_CALLER_TEMPLATE_NAME_CC)
{
+            UnifiedCall caller;
+            try {
+                caller = env.getMacroCaller();
+            } catch (IllegalStateException e) {
+                throw new TemplateException("Failed to resolve ." + name + ": " + e.getMessage(),
e, env);
+            }
+            String name = caller.getTemplate().getName();
+            return name != null ? new SimpleScalar(name) : SimpleScalar.EMPTY_STRING;
+        }
         
         throw new _MiscTemplateException(this,
                 "Invalid special variable: ", name);

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/4a5eec42/src/main/java/freemarker/core/Environment.java
----------------------------------------------------------------------
diff --git a/src/main/java/freemarker/core/Environment.java b/src/main/java/freemarker/core/Environment.java
index 209464c..5f20c26 100644
--- a/src/main/java/freemarker/core/Environment.java
+++ b/src/main/java/freemarker/core/Environment.java
@@ -281,6 +281,36 @@ public final class Environment extends Configurable {
     }
 
     /**
+     * Gets the non-{@code null} {@link UnifiedCall} of the caller of the macro whose context
we are in; note
+     * that you can't call this from everywhere. Specifically, the FTL call stack must not
contain {@code #nested} or a
+     * call to user-defined-directive after the stack entry of the {@code #macro} directive.
This practically means that
+     * this should be called on the top-level inside {@code #macro}, or inside some core
directives like {@code #if}.
+     * 
+     * @throws IllegalStateException
+     *             If there's no macro caller or it can't be figured out at this point of
the template execution.
+     */
+    UnifiedCall getMacroCaller() throws IllegalStateException {
+        for (int ln = instructionStackSize - 1; ln > 0; ln--) {
+            TemplateElement te = instructionStack[ln];
+            if (te instanceof Macro) {
+                TemplateElement macroCaller = instructionStack[ln - 1];
+                if (macroCaller instanceof UnifiedCall) {
+                    return (UnifiedCall) macroCaller;
+                }
+            }
+            // Avoid returning the caller of @nested in `<#macro called><@inner>${getMacroCallerHere()}</@></#macro>`;
+            // the #macro that defines "inner" would break our logic above.
+            if (te instanceof BodyInstruction) {
+                throw new IllegalStateException(
+                        "Can't get the location of the macro caller here, because you are
inside an user-defined "
+                        + "directive call that's nested inside the #macro directive. (You
may "
+                        + "need to get the caller location earlier, and store it in a local
variable for later use.)");
+            }
+        }
+        throw new IllegalStateException("There's no macro caller at this point.");
+    }
+
+    /**
      * Deletes cached values that meant to be valid only during a single template execution.
      */
     private void clearCachedValues() {

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/4a5eec42/src/main/java/freemarker/template/Configuration.java
----------------------------------------------------------------------
diff --git a/src/main/java/freemarker/template/Configuration.java b/src/main/java/freemarker/template/Configuration.java
index c560d70..3673272 100644
--- a/src/main/java/freemarker/template/Configuration.java
+++ b/src/main/java/freemarker/template/Configuration.java
@@ -863,7 +863,8 @@ public class Configuration extends Configurable implements Cloneable,
ParserConf
      *           argument list contains {@code .current_template_name}, now it will correctly
evaluate to the template
      *           that contains the call, rather than to the template that contains the macro
or function definition.
      *           (Of course, the parameter default value expression is still evaluated in
the context of the called
-     *           macro or function.) 
+     *           macro or function.) Similarly, {@code .macro_caller_template_name} (which
itself was added in 2.3.28),
+     *           when used in a macro call argument, won't be incorrectly evaluated in the
context of the called macro.
      *     </ul>
      *   </li>
      * </ul>

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/4a5eec42/src/manual/en_US/book.xml
----------------------------------------------------------------------
diff --git a/src/manual/en_US/book.xml b/src/manual/en_US/book.xml
index 2346be1..6c9c8bb 100644
--- a/src/manual/en_US/book.xml
+++ b/src/manual/en_US/book.xml
@@ -23311,6 +23311,27 @@ There was no specific handler for node y
         </listitem>
 
         <listitem>
+          <para><indexterm>
+              <primary>macro_caller_template_name</primary>
+            </indexterm><literal>macro_caller_template_name</literal>:
Returns
+          the name (path) of the template from which the current macro was
+          called. It's mostly useful if you want to resolve paths relative to
+          the caller template. If the caller template is nameless, this will
+          be an empty string (not a missing value). Reading this variable will
+          cause error if you aren't inside a macro call, also if you are
+          inside an user-defined directive call
+          (<literal>&lt;@<replaceable>...</replaceable>&gt;</literal>)
that's
+          nested inside the <literal>macro</literal> directive (as in
+          <literal>&lt;#macro
+          m&gt;&lt;@x&gt;${.macro_caller_template_name}&lt;#-- FAILS!
+          --&gt;&lt;/@&gt;&lt;/#macro&gt;</literal>). (Note that
if <link
+          linkend="pgui_config_incompatible_improvements">incompatible
+          improvements</link> is set to less than 2.3.28, then when using this
+          variable in an argument to a macro, it will be incorrectly evaluated
+          to the caller of the called macro.)</para>
+        </listitem>
+
+        <listitem>
           <para><literal>main</literal>: A hash that you can use to access
the
           main <link linkend="dgui_misc_namespace">namespace</link>. Note that
           global variables like the variables of data-model are
@@ -27269,6 +27290,16 @@ TemplateModel x = env.getVariable("x");  // get variable x</programlisting>
             </listitem>
 
             <listitem>
+              <para>Added new <link linkend="ref_specvar">special
+              variable</link>, <literal>macro_caller_template_name</literal>
+              (<link
+              xlink:href="https://issues.apache.org/jira/browse/FREEMARKER-83">FREEMARKER-83</link>),
+              which returns the name (path) of the template from which the
+              current macro was called. It's mostly useful if you want to
+              resolve paths relative to the caller template.</para>
+            </listitem>
+
+            <listitem>
               <para>Bug fixed (<link
               xlink:href="https://issues.apache.org/jira/browse/FREEMARKER-83">FREEMARKER-83</link>);
               this fix is only active when <link

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/4a5eec42/src/test/java/freemarker/core/MacroCallerTemplateNameTest.java
----------------------------------------------------------------------
diff --git a/src/test/java/freemarker/core/MacroCallerTemplateNameTest.java b/src/test/java/freemarker/core/MacroCallerTemplateNameTest.java
new file mode 100644
index 0000000..32b1d4e
--- /dev/null
+++ b/src/test/java/freemarker/core/MacroCallerTemplateNameTest.java
@@ -0,0 +1,155 @@
+/*
+ * 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 org.junit.Test;
+
+import freemarker.template.Configuration;
+import freemarker.test.TemplateTest;
+
+public class MacroCallerTemplateNameTest extends TemplateTest {
+
+    @Override
+    protected Configuration createConfiguration() throws Exception {
+        Configuration cfg = super.createConfiguration();
+        cfg.setIncompatibleImprovements(Configuration.VERSION_2_3_28);
+        return cfg;
+    }
+
+    @Test
+    public void testNoCaller() throws Exception {
+        assertErrorContains("${.macroCallerTemplateName}", "no macro caller", ".macroCallerTemplateName");
+        assertErrorContains("${.macro_caller_template_name}", "no macro caller", ".macro_caller_template_name");
+
+        assertErrorContains(""
+                + "<#macro m><#nested></#macro>"
+                + "<@m>${.macroCallerTemplateName}</@>",
+                "nested", ".macroCallerTemplateName");
+
+        assertErrorContains(""
+                + "<#macro m><#nested></#macro>"
+                + "<#macro m2><@m>${.macroCallerTemplateName}</@></#macro>"
+                + "<@m2/>",
+                "nested", ".macroCallerTemplateName");
+        assertOutput(""
+                + "<#macro m2>${.macroCallerTemplateName}</#macro>"
+                + "[<@m2/>]",
+                "[]");
+
+        addTemplate("main.ftl", "${.macroCallerTemplateName}");
+        assertErrorContainsForNamed("main.ftl", "no macro caller");
+    }
+
+    @Test
+    public void testSameTemplateCaller() throws Exception {
+        addTemplate("main.ftl", ""
+                + "<#macro m>${.macroCallerTemplateName}</#macro>"
+                + "<@m />, <#attempt>${.macroCallerTemplateName}<#recover>-</#attempt>");
+        assertOutputForNamed("main.ftl", "main.ftl, -");
+    }
+
+    @Test
+    public void testIncludedTemplateCaller() throws Exception {
+        addTemplate("main.ftl", ""
+                + "<#include 'lib/foo.ftl'>"
+                + "<@m />, <@m2 />");
+        addTemplate("lib/foo.ftl", ""
+                + "<#macro m>${.macroCallerTemplateName}</#macro>"
+                + "<#macro m2><@m3/></#macro>"
+                + "<#macro m3>${.macroCallerTemplateName}</#macro>");
+        assertOutputForNamed("main.ftl",
+                "main.ftl, lib/foo.ftl");
+    }
+
+    @Test
+    public void testImportedTemplateCaller() throws Exception {
+        addTemplate("main.ftl", ""
+                + "<#import 'lib/foo.ftl' as foo>"
+                + "<@foo.m />, <@foo.m2 />");
+        addTemplate("lib/foo.ftl", ""
+                + "<#macro m>${.macroCallerTemplateName}</#macro>"
+                + "<#macro m2><@m3/></#macro>"
+                + "<#macro m3>${.macroCallerTemplateName}</#macro>");
+        assertOutputForNamed("main.ftl",
+                "main.ftl, lib/foo.ftl");
+    }
+    
+    @Test
+    public void testNestedIntoNonUserDirectives() throws Exception {
+        addTemplate("main.ftl", ""
+                + "<#macro m><#list 1..2 as _><#if true>${.macroCallerTemplateName}</#if>;</#list></#macro>"
+                + "<@m/>");
+        assertOutputForNamed("main.ftl", "main.ftl;main.ftl;");
+    }
+
+    @Test
+    public void testMulitpleLevels() throws Exception {
+        addTemplate("main.ftl", ""
+                + "<#include 'inc1.ftl'>"
+                + "<@m1 />");
+        addTemplate("inc1.ftl", ""
+                + "<#include 'inc2.ftl'>"
+                + "<#macro m1>m1: ${.macroCallerTemplateName}; <@m2 /></#macro>");
+        addTemplate("inc2.ftl", ""
+                + "<#macro m2>m2: ${.macroCallerTemplateName};</#macro>");
+        assertOutputForNamed("main.ftl", "m1: main.ftl; m2: inc1.ftl;");
+    }
+
+    @Test
+    public void testUsedInArgument() throws Exception {
+        addTemplate("main.ftl", ""
+                + "<#include 'inc.ftl'>"
+                + "<#macro start>"
+                + "<@m .macroCallerTemplateName />"
+                + "<@m2 />"
+                + "</#macro>"
+                + "<@start />");
+        addTemplate("inc.ftl", ""
+                + "<#macro m x y=.macroCallerTemplateName>"
+                + "x: ${x}; y: ${y}; caller: ${.macroCallerTemplateName};"
+                + "</#macro>"
+                + "<#macro m2><@m .macroCallerTemplateName /></#macro>");
+        
+        assertOutputForNamed("main.ftl", ""
+                + "x: main.ftl; y: main.ftl; caller: main.ftl;"
+                + "x: main.ftl; y: inc.ftl; caller: inc.ftl;");
+        getConfiguration().setIncompatibleImprovements(Configuration.VERSION_2_3_27);
+        assertOutputForNamed("main.ftl", ""
+                + "x: main.ftl; y: main.ftl; caller: main.ftl;"
+                + "x: inc.ftl; y: inc.ftl; caller: inc.ftl;");
+    }
+    
+    @Test
+    public void testReturnsLookupName() throws Exception {
+        addTemplate("main_en.ftl", ""
+                + "<#macro m>${.macroCallerTemplateName}</#macro>"
+                + "<@m />");
+        assertOutputForNamed("main.ftl", "main.ftl"); // Not main_en.ftl
+    }
+    
+    @Test
+    public void testLegacyCall() throws Exception {
+        addTemplate("main_en.ftl", ""
+                + "<#macro m>${.macroCallerTemplateName}</#macro>"
+                + "<#call m>");
+        assertOutputForNamed("main.ftl", "main.ftl"); // Not main_en.ftl
+    }
+    
+}


Mime
View raw message