This is an automated email from the ASF dual-hosted git repository.
ddekany pushed a commit to branch 2.3-gae
in repository https://gitbox.apache.org/repos/asf/freemarker.git
The following commit(s) were added to refs/heads/2.3-gae by this push:
new bcde758 Renamed ?spread_args to ?with_args
bcde758 is described below
commit bcde758d4a00b3a2e2de2deb4d7e6b56cfbaada7
Author: ddekany <[email protected]>
AuthorDate: Fri Nov 1 18:06:18 2019 +0100
Renamed ?spread_args to ?with_args
---
src/main/java/freemarker/core/BuiltIn.java | 6 +-
.../java/freemarker/core/BuiltInsForCallables.java | 42 +--
src/main/java/freemarker/core/Environment.java | 34 +-
src/main/java/freemarker/core/Macro.java | 29 +-
src/manual/en_US/book.xml | 383 ++++++++++-----------
...gsBuiltInTest.java => WithArgsBuiltInTest.java} | 260 +++++++-------
...readArgsExamples.java => WithArgsExamples.java} | 4 +-
...hArgsExamples-usingWithArgsSpecialVariable.ftl} | 2 +-
...sExamples-usingWithArgsSpecialVariable.ftl.out} | 0
9 files changed, 380 insertions(+), 380 deletions(-)
diff --git a/src/main/java/freemarker/core/BuiltIn.java
b/src/main/java/freemarker/core/BuiltIn.java
index f1be894..da9ffc6 100644
--- a/src/main/java/freemarker/core/BuiltIn.java
+++ b/src/main/java/freemarker/core/BuiltIn.java
@@ -87,8 +87,8 @@ abstract class BuiltIn extends Expression implements
Cloneable {
static final int NUMBER_OF_BIS = 287;
static final HashMap<String, BuiltIn> BUILT_INS_BY_NAME = new
HashMap(NUMBER_OF_BIS * 3 / 2 + 1, 1f);
- static final String BI_NAME_SNAKE_CASE_SPREAD_ARGS = "spread_args";
- static final String BI_NAME_CAMEL_CASE_SPREAD_ARGS = "spreadArgs";
+ static final String BI_NAME_SNAKE_CASE_WITH_ARGS = "with_args";
+ static final String BI_NAME_CAMEL_CASE_WITH_ARGS = "withArgs";
static {
// Note that you must update NUMBER_OF_BIS if you add new items here!
@@ -278,7 +278,6 @@ abstract class BuiltIn extends Expression implements
Cloneable {
putBI("sort_by", "sortBy", new sort_byBI());
putBI("sort", new sortBI());
putBI("split", new BuiltInsForStringsBasic.split_BI());
- putBI(BI_NAME_SNAKE_CASE_SPREAD_ARGS, BI_NAME_CAMEL_CASE_SPREAD_ARGS,
new BuiltInsForCallables.spread_argsBI());
putBI("switch", new BuiltInsWithLazyConditionals.switch_BI());
putBI("starts_with", "startsWith", new
BuiltInsForStringsBasic.starts_withBI());
putBI("string", new BuiltInsForMultipleTypes.stringBI());
@@ -301,6 +300,7 @@ abstract class BuiltIn extends Expression implements
Cloneable {
putBI("url_path", "urlPath", new
BuiltInsForStringsEncoding.urlPathBI());
putBI("values", new BuiltInsForHashes.valuesBI());
putBI("web_safe", "webSafe", BUILT_INS_BY_NAME.get("html")); //
deprecated; use ?html instead
+ putBI(BI_NAME_SNAKE_CASE_WITH_ARGS, BI_NAME_CAMEL_CASE_WITH_ARGS, new
BuiltInsForCallables.with_argsBI());
putBI("word_list", "wordList", new
BuiltInsForStringsBasic.word_listBI());
putBI("xhtml", new BuiltInsForStringsEncoding.xhtmlBI());
putBI("xml", new BuiltInsForStringsEncoding.xmlBI());
diff --git a/src/main/java/freemarker/core/BuiltInsForCallables.java
b/src/main/java/freemarker/core/BuiltInsForCallables.java
index 477f103..acc4245 100644
--- a/src/main/java/freemarker/core/BuiltInsForCallables.java
+++ b/src/main/java/freemarker/core/BuiltInsForCallables.java
@@ -40,7 +40,7 @@ import freemarker.template.utility.TemplateModelUtils;
class BuiltInsForCallables {
- static class spread_argsBI extends BuiltIn {
+ static class with_argsBI extends BuiltIn {
TemplateModel _eval(Environment env) throws TemplateException {
TemplateModel model = target.eval(env);
@@ -71,20 +71,20 @@ class BuiltInsForCallables {
checkMethodArgCount(args.size(), 1);
TemplateModel argTM = (TemplateModel) args.get(0);
- Macro.SpreadArgs spreadArgs;
+ Macro.WithArgs withArgs;
if (argTM instanceof TemplateSequenceModel) {
- spreadArgs = new Macro.SpreadArgs((TemplateSequenceModel)
argTM);
+ withArgs = new Macro.WithArgs((TemplateSequenceModel)
argTM);
} else if (argTM instanceof TemplateHashModelEx) {
if (macroOrFunction.isFunction()) {
throw new _TemplateModelException("When applied on a
function, ?", key,
" can't have a hash argument. Use a sequence
argument.");
}
- spreadArgs = new Macro.SpreadArgs((TemplateHashModelEx)
argTM);
+ withArgs = new Macro.WithArgs((TemplateHashModelEx) argTM);
} else {
throw
_MessageUtil.newMethodArgMustBeExtendedHashOrSequnceException("?" + key, 0,
argTM);
}
- return new Macro(macroOrFunction, spreadArgs);
+ return new Macro(macroOrFunction, withArgs);
}
}
@@ -102,16 +102,16 @@ class BuiltInsForCallables {
TemplateModel argTM = (TemplateModel) args.get(0);
if (argTM instanceof TemplateSequenceModel) {
- final TemplateSequenceModel spreadArgs =
(TemplateSequenceModel) argTM;
+ final TemplateSequenceModel withArgs =
(TemplateSequenceModel) argTM;
if (method instanceof TemplateMethodModelEx) {
return new TemplateMethodModelEx() {
public Object exec(List origArgs) throws
TemplateModelException {
- int spreadArgsSize = spreadArgs.size();
+ int withArgsSize = withArgs.size();
List<TemplateModel> newArgs = new
ArrayList<TemplateModel>(
- spreadArgsSize + origArgs.size());
+ withArgsSize + origArgs.size());
- for (int i = 0; i < spreadArgsSize; i++) {
- newArgs.add(spreadArgs.get(i));
+ for (int i = 0; i < withArgsSize; i++) {
+ newArgs.add(withArgs.get(i));
}
newArgs.addAll(origArgs);
@@ -122,12 +122,12 @@ class BuiltInsForCallables {
} else {
return new TemplateMethodModel() {
public Object exec(List origArgs) throws
TemplateModelException {
- int spreadArgsSize = spreadArgs.size();
+ int withArgsSize = withArgs.size();
List<String> newArgs = new ArrayList<String>(
- spreadArgsSize + origArgs.size());
+ withArgsSize + origArgs.size());
- for (int i = 0; i < spreadArgsSize; i++) {
- TemplateModel argVal = spreadArgs.get(i);
+ for (int i = 0; i < withArgsSize; i++) {
+ TemplateModel argVal = withArgs.get(i);
newArgs.add(argValueToString(argVal));
}
@@ -182,18 +182,18 @@ class BuiltInsForCallables {
TemplateModel argTM = (TemplateModel) args.get(0);
if (argTM instanceof TemplateHashModelEx) {
- final TemplateHashModelEx spreadArgs =
(TemplateHashModelEx) argTM;
+ final TemplateHashModelEx withArgs = (TemplateHashModelEx)
argTM;
return new TemplateDirectiveModel() {
public void execute(Environment env, Map origArgs,
TemplateModel[] loopVars,
TemplateDirectiveBody body) throws
TemplateException, IOException {
- int spreadArgsSize = spreadArgs.size();
+ int withArgsSize = withArgs.size();
Map<String, TemplateModel> newArgs = new
LinkedHashMap<String, TemplateModel>(
- (spreadArgsSize + origArgs.size()) * 4 /
3, 1f);
+ (withArgsSize + origArgs.size()) * 4 / 3,
1f);
- TemplateHashModelEx2.KeyValuePairIterator
spreadArgsIter =
-
TemplateModelUtils.getKeyValuePairIterator(spreadArgs);
- while (spreadArgsIter.hasNext()) {
- TemplateHashModelEx2.KeyValuePair spreadArgKVP
= spreadArgsIter.next();
+ TemplateHashModelEx2.KeyValuePairIterator
withArgsIter =
+
TemplateModelUtils.getKeyValuePairIterator(withArgs);
+ while (withArgsIter.hasNext()) {
+ TemplateHashModelEx2.KeyValuePair spreadArgKVP
= withArgsIter.next();
TemplateModel argNameTM =
spreadArgKVP.getKey();
if (!(argNameTM instanceof
TemplateScalarModel)) {
diff --git a/src/main/java/freemarker/core/Environment.java
b/src/main/java/freemarker/core/Environment.java
index 7338895..8ca29b1 100644
--- a/src/main/java/freemarker/core/Environment.java
+++ b/src/main/java/freemarker/core/Environment.java
@@ -903,16 +903,16 @@ public final class Environment extends Configurable {
SimpleSequence positionalCatchAllParamValue = null;
int nextPositionalArgToAssignIdx = 0;
- // Used for ?spread_args(...):
- Macro.SpreadArgs spreadArgs = macro.getSpreadArgs();
- if (spreadArgs != null) {
- TemplateHashModelEx byNameSpreadArgs = spreadArgs.getByName();
- TemplateSequenceModel byPositionSpreadArgs =
spreadArgs.getByPosition();
-
- if (byNameSpreadArgs != null) {
- new HashMap<String, TemplateModel>(byNameSpreadArgs.size() * 4
/ 3, 1f);
+ // Used for ?with_args(...):
+ Macro.WithArgs withArgs = macro.getWithArgs();
+ if (withArgs != null) {
+ TemplateHashModelEx byNameWithArgs = withArgs.getByName();
+ TemplateSequenceModel byPositionWithArgs =
withArgs.getByPosition();
+
+ if (byNameWithArgs != null) {
+ new HashMap<String, TemplateModel>(byNameWithArgs.size() * 4 /
3, 1f);
TemplateHashModelEx2.KeyValuePairIterator
namedParamValueOverridesIter =
-
TemplateModelUtils.getKeyValuePairIterator(byNameSpreadArgs);
+
TemplateModelUtils.getKeyValuePairIterator(byNameWithArgs);
while (namedParamValueOverridesIter.hasNext()) {
TemplateHashModelEx2.KeyValuePair defaultArgHashKVP =
namedParamValueOverridesIter.next();
@@ -943,14 +943,14 @@ public final class Environment extends Configurable {
throw newUndeclaredParamNameException(macro, argName);
}
}
- } else if (byPositionSpreadArgs != null) {
+ } else if (byPositionWithArgs != null) {
String[] argNames = macro.getArgumentNamesInternal();
- final int argsCnt = byPositionSpreadArgs.size();
+ final int argsCnt = byPositionWithArgs.size();
if (argNames.length < argsCnt && catchAllParamName == null) {
throw newTooManyArgumentsException(macro, argNames,
argsCnt);
}
for (int i = 0; i < argsCnt; i++) {
- TemplateModel argValue = byPositionSpreadArgs.get(i);
+ TemplateModel argValue = byPositionWithArgs.get(i);
try {
if (nextPositionalArgToAssignIdx < argNames.length) {
String argName =
argNames[nextPositionalArgToAssignIdx++];
@@ -970,7 +970,7 @@ public final class Environment extends Configurable {
if (namedArgs != null) {
if (catchAllParamName != null && namedCatchAllParamValue == null
&& positionalCatchAllParamValue == null) {
- if (namedArgs.isEmpty() && spreadArgs != null &&
spreadArgs.getByPosition() != null) {
+ if (namedArgs.isEmpty() && withArgs != null &&
withArgs.getByPosition() != null) {
positionalCatchAllParamValue =
initPositionalCatchAllParameter(macroCtx, catchAllParamName);
} else {
namedCatchAllParamValue =
initNamedCatchAllParameter(macroCtx, catchAllParamName);
@@ -998,7 +998,7 @@ public final class Environment extends Configurable {
}
} else if (positionalArgs != null) {
if (catchAllParamName != null && positionalCatchAllParamValue ==
null && namedCatchAllParamValue == null) {
- if (positionalArgs.isEmpty() && spreadArgs != null &&
spreadArgs.getByName() != null) {
+ if (positionalArgs.isEmpty() && withArgs != null &&
withArgs.getByName() != null) {
namedCatchAllParamValue =
initNamedCatchAllParameter(macroCtx, catchAllParamName);
} else {
positionalCatchAllParamValue =
initPositionalCatchAllParameter(macroCtx, catchAllParamName);
@@ -1007,12 +1007,12 @@ public final class Environment extends Configurable {
String[] argNames = macro.getArgumentNamesInternal();
final int argsCnt = positionalArgs.size();
- final int argsWithSpreadArgsCnt = argsCnt +
nextPositionalArgToAssignIdx;
- if (argNames.length < argsWithSpreadArgsCnt &&
positionalCatchAllParamValue == null) {
+ final int argsWithWithArgsCnt = argsCnt +
nextPositionalArgToAssignIdx;
+ if (argNames.length < argsWithWithArgsCnt &&
positionalCatchAllParamValue == null) {
if (namedCatchAllParamValue != null) {
throw
newBothNamedAndPositionalCatchAllParamsException(macro);
} else {
- throw newTooManyArgumentsException(macro, argNames,
argsWithSpreadArgsCnt);
+ throw newTooManyArgumentsException(macro, argNames,
argsWithWithArgsCnt);
}
}
for (int srcPosArgIdx = 0; srcPosArgIdx < argsCnt; srcPosArgIdx++)
{
diff --git a/src/main/java/freemarker/core/Macro.java
b/src/main/java/freemarker/core/Macro.java
index 17c63f6..d1a81d0 100644
--- a/src/main/java/freemarker/core/Macro.java
+++ b/src/main/java/freemarker/core/Macro.java
@@ -58,7 +58,7 @@ public final class Macro extends TemplateElement implements
TemplateModel {
private final String name;
private final String[] paramNames;
private final Map<String, Expression> paramNamesWithDefault;
- private final SpreadArgs spreadArgs;
+ private final WithArgs withArgs;
private boolean requireArgsSpecialVariable;
private final String catchAllParamName;
private final boolean function;
@@ -78,7 +78,7 @@ public final class Macro extends TemplateElement implements
TemplateModel {
this.paramNamesWithDefault = paramNamesWithDefault;
this.paramNames = paramNamesWithDefault.keySet().toArray(new
String[0]);
this.catchAllParamName = catchAllParamName;
- this.spreadArgs = null;
+ this.withArgs = null;
this.requireArgsSpecialVariable = requireArgsSpecialVariable;
this.function = function;
this.setChildren(children);
@@ -87,20 +87,20 @@ public final class Macro extends TemplateElement implements
TemplateModel {
}
/**
- * Copy-constructor with replacing {@link #spreadArgs} (with the quirk
that the parent of the
+ * Copy-constructor with replacing {@link #withArgs} (with the quirk that
the parent of the
* child AST elements will stay the copied macro).
*
- * @param spreadArgs Usually {@code null}; used by {@link
BuiltInsForCallables.spread_argsBI} to
+ * @param withArgs Usually {@code null}; used by {@link
BuiltInsForCallables.with_argsBI} to
* set arbitrary default value to parameters. Note that the defaults
aren't
* {@link Expression}-s, but {@link TemplateModel}-s.
*/
- Macro(Macro that, SpreadArgs spreadArgs) {
+ Macro(Macro that, WithArgs withArgs) {
// Attention! Keep this constructor in sync with the other constructor!
this.name = that.name;
this.paramNamesWithDefault = that.paramNamesWithDefault;
this.paramNames = that.paramNames;
this.catchAllParamName = that.catchAllParamName;
- this.spreadArgs = spreadArgs; // Using the argument value here
+ this.withArgs = withArgs; // Using the argument value here
this.requireArgsSpecialVariable = that.requireArgsSpecialVariable;
this.function = that.function;
this.namespaceLookupKey = that.namespaceLookupKey;
@@ -132,8 +132,9 @@ public final class Macro extends TemplateElement implements
TemplateModel {
return name;
}
- public SpreadArgs getSpreadArgs() {
- return spreadArgs;
+ /** The arguments added via {@code ?with_args}; maybe {@code null}. */
+ public WithArgs getWithArgs() {
+ return withArgs;
}
public Object getNamespaceLookupKey() {
@@ -151,12 +152,12 @@ public final class Macro extends TemplateElement
implements TemplateModel {
StringBuilder sb = new StringBuilder();
if (canonical) sb.append('<');
sb.append(getNodeTypeSymbol());
- if (spreadArgs != null) {
+ if (withArgs != null) {
// As such a node won't be part of a template, this is probably
never needed.
sb.append('?')
.append(getTemplate().getActualNamingConvention() ==
Configuration.CAMEL_CASE_NAMING_CONVENTION
- ? BuiltIn.BI_NAME_CAMEL_CASE_SPREAD_ARGS
- : BuiltIn.BI_NAME_SNAKE_CASE_SPREAD_ARGS)
+ ? BuiltIn.BI_NAME_CAMEL_CASE_WITH_ARGS
+ : BuiltIn.BI_NAME_SNAKE_CASE_WITH_ARGS)
.append("(...)");
}
sb.append(' ');
@@ -476,16 +477,16 @@ public final class Macro extends TemplateElement
implements TemplateModel {
return true;
}
- static final class SpreadArgs {
+ static final class WithArgs {
private final TemplateHashModelEx byName;
private final TemplateSequenceModel byPosition;
- SpreadArgs(TemplateHashModelEx byName) {
+ WithArgs(TemplateHashModelEx byName) {
this.byName = byName;
this.byPosition = null;
}
- SpreadArgs(TemplateSequenceModel byPosition) {
+ WithArgs(TemplateSequenceModel byPosition) {
this.byName = null;
this.byPosition = byPosition;
}
diff --git a/src/manual/en_US/book.xml b/src/manual/en_US/book.xml
index f712b44..afe59d7 100644
--- a/src/manual/en_US/book.xml
+++ b/src/manual/en_US/book.xml
@@ -13060,11 +13060,6 @@ grant codeBase "file:/path/to/freemarker.jar"
</listitem>
<listitem>
- <para><link
- linkend="ref_builtin_spread_args">spread_args</link></para>
- </listitem>
-
- <listitem>
<para><link linkend="ref_builtin_rtf">rtf</link></para>
</listitem>
@@ -13183,6 +13178,11 @@ grant codeBase "file:/path/to/freemarker.jar"
<listitem>
<para><link
+ linkend="ref_builtin_with_args">with_args</link></para>
+ </listitem>
+
+ <listitem>
+ <para><link
linkend="ref_builtin_word_list">word_list</link></para>
</listitem>
@@ -19132,189 +19132,6 @@ Filer for positives:
a <literal>long</literal>.</para>
</section>
- <section xml:id="ref_builtin_spread_args">
- <title>spread_args</title>
-
- <note>
- <para>This built-in is available since 2.3.30</para>
- </note>
-
- <para>The goal of this built-in is to add parameters
- <emphasis>dynamically</emphasis> to the call of a directive (like a
- macro), function or method. Dynamically means that parameters are
- added based on a hash value (like <literal>{'a': 1, 'b': 2, 'c':
- 3}</literal> or a Java <literal>Map</literal>), or a sequence value
- (like <literal>[1, 2, 3]</literal> or a Java
- <literal>List</literal>), whose the actual content is might only
- known at the moment when the call happens.</para>
-
- <para>For example, we have this macro <literal>m</literal>:</para>
-
- <programlisting role="template"><#macro m a b c>a=${a},
b=${b}, c=${c}</#macro></programlisting>
-
- <para>Normally you call it like:</para>
-
- <programlisting role="template"><@m a=1 b=2 c=3
/></programlisting>
-
- <para>Below call does the same, assuming <literal>dynArgs</literal>
- is the hash <literal>{'a': 1, 'b': 2, 'c': 3}</literal>:</para>
-
- <programlisting role="template"><@m?spread_args(dynArgs)
/></programlisting>
-
- <programlisting role="output">a=1, b=1, c=1</programlisting>
-
- <para>Below call also does the same, but combines dynamic arguments
- from <literal>dynArgsAB</literal>, assumed to be <literal>{'a': 1,
- 'b': 2}</literal>, and argument <literal>c</literal> specified
- directly:</para>
-
- <programlisting role="template"><@m?spread_args(dynArgsAB) c=3
/></programlisting>
-
- <programlisting role="output">a=1, b=1, c=1</programlisting>
-
- <para>To understand why this works, you need to realize that macros,
- custom directives, functions, and methods in FreeMarker are just
- values, just like numbers, strings, etc. <literal><#macro m
- <replaceable>...</replaceable>></literal> just creates a value
- that's a macro (as opposed to a number, or string, etc.), and then
- assigns it to variable <literal>m</literal>. Thus,
- <literal>m</literal> in itself is a valid expression, which
- evaluates to the macro (but it doesn't <emphasis>call</emphasis> the
- macro). <literal><@m <replaceable>...</replaceable>
- /></literal> evaluates the expression <literal>m</literal> (and
- you can use arbitrarily complex expressions there too, like
- <literal>m?spread_args(<replaceable>...</replaceable>)</literal>),
- and then <emphasis>calls</emphasis> the resulting macro.
- <literal>m?spread_args(<replaceable>dynArgs</replaceable>)</literal>
- returns a macro that's very similar to the original macro (that's
- stored in <literal>m</literal>), but its arguments default to the
- values specified in
- <literal><replaceable>dynArgs</replaceable></literal>. So the result
- of <literal>m?spread_args({'b': 22, 'c': 33})</literal> is similar
- to a modified macro that was created as <literal><#macro
- <replaceable>unspefiedName</replaceable> a b=22 c=33></literal>.
- With an example:</para>
-
- <programlisting role="template"><#assign mWithDefs =
m?spread_args({'b': 22, 'c': 33})>
-<@myWithDefs a=1 c='overridden'/></programlisting>
-
- <programlisting role="output">a=1, b=22,
c=overridden</programlisting>
-
- <para>Above we have created a new macro based on the value of
- <literal>m</literal>, stored it in variable
- <literal>mWithDefs</literal>, and then later we called it with
- <literal><@myWithDefs <replaceable>...</replaceable>
- /></literal>.</para>
-
- <para><literal>spread_args</literal> can also be applied on
- functions (crated with <literal><#function
- <replaceable>...</replaceable>></literal>) and Java methods
- (usually get from the data-model, like
- <literal>myObject.myMethod</literal>). But because functions and
- methods can only be called with positional arguments (like
- <literal>f(1, 2, 3)</literal>, and <emphasis>not</emphasis> as
- <literal>f(a=1, b=2, c=3)</literal>), the argument to
- <literal>spread_args</literal> must be a sequence instead of a hash.
- Other than that, the same tricks work as with macros:</para>
-
- <programlisting role="template"><#function f(a, b,
c)><#return "a=${a}, b=${b}, c=${c}"></#function>
-<#assign dynArgs=[1, 2, 3]>
-
-${f(1, 2, 3)}
-Same as:
-${f?spread_args(dynArgs)()}
-or as:
-${f?spread_args([1, 2])(3)}
-or as:
-${f?spread_args([1])(2, 3)}
-
-<#assign fWithOneAsFirstArg = f?spread_args([1])>
-${fWithOneAsFirstArg(2, 3)} <#-- same as f(1, 2, 3) --></programlisting>
-
- <para>Note the double application of
- <literal>(<replaceable>...</replaceable>)</literal> above, like in
-
<literal>f?spread_args(<replaceable>dynArgs</replaceable>)()</literal>.
- That's because
- <literal>f?spread_args(<replaceable>dynArgs</replaceable>)</literal>
- just returns a new function (which is just a value), but doesn't
- call it. So if you want to call that new function immediately (as
- opposed to assigning it to a variable for example), you need the
- second <literal>()</literal>.</para>
-
- <para>Because macro calls support both named and positional
- arguments, the <literal>spread_args</literal> argument can be a
- sequence for macros as well (though using a hash is usually a better
- practice):</para>
-
- <programlisting role="template"><#macro m a b c>a=${a},
b=${b}, c=${c}</#macro>
-
-<#-- Called with named parameters: -->
-<@m a=1 b=2 c=3 />
-Same as:
-<#-- Called with positional parameters: -->
-<@m 1 2 3 />
-Same as:
-<@m?spread_args([1, 2, 3]) />
-Same as:
-<#-- Sequence spread_args with positional c parameter: -->
-<@m?spread_args([1, 2]) 3 />
-Same as:
-<#-- Sequence spread_args with named c parameter: -->
-<@m?spread_args([1, 2]) c=3 /></programlisting>
-
- <para>To summarize, depending on the type of the value
- <literal>spread_args</literal> is applied on, the type of argument
- to <literal>spread_args</literal> can be:</para>
-
- <itemizedlist>
- <listitem>
- <para>Function or method: sequence. Note that WRONG
- <literal>f?spread_args(1, 2)</literal> is WRONG, the correct
- form is <literal>f?spread_args([1, 2])</literal>.</para>
- </listitem>
-
- <listitem>
- <para>Macro: hash or sequence</para>
- </listitem>
-
- <listitem>
- <para>Directive (user defined): hash</para>
- </listitem>
- </itemizedlist>
-
- <para>The return type of <literal>spread_args</literal> is the same
- as the type of value it was applied on, like if it's applied on a
- method (like
- <literal>myObj.myMethod?spread_args(dynArgs)</literal>), then it
- returns a method.</para>
-
- <para>Note that it's not possible to apply
- <literal>spread_args</literal> on built-in directives, like
- <literal><#if <replaceable>...</replaceable>></literal>,
- <literal><#list <replaceable>...</replaceable>></literal>,
- etc., because they aren't available as values.</para>
-
- <para>This built-in is often used together with the <link
- linkend="specvar.args"><literal>.args</literal> special
- variable</link>. For example:</para>
-
- <programlisting role="template"><#macro m1 a b c>
- m1 does things with ${a}, ${b}, ${c}
-</#macro>
-
-<#macro m2 a b c>
- m2 does things with ${a}, ${b}, ${c}
- Delegate to m1:
- <@m1?spread_args(.args) />
-</#macro>
-
-<@m2 a=1 b=2 c=3 /></programlisting>
-
- <programlisting role="output"> m2 does things with 1, 2, 3
- Delegate to m1:
- m1 does things with 1, 2, 3</programlisting>
- </section>
-
<section xml:id="ref_builtin_eval">
<title>eval</title>
@@ -19871,6 +19688,188 @@ Again:
chained together.</para>
</simplesect>
</section>
+
+ <section xml:id="ref_builtin_with_args">
+ <title>with_args</title>
+
+ <note>
+ <para>This built-in is available since 2.3.30</para>
+ </note>
+
+ <para>The goal of this built-in is to add parameters
+ <emphasis>dynamically</emphasis> to the call of a directive (like a
+ macro), function or method. Dynamically means that parameters are
+ added based on a hash value (like <literal>{'a': 1, 'b': 2, 'c':
+ 3}</literal> or a Java <literal>Map</literal>), or a sequence value
+ (like <literal>[1, 2, 3]</literal> or a Java
+ <literal>List</literal>), whose actual content is might only known
+ at the moment when the call happens.</para>
+
+ <para>For example, we have this macro <literal>m</literal>:</para>
+
+ <programlisting role="template"><#macro m a b c>a=${a},
b=${b}, c=${c}</#macro></programlisting>
+
+ <para>Normally you call it like:</para>
+
+ <programlisting role="template"><@m a=1 b=2 c=3
/></programlisting>
+
+ <para>Below call does the same, assuming <literal>dynArgs</literal>
+ is the hash <literal>{'a': 1, 'b': 2, 'c': 3}</literal>:</para>
+
+ <programlisting role="template"><@m?with_args(dynArgs)
/></programlisting>
+
+ <programlisting role="output">a=1, b=1, c=1</programlisting>
+
+ <para>Below call also does the same, but combines dynamic arguments
+ from <literal>dynArgsAB</literal>, assumed to be <literal>{'a': 1,
+ 'b': 2}</literal>, and argument <literal>c</literal> specified
+ directly:</para>
+
+ <programlisting role="template"><@m?with_args(dynArgsAB) c=3
/></programlisting>
+
+ <programlisting role="output">a=1, b=1, c=1</programlisting>
+
+ <para>To understand why this works, you need to realize that macros,
+ custom directives, functions, and methods in FreeMarker are just
+ values, just like numbers, strings, etc. <literal><#macro m
+ <replaceable>...</replaceable>></literal> just creates a value
+ that's a macro (as opposed to a number, or string, etc.), and then
+ assigns it to variable <literal>m</literal>. Thus,
+ <literal>m</literal> in itself is a valid expression, which
+ evaluates to the macro (but it doesn't <emphasis>call</emphasis> the
+ macro). <literal><@m <replaceable>...</replaceable>
+ /></literal> evaluates the expression <literal>m</literal> (and
+ you can use arbitrarily complex expressions there too, like
+ <literal>m?with_args(<replaceable>...</replaceable>)</literal>), and
+ then <emphasis>calls</emphasis> the resulting macro.
+ <literal>m?with_args(<replaceable>dynArgs</replaceable>)</literal>
+ returns a macro that's very similar to the original macro (that's
+ stored in <literal>m</literal>), but its arguments
+ <emphasis>default</emphasis> to the values specified in
+ <literal><replaceable>dynArgs</replaceable></literal>. So the result
+ of <literal>m?with_args({'b': 22, 'c': 33})</literal> is similar to
+ a modified macro that was created as <literal><#macro
+ <replaceable>unspefiedName</replaceable> a b=22 c=33></literal>.
+ With an example:</para>
+
+ <programlisting role="template"><#assign mWithDefs =
m?with_args({'b': 22, 'c': 33})>
+<@myWithDefs a=1 c='overridden'/></programlisting>
+
+ <programlisting role="output">a=1, b=22,
c=overridden</programlisting>
+
+ <para>Above we have created a new macro based on the value of
+ <literal>m</literal>, stored it in variable
+ <literal>mWithDefs</literal>, and then later we called it with
+ <literal><@myWithDefs <replaceable>...</replaceable>
+ /></literal>.</para>
+
+ <para><literal>with_args</literal> can also be applied on functions
+ (crated with <literal><#function
+ <replaceable>...</replaceable>></literal>) and Java methods
+ (usually get from the data-model, like
+ <literal>myObject.myMethod</literal>). But because functions and
+ methods can only be called with positional arguments (like
+ <literal>f(1, 2, 3)</literal>, and <emphasis>not</emphasis> as
+ <literal>f(a=1, b=2, c=3)</literal>), the argument to
+ <literal>with_args</literal> must be a sequence instead of a hash.
+ Other than that, the same tricks work as with macros:</para>
+
+ <programlisting role="template"><#function f(a, b,
c)><#return "a=${a}, b=${b}, c=${c}"></#function>
+<#assign dynArgs=[1, 2, 3]>
+
+${f(1, 2, 3)}
+Same as:
+${f?with_args(dynArgs)()}
+or as:
+${f?with_args([1, 2])(3)}
+or as:
+${f?with_args([1])(2, 3)}
+
+<#assign fWithOneAsFirstArg = f?with_args([1])>
+${fWithOneAsFirstArg(2, 3)} <#-- same as f(1, 2, 3) --></programlisting>
+
+ <para>Note the double application of
+ <literal>(<replaceable>...</replaceable>)</literal> above, like in
+ <literal>f?with_args(<replaceable>dynArgs</replaceable>)()</literal>.
+ That's because
+ <literal>f?with_args(<replaceable>dynArgs</replaceable>)</literal>
+ just returns a new function (which is just a value), but doesn't
+ call it. So if you want to call that new function immediately (as
+ opposed to assigning it to a variable for example), you need the
+ second <literal>()</literal>.</para>
+
+ <para>Because macro calls support both named and positional
+ arguments, the <literal>with_args</literal> argument can be a
+ sequence for macros as well (though using a hash is usually a better
+ practice):</para>
+
+ <programlisting role="template"><#macro m a b c>a=${a},
b=${b}, c=${c}</#macro>
+
+<#-- Called with named parameters: -->
+<@m a=1 b=2 c=3 />
+Same as:
+<#-- Called with positional parameters: -->
+<@m 1 2 3 />
+Same as:
+<@m?with_args([1, 2, 3]) />
+Same as:
+<#-- Sequence with_args with positional c parameter: -->
+<@m?with_args([1, 2]) 3 />
+Same as:
+<#-- Sequence with_args with named c parameter: -->
+<@m?with_args([1, 2]) c=3 /></programlisting>
+
+ <para>To summarize, depending on the type of the value
+ <literal>with_args</literal> is applied on, the type of argument to
+ <literal>with_args</literal> can be:</para>
+
+ <itemizedlist>
+ <listitem>
+ <para>Function or method: sequence. Note that WRONG
+ <literal>f?with_args(1, 2)</literal> is WRONG, the correct form
+ is <literal>f?with_args([1, 2])</literal>.</para>
+ </listitem>
+
+ <listitem>
+ <para>Macro: hash or sequence</para>
+ </listitem>
+
+ <listitem>
+ <para>Directive (user defined): hash</para>
+ </listitem>
+ </itemizedlist>
+
+ <para>The return type of <literal>with_args</literal> is the same as
+ the type of value it was applied on, like if it's applied on a
+ method (like <literal>myObj.myMethod?with_args(dynArgs)</literal>),
+ then it returns a method.</para>
+
+ <para>Note that it's not possible to apply
+ <literal>with_args</literal> on built-in directives, like
+ <literal><#if <replaceable>...</replaceable>></literal>,
+ <literal><#list <replaceable>...</replaceable>></literal>,
+ etc., because they aren't available as values.</para>
+
+ <para>This built-in is often used together with the <link
+ linkend="specvar.args"><literal>.args</literal> special
+ variable</link>. For example:</para>
+
+ <programlisting role="template"><#macro m1 a b c>
+ m1 does things with ${a}, ${b}, ${c}
+</#macro>
+
+<#macro m2 a b c>
+ m2 does things with ${a}, ${b}, ${c}
+ Delegate to m1:
+ <@m1?with_args(.args) />
+</#macro>
+
+<@m2 a=1 b=2 c=3 /></programlisting>
+
+ <programlisting role="output"> m2 does things with 1, 2, 3
+ Delegate to m1:
+ m1 does things with 1, 2, 3</programlisting>
+ </section>
</section>
</chapter>
@@ -24694,7 +24693,7 @@ There was no specific handler for node y
directives, it returns all arguments of the current invocation of
the macro or function. This allows processing all the arguments in
an uniform way (like pass them to the <link
- linkend="ref_builtin_spread_args"><literal>spread_args</literal>
+ linkend="ref_builtin_with_args"><literal>with_args</literal>
built-in</link>). Further details:</para>
<itemizedlist>
@@ -28972,11 +28971,11 @@ TemplateModel x = env.getVariable("x"); // get
variable x</programlisting>
<para><link
xlink:href="https://issues.apache.org/jira/browse/FREEMARKER-107">FREEMARKER-107</link>:
Added
-
<literal>?<replaceable>spread_args</replaceable>(dynamicArguments)</literal>
+
<literal>?<replaceable>with_args</replaceable>(dynamicArguments)</literal>
to add parameters dynamically to directive (like macro),
function and method calls. Actually, this built-in returns
directive or macro or function that has different parameter
- defaults. <link linkend="ref_builtin_spread_args">See more
+ defaults. <link linkend="ref_builtin_with_args">See more
here...</link></para>
</listitem>
@@ -28989,7 +28988,7 @@ TemplateModel x = env.getVariable("x"); // get
variable x</programlisting>
which contains all the arguments. This is useful for operations
that act on all the arguments uniformly, like for example to
pass the arguments to <link
-
linkend="ref_builtin_spread_args"><literal>?spread_args(<replaceable>...</replaceable>)</literal></link>.</para>
+
linkend="ref_builtin_with_args"><literal>?with_args(<replaceable>...</replaceable>)</literal></link>.</para>
</listitem>
<listitem>
diff --git a/src/test/java/freemarker/core/SpreadArgsBuiltInTest.java
b/src/test/java/freemarker/core/WithArgsBuiltInTest.java
similarity index 51%
rename from src/test/java/freemarker/core/SpreadArgsBuiltInTest.java
rename to src/test/java/freemarker/core/WithArgsBuiltInTest.java
index 1ecc81c..3712509 100644
--- a/src/test/java/freemarker/core/SpreadArgsBuiltInTest.java
+++ b/src/test/java/freemarker/core/WithArgsBuiltInTest.java
@@ -40,7 +40,7 @@ import freemarker.template.TemplateModel;
import freemarker.template.TemplateModelException;
import freemarker.test.TemplateTest;
-public class SpreadArgsBuiltInTest extends TemplateTest {
+public class WithArgsBuiltInTest extends TemplateTest {
private static final String PRINT_O = "o=<#if o?isSequence>[${o?join(',
')}]" +
"<#else>{<#list o as k,v>${k}=${v!'null'}<#sep>, </#list>}" +
@@ -86,140 +86,140 @@ public class SpreadArgsBuiltInTest extends TemplateTest {
}
@Test
- public void testMacroWithNamedSpreadArgs() throws Exception {
+ public void testMacroWithNamedWithArgs() throws Exception {
assertOutput("<@m b=2 a=1 />", "a=1; b=2; c=d3");
- assertOutput("<@m?spreadArgs({'b': 2, 'a': 1}) />", "a=1; b=2; c=d3");
- assertOutput("<@m?spreadArgs({'b': 2, 'a': 1}) a=11 />", "a=11; b=2;
c=d3");
- assertOutput("<@m?spreadArgs({'b': 2, 'a': 1}) a=11 b=22 />", "a=11;
b=22; c=d3");
- assertOutput("<@m?spreadArgs({'b': 2, 'c': 3}) a=1 />", "a=1; b=2;
c=3");
- assertOutput("<@m?spreadArgs({}) b=2 c=3 a=1 />", "a=1; b=2; c=3");
+ assertOutput("<@m?withArgs({'b': 2, 'a': 1}) />", "a=1; b=2; c=d3");
+ assertOutput("<@m?withArgs({'b': 2, 'a': 1}) a=11 />", "a=11; b=2;
c=d3");
+ assertOutput("<@m?withArgs({'b': 2, 'a': 1}) a=11 b=22 />", "a=11;
b=22; c=d3");
+ assertOutput("<@m?withArgs({'b': 2, 'c': 3}) a=1 />", "a=1; b=2; c=3");
+ assertOutput("<@m?withArgs({}) b=2 c=3 a=1 />", "a=1; b=2; c=3");
assertOutput("<@mCA a=1 b=2 />", "a=1; b=2; o={}");
- assertOutput("<@mCA?spreadArgs({'a': 1, 'b': 2}) />", "a=1; b=2;
o={}");
- assertOutput("<@mCA?spreadArgs({'a': 1}) b=2 />", "a=1; b=2; o={}");
- assertOutput("<@mCA?spreadArgs({}) a=1 b=2 />", "a=1; b=2; o={}");
- assertOutput("<@mCA?spreadArgs({'a': 1, 'b': 2, 'c': 3}) />", "a=1;
b=2; o={c=3}");
- assertOutput("<@mCA?spreadArgs({'a': 1, 'b': 2}) c=3 />", "a=1; b=2;
o={c=3}");
- assertOutput("<@mCA?spreadArgs({'a': 1}) b=2 c=3 />", "a=1; b=2;
o={c=3}");
- assertOutput("<@mCA?spreadArgs({}) a=1 b=2 c=3 />", "a=1; b=2;
o={c=3}");
+ assertOutput("<@mCA?withArgs({'a': 1, 'b': 2}) />", "a=1; b=2; o={}");
+ assertOutput("<@mCA?withArgs({'a': 1}) b=2 />", "a=1; b=2; o={}");
+ assertOutput("<@mCA?withArgs({}) a=1 b=2 />", "a=1; b=2; o={}");
+ assertOutput("<@mCA?withArgs({'a': 1, 'b': 2, 'c': 3}) />", "a=1; b=2;
o={c=3}");
+ assertOutput("<@mCA?withArgs({'a': 1, 'b': 2}) c=3 />", "a=1; b=2;
o={c=3}");
+ assertOutput("<@mCA?withArgs({'a': 1}) b=2 c=3 />", "a=1; b=2;
o={c=3}");
+ assertOutput("<@mCA?withArgs({}) a=1 b=2 c=3 />", "a=1; b=2; o={c=3}");
assertOutput("<@mCA a=1 b=2 c=3 />", "a=1; b=2; o={c=3}");
assertOutput("<@mCA a=1 b=2 c=3 d=4 />", "a=1; b=2; o={c=3, d=4}");
- assertOutput("<@mCA?spreadArgs({'a': 1, 'b': 2, 'c': 3, 'd': 4}) />",
"a=1; b=2; o={c=3, d=4}");
- assertOutput("<@mCA?spreadArgs({'a': 1, 'b': 2, 'c': 3, 'd': 4}) b=22
/>", "a=1; b=22; o={c=3, d=4}");
- assertOutput("<@mCA?spreadArgs({'a': 1, 'b': 2, 'c': 3, 'd': 4}) b=22
e=5 />", "a=1; b=22; o={c=3, d=4, e=5}");
- assertOutput("<@mCA?spreadArgs({'a': 1, 'b': 2, 'c': 3, 'd': 4}) 11 22
/>", "a=11; b=22; o={c=3, d=4}");
- assertOutput("<@mCA?spreadArgs({'a': 1, 'b': 2}) 11 22 33 />", "a=11;
b=22; o=[33]");
- assertErrorContains("<@mCA?spreadArgs({'a': 1, 'b': 2, 'c': 3}) 11 22
33 />",
+ assertOutput("<@mCA?withArgs({'a': 1, 'b': 2, 'c': 3, 'd': 4}) />",
"a=1; b=2; o={c=3, d=4}");
+ assertOutput("<@mCA?withArgs({'a': 1, 'b': 2, 'c': 3, 'd': 4}) b=22
/>", "a=1; b=22; o={c=3, d=4}");
+ assertOutput("<@mCA?withArgs({'a': 1, 'b': 2, 'c': 3, 'd': 4}) b=22
e=5 />", "a=1; b=22; o={c=3, d=4, e=5}");
+ assertOutput("<@mCA?withArgs({'a': 1, 'b': 2, 'c': 3, 'd': 4}) 11 22
/>", "a=11; b=22; o={c=3, d=4}");
+ assertOutput("<@mCA?withArgs({'a': 1, 'b': 2}) 11 22 33 />", "a=11;
b=22; o=[33]");
+ assertErrorContains("<@mCA?withArgs({'a': 1, 'b': 2, 'c': 3}) 11 22 33
/>",
"both named and positional", "catch-all");
- assertOutput("<@mCAO?spreadArgs({'a': 1, 'b': 2}) />", "o={a=1, b=2}");
- assertOutput("<@mCAO?spreadArgs({'a': 1}) b=2 />", "o={a=1, b=2}");
- assertOutput("<@mCAO?spreadArgs({}) a=1 b=2 />", "o={a=1, b=2}");
+ assertOutput("<@mCAO?withArgs({'a': 1, 'b': 2}) />", "o={a=1, b=2}");
+ assertOutput("<@mCAO?withArgs({'a': 1}) b=2 />", "o={a=1, b=2}");
+ assertOutput("<@mCAO?withArgs({}) a=1 b=2 />", "o={a=1, b=2}");
assertOutput("<@mCAO a=1 b=2 />", "o={a=1, b=2}");
assertOutput("<@mCAO />", "o=[]");
- assertOutput("<@mCAO?spreadArgs({}) />", "o={}");
+ assertOutput("<@mCAO?withArgs({}) />", "o={}");
assertOutput("<@m b=2 a=1 c=null />", "a=1; b=2; c=d3");
Map<String, Integer> cNull = new HashMap<String, Integer>();
cNull.put("c", null);
addToDataModel("cNull", cNull);
- assertOutput("<@m?spreadArgs(cNull) b=2 a=1 />", "a=1; b=2; c=d3");
+ assertOutput("<@m?withArgs(cNull) b=2 a=1 />", "a=1; b=2; c=d3");
}
@Test
- public void testNullsWithMacroWithNamedSpreadArgs() throws Exception {
- // Null-s in ?spreadArgs should behave similarly as if they were given
directly as argument.
+ public void testNullsWithMacroWithNamedWithArgs() throws Exception {
+ // Null-s in ?withArgs should behave similarly as if they were given
directly as argument.
assertOutput("<@mCAO a=null b=null />", "o={a=null, b=null}");
Map<String, Integer> aNullBNull = new LinkedHashMap<String, Integer>();
aNullBNull.put("a", null);
aNullBNull.put("b", null);
addToDataModel("aNullBNull", aNullBNull);
- assertOutput("<@mCAO?spreadArgs(aNullBNull) />", "o={a=null, b=null}");
+ assertOutput("<@mCAO?withArgs(aNullBNull) />", "o={a=null, b=null}");
- assertOutput("<@m?spreadArgs({'a': 11, 'b': 22, 'c': 33}) a=111 b=222
c=null />", "a=111; b=222; c=d3");
- assertErrorContains("<@m?spreadArgs({'a': 11, 'b': 22, 'c': 33}) a=111
b=null c=333 />", "required", "\"b\"");
- assertOutput("<@mCAO?spreadArgs({'a': 1, 'b': 2}) a=null b=22 c=33
/>", "o={a=null, b=22, c=33}");
+ assertOutput("<@m?withArgs({'a': 11, 'b': 22, 'c': 33}) a=111 b=222
c=null />", "a=111; b=222; c=d3");
+ assertErrorContains("<@m?withArgs({'a': 11, 'b': 22, 'c': 33}) a=111
b=null c=333 />", "required", "\"b\"");
+ assertOutput("<@mCAO?withArgs({'a': 1, 'b': 2}) a=null b=22 c=33 />",
"o={a=null, b=22, c=33}");
}
@Test
- public void testMacroWithPositionalSpreadArgs() throws Exception {
+ public void testMacroWithPositionalWithArgs() throws Exception {
assertOutput("<@m 1 2 />", "a=1; b=2; c=d3");
- assertOutput("<@m?spreadArgs([1, 2]) />", "a=1; b=2; c=d3");
- assertOutput("<@m?spreadArgs([1]) 2 />", "a=1; b=2; c=d3");
- assertOutput("<@m?spreadArgs([]) 1 2 />", "a=1; b=2; c=d3");
+ assertOutput("<@m?withArgs([1, 2]) />", "a=1; b=2; c=d3");
+ assertOutput("<@m?withArgs([1]) 2 />", "a=1; b=2; c=d3");
+ assertOutput("<@m?withArgs([]) 1 2 />", "a=1; b=2; c=d3");
assertOutput("<@m 1 2 3 />", "a=1; b=2; c=3");
- assertOutput("<@m?spreadArgs([1, 2, 3]) />", "a=1; b=2; c=3");
- assertOutput("<@m?spreadArgs([1, 2]) c=3 />", "a=1; b=2; c=3");
- assertOutput("<@m?spreadArgs([1, 2, 0]) c=3 />", "a=1; b=2; c=3");
- assertOutput("<@m?spreadArgs([1, 0, 3]) b=2 />", "a=1; b=2; c=3");
+ assertOutput("<@m?withArgs([1, 2, 3]) />", "a=1; b=2; c=3");
+ assertOutput("<@m?withArgs([1, 2]) c=3 />", "a=1; b=2; c=3");
+ assertOutput("<@m?withArgs([1, 2, 0]) c=3 />", "a=1; b=2; c=3");
+ assertOutput("<@m?withArgs([1, 0, 3]) b=2 />", "a=1; b=2; c=3");
assertOutput("<@mCA 1 2 />", "a=1; b=2; o=[]");
- assertOutput("<@mCA?spreadArgs([1, 2]) />", "a=1; b=2; o=[]");
- assertOutput("<@mCA?spreadArgs([1]) 2 />", "a=1; b=2; o=[]");
- assertOutput("<@mCA?spreadArgs([]) 1 2 />", "a=1; b=2; o=[]");
+ assertOutput("<@mCA?withArgs([1, 2]) />", "a=1; b=2; o=[]");
+ assertOutput("<@mCA?withArgs([1]) 2 />", "a=1; b=2; o=[]");
+ assertOutput("<@mCA?withArgs([]) 1 2 />", "a=1; b=2; o=[]");
assertOutput("<@mCA 1 2 3 />", "a=1; b=2; o=[3]");
- assertOutput("<@mCA?spreadArgs([1, 2, 3]) />", "a=1; b=2; o=[3]");
- assertOutput("<@mCA?spreadArgs([1]) 2, 3 />", "a=1; b=2; o=[3]");
- assertOutput("<@mCA?spreadArgs([1, 2]) 3 />", "a=1; b=2; o=[3]");
- assertOutput("<@mCA?spreadArgs([1]) b=2 c=3 />", "a=1; b=2; o={c=3}");
- assertOutput("<@mCA?spreadArgs([]) a=1 b=2 c=3 />", "a=1; b=2;
o={c=3}");
- assertOutput("<@mCA?spreadArgs([1, 2]) c=3 />", "a=1; b=2; o={c=3}");
- assertOutput("<@mCA?spreadArgs([1, 0]) b=2 c=3 />", "a=1; b=2;
o={c=3}");
- assertErrorContains("<@mCA?spreadArgs([1, 2, 3]) d=4 />",
+ assertOutput("<@mCA?withArgs([1, 2, 3]) />", "a=1; b=2; o=[3]");
+ assertOutput("<@mCA?withArgs([1]) 2, 3 />", "a=1; b=2; o=[3]");
+ assertOutput("<@mCA?withArgs([1, 2]) 3 />", "a=1; b=2; o=[3]");
+ assertOutput("<@mCA?withArgs([1]) b=2 c=3 />", "a=1; b=2; o={c=3}");
+ assertOutput("<@mCA?withArgs([]) a=1 b=2 c=3 />", "a=1; b=2; o={c=3}");
+ assertOutput("<@mCA?withArgs([1, 2]) c=3 />", "a=1; b=2; o={c=3}");
+ assertOutput("<@mCA?withArgs([1, 0]) b=2 c=3 />", "a=1; b=2; o={c=3}");
+ assertErrorContains("<@mCA?withArgs([1, 2, 3]) d=4 />",
"both named and positional", "catch-all");
- assertOutput("<@mCAO?spreadArgs([1, 2]) />", "o=[1, 2]");
- assertOutput("<@mCAO?spreadArgs([1]) 2 />", "o=[1, 2]");
+ assertOutput("<@mCAO?withArgs([1, 2]) />", "o=[1, 2]");
+ assertOutput("<@mCAO?withArgs([1]) 2 />", "o=[1, 2]");
assertOutput("<@mCAO 1, 2 />", "o=[1, 2]");
- assertOutput("<@mCAO?spreadArgs([]) />", "o=[]");
+ assertOutput("<@mCAO?withArgs([]) />", "o=[]");
}
@Test
- public void testNullsWithMacroWithPositionalSpreadArgs() throws Exception {
- // Null-s in ?spreadArgs should behave similarly as if they were given
directly as argument.
+ public void testNullsWithMacroWithPositionalWithArgs() throws Exception {
+ // Null-s in ?withArgs should behave similarly as if they were given
directly as argument.
assertOutput("<@mCAO 1 null null 4 />", "o=[1, 4]"); // [FM3] Should
be: 1, null, null, 4
addToDataModel("args", Arrays.asList(1, null, null, 4));
- assertOutput("<@mCAO?spreadArgs(args) />", "o=[1, 4]"); // [FM3] See
above
- assertOutput("<@mCAO?spreadArgs(args) null 5 6 />", "o=[1, 4, 5, 6]");
// [FM3] See above
+ assertOutput("<@mCAO?withArgs(args) />", "o=[1, 4]"); // [FM3] See
above
+ assertOutput("<@mCAO?withArgs(args) null 5 6 />", "o=[1, 4, 5, 6]");
// [FM3] See above
}
@Test
public void testFunction() throws Exception {
assertOutput("${f(1, 2)}", "a=1; b=2; c=d3");
- assertOutput("${f?spreadArgs([1, 2])()}", "a=1; b=2; c=d3");
- assertOutput("${f?spreadArgs([1])(2)}", "a=1; b=2; c=d3");
- assertOutput("${f?spreadArgs([])(1, 2)}", "a=1; b=2; c=d3");
+ assertOutput("${f?withArgs([1, 2])()}", "a=1; b=2; c=d3");
+ assertOutput("${f?withArgs([1])(2)}", "a=1; b=2; c=d3");
+ assertOutput("${f?withArgs([])(1, 2)}", "a=1; b=2; c=d3");
assertOutput("${f(1, 2, 3)}", "a=1; b=2; c=3");
- assertOutput("${f?spreadArgs([1, 2, 3])()}", "a=1; b=2; c=3");
+ assertOutput("${f?withArgs([1, 2, 3])()}", "a=1; b=2; c=3");
assertOutput("${fCA(1, 2)}", "a=1; b=2; o=[]");
- assertOutput("${fCA?spreadArgs([1, 2])()}", "a=1; b=2; o=[]");
- assertOutput("${fCA?spreadArgs([1])(2)}", "a=1; b=2; o=[]");
- assertOutput("${fCA?spreadArgs([])(1, 2)}", "a=1; b=2; o=[]");
+ assertOutput("${fCA?withArgs([1, 2])()}", "a=1; b=2; o=[]");
+ assertOutput("${fCA?withArgs([1])(2)}", "a=1; b=2; o=[]");
+ assertOutput("${fCA?withArgs([])(1, 2)}", "a=1; b=2; o=[]");
assertOutput("${fCA(1, 2, 3)}", "a=1; b=2; o=[3]");
- assertOutput("${fCA?spreadArgs([1, 2, 3])()}", "a=1; b=2; o=[3]");
- assertOutput("${fCA?spreadArgs([1])(2, 3)}", "a=1; b=2; o=[3]");
- assertOutput("${fCA?spreadArgs([1, 2])(3)}", "a=1; b=2; o=[3]");
- assertOutput("${fCA?spreadArgs([])(1, 2, 3)}", "a=1; b=2; o=[3]");
+ assertOutput("${fCA?withArgs([1, 2, 3])()}", "a=1; b=2; o=[3]");
+ assertOutput("${fCA?withArgs([1])(2, 3)}", "a=1; b=2; o=[3]");
+ assertOutput("${fCA?withArgs([1, 2])(3)}", "a=1; b=2; o=[3]");
+ assertOutput("${fCA?withArgs([])(1, 2, 3)}", "a=1; b=2; o=[3]");
assertOutput("${fCAO(1, 2)}", "o=[1, 2]");
- assertOutput("${fCAO?spreadArgs([1, 2])()}", "o=[1, 2]");
- assertOutput("${fCAO?spreadArgs([1])(2)}", "o=[1, 2]");
- assertOutput("${fCAO?spreadArgs([])(1, 2)}", "o=[1, 2]");
+ assertOutput("${fCAO?withArgs([1, 2])()}", "o=[1, 2]");
+ assertOutput("${fCAO?withArgs([1])(2)}", "o=[1, 2]");
+ assertOutput("${fCAO?withArgs([])(1, 2)}", "o=[1, 2]");
- assertErrorContains("${f?spreadArgs({'a': 1, 'b': 2})}",
- "function", "hash", "sequence", "?spreadArgs");
+ assertErrorContains("${f?withArgs({'a': 1, 'b': 2})}",
+ "function", "hash", "sequence", "?withArgs");
}
@Test
public void testNullsWithFunction() throws Exception {
- // Null-s in ?spreadArgs should behave similarly as if they were given
directly as argument.
+ // Null-s in ?withArgs should behave similarly as if they were given
directly as argument.
assertOutput("${fCAO(1, null, null, 4)}", "o=[1, 4]"); // [FM3] Should
be: 1, null, null, 4
addToDataModel("args", Arrays.asList(1, null, null, 4));
- assertOutput("${fCAO?spreadArgs(args)()}", "o=[1, 4]"); // [FM3] See
above
- assertOutput("${fCAO?spreadArgs(args)(null, 5, 6)}", "o=[1, 4, 5,
6]"); // [FM3] See above
+ assertOutput("${fCAO?withArgs(args)()}", "o=[1, 4]"); // [FM3] See
above
+ assertOutput("${fCAO?withArgs(args)(null, 5, 6)}", "o=[1, 4, 5, 6]");
// [FM3] See above
}
@Test
@@ -238,7 +238,7 @@ public class SpreadArgsBuiltInTest extends TemplateTest {
"<#import 'ns1.ftl' as ns1>" +
"<#assign v = 'NS0'>" +
"<@ns1.m 1; n>n=${n} v=${v}</@>; " +
- "<#assign m2 = ns1.m?spreadArgs([2])>" +
+ "<#assign m2 = ns1.m?withArgs([2])>" +
"<@m2; n>n=${n} v=${v}</@>",
"p=1 v=NS1 v=L {n=1 v=NS0} v=L; " +
"p=2 v=NS1 v=L {n=2 v=NS0} v=L");
@@ -250,49 +250,49 @@ public class SpreadArgsBuiltInTest extends TemplateTest {
// No error:
assertOutput(macroDef + "<@m 1 2 3 />", "1, 2, 3");
- assertOutput(macroDef + "<@m?spread_args([1, 2, 3]) />", "1, 2, 3");
- assertOutput(macroDef + "<@m?spread_args([1, 2]) 3 />", "1, 2, 3");
+ assertOutput(macroDef + "<@m?with_args([1, 2, 3]) />", "1, 2, 3");
+ assertOutput(macroDef + "<@m?with_args([1, 2]) 3 />", "1, 2, 3");
// Too many args:
assertErrorContains(macroDef + "<@m 1 2 3 4 />", "accepts 3", "got 4");
- assertErrorContains(macroDef + "<@m?spread_args([1, 2, 3, 4]) />",
"accepts 3", "got 4");
- assertErrorContains(macroDef + "<@m?spread_args([1, 2, 3]) 5 />",
"accepts 3", "got 4");
- assertErrorContains(macroDef + "<@m?spread_args([1]) 2 3 4 />",
"accepts 3", "got 4");
+ assertErrorContains(macroDef + "<@m?with_args([1, 2, 3, 4]) />",
"accepts 3", "got 4");
+ assertErrorContains(macroDef + "<@m?with_args([1, 2, 3]) 5 />",
"accepts 3", "got 4");
+ assertErrorContains(macroDef + "<@m?with_args([1]) 2 3 4 />", "accepts
3", "got 4");
// Too few args:
assertErrorContains(macroDef + "<@m 1 2 />", "\"c\"", "was not
specified");
- assertErrorContains(macroDef + "<@m?spread_args([1, 2]) />", "\"c\"",
"was not specified");
- assertErrorContains(macroDef + "<@m?spread_args([1]) 2 />", "\"c\"",
"was not specified");
- assertErrorContains(macroDef + "<@m?spread_args([]) 1 2 />", "\"c\"",
"was not specified");
+ assertErrorContains(macroDef + "<@m?with_args([1, 2]) />", "\"c\"",
"was not specified");
+ assertErrorContains(macroDef + "<@m?with_args([1]) 2 />", "\"c\"",
"was not specified");
+ assertErrorContains(macroDef + "<@m?with_args([]) 1 2 />", "\"c\"",
"was not specified");
}
@Test
public void testDefaultsThenCatchAll() throws IOException,
TemplateException {
String macroDef = "<#macro m a=1 b=2 c=3 o...>a=${a} b=${b} c=${c} " +
PRINT_O + "</#macro>";
- assertOutput(macroDef + "<@m?spreadArgs([]) />", "a=1 b=2 c=3 o=[]");
- assertOutput(macroDef + "<@m?spreadArgs([11]) />", "a=11 b=2 c=3
o=[]");
- assertOutput(macroDef + "<@m?spreadArgs([11, 22]) />", "a=11 b=22 c=3
o=[]");
- assertOutput(macroDef + "<@m?spreadArgs([11, 22, 33]) />", "a=11 b=22
c=33 o=[]");
- assertOutput(macroDef + "<@m?spreadArgs([11, 22, 33, 44]) />", "a=11
b=22 c=33 o=[44]");
- assertOutput(macroDef + "<@m?spreadArgs([11, 22, 33, 44, 55]) />",
"a=11 b=22 c=33 o=[44, 55]");
-
- assertOutput(macroDef + "<@m?spreadArgs([]) 11 />", "a=11 b=2 c=3
o=[]");
- assertOutput(macroDef + "<@m?spreadArgs([11]) 22 />", "a=11 b=22 c=3
o=[]");
- assertOutput(macroDef + "<@m?spreadArgs([11, 22]) 33 />", "a=11 b=22
c=33 o=[]");
- assertOutput(macroDef + "<@m?spreadArgs([11, 22, 33]) 44 />", "a=11
b=22 c=33 o=[44]");
- assertOutput(macroDef + "<@m?spreadArgs([11, 22, 33, 44]) 55 />",
"a=11 b=22 c=33 o=[44, 55]");
-
- assertOutput(macroDef + "<@m?spreadArgs({}) />", "a=1 b=2 c=3 o={}");
- assertOutput(macroDef + "<@m?spreadArgs({'b':22}) />", "a=1 b=22 c=3
o={}");
- assertOutput(macroDef + "<@m?spreadArgs({'b':22, 'c':33}) />", "a=1
b=22 c=33 o={}");
- assertOutput(macroDef + "<@m?spreadArgs({'b':22, 'c':33, 'd':55}) />",
"a=1 b=22 c=33 o={d=55}");
- assertOutput(macroDef + "<@m?spreadArgs({'b':22, 'd':55, 'e':66}) />",
"a=1 b=22 c=3 o={d=55, e=66}");
-
- assertOutput(macroDef + "<@m?spreadArgs({}) b=22 />", "a=1 b=22 c=3
o={}");
- assertOutput(macroDef + "<@m?spreadArgs({'b':22}) c=33 />", "a=1 b=22
c=33 o={}");
- assertOutput(macroDef + "<@m?spreadArgs({'b':22, 'c':33}) d=55 />",
"a=1 b=22 c=33 o={d=55}");
- assertOutput(macroDef + "<@m?spreadArgs({'b':22, 'd':55}) e=66 />",
"a=1 b=22 c=3 o={d=55, e=66}");
+ assertOutput(macroDef + "<@m?withArgs([]) />", "a=1 b=2 c=3 o=[]");
+ assertOutput(macroDef + "<@m?withArgs([11]) />", "a=11 b=2 c=3 o=[]");
+ assertOutput(macroDef + "<@m?withArgs([11, 22]) />", "a=11 b=22 c=3
o=[]");
+ assertOutput(macroDef + "<@m?withArgs([11, 22, 33]) />", "a=11 b=22
c=33 o=[]");
+ assertOutput(macroDef + "<@m?withArgs([11, 22, 33, 44]) />", "a=11
b=22 c=33 o=[44]");
+ assertOutput(macroDef + "<@m?withArgs([11, 22, 33, 44, 55]) />", "a=11
b=22 c=33 o=[44, 55]");
+
+ assertOutput(macroDef + "<@m?withArgs([]) 11 />", "a=11 b=2 c=3 o=[]");
+ assertOutput(macroDef + "<@m?withArgs([11]) 22 />", "a=11 b=22 c=3
o=[]");
+ assertOutput(macroDef + "<@m?withArgs([11, 22]) 33 />", "a=11 b=22
c=33 o=[]");
+ assertOutput(macroDef + "<@m?withArgs([11, 22, 33]) 44 />", "a=11 b=22
c=33 o=[44]");
+ assertOutput(macroDef + "<@m?withArgs([11, 22, 33, 44]) 55 />", "a=11
b=22 c=33 o=[44, 55]");
+
+ assertOutput(macroDef + "<@m?withArgs({}) />", "a=1 b=2 c=3 o={}");
+ assertOutput(macroDef + "<@m?withArgs({'b':22}) />", "a=1 b=22 c=3
o={}");
+ assertOutput(macroDef + "<@m?withArgs({'b':22, 'c':33}) />", "a=1 b=22
c=33 o={}");
+ assertOutput(macroDef + "<@m?withArgs({'b':22, 'c':33, 'd':55}) />",
"a=1 b=22 c=33 o={d=55}");
+ assertOutput(macroDef + "<@m?withArgs({'b':22, 'd':55, 'e':66}) />",
"a=1 b=22 c=3 o={d=55, e=66}");
+
+ assertOutput(macroDef + "<@m?withArgs({}) b=22 />", "a=1 b=22 c=3
o={}");
+ assertOutput(macroDef + "<@m?withArgs({'b':22}) c=33 />", "a=1 b=22
c=33 o={}");
+ assertOutput(macroDef + "<@m?withArgs({'b':22, 'c':33}) d=55 />", "a=1
b=22 c=33 o={d=55}");
+ assertOutput(macroDef + "<@m?withArgs({'b':22, 'd':55}) e=66 />", "a=1
b=22 c=3 o={d=55, e=66}");
}
@Test
@@ -300,26 +300,26 @@ public class SpreadArgsBuiltInTest extends TemplateTest {
addToDataModel("obj", new MethodHolder());
assertOutput("${obj.m3p(1, 2, 3)}", "1, 2, 3");
- assertOutput("${obj.m3p?spreadArgs([1, 2, 3])()}", "1, 2, 3");
- assertOutput("${obj.m3p?spreadArgs([1, 2])(3)}", "1, 2, 3");
- assertOutput("${obj.m3p?spreadArgs([1])(2, 3)}", "1, 2, 3");
- assertOutput("${obj.m3p?spreadArgs([])(1, 2, 3)}", "1, 2, 3");
+ assertOutput("${obj.m3p?withArgs([1, 2, 3])()}", "1, 2, 3");
+ assertOutput("${obj.m3p?withArgs([1, 2])(3)}", "1, 2, 3");
+ assertOutput("${obj.m3p?withArgs([1])(2, 3)}", "1, 2, 3");
+ assertOutput("${obj.m3p?withArgs([])(1, 2, 3)}", "1, 2, 3");
assertOutput("${obj.m0p()}", "OK");
- assertOutput("${obj.m0p?spreadArgs([])()}", "OK");
+ assertOutput("${obj.m0p?withArgs([])()}", "OK");
assertOutput("${obj.mVA(1, 2, 3, 4)}", "1, 2, o=[3, 4]");
- assertOutput("${obj.mVA?spreadArgs([1, 2, 3, 4])()}", "1, 2, o=[3,
4]");
- assertOutput("${obj.mVA?spreadArgs([1, 2, 3])(4)}", "1, 2, o=[3, 4]");
- assertOutput("${obj.mVA?spreadArgs([1, 2])(3, 4)}", "1, 2, o=[3, 4]");
- assertOutput("${obj.mVA?spreadArgs([1])(2, 3, 4)}", "1, 2, o=[3, 4]");
- assertOutput("${obj.mVA?spreadArgs([])(1, 2, 3, 4)}", "1, 2, o=[3,
4]");
+ assertOutput("${obj.mVA?withArgs([1, 2, 3, 4])()}", "1, 2, o=[3, 4]");
+ assertOutput("${obj.mVA?withArgs([1, 2, 3])(4)}", "1, 2, o=[3, 4]");
+ assertOutput("${obj.mVA?withArgs([1, 2])(3, 4)}", "1, 2, o=[3, 4]");
+ assertOutput("${obj.mVA?withArgs([1])(2, 3, 4)}", "1, 2, o=[3, 4]");
+ assertOutput("${obj.mVA?withArgs([])(1, 2, 3, 4)}", "1, 2, o=[3, 4]");
- assertErrorContains("${obj.mVA?spreadArgs({})}", "hash", "sequence",
"argument");
+ assertErrorContains("${obj.mVA?withArgs({})}", "hash", "sequence",
"argument");
assertOutput("${obj.mNullable(null, 2, null)}", "null, 2, null");
addToDataModel("args", Arrays.asList(null, 2, null));
- assertOutput("${obj.mNullable?spreadArgs(args)()}", "null, 2, null");
+ assertOutput("${obj.mNullable?withArgs(args)()}", "null, 2, null");
}
public static class MethodHolder {
@@ -355,9 +355,9 @@ public class SpreadArgsBuiltInTest extends TemplateTest {
addToDataModel("legacyMethod", new LegacyMethodModel());
getConfiguration().setNumberFormat("0.00");
assertOutput("${legacyMethod(1, '2')}", "[1.00, 2]");
- assertOutput("${legacyMethod?spreadArgs([1, '2'])()}", "[1.00, 2]");
- assertOutput("${legacyMethod?spreadArgs([1])('2')}", "[1.00, 2]");
- assertOutput("${legacyMethod?spreadArgs([])(1, '2')}", "[1.00, 2]");
+ assertOutput("${legacyMethod?withArgs([1, '2'])()}", "[1.00, 2]");
+ assertOutput("${legacyMethod?withArgs([1])('2')}", "[1.00, 2]");
+ assertOutput("${legacyMethod?withArgs([])(1, '2')}", "[1.00, 2]");
}
private static class LegacyMethodModel implements TemplateMethodModel {
@@ -377,18 +377,18 @@ public class SpreadArgsBuiltInTest extends TemplateTest {
assertOutput("<@directive a=1 b=2 c=3; u, v>${u} ${v}</@>",
"{a=1, b=2, c=3}{11 22}");
- assertOutput("<@directive?spreadArgs({'a': 1, 'b': 2, 'c': 3}); u,
v>${u} ${v}</@>",
+ assertOutput("<@directive?withArgs({'a': 1, 'b': 2, 'c': 3}); u,
v>${u} ${v}</@>",
"{a=1, b=2, c=3}{11 22}");
- assertOutput("<@directive?spreadArgs({'a': 1, 'b': 2}) c=3; u, v>${u}
${v}</@>",
+ assertOutput("<@directive?withArgs({'a': 1, 'b': 2}) c=3; u, v>${u}
${v}</@>",
"{a=1, b=2, c=3}{11 22}");
- assertOutput("<@directive?spreadArgs({'a': 1}) b=2 c=3; u, v>${u}
${v}</@>",
+ assertOutput("<@directive?withArgs({'a': 1}) b=2 c=3; u, v>${u}
${v}</@>",
"{a=1, b=2, c=3}{11 22}");
- assertOutput("<@directive?spreadArgs({}) a=1 b=2 c=3; u, v>${u}
${v}</@>",
+ assertOutput("<@directive?withArgs({}) a=1 b=2 c=3; u, v>${u}
${v}</@>",
"{a=1, b=2, c=3}{11 22}");
- assertOutput("<@directive?spreadArgs({}); u, v>${u} ${v}</@>",
+ assertOutput("<@directive?withArgs({}); u, v>${u} ${v}</@>",
"{}{11 22}");
- assertOutput("<@directive?spreadArgs({'a': 1, 'b': 2}) b=22 c=3;
u>${u}</@>",
+ assertOutput("<@directive?withArgs({'a': 1, 'b': 2}) b=22 c=3;
u>${u}</@>",
"{a=1, b=22, c=3}{11}");
Map<String, Integer> args = new LinkedHashMap<String, Integer>();
args.put("a", null);
@@ -396,7 +396,7 @@ public class SpreadArgsBuiltInTest extends TemplateTest {
args.put("c", 3);
args.put("e", 6);
addToDataModel("args", args);
- assertOutput("<@directive?spreadArgs(args) b=22 c=null d=55 />",
+ assertOutput("<@directive?withArgs(args) b=22 c=null d=55 />",
"{a=null, b=22, c=null, e=6, d=55}{}");
}
diff --git a/src/test/java/freemarker/manual/SpreadArgsExamples.java
b/src/test/java/freemarker/manual/WithArgsExamples.java
similarity index 88%
rename from src/test/java/freemarker/manual/SpreadArgsExamples.java
rename to src/test/java/freemarker/manual/WithArgsExamples.java
index 95ad3cd..51d10b8 100644
--- a/src/test/java/freemarker/manual/SpreadArgsExamples.java
+++ b/src/test/java/freemarker/manual/WithArgsExamples.java
@@ -25,11 +25,11 @@ import org.junit.Test;
import freemarker.template.TemplateException;
-public class SpreadArgsExamples extends ExamplesTest {
+public class WithArgsExamples extends ExamplesTest {
@Test
public void usingWithArgsSpecialVariable() throws IOException,
TemplateException {
-
assertOutputForNamed("SpreadArgsExamples-usingWithArgsSpecialVariable.ftl");
+
assertOutputForNamed("WithArgsExamples-usingWithArgsSpecialVariable.ftl");
}
}
diff --git
a/src/test/resources/freemarker/manual/SpreadArgsExamples-usingWithArgsSpecialVariable.ftl
b/src/test/resources/freemarker/manual/WithArgsExamples-usingWithArgsSpecialVariable.ftl
similarity index 85%
rename from
src/test/resources/freemarker/manual/SpreadArgsExamples-usingWithArgsSpecialVariable.ftl
rename to
src/test/resources/freemarker/manual/WithArgsExamples-usingWithArgsSpecialVariable.ftl
index ceb3941..9819fa0 100644
---
a/src/test/resources/freemarker/manual/SpreadArgsExamples-usingWithArgsSpecialVariable.ftl
+++
b/src/test/resources/freemarker/manual/WithArgsExamples-usingWithArgsSpecialVariable.ftl
@@ -5,7 +5,7 @@
<#macro m2 a b c>
m2 does things with ${a}, ${b}, ${c}
Delegate to m1:
- <@m1?spread_args(.args) />
+ <@m1?with_args(.args) />
</#macro>
<@m2 a=1 b=2 c=3 />
diff --git
a/src/test/resources/freemarker/manual/SpreadArgsExamples-usingWithArgsSpecialVariable.ftl.out
b/src/test/resources/freemarker/manual/WithArgsExamples-usingWithArgsSpecialVariable.ftl.out
similarity index 100%
rename from
src/test/resources/freemarker/manual/SpreadArgsExamples-usingWithArgsSpecialVariable.ftl.out
rename to
src/test/resources/freemarker/manual/WithArgsExamples-usingWithArgsSpecialVariable.ftl.out