🏡 drafts\mustache_specs.nim

Mustache Specs

Autogenerate a document containing the results of nim-mustache running mustache specs v1.2.1.

import
  mustache

import
  json

import
  strformat

import
  tables

const
  specfiles = ["interpolation.json", "sections.json", "inverted.json",
    "comments.json", "delimiters.json", "partials.json", "~inheritance.json"]
var
  specs = newJObject()
  tmpl: string
  tests: JsonNode
  data: JsonNode
  context: Context
  partials: Table[string, string]
for specfile in specfiles:
  echo "📃 " & specfile
  specs[specfile] = parseFile("mustache-specs/" & specfile)
  tests = specs[specfile]["tests"]
  assert tests.kind == JArray
  for test in tests.mitems:
    assert test.kind == JObject
    tmpl = test["template"].getStr
    data = test["data"]
    partials = initTable[string, string]()
    if "partials" in test:
      for key, val in test["partials"]:
        partials[key] = val.getStr
    context = newContext(searchDirs = @[], partials = partials,
                         values = data.toValues)
    test["output"] = %tmpl.render(context)
    if test["output"].getStr == test["expected"].getStr:
      test["ok"] = %"✅"
      echo "  ✅ ", test["name"].getStr
    else:
      test["ok"] = %"❌"
      echo "  ❌ ", test["name"].getStr
proc specsToMarkdown(specs: JsonNode): string =
  var partials: string
  for specfile, spec in specs.pairs:
    result.add "## " & specfile & "\n\n"
    result.add spec["overview"].getStr & "\n"
    for test in spec["tests"]:
      partials = ""
      if "partials" in test:
        assert test["partials"].kind == JObject
        partials = "Partials:\n\n```\n"
        for key, val in test["partials"]:
          partials.add &"{key}: {$val}\n"
        partials.add "```\n"
      result.add fmt"""### {test["ok"].getStr} {test["name"].getStr}

{test["desc"].getStr}

Template:

```
{test["template"].getStr}
```

Data:

```
{$(test["data"])}
```

{partials}Expected:

```
{test["expected"].getStr}
```

Output:

```
{test["output"].getStr}
```

"""
📃 interpolation.json
  ✅ No Interpolation
  ✅ Basic Interpolation
  ✅ HTML Escaping
  ✅ Triple Mustache
  ✅ Ampersand
  ✅ Basic Integer Interpolation
  ✅ Triple Mustache Integer Interpolation
  ✅ Ampersand Integer Interpolation
  ✅ Basic Decimal Interpolation
  ✅ Triple Mustache Decimal Interpolation
  ✅ Ampersand Decimal Interpolation
  ✅ Basic Null Interpolation
  ✅ Triple Mustache Null Interpolation
  ✅ Ampersand Null Interpolation
  ✅ Basic Context Miss Interpolation
  ✅ Triple Mustache Context Miss Interpolation
  ✅ Ampersand Context Miss Interpolation
  ✅ Dotted Names - Basic Interpolation
  ✅ Dotted Names - Triple Mustache Interpolation
  ✅ Dotted Names - Ampersand Interpolation
  ✅ Dotted Names - Arbitrary Depth
  ✅ Dotted Names - Broken Chains
  ✅ Dotted Names - Broken Chain Resolution
  ✅ Dotted Names - Initial Resolution
  ✅ Dotted Names - Context Precedence
  ✅ Implicit Iterators - Basic Interpolation
  ✅ Implicit Iterators - HTML Escaping
  ✅ Implicit Iterators - Triple Mustache
  ✅ Implicit Iterators - Ampersand
  ✅ Implicit Iterators - Basic Integer Interpolation
  ✅ Interpolation - Surrounding Whitespace
  ✅ Triple Mustache - Surrounding Whitespace
  ✅ Ampersand - Surrounding Whitespace
  ✅ Interpolation - Standalone
  ✅ Triple Mustache - Standalone
  ✅ Ampersand - Standalone
  ✅ Interpolation With Padding
  ✅ Triple Mustache With Padding
  ✅ Ampersand With Padding
📃 sections.json
  ✅ Truthy
  ✅ Falsey
  ✅ Null is falsey
  ✅ Context
  ✅ Parent contexts
  ✅ Variable test
  ✅ List Contexts
  ✅ Deeply Nested Contexts
  ✅ List
  ✅ Empty List
  ✅ Doubled
  ✅ Nested (Truthy)
  ✅ Nested (Falsey)
  ✅ Context Misses
  ✅ Implicit Iterator - String
  ✅ Implicit Iterator - Integer
  ✅ Implicit Iterator - Decimal
  ✅ Implicit Iterator - Array
  ✅ Dotted Names - Truthy
  ✅ Dotted Names - Falsey
  ✅ Dotted Names - Broken Chains
  ✅ Surrounding Whitespace
  ✅ Internal Whitespace
  ✅ Indented Inline Sections
  ✅ Standalone Lines
  ✅ Indented Standalone Lines
  ✅ Standalone Line Endings
  ✅ Standalone Without Previous Line
  ✅ Standalone Without Newline
  ✅ Padding
📃 inverted.json
  ✅ Falsey
  ✅ Truthy
  ✅ Null is falsey
  ✅ Context
  ✅ List
  ✅ Empty List
  ✅ Doubled
  ✅ Nested (Falsey)
  ✅ Nested (Truthy)
  ✅ Context Misses
  ✅ Dotted Names - Truthy
  ✅ Dotted Names - Falsey
  ✅ Dotted Names - Broken Chains
  ✅ Surrounding Whitespace
  ✅ Internal Whitespace
  ✅ Indented Inline Sections
  ✅ Standalone Lines
  ✅ Standalone Indented Lines
  ✅ Standalone Line Endings
  ✅ Standalone Without Previous Line
  ✅ Standalone Without Newline
  ✅ Padding
📃 comments.json
  ✅ Inline
  ✅ Multiline
  ✅ Standalone
  ✅ Indented Standalone
  ✅ Standalone Line Endings
  ✅ Standalone Without Previous Line
  ✅ Standalone Without Newline
  ✅ Multiline Standalone
  ✅ Indented Multiline Standalone
  ✅ Indented Inline
  ✅ Surrounding Whitespace
📃 delimiters.json
  ✅ Pair Behavior
  ✅ Special Characters
  ✅ Sections
  ✅ Inverted Sections
  ✅ Partial Inheritence
  ✅ Post-Partial Behavior
  ✅ Surrounding Whitespace
  ✅ Outlying Whitespace (Inline)
  ✅ Standalone Tag
  ✅ Indented Standalone Tag
  ✅ Standalone Line Endings
  ✅ Standalone Without Previous Line
  ✅ Standalone Without Newline
  ✅ Pair with Padding
📃 partials.json
  ✅ Basic Behavior
  ✅ Failed Lookup
  ✅ Context
  ✅ Recursion
  ✅ Surrounding Whitespace
  ✅ Inline Indentation
  ✅ Standalone Line Endings
  ✅ Standalone Without Previous Line
  ✅ Standalone Without Newline
  ✅ Standalone Indentation
  ✅ Padding Whitespace
📃 ~inheritance.json
  ✅ Default
  ✅ Variable
  ✅ Triple Mustache
  ✅ Sections
  ✅ Negative Sections
  ✅ Mustache Injection
  ✅ Inherit
  ✅ Overridden content
  ✅ Data does not override block
  ✅ Data does not override block default
  ✅ Overridden parent
  ✅ Two overridden parents
  ✅ Override parent with newlines
  ✅ Inherit indentation
  ✅ Only one override
  ✅ Parent template
  ✅ Recursion
  ✅ Multi-level inheritance
  ✅ Multi-level inheritance, no sub child
  ✅ Text inside parent
  ✅ Text inside parent

interpolation.json

Interpolation tags are used to integrate dynamic content into the template.

The tag's content MUST be a non-whitespace character sequence NOT containing the current closing delimiter.

This tag's content names the data to replace the tag. A single period (.) indicates that the item currently sitting atop the context stack should be used; otherwise, name resolution is as follows:

  1. Split the name on periods; the first part is the name to resolve, any remaining parts should be retained.
  2. Walk the context stack from top to bottom, finding the first context that is a) a hash containing the name as a key OR b) an object responding to a method with the given name.
  3. If the context is a hash, the data is the value associated with the name.
  4. If the context is an object, the data is the value returned by the method with the given name.
  5. If any name parts were retained in step 1, each should be resolved against a context stack containing only the result from the former resolution. If any part fails resolution, the result should be considered falsey, and should interpolate as the empty string. Data should be coerced into a string (and escaped, if appropriate) before interpolation.

The Interpolation tags MUST NOT be treated as standalone.

✅ No Interpolation

Mustache-free templates should render as-is.

Template:

Hello from {Mustache}!

Data:

{}

Expected:

Hello from {Mustache}!

Output:

Hello from {Mustache}!

✅ Basic Interpolation

Unadorned tags should interpolate content into the template.

Template:

Hello, {{subject}}!

Data:

{"subject":"world"}

Expected:

Hello, world!

Output:

Hello, world!

✅ HTML Escaping

Basic interpolation should be HTML escaped.

Template:

These characters should be HTML escaped: {{forbidden}}

Data:

{"forbidden":"& \" < >"}

Expected:

These characters should be HTML escaped: &amp; &quot; &lt; &gt;

Output:

These characters should be HTML escaped: &amp; &quot; &lt; &gt;

✅ Triple Mustache

Triple mustaches should interpolate without HTML escaping.

Template:

These characters should not be HTML escaped: {{{forbidden}}}

Data:

{"forbidden":"& \" < >"}

Expected:

These characters should not be HTML escaped: & " < >

Output:

These characters should not be HTML escaped: & " < >

✅ Ampersand

Ampersand should interpolate without HTML escaping.

Template:

These characters should not be HTML escaped: {{&forbidden}}

Data:

{"forbidden":"& \" < >"}

Expected:

These characters should not be HTML escaped: & " < >

Output:

These characters should not be HTML escaped: & " < >

✅ Basic Integer Interpolation

Integers should interpolate seamlessly.

Template:

"{{mph}} miles an hour!"

Data:

{"mph":85}

Expected:

"85 miles an hour!"

Output:

"85 miles an hour!"

✅ Triple Mustache Integer Interpolation

Integers should interpolate seamlessly.

Template:

"{{{mph}}} miles an hour!"

Data:

{"mph":85}

Expected:

"85 miles an hour!"

Output:

"85 miles an hour!"

✅ Ampersand Integer Interpolation

Integers should interpolate seamlessly.

Template:

"{{&mph}} miles an hour!"

Data:

{"mph":85}

Expected:

"85 miles an hour!"

Output:

"85 miles an hour!"

✅ Basic Decimal Interpolation

Decimals should interpolate seamlessly with proper significance.

Template:

"{{power}} jiggawatts!"

Data:

{"power":1.21}

Expected:

"1.21 jiggawatts!"

Output:

"1.21 jiggawatts!"

✅ Triple Mustache Decimal Interpolation

Decimals should interpolate seamlessly with proper significance.

Template:

"{{{power}}} jiggawatts!"

Data:

{"power":1.21}

Expected:

"1.21 jiggawatts!"

Output:

"1.21 jiggawatts!"

✅ Ampersand Decimal Interpolation

Decimals should interpolate seamlessly with proper significance.

Template:

"{{&power}} jiggawatts!"

Data:

{"power":1.21}

Expected:

"1.21 jiggawatts!"

Output:

"1.21 jiggawatts!"

✅ Basic Null Interpolation

Nulls should interpolate as the empty string.

Template:

I ({{cannot}}) be seen!

Data:

{"cannot":null}

Expected:

I () be seen!

Output:

I () be seen!

✅ Triple Mustache Null Interpolation

Nulls should interpolate as the empty string.

Template:

I ({{{cannot}}}) be seen!

Data:

{"cannot":null}

Expected:

I () be seen!

Output:

I () be seen!

✅ Ampersand Null Interpolation

Nulls should interpolate as the empty string.

Template:

I ({{&cannot}}) be seen!

Data:

{"cannot":null}

Expected:

I () be seen!

Output:

I () be seen!

✅ Basic Context Miss Interpolation

Failed context lookups should default to empty strings.

Template:

I ({{cannot}}) be seen!

Data:

{}

Expected:

I () be seen!

Output:

I () be seen!

✅ Triple Mustache Context Miss Interpolation

Failed context lookups should default to empty strings.

Template:

I ({{{cannot}}}) be seen!

Data:

{}

Expected:

I () be seen!

Output:

I () be seen!

✅ Ampersand Context Miss Interpolation

Failed context lookups should default to empty strings.

Template:

I ({{&cannot}}) be seen!

Data:

{}

Expected:

I () be seen!

Output:

I () be seen!

✅ Dotted Names - Basic Interpolation

Dotted names should be considered a form of shorthand for sections.

Template:

"{{person.name}}" == "{{#person}}{{name}}{{/person}}"

Data:

{"person":{"name":"Joe"}}

Expected:

"Joe" == "Joe"

Output:

"Joe" == "Joe"

✅ Dotted Names - Triple Mustache Interpolation

Dotted names should be considered a form of shorthand for sections.

Template:

"{{{person.name}}}" == "{{#person}}{{{name}}}{{/person}}"

Data:

{"person":{"name":"Joe"}}

Expected:

"Joe" == "Joe"

Output:

"Joe" == "Joe"

✅ Dotted Names - Ampersand Interpolation

Dotted names should be considered a form of shorthand for sections.

Template:

"{{&person.name}}" == "{{#person}}{{&name}}{{/person}}"

Data:

{"person":{"name":"Joe"}}

Expected:

"Joe" == "Joe"

Output:

"Joe" == "Joe"

✅ Dotted Names - Arbitrary Depth

Dotted names should be functional to any level of nesting.

Template:

"{{a.b.c.d.e.name}}" == "Phil"

Data:

{"a":{"b":{"c":{"d":{"e":{"name":"Phil"}}}}}}

Expected:

"Phil" == "Phil"

Output:

"Phil" == "Phil"

✅ Dotted Names - Broken Chains

Any falsey value prior to the last part of the name should yield ''.

Template:

"{{a.b.c}}" == ""

Data:

{"a":{}}

Expected:

"" == ""

Output:

"" == ""

✅ Dotted Names - Broken Chain Resolution

Each part of a dotted name should resolve only against its parent.

Template:

"{{a.b.c.name}}" == ""

Data:

{"a":{"b":{}},"c":{"name":"Jim"}}

Expected:

"" == ""

Output:

"" == ""

✅ Dotted Names - Initial Resolution

The first part of a dotted name should resolve as any other name.

Template:

"{{#a}}{{b.c.d.e.name}}{{/a}}" == "Phil"

Data:

{"a":{"b":{"c":{"d":{"e":{"name":"Phil"}}}}},"b":{"c":{"d":{"e":{"name":"Wrong"}}}}}

Expected:

"Phil" == "Phil"

Output:

"Phil" == "Phil"

✅ Dotted Names - Context Precedence

Dotted names should be resolved against former resolutions.

Template:

{{#a}}{{b.c}}{{/a}}

Data:

{"a":{"b":{}},"b":{"c":"ERROR"}}

Expected:


Output:


✅ Implicit Iterators - Basic Interpolation

Unadorned tags should interpolate content into the template.

Template:

Hello, {{.}}!

Data:

"world"

Expected:

Hello, world!

Output:

Hello, world!

✅ Implicit Iterators - HTML Escaping

Basic interpolation should be HTML escaped.

Template:

These characters should be HTML escaped: {{.}}

Data:

"& \" < >"

Expected:

These characters should be HTML escaped: &amp; &quot; &lt; &gt;

Output:

These characters should be HTML escaped: &amp; &quot; &lt; &gt;

✅ Implicit Iterators - Triple Mustache

Triple mustaches should interpolate without HTML escaping.

Template:

These characters should not be HTML escaped: {{{.}}}

Data:

"& \" < >"

Expected:

These characters should not be HTML escaped: & " < >

Output:

These characters should not be HTML escaped: & " < >

✅ Implicit Iterators - Ampersand

Ampersand should interpolate without HTML escaping.

Template:

These characters should not be HTML escaped: {{&.}}

Data:

"& \" < >"

Expected:

These characters should not be HTML escaped: & " < >

Output:

These characters should not be HTML escaped: & " < >

✅ Implicit Iterators - Basic Integer Interpolation

Integers should interpolate seamlessly.

Template:

"{{.}} miles an hour!"

Data:

85

Expected:

"85 miles an hour!"

Output:

"85 miles an hour!"

✅ Interpolation - Surrounding Whitespace

Interpolation should not alter surrounding whitespace.

Template:

| {{string}} |

Data:

{"string":"---"}

Expected:

| --- |

Output:

| --- |

✅ Triple Mustache - Surrounding Whitespace

Interpolation should not alter surrounding whitespace.

Template:

| {{{string}}} |

Data:

{"string":"---"}

Expected:

| --- |

Output:

| --- |

✅ Ampersand - Surrounding Whitespace

Interpolation should not alter surrounding whitespace.

Template:

| {{&string}} |

Data:

{"string":"---"}

Expected:

| --- |

Output:

| --- |

✅ Interpolation - Standalone

Standalone interpolation should not alter surrounding whitespace.

Template:

  {{string}}

Data:

{"string":"---"}

Expected:

  ---

Output:

  ---

✅ Triple Mustache - Standalone

Standalone interpolation should not alter surrounding whitespace.

Template:

  {{{string}}}

Data:

{"string":"---"}

Expected:

  ---

Output:

  ---

✅ Ampersand - Standalone

Standalone interpolation should not alter surrounding whitespace.

Template:

  {{&string}}

Data:

{"string":"---"}

Expected:

  ---

Output:

  ---

✅ Interpolation With Padding

Superfluous in-tag whitespace should be ignored.

Template:

|{{ string }}|

Data:

{"string":"---"}

Expected:

|---|

Output:

|---|

✅ Triple Mustache With Padding

Superfluous in-tag whitespace should be ignored.

Template:

|{{{ string }}}|

Data:

{"string":"---"}

Expected:

|---|

Output:

|---|

✅ Ampersand With Padding

Superfluous in-tag whitespace should be ignored.

Template:

|{{& string }}|

Data:

{"string":"---"}

Expected:

|---|

Output:

|---|

sections.json

Section tags and End Section tags are used in combination to wrap a section of the template for iteration

These tags' content MUST be a non-whitespace character sequence NOT containing the current closing delimiter; each Section tag MUST be followed by an End Section tag with the same content within the same section.

This tag's content names the data to replace the tag. Name resolution is as follows:

  1. Split the name on periods; the first part is the name to resolve, any remaining parts should be retained.
  2. Walk the context stack from top to bottom, finding the first context that is a) a hash containing the name as a key OR b) an object responding to a method with the given name.
  3. If the context is a hash, the data is the value associated with the name.
  4. If the context is an object and the method with the given name has an arity of 1, the method SHOULD be called with a String containing the unprocessed contents of the sections; the data is the value returned.
  5. Otherwise, the data is the value returned by calling the method with the given name.
  6. If any name parts were retained in step 1, each should be resolved against a context stack containing only the result from the former resolution. If any part fails resolution, the result should be considered falsey, and should interpolate as the empty string. If the data is not of a list type, it is coerced into a list as follows: if the data is truthy (e.g. !!data == true), use a single-element list containing the data, otherwise use an empty list.

For each element in the data list, the element MUST be pushed onto the context stack, the section MUST be rendered, and the element MUST be popped off the context stack.

Section and End Section tags SHOULD be treated as standalone when appropriate.

✅ Truthy

Truthy sections should have their contents rendered.

Template:

"{{#boolean}}This should be rendered.{{/boolean}}"

Data:

{"boolean":true}

Expected:

"This should be rendered."

Output:

"This should be rendered."

✅ Falsey

Falsey sections should have their contents omitted.

Template:

"{{#boolean}}This should not be rendered.{{/boolean}}"

Data:

{"boolean":false}

Expected:

""

Output:

""

✅ Null is falsey

Null is falsey.

Template:

"{{#null}}This should not be rendered.{{/null}}"

Data:

{"null":null}

Expected:

""

Output:

""

✅ Context

Objects and hashes should be pushed onto the context stack.

Template:

"{{#context}}Hi {{name}}.{{/context}}"

Data:

{"context":{"name":"Joe"}}

Expected:

"Hi Joe."

Output:

"Hi Joe."

✅ Parent contexts

Names missing in the current context are looked up in the stack.

Template:

"{{#sec}}{{a}}, {{b}}, {{c.d}}{{/sec}}"

Data:

{"a":"foo","b":"wrong","sec":{"b":"bar"},"c":{"d":"baz"}}

Expected:

"foo, bar, baz"

Output:

"foo, bar, baz"

✅ Variable test

Non-false sections have their value at the top of context, accessible as {{.}} or through the parent context. This gives a simple way to display content conditionally if a variable exists.

Template:

"{{#foo}}{{.}} is {{foo}}{{/foo}}"

Data:

{"foo":"bar"}

Expected:

"bar is bar"

Output:

"bar is bar"

✅ List Contexts

All elements on the context stack should be accessible within lists.

Template:

{{#tops}}{{#middles}}{{tname.lower}}{{mname}}.{{#bottoms}}{{tname.upper}}{{mname}}{{bname}}.{{/bottoms}}{{/middles}}{{/tops}}

Data:

{"tops":[{"tname":{"upper":"A","lower":"a"},"middles":[{"mname":"1","bottoms":[{"bname":"x"},{"bname":"y"}]}]}]}

Expected:

a1.A1x.A1y.

Output:

a1.A1x.A1y.

✅ Deeply Nested Contexts

All elements on the context stack should be accessible.

Template:

{{#a}}
{{one}}
{{#b}}
{{one}}{{two}}{{one}}
{{#c}}
{{one}}{{two}}{{three}}{{two}}{{one}}
{{#d}}
{{one}}{{two}}{{three}}{{four}}{{three}}{{two}}{{one}}
{{#five}}
{{one}}{{two}}{{three}}{{four}}{{five}}{{four}}{{three}}{{two}}{{one}}
{{one}}{{two}}{{three}}{{four}}{{.}}6{{.}}{{four}}{{three}}{{two}}{{one}}
{{one}}{{two}}{{three}}{{four}}{{five}}{{four}}{{three}}{{two}}{{one}}
{{/five}}
{{one}}{{two}}{{three}}{{four}}{{three}}{{two}}{{one}}
{{/d}}
{{one}}{{two}}{{three}}{{two}}{{one}}
{{/c}}
{{one}}{{two}}{{one}}
{{/b}}
{{one}}
{{/a}}

Data:

{"a":{"one":1},"b":{"two":2},"c":{"three":3,"d":{"four":4,"five":5}}}

Expected:

1
121
12321
1234321
123454321
12345654321
123454321
1234321
12321
121
1

Output:

1
121
12321
1234321
123454321
12345654321
123454321
1234321
12321
121
1

✅ List

Lists should be iterated; list items should visit the context stack.

Template:

"{{#list}}{{item}}{{/list}}"

Data:

{"list":[{"item":1},{"item":2},{"item":3}]}

Expected:

"123"

Output:

"123"

✅ Empty List

Empty lists should behave like falsey values.

Template:

"{{#list}}Yay lists!{{/list}}"

Data:

{"list":[]}

Expected:

""

Output:

""

✅ Doubled

Multiple sections per template should be permitted.

Template:

{{#bool}}
* first
{{/bool}}
* {{two}}
{{#bool}}
* third
{{/bool}}

Data:

{"bool":true,"two":"second"}

Expected:

* first
* second
* third

Output:

* first
* second
* third

✅ Nested (Truthy)

Nested truthy sections should have their contents rendered.

Template:

| A {{#bool}}B {{#bool}}C{{/bool}} D{{/bool}} E |

Data:

{"bool":true}

Expected:

| A B C D E |

Output:

| A B C D E |

✅ Nested (Falsey)

Nested falsey sections should be omitted.

Template:

| A {{#bool}}B {{#bool}}C{{/bool}} D{{/bool}} E |

Data:

{"bool":false}

Expected:

| A  E |

Output:

| A  E |

✅ Context Misses

Failed context lookups should be considered falsey.

Template:

[{{#missing}}Found key 'missing'!{{/missing}}]

Data:

{}

Expected:

[]

Output:

[]

✅ Implicit Iterator - String

Implicit iterators should directly interpolate strings.

Template:

"{{#list}}({{.}}){{/list}}"

Data:

{"list":["a","b","c","d","e"]}

Expected:

"(a)(b)(c)(d)(e)"

Output:

"(a)(b)(c)(d)(e)"

✅ Implicit Iterator - Integer

Implicit iterators should cast integers to strings and interpolate.

Template:

"{{#list}}({{.}}){{/list}}"

Data:

{"list":[1,2,3,4,5]}

Expected:

"(1)(2)(3)(4)(5)"

Output:

"(1)(2)(3)(4)(5)"

✅ Implicit Iterator - Decimal

Implicit iterators should cast decimals to strings and interpolate.

Template:

"{{#list}}({{.}}){{/list}}"

Data:

{"list":[1.1,2.2,3.3,4.4,5.5]}

Expected:

"(1.1)(2.2)(3.3)(4.4)(5.5)"

Output:

"(1.1)(2.2)(3.3)(4.4)(5.5)"

✅ Implicit Iterator - Array

Implicit iterators should allow iterating over nested arrays.

Template:

"{{#list}}({{#.}}{{.}}{{/.}}){{/list}}"

Data:

{"list":[[1,2,3],["a","b","c"]]}

Expected:

"(123)(abc)"

Output:

"(123)(abc)"

✅ Dotted Names - Truthy

Dotted names should be valid for Section tags.

Template:

"{{#a.b.c}}Here{{/a.b.c}}" == "Here"

Data:

{"a":{"b":{"c":true}}}

Expected:

"Here" == "Here"

Output:

"Here" == "Here"

✅ Dotted Names - Falsey

Dotted names should be valid for Section tags.

Template:

"{{#a.b.c}}Here{{/a.b.c}}" == ""

Data:

{"a":{"b":{"c":false}}}

Expected:

"" == ""

Output:

"" == ""

✅ Dotted Names - Broken Chains

Dotted names that cannot be resolved should be considered falsey.

Template:

"{{#a.b.c}}Here{{/a.b.c}}" == ""

Data:

{"a":{}}

Expected:

"" == ""

Output:

"" == ""

✅ Surrounding Whitespace

Sections should not alter surrounding whitespace.

Template:

 | {{#boolean}}	|	{{/boolean}} | 

Data:

{"boolean":true}

Expected:

 | 	|	 | 

Output:

 | 	|	 | 

✅ Internal Whitespace

Sections should not alter internal whitespace.

Template:

 | {{#boolean}} {{! Important Whitespace }}
 {{/boolean}} | 

Data:

{"boolean":true}

Expected:

 |  
  | 

Output:

 |  
  | 

✅ Indented Inline Sections

Single-line sections should not alter surrounding whitespace.

Template:

 {{#boolean}}YES{{/boolean}}
 {{#boolean}}GOOD{{/boolean}}

Data:

{"boolean":true}

Expected:

 YES
 GOOD

Output:

 YES
 GOOD

✅ Standalone Lines

Standalone lines should be removed from the template.

Template:

| This Is
{{#boolean}}
|
{{/boolean}}
| A Line

Data:

{"boolean":true}

Expected:

| This Is
|
| A Line

Output:

| This Is
|
| A Line

✅ Indented Standalone Lines

Indented standalone lines should be removed from the template.

Template:

| This Is
  {{#boolean}}
|
  {{/boolean}}
| A Line

Data:

{"boolean":true}

Expected:

| This Is
|
| A Line

Output:

| This Is
|
| A Line

✅ Standalone Line Endings

"\r\n" should be considered a newline for standalone tags.

Template:

|
{{#boolean}}
{{/boolean}}
|

Data:

{"boolean":true}

Expected:

|
|

Output:

|
|

✅ Standalone Without Previous Line

Standalone tags should not require a newline to precede them.

Template:

  {{#boolean}}
#{{/boolean}}
/

Data:

{"boolean":true}

Expected:

#
/

Output:

#
/

✅ Standalone Without Newline

Standalone tags should not require a newline to follow them.

Template:

#{{#boolean}}
/
  {{/boolean}}

Data:

{"boolean":true}

Expected:

#
/

Output:

#
/

✅ Padding

Superfluous in-tag whitespace should be ignored.

Template:

|{{# boolean }}={{/ boolean }}|

Data:

{"boolean":true}

Expected:

|=|

Output:

|=|

inverted.json

Inverted Section tags and End Section tags are used in combination to wrap a section of the template.

These tags' content MUST be a non-whitespace character sequence NOT containing the current closing delimiter; each Inverted Section tag MUST be followed by an End Section tag with the same content within the same section.

This tag's content names the data to replace the tag. Name resolution is as follows:

  1. Split the name on periods; the first part is the name to resolve, any remaining parts should be retained.
  2. Walk the context stack from top to bottom, finding the first context that is a) a hash containing the name as a key OR b) an object responding to a method with the given name.
  3. If the context is a hash, the data is the value associated with the name.
  4. If the context is an object and the method with the given name has an arity of 1, the method SHOULD be called with a String containing the unprocessed contents of the sections; the data is the value returned.
  5. Otherwise, the data is the value returned by calling the method with the given name.
  6. If any name parts were retained in step 1, each should be resolved against a context stack containing only the result from the former resolution. If any part fails resolution, the result should be considered falsey, and should interpolate as the empty string. If the data is not of a list type, it is coerced into a list as follows: if the data is truthy (e.g. !!data == true), use a single-element list containing the data, otherwise use an empty list.

This section MUST NOT be rendered unless the data list is empty.

Inverted Section and End Section tags SHOULD be treated as standalone when appropriate.

✅ Falsey

Falsey sections should have their contents rendered.

Template:

"{{^boolean}}This should be rendered.{{/boolean}}"

Data:

{"boolean":false}

Expected:

"This should be rendered."

Output:

"This should be rendered."

✅ Truthy

Truthy sections should have their contents omitted.

Template:

"{{^boolean}}This should not be rendered.{{/boolean}}"

Data:

{"boolean":true}

Expected:

""

Output:

""

✅ Null is falsey

Null is falsey.

Template:

"{{^null}}This should be rendered.{{/null}}"

Data:

{"null":null}

Expected:

"This should be rendered."

Output:

"This should be rendered."

✅ Context

Objects and hashes should behave like truthy values.

Template:

"{{^context}}Hi {{name}}.{{/context}}"

Data:

{"context":{"name":"Joe"}}

Expected:

""

Output:

""

✅ List

Lists should behave like truthy values.

Template:

"{{^list}}{{n}}{{/list}}"

Data:

{"list":[{"n":1},{"n":2},{"n":3}]}

Expected:

""

Output:

""

✅ Empty List

Empty lists should behave like falsey values.

Template:

"{{^list}}Yay lists!{{/list}}"

Data:

{"list":[]}

Expected:

"Yay lists!"

Output:

"Yay lists!"

✅ Doubled

Multiple inverted sections per template should be permitted.

Template:

{{^bool}}
* first
{{/bool}}
* {{two}}
{{^bool}}
* third
{{/bool}}

Data:

{"bool":false,"two":"second"}

Expected:

* first
* second
* third

Output:

* first
* second
* third

✅ Nested (Falsey)

Nested falsey sections should have their contents rendered.

Template:

| A {{^bool}}B {{^bool}}C{{/bool}} D{{/bool}} E |

Data:

{"bool":false}

Expected:

| A B C D E |

Output:

| A B C D E |

✅ Nested (Truthy)

Nested truthy sections should be omitted.

Template:

| A {{^bool}}B {{^bool}}C{{/bool}} D{{/bool}} E |

Data:

{"bool":true}

Expected:

| A  E |

Output:

| A  E |

✅ Context Misses

Failed context lookups should be considered falsey.

Template:

[{{^missing}}Cannot find key 'missing'!{{/missing}}]

Data:

{}

Expected:

[Cannot find key 'missing'!]

Output:

[Cannot find key 'missing'!]

✅ Dotted Names - Truthy

Dotted names should be valid for Inverted Section tags.

Template:

"{{^a.b.c}}Not Here{{/a.b.c}}" == ""

Data:

{"a":{"b":{"c":true}}}

Expected:

"" == ""

Output:

"" == ""

✅ Dotted Names - Falsey

Dotted names should be valid for Inverted Section tags.

Template:

"{{^a.b.c}}Not Here{{/a.b.c}}" == "Not Here"

Data:

{"a":{"b":{"c":false}}}

Expected:

"Not Here" == "Not Here"

Output:

"Not Here" == "Not Here"

✅ Dotted Names - Broken Chains

Dotted names that cannot be resolved should be considered falsey.

Template:

"{{^a.b.c}}Not Here{{/a.b.c}}" == "Not Here"

Data:

{"a":{}}

Expected:

"Not Here" == "Not Here"

Output:

"Not Here" == "Not Here"

✅ Surrounding Whitespace

Inverted sections should not alter surrounding whitespace.

Template:

 | {{^boolean}}	|	{{/boolean}} | 

Data:

{"boolean":false}

Expected:

 | 	|	 | 

Output:

 | 	|	 | 

✅ Internal Whitespace

Inverted should not alter internal whitespace.

Template:

 | {{^boolean}} {{! Important Whitespace }}
 {{/boolean}} | 

Data:

{"boolean":false}

Expected:

 |  
  | 

Output:

 |  
  | 

✅ Indented Inline Sections

Single-line sections should not alter surrounding whitespace.

Template:

 {{^boolean}}NO{{/boolean}}
 {{^boolean}}WAY{{/boolean}}

Data:

{"boolean":false}

Expected:

 NO
 WAY

Output:

 NO
 WAY

✅ Standalone Lines

Standalone lines should be removed from the template.

Template:

| This Is
{{^boolean}}
|
{{/boolean}}
| A Line

Data:

{"boolean":false}

Expected:

| This Is
|
| A Line

Output:

| This Is
|
| A Line

✅ Standalone Indented Lines

Standalone indented lines should be removed from the template.

Template:

| This Is
  {{^boolean}}
|
  {{/boolean}}
| A Line

Data:

{"boolean":false}

Expected:

| This Is
|
| A Line

Output:

| This Is
|
| A Line

✅ Standalone Line Endings

"\r\n" should be considered a newline for standalone tags.

Template:

|
{{^boolean}}
{{/boolean}}
|

Data:

{"boolean":false}

Expected:

|
|

Output:

|
|

✅ Standalone Without Previous Line

Standalone tags should not require a newline to precede them.

Template:

  {{^boolean}}
^{{/boolean}}
/

Data:

{"boolean":false}

Expected:

^
/

Output:

^
/

✅ Standalone Without Newline

Standalone tags should not require a newline to follow them.

Template:

^{{^boolean}}
/
  {{/boolean}}

Data:

{"boolean":false}

Expected:

^
/

Output:

^
/

✅ Padding

Superfluous in-tag whitespace should be ignored.

Template:

|{{^ boolean }}={{/ boolean }}|

Data:

{"boolean":false}

Expected:

|=|

Output:

|=|

comments.json

Comment tags represent content that should never appear in the resulting output.

The tag's content may contain any substring (including newlines) EXCEPT the closing delimiter.

Comment tags SHOULD be treated as standalone when appropriate.

✅ Inline

Comment blocks should be removed from the template.

Template:

12345{{! Comment Block! }}67890

Data:

{}

Expected:

1234567890

Output:

1234567890

✅ Multiline

Multiline comments should be permitted.

Template:

12345{{!
  This is a
  multi-line comment...
}}67890

Data:

{}

Expected:

1234567890

Output:

1234567890

✅ Standalone

All standalone comment lines should be removed.

Template:

Begin.
{{! Comment Block! }}
End.

Data:

{}

Expected:

Begin.
End.

Output:

Begin.
End.

✅ Indented Standalone

All standalone comment lines should be removed.

Template:

Begin.
  {{! Indented Comment Block! }}
End.

Data:

{}

Expected:

Begin.
End.

Output:

Begin.
End.

✅ Standalone Line Endings

"\r\n" should be considered a newline for standalone tags.

Template:

|
{{! Standalone Comment }}
|

Data:

{}

Expected:

|
|

Output:

|
|

✅ Standalone Without Previous Line

Standalone tags should not require a newline to precede them.

Template:

  {{! I'm Still Standalone }}
!

Data:

{}

Expected:

!

Output:

!

✅ Standalone Without Newline

Standalone tags should not require a newline to follow them.

Template:

!
  {{! I'm Still Standalone }}

Data:

{}

Expected:

!

Output:

!

✅ Multiline Standalone

All standalone comment lines should be removed.

Template:

Begin.
{{!
Something's going on here...
}}
End.

Data:

{}

Expected:

Begin.
End.

Output:

Begin.
End.

✅ Indented Multiline Standalone

All standalone comment lines should be removed.

Template:

Begin.
  {{!
    Something's going on here...
  }}
End.

Data:

{}

Expected:

Begin.
End.

Output:

Begin.
End.

✅ Indented Inline

Inline comments should not strip whitespace

Template:

  12 {{! 34 }}

Data:

{}

Expected:

  12 

Output:

  12 

✅ Surrounding Whitespace

Comment removal should preserve surrounding whitespace.

Template:

12345 {{! Comment Block! }} 67890

Data:

{}

Expected:

12345  67890

Output:

12345  67890

delimiters.json

Set Delimiter tags are used to change the tag delimiters for all content following the tag in the current compilation unit.

The tag's content MUST be any two non-whitespace sequences (separated by whitespace) EXCEPT an equals sign ('=') followed by the current closing delimiter.

Set Delimiter tags SHOULD be treated as standalone when appropriate.

✅ Pair Behavior

The equals sign (used on both sides) should permit delimiter changes.

Template:

{{=<% %>=}}(<%text%>)

Data:

{"text":"Hey!"}

Expected:

(Hey!)

Output:

(Hey!)

✅ Special Characters

Characters with special meaning regexen should be valid delimiters.

Template:

({{=[ ]=}}[text])

Data:

{"text":"It worked!"}

Expected:

(It worked!)

Output:

(It worked!)

✅ Sections

Delimiters set outside sections should persist.

Template:

[
{{#section}}
  {{data}}
  |data|
{{/section}}

{{= | | =}}
|#section|
  {{data}}
  |data|
|/section|
]

Data:

{"section":true,"data":"I got interpolated."}

Expected:

[
  I got interpolated.
  |data|

  {{data}}
  I got interpolated.
]

Output:

[
  I got interpolated.
  |data|

  {{data}}
  I got interpolated.
]

✅ Inverted Sections

Delimiters set outside inverted sections should persist.

Template:

[
{{^section}}
  {{data}}
  |data|
{{/section}}

{{= | | =}}
|^section|
  {{data}}
  |data|
|/section|
]

Data:

{"section":false,"data":"I got interpolated."}

Expected:

[
  I got interpolated.
  |data|

  {{data}}
  I got interpolated.
]

Output:

[
  I got interpolated.
  |data|

  {{data}}
  I got interpolated.
]

✅ Partial Inheritence

Delimiters set in a parent template should not affect a partial.

Template:

[ {{>include}} ]
{{= | | =}}
[ |>include| ]

Data:

{"value":"yes"}

Partials:

include: ".{{value}}."

Expected:

[ .yes. ]
[ .yes. ]

Output:

[ .yes. ]
[ .yes. ]

✅ Post-Partial Behavior

Delimiters set in a partial should not affect the parent template.

Template:

[ {{>include}} ]
[ .{{value}}.  .|value|. ]

Data:

{"value":"yes"}

Partials:

include: ".{{value}}. {{= | | =}} .|value|."

Expected:

[ .yes.  .yes. ]
[ .yes.  .|value|. ]

Output:

[ .yes.  .yes. ]
[ .yes.  .|value|. ]

✅ Surrounding Whitespace

Surrounding whitespace should be left untouched.

Template:

| {{=@ @=}} |

Data:

{}

Expected:

|  |

Output:

|  |

✅ Outlying Whitespace (Inline)

Whitespace should be left untouched.

Template:

 | {{=@ @=}}

Data:

{}

Expected:

 | 

Output:

 | 

✅ Standalone Tag

Standalone lines should be removed from the template.

Template:

Begin.
{{=@ @=}}
End.

Data:

{}

Expected:

Begin.
End.

Output:

Begin.
End.

✅ Indented Standalone Tag

Indented standalone lines should be removed from the template.

Template:

Begin.
  {{=@ @=}}
End.

Data:

{}

Expected:

Begin.
End.

Output:

Begin.
End.

✅ Standalone Line Endings

"\r\n" should be considered a newline for standalone tags.

Template:

|
{{= @ @ =}}
|

Data:

{}

Expected:

|
|

Output:

|
|

✅ Standalone Without Previous Line

Standalone tags should not require a newline to precede them.

Template:

  {{=@ @=}}
=

Data:

{}

Expected:

=

Output:

=

✅ Standalone Without Newline

Standalone tags should not require a newline to follow them.

Template:

=
  {{=@ @=}}

Data:

{}

Expected:

=

Output:

=

✅ Pair with Padding

Superfluous in-tag whitespace should be ignored.

Template:

|{{= @   @ =}}|

Data:

{}

Expected:

||

Output:

||

partials.json

Partial tags are used to expand an external template into the current template.

The tag's content MUST be a non-whitespace character sequence NOT containing the current closing delimiter.

This tag's content names the partial to inject. Set Delimiter tags MUST NOT affect the parsing of a partial. The partial MUST be rendered against the context stack local to the tag. If the named partial cannot be found, the empty string SHOULD be used instead, as in interpolations.

Partial tags SHOULD be treated as standalone when appropriate. If this tag is used standalone, any whitespace preceding the tag should treated as indentation, and prepended to each line of the partial before rendering.

✅ Basic Behavior

The greater-than operator should expand to the named partial.

Template:

"{{>text}}"

Data:

{}

Partials:

text: "from partial"

Expected:

"from partial"

Output:

"from partial"

✅ Failed Lookup

The empty string should be used when the named partial is not found.

Template:

"{{>text}}"

Data:

{}

Partials:

Expected:

""

Output:

""

✅ Context

The greater-than operator should operate within the current context.

Template:

"{{>partial}}"

Data:

{"text":"content"}

Partials:

partial: "*{{text}}*"

Expected:

"*content*"

Output:

"*content*"

✅ Recursion

The greater-than operator should properly recurse.

Template:

{{>node}}

Data:

{"content":"X","nodes":[{"content":"Y","nodes":[]}]}

Partials:

node: "{{content}}<{{#nodes}}{{>node}}{{/nodes}}>"

Expected:

X<Y<>>

Output:

X<Y<>>

✅ Surrounding Whitespace

The greater-than operator should not alter surrounding whitespace.

Template:

| {{>partial}} |

Data:

{}

Partials:

partial: "\t|\t"

Expected:

| 	|	 |

Output:

| 	|	 |

✅ Inline Indentation

Whitespace should be left untouched.

Template:

  {{data}}  {{> partial}}

Data:

{"data":"|"}

Partials:

partial: ">\n>"

Expected:

  |  >
>

Output:

  |  >
>

✅ Standalone Line Endings

"\r\n" should be considered a newline for standalone tags.

Template:

|
{{>partial}}
|

Data:

{}

Partials:

partial: ">"

Expected:

|
>|

Output:

|
>|

✅ Standalone Without Previous Line

Standalone tags should not require a newline to precede them.

Template:

  {{>partial}}
>

Data:

{}

Partials:

partial: ">\n>"

Expected:

  >
  >>

Output:

  >
  >>

✅ Standalone Without Newline

Standalone tags should not require a newline to follow them.

Template:

>
  {{>partial}}

Data:

{}

Partials:

partial: ">\n>"

Expected:

>
  >
  >

Output:

>
  >
  >

✅ Standalone Indentation

Each line of the partial should be indented before rendering.

Template:

\
 {{>partial}}
/

Data:

{"content":"<\n->"}

Partials:

partial: "|\n{{{content}}}\n|\n"

Expected:

\
 |
 <
->
 |
/

Output:

\
 |
 <
->
 |
/

✅ Padding Whitespace

Superfluous in-tag whitespace should be ignored.

Template:

|{{> partial }}|

Data:

{"boolean":true}

Partials:

partial: "[]"

Expected:

|[]|

Output:

|[]|

~inheritance.json

Like partials, Parent tags are used to expand an external template into the current template. Unlike partials, Parent tags may contain optional arguments delimited by Block tags. For this reason, Parent tags may also be referred to as Parametric Partials.

The Parent tags' content MUST be a non-whitespace character sequence NOT containing the current closing delimiter; each Parent tag MUST be followed by an End Section tag with the same content within the matching Parent tag.

This tag's content names the Parent template to inject. Set Delimiter tags Preceding a Parent tag MUST NOT affect the parsing of the injected external template. The Parent MUST be rendered against the context stack local to the tag. If the named Parent cannot be found, the empty string SHOULD be used instead, as in interpolations.

Parent tags SHOULD be treated as standalone when appropriate. If this tag is used standalone, any whitespace preceding the tag should be treated as indentation, and prepended to each line of the Parent before rendering.

The Block tags' content MUST be a non-whitespace character sequence NOT containing the current closing delimiter. Each Block tag MUST be followed by an End Section tag with the same content within the matching Block tag. This tag's content determines the parameter or argument name.

Block tags may appear both inside and outside of Parent tags. In both cases, they specify a position within the template that can be overridden; it is a parameter of the containing template. The template text between the Block tag and its matching End Section tag defines the default content to render when the parameter is not overridden from outside.

In addition, when used inside of a Parent tag, the template text between a Block tag and its matching End Section tag defines content that replaces the default defined in the Parent template. This content is the argument passed to the Parent template.

The practice of injecting an external template using a Parent tag is referred to as inheritance. If the Parent tag includes a Block tag that overrides a parameter of the Parent template, this may also be referred to as substitution.

Parent templates are taken from the same namespace as regular Partial templates and in fact, injecting a regular Partial is exactly equivalent to injecting a Parent without making any substitutions. Parameter and arguments names live in a namespace that is distinct from both Partials and the context.

✅ Default

Default content should be rendered if the block isn't overridden

Template:

{{$title}}Default title{{/title}}

Data:

{}

Expected:

Default title

Output:

Default title

✅ Variable

Default content renders variables

Template:

{{$foo}}default {{bar}} content{{/foo}}

Data:

{"bar":"baz"}

Expected:

default baz content

Output:

default baz content

✅ Triple Mustache

Default content renders triple mustache variables

Template:

{{$foo}}default {{{bar}}} content{{/foo}}

Data:

{"bar":"<baz>"}

Expected:

default <baz> content

Output:

default <baz> content

✅ Sections

Default content renders sections

Template:

{{$foo}}default {{#bar}}{{baz}}{{/bar}} content{{/foo}}

Data:

{"bar":{"baz":"qux"}}

Expected:

default qux content

Output:

default qux content

✅ Negative Sections

Default content renders negative sections

Template:

{{$foo}}default {{^bar}}{{baz}}{{/bar}} content{{/foo}}

Data:

{"baz":"three"}

Expected:

default three content

Output:

default three content

✅ Mustache Injection

Mustache injection in default content

Template:

{{$foo}}default {{#bar}}{{baz}}{{/bar}} content{{/foo}}

Data:

{"bar":{"baz":"{{qux}}"}}

Expected:

default {{qux}} content

Output:

default {{qux}} content

✅ Inherit

Default content rendered inside inherited templates

Template:

{{<include}}{{/include}}

Data:

{}

Partials:

include: "{{$foo}}default content{{/foo}}"

Expected:

default content

Output:

default content

✅ Overridden content

Overridden content

Template:

{{<super}}{{$title}}sub template title{{/title}}{{/super}}

Data:

{}

Partials:

super: "...{{$title}}Default title{{/title}}..."

Expected:

...sub template title...

Output:

...sub template title...

✅ Data does not override block

Context does not override argument passed into parent

Template:

{{<include}}{{$var}}var in template{{/var}}{{/include}}

Data:

{"var":"var in data"}

Partials:

include: "{{$var}}var in include{{/var}}"

Expected:

var in template

Output:

var in template

✅ Data does not override block default

Context does not override default content of block

Template:

{{<include}}{{/include}}

Data:

{"var":"var in data"}

Partials:

include: "{{$var}}var in include{{/var}}"

Expected:

var in include

Output:

var in include

✅ Overridden parent

Overridden parent

Template:

test {{<parent}}{{$stuff}}override{{/stuff}}{{/parent}}

Data:

{}

Partials:

parent: "{{$stuff}}...{{/stuff}}"

Expected:

test override

Output:

test override

✅ Two overridden parents

Two overridden parents with different content

Template:

test {{<parent}}{{$stuff}}override1{{/stuff}}{{/parent}} {{<parent}}{{$stuff}}override2{{/stuff}}{{/parent}}

Data:

{}

Partials:

parent: "|{{$stuff}}...{{/stuff}}{{$default}} default{{/default}}|"

Expected:

test |override1 default| |override2 default|

Output:

test |override1 default| |override2 default|

✅ Override parent with newlines

Override parent with newlines

Template:

{{<parent}}{{$ballmer}}
peaked

:(
{{/ballmer}}{{/parent}}

Data:

{}

Partials:

parent: "{{$ballmer}}peaking{{/ballmer}}"

Expected:

peaked

:(

Output:

peaked

:(

✅ Inherit indentation

Inherit indentation when overriding a parent

Template:

{{<parent}}{{$nineties}}hammer time{{/nineties}}{{/parent}}

Data:

{}

Partials:

parent: "stop:\n  {{$nineties}}collaborate and listen{{/nineties}}\n"

Expected:

stop:
  hammer time

Output:

stop:
  hammer time

✅ Only one override

Override one parameter but not the other

Template:

{{<parent}}{{$stuff2}}override two{{/stuff2}}{{/parent}}

Data:

{}

Partials:

parent: "{{$stuff}}new default one{{/stuff}}, {{$stuff2}}new default two{{/stuff2}}"

Expected:

new default one, override two

Output:

new default one, override two

✅ Parent template

Parent templates behave identically to partials when called with no parameters

Template:

{{>parent}}|{{<parent}}{{/parent}}

Data:

{}

Partials:

parent: "{{$foo}}default content{{/foo}}"

Expected:

default content|default content

Output:

default content|default content

✅ Recursion

Recursion in inherited templates

Template:

{{<parent}}{{$foo}}override{{/foo}}{{/parent}}

Data:

{}

Partials:

parent: "{{$foo}}default content{{/foo}} {{$bar}}{{<parent2}}{{/parent2}}{{/bar}}"
parent2: "{{$foo}}parent2 default content{{/foo}} {{<parent}}{{$bar}}don't recurse{{/bar}}{{/parent}}"

Expected:

override override override don't recurse

Output:

override override override don't recurse

✅ Multi-level inheritance

Top-level substitutions take precedence in multi-level inheritance

Template:

{{<parent}}{{$a}}c{{/a}}{{/parent}}

Data:

{}

Partials:

parent: "{{<older}}{{$a}}p{{/a}}{{/older}}"
older: "{{<grandParent}}{{$a}}o{{/a}}{{/grandParent}}"
grandParent: "{{$a}}g{{/a}}"

Expected:

c

Output:

c

✅ Multi-level inheritance, no sub child

Top-level substitutions take precedence in multi-level inheritance

Template:

{{<parent}}{{/parent}}

Data:

{}

Partials:

parent: "{{<older}}{{$a}}p{{/a}}{{/older}}"
older: "{{<grandParent}}{{$a}}o{{/a}}{{/grandParent}}"
grandParent: "{{$a}}g{{/a}}"

Expected:

p

Output:

p

✅ Text inside parent

Ignores text inside parent templates, but does parse $ tags

Template:

{{<parent}} asdfasd {{$foo}}hmm{{/foo}} asdfasdfasdf {{/parent}}

Data:

{}

Partials:

parent: "{{$foo}}default content{{/foo}}"

Expected:

hmm

Output:

hmm

✅ Text inside parent

Allows text inside a parent tag, but ignores it

Template:

{{<parent}} asdfasd asdfasdfasdf {{/parent}}

Data:

{}

Partials:

parent: "{{$foo}}default content{{/foo}}"

Expected:

default content

Output:

default content
import nimib, os

nbInit
nbDoc.darkMode
setCurrentDir nbThisDir.string
nbText: """ # Mustache Specs

Autogenerate a document containing the results of [nim-mustache](https://github.com/soasme/nim-mustache)
running [mustache specs v1.2.1](https://github.com/mustache/spec/releases/tag/v1.2.1).

"""# I should use [jsony](https://github.com/treeform/jsony)
nbCode:
  import mustache
  import json
  import strformat
  import tables
  
  const specfiles = ["interpolation.json", "sections.json", "inverted.json", "comments.json", "delimiters.json", "partials.json", "~inheritance.json"]
  
  var
    specs = newJObject()
    tmpl: string
    tests: JsonNode
    data: JsonNode
    context: Context
    partials: Table[string, string]

  for specfile in specfiles:
    echo "📃 " & specfile
    specs[specfile] = parseFile("mustache-specs/" & specfile)
    tests = specs[specfile]["tests"]
    assert tests.kind == JArray
    for test in tests.mitems:
      assert test.kind == JObject
      tmpl = test["template"].getStr
      data = test["data"]
      partials = initTable[string, string]()
      if "partials" in test:
        for key, val in test["partials"]:
          partials[key] = val.getStr
      context = newContext(searchDirs = @[], partials=partials, values=data.toValues)
      test["output"] = % tmpl.render(context)
      if test["output"].getStr == test["expected"].getStr:
        test["ok"] = % "✅"
        echo "  ✅ ", test["name"].getStr
      else:
        test["ok"] = % "❌"
        echo "  ❌ ", test["name"].getStr

  proc specsToMarkdown(specs: JsonNode): string =
    var partials: string
    for specfile, spec in specs.pairs:
      result.add "## " & specfile & "\n\n"
      result.add spec["overview"].getStr & "\n"
      for test in spec["tests"]:
        partials = ""
        if "partials" in test:
          assert test["partials"].kind == JObject
          partials = "Partials:\n\n```\n"
          for key, val in test["partials"]:
            partials.add &"{key}: {$val}\n"
          partials.add "```\n"
        result.add fmt"""
### {test["ok"].getStr} {test["name"].getStr}

{test["desc"].getStr}

Template:

```
{test["template"].getStr}
```

Data:

```
{$(test["data"])}
```

{partials}Expected:

```
{test["expected"].getStr}
```

Output:

```
{test["output"].getStr}
```

"""

nbText: specs.specsToMarkdown

nbSave