freemarker-dev mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From Daniel Dekany <>
Subject Re: Proposal for FREEMARKER-84: More flexible handlig of missing templates
Date Tue, 13 Feb 2018 08:59:41 GMT
Tuesday, February 13, 2018, 9:28:18 AM, Jacopo Cappellato wrote:

> For less common use cases like this my preference is to defer the
> implementation to the template developer rather than adding complexity to
> the language.
> If I understand the use case that originated this request, something
> similar could be achieved with a simple trick like the following:
> 1) the calling code would be:
> <#include "possibly-missing-template.ftl" ignore_missing=true>
> <#if !processed??>
>         The template was not found or processed!
> </#if>
> 2) somewhere in possibly-missing-template.ftl (i.e. at the bottom of it) we
> add an assign directive like:
> <#assign processed=true>
> There are some cons to this approach (the most relevant one is that the
> referenced template has to contain the #assign directive) but FM users
> could live with this approach and in the meantime we could try to get their
> feedback to better understand how much this requirement is desired to
> justify a change to the codebase.

The need for optional includes is something that was brought up for
several times during the years. It's mostly needed for some custom
fallback logic as far as I can tell. (While there's #include
ignore_missing=true for a while, it doesn't let you to take some extra
action depending on if the template is missing.)

As of it's important enough to add a new feature of this weight (which
low, as it's just yet another special variable, no new directive or
syntax): It's a template language, and in that context
including/importing other templates is probably an important enough
topic to warrant some extras.

> Jacopo
> On Sun, Feb 11, 2018 at 10:02 PM, Daniel Dekany <> wrote:
>> See the RFE here:
>> As you see, the first consensus was introducing `.last_include_found`,
>> but it has two problems:
>> * Sometimes it happens that if, and only if the template exists then
>>   you want to do (usually print) something *before* it. Problem is, by
>>   the time you know that from `.last_include_found`, it's too late, as
>>   the template was already processed.
>> * Like many global state variables in general, this can lead to some
>>   confusing edge cases and hard-to-follow code. Like, if `#include`
>>   throws an exception, which is then handled by the user with
>>   `#attempt`/`#recover`, then `.last_include_found` may or may not be
>>   updated, as perhaps we haven't yet reached the point where it can be
>>   told if the template exists. (Consider an expression evaluation
>>   error in the `#include` parameter, or an I/O error due to which we
>>   can't access the template directory). Also there are some public
>>   `include` methods in the `Environment` API, but they can't set this
>>   variable, as they return a `Template`, and the variable must be set
>>   after the `Template` was processed, unless the template was missing.
>>   (If you can't figure out why it must be done that way, that proves
>>   again how tricky this is... think about includes inside includes.)
>> So, I propose the solution below. Maybe somewhat difficult to grasp
>> first, but it meant to be used rarely, and mostly by "experts"...
>> Let's hope SO and examples in the Manual will help people though. (-;
>> Introduce a new special variable (see
>> called
>> "get_optional_template", which is a TemplateMethodModelEx with these
>> parameters:
>> 1. template name (maybe a relative path, resolved as #include/#import
>> does it) 2. A hash that can have the following keys: "parse",
>> "encoding" (similarly to
>> html#ref.directive.include).
>> Example method call (the `.` prefix is the special variable reference
>> syntax):
>>   <#assign t = .get_optional_template("foo.ftl", { 'encoding': 'utf-8' })>
>> The method returns a hash (`t` above) that contains the following keys:
>> - "include": directive (or missing); `<@t.include />` has similar
>>   effect as `<#include "foo.ftl">`
>> - "import": method (or missing); returns a namespace. `<#assign f =
>>   t.import()>` has similar effect as `<#import 'foo.ftl' as f>`
>> - "exists": boolean; returns if the template was found.
>> The method call loads the target template eagerly, i.e., it won't wait
>> until `t.include`, `t.exist` etc. is actually used.
>> Note that the hash is returned even if the template wasn't found (but
>> then it won't contain "include" and "import", and "exists" will be
>> `false`). If some other error occurs, like an I/O error other than a
>> "template not found", or the template has invalid syntax, it will
>> throw exception (just like #include).
>> Use cases:
>> - `#include` with fallback templates or fallback macro (note how we
>>   can use the `exp!defaultExp` operator):
>>   <@.get_optional_template('foo.ftl')
>>       !.get_optional_template('bar.ftl').include
>>       !defaultMacro  />
>> - Doing something before `#include` if the template exists:
>>     <#assign t = .get_optional_template('foo.ftl')>
>>     <#if t.exists>
>>       Do before existing template
>>       <@t.include />
>>     </#if>
>> Opinions?
>> --
>> Thanks,
>>  Daniel Dekany

 Daniel Dekany

View raw message