See the RFE here:
https://issues.apache.org/jira/browse/FREEMARKER-84

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
https://freemarker.apache.org/docs/ref_specvar.html) 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
https://freemarker.apache.org/docs/ref_directive_include.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

Reply via email to