Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Proposition for a mechanism to add header/footer to sections. #147

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
168 changes: 168 additions & 0 deletions specs/~sequence-check.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,168 @@
overview: |
Rationale:

The core specification of Mustache does not provide a convenient mechanism
to emit a header or footer related to an iterable section.

As this is a very common need, various patterns have emerged to fill the gap.
These patterns tends to be either implementation dependent (making changing
mustache engines or sharing templates accross applications difficult) or
involve adding decoration in the rendered data (requiring ad-hoc logic to
alter the data before rendering).

This optional module provides a standard mechanism to fill the gap.


Proposed mechanism

The proposed implementation takes inspiration from ~dynamic-names, adding
a '?' qualifier after the section sigil for the optional interpretation.

```
{{#?list}}
<h1>heading</j1>
{{#list}}
<p>{{item}}</p>
{{/list}}
{{/?list}}
```

The new qualified section should not affect the stack and should be
rendered for non-empty sequences only, ignored for anything else (the normal
section tag already covers the need to test for truthyness on non-sequence
types).

Callables should be called, and the section rendered if and only if
the result is a non-empty list.

Lambda should NOT be called, and the section should not be rendered.


Discussion:

Current view on options

Approach #1
Preprocess data to add a boolean ({{#listNonEmpty}}{{/listNonEmpty}})
Pros:
- Portable, no template language modifications required
Cons:
- Convenience: requires coordinated deployment of code and templates
Approach #2
Host language specific notation ({{#list.0}}{{/list.0}}, {{#list.size}}{{/list.size}}, ...)
Pros:
- No template language modification required
Cons:
- Implementation-specific, convenient subkey not always available (ex
native environment without scripting capability)
- Stacking of tested fields affect resolution (bactktracking required)
Approach #3
Special purpose sigil ({{?list}}{{/list}})
Pros:
- Shortest notation in this comparison
Cons:
- Sacrifices a special character for a very narrow use case
Approach #4
Power lambdas (#135, {{#nonEmpty.list}}{{/nonEmpty.list}} or similar)
Pros:
- Powerful language feature that facilitates many use cases at once and does not introduce new special notation
Cons:
- Not widely implemented
- Requires a scripting execution environement
Approach %5
Current proposal: key modifier ({{#?list}}{{/?list}})
Pros:
- Short notation, does not sacrifice a sigil, no data preprocessing needed
Cons:
- Gliding scale: are we going to introduce key modifiers for every narrow use case? Also not widely implemented.


Opened questions:

This proposition breaks rendering of existing templates where sections
refers to symbols starting with '?' that are not used as list indicators.
Is it a significant concern or can we ignore this?
=> one simple option is to require mustache engines have API to activate
the feature but it is not really in scope of this specification.

tests:
- name: Basic sequence check
desc: Non-empty sequence emits the conditional block
data:
list: [1, 2]
heading: "Heading"
template: |
{{#?list}}
{{heading}}
{{/?list}}
expected: |
Heading
- name: Basic sequence check
desc: Empty sequence does not emit the header
data:
list: []
heading: "Heading"
template: |
{{#?list}}
{{heading}}
{{/?list}}
expected: ""
- name: Basic sequence check
desc: Dotted names can be checked
data:
obj:
list1: [1, 2]
list2: []
template: |
{{#?obj.list1}}
Ok
{{/?obj.list1}}
{{#?obj.list2}}
Wrong
{{/?obj.list2}}
expected: |
Ok
- name: Implicit iterator
desc: Implicit iterator can be checked
data:
list: [[1, 2, 3], [], ['a', 'b', 'c']]
before: "-> "
after: " <-"
template: |
{{#list}}{{#?.}}
{{&before}}{{#.}}{{.}}{{/.}}{{&after}}
{{/?.}}{{/list}}
expected: |
-> 123 <-
-> abc <-
- name: Non-Sequence
desc: Strings don't trigger sequence-check section
data: {"x" : "wrong"}
template: "{{#?x}}{{x}}{{/?x}}"
expected: ""
- name: Non-Sequence
desc: Objects don't trigger sequence-check section
data: {"x": {"y": "wrong"}}
template: "{{#?x}}{{x.y}}{{/?x}}"
expected: ""
- name: Non-Sequence
desc: Boolean true don't trigger sequence-check section
data: { "x": true }
template: "{{#?x}}wrong{{/?x}}"
expected: ""
- name: Whitespace sensitivity
desc: Sequence check can be in a standalone tag sequence
data: {list: [1, 2]}
template: |
{{#?list}}
Before
{{/?list}}{{#list}}
- {{.}}
{{/list}}{{#?list}}
After
{{/?list}}
expected: |
Before
- 1
- 2
After