Skip to content

Commit

Permalink
add partials support
Browse files Browse the repository at this point in the history
  • Loading branch information
fenekku committed Jan 16, 2016
1 parent f8198e6 commit ea36948
Show file tree
Hide file tree
Showing 5 changed files with 170 additions and 7 deletions.
16 changes: 13 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,16 @@ $ moustachu <context>.json <template>.moustache --file=<output>

The first version will print to stdout and the second will generate a file.

##Compliance

Moustachu supports the specs found in its specs directory:

- comments
- interpolation
- inverted
- partials
- sections

##Installation

The recommended way to install moustachu is through [nimble](https://github.com/nim-lang/nimble):
Expand All @@ -54,7 +64,7 @@ The moustachu package includes the moustachu binary to use on the command line a
##Design

- Make the interfaces with the data structures as dynamic-like as possible
- No lambdas, nor partials, nor set delimiters. At least for now. Let's keep it simple please.
- No lambdas, nor set delimiters. At least for now. Let's keep it simple please.

##Test

Expand All @@ -68,8 +78,8 @@ This will test against the selected specs copied from [mustache/spec](https://gi

##TODO

- lots of code refactorings
- lots of code refactorings: use a tokenizer + state machine approach
- assumes well-formed template: remove that assumption
- Exception throwing toggle
- make faster
- Use to see what else to do/fix
- Use to see what else to do/fix
25 changes: 22 additions & 3 deletions moustachu.nim
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ type
let
tagOpening = r"\{\{"
tagClosing = r"\}\}"
tagRegex = re(tagOpening & r"(\#|&|\^|!|\{|/)?((?:.|\s)+?)\}?" & tagClosing)
tagRegex = re(tagOpening & r"(\#|&|\^|!|\{|/|>)?((?:.|\s)+?)\}?" & tagClosing)
htmlEscapeReplace = [(re"&", "&amp;"),
(re"<", "&lt;"),
(re">", "&gt;"),
Expand Down Expand Up @@ -82,6 +82,11 @@ proc `[]=`*(c: var Context, key: string, value: Context) =
return
c.fields.add((key, value))

proc `[]=`*(c: var Context, key: string, value: JsonNode) =
## Convert and assign `value` to `key` in `c`
assert(c.kind == CObject)
c[key] = newContext(value)

proc `[]=`*(c: var Context; key: string, value: BiggestInt) =
## Assign `value` to `key` in Context `c`
assert(c.kind == CObject)
Expand Down Expand Up @@ -302,8 +307,10 @@ proc render*(tmplate: string, c: Context): string =
else:
tag.key = found.captures[1]

let originalBounds = tag.bounds

# A tag
if tag.symbol in @["!", "#", "^", "/"]:
if tag.symbol in @["!", "#", "^", "/", ">"]:
# potentially standalone tag
adjustForStandaloneIndentation(tag.bounds, tmplate)

Expand Down Expand Up @@ -412,8 +419,20 @@ proc render*(tmplate: string, c: Context): string =
contexts.add(ctx.elems[ctx.elems.len - loopCounters[^1]])
pos = loopPositions[^1]

of ">":
# Partial tag
var partialTemplate = resolveString(contexts, tag.key)
let indentation = originalBounds.a - tag.bounds.a
let indent = " ".repeat(indentation)
var lines : seq[string] = @[]
for line in partialTemplate.splitLines():
if line != "": lines.add(indent & line)
else: lines.add(line)
partialTemplate = lines.join("\n")
renderings.add(render(partialTemplate, contexts[contexts.high]))

else:
#Normal substitution
# Normal substitution
let htmlEscaped = resolveString(contexts, tag.key).parallelReplace(htmlEscapeReplace)
renderings.add(htmlEscaped)

Expand Down
2 changes: 1 addition & 1 deletion moustachu.nimble
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[Package]
name = "moustachu"
version = "0.8.1"
version = "0.9.0"
author = "Guillaume Viger"
description = "Mustache templating for Nim"
license = "MIT"
Expand Down
4 changes: 4 additions & 0 deletions runTests.nim
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,9 @@ for kind, fn in walkDir("specs"):

for jn in j["tests"].items():
var aContext = newContext(jn["data"])
if jn.hasKey("partials"):
for key, value in jn["partials"].pairs():
aContext[key] = value
try:
doAssert(render(jn["template"].str, aContext) == jn["expected"].str)
echo "Pass!"
Expand All @@ -24,6 +27,7 @@ for kind, fn in walkDir("specs"):
echo "Template: ", escape(jn["template"].str)
echo "Template: ", jn["template"].str
echo "data: ", jn["data"]
if jn.hasKey("partials"): echo "partials: ", jn["partials"]
echo "Context: ", aContext
echo "Render: ", escape(render(jn["template"].str, aContext))
echo "Expected: ", escape(jn["expected"].str)
Expand Down
130 changes: 130 additions & 0 deletions specs/partials.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
{
"__ATTN__": "Do not edit this file; changes belong in the appropriate YAML file.",
"overview": "Partial tags are used to expand an external template into the current\ntemplate.\n\nThe tag's content MUST be a non-whitespace character sequence NOT containing\nthe current closing delimiter.\n\nThis tag's content names the partial to inject. Set Delimiter tags MUST NOT\naffect the parsing of a partial. The partial MUST be rendered against the\ncontext stack local to the tag. If the named partial cannot be found, the\nempty string SHOULD be used instead, as in interpolations.\n\nPartial tags SHOULD be treated as standalone when appropriate. If this tag\nis used standalone, any whitespace preceding the tag should treated as\nindentation, and prepended to each line of the partial before rendering.\n",
"tests": [
{
"data": {},
"desc": "The greater-than operator should expand to the named partial.",
"expected": "\"from partial\"",
"name": "Basic Behavior",
"partials": {
"text": "from partial"
},
"template": "\"{{>text}}\""
},
{
"data": {},
"desc": "The empty string should be used when the named partial is not found.",
"expected": "\"\"",
"name": "Failed Lookup",
"partials": {},
"template": "\"{{>text}}\""
},
{
"data": {
"text": "content"
},
"desc": "The greater-than operator should operate within the current context.",
"expected": "\"*content*\"",
"name": "Context",
"partials": {
"partial": "*{{text}}*"
},
"template": "\"{{>partial}}\""
},
{
"data": {
"content": "X",
"nodes": [
{
"content": "Y",
"nodes": []
}
]
},
"desc": "The greater-than operator should properly recurse.",
"expected": "X<Y<>>",
"name": "Recursion",
"partials": {
"node": "{{content}}<{{#nodes}}{{>node}}{{/nodes}}>"
},
"template": "{{>node}}"
},
{
"data": {},
"desc": "The greater-than operator should not alter surrounding whitespace.",
"expected": "| \t|\t |",
"name": "Surrounding Whitespace",
"partials": {
"partial": "\t|\t"
},
"template": "| {{>partial}} |"
},
{
"data": {
"data": "|"
},
"desc": "Whitespace should be left untouched.",
"expected": " | >\n>\n",
"name": "Inline Indentation",
"partials": {
"partial": ">\n>"
},
"template": " {{data}} {{> partial}}\n"
},
{
"data": {},
"desc": "\"\\r\\n\" should be considered a newline for standalone tags.",
"expected": "|\r\n>|",
"name": "Standalone Line Endings",
"partials": {
"partial": ">"
},
"template": "|\r\n{{>partial}}\r\n|"
},
{
"data": {},
"desc": "Standalone tags should not require a newline to precede them.",
"expected": " >\n >>",
"name": "Standalone Without Previous Line",
"partials": {
"partial": ">\n>"
},
"template": " {{>partial}}\n>"
},
{
"data": {},
"desc": "Standalone tags should not require a newline to follow them.",
"expected": ">\n >\n >",
"name": "Standalone Without Newline",
"partials": {
"partial": ">\n>"
},
"template": ">\n {{>partial}}"
},
{
"data": {
"content": "<\n->"
},
"desc": "Each line of the partial should be indented before rendering.",
"expected": "\\\n |\n <\n->\n |\n/\n",
"name": "Standalone Indentation",
"partials": {
"partial": "|\n{{{content}}}\n|\n"
},
"template": "\\\n {{>partial}}\n/\n"
},
{
"data": {
"boolean": true
},
"desc": "Superfluous in-tag whitespace should be ignored.",
"expected": "|[]|",
"name": "Padding Whitespace",
"partials": {
"partial": "[]"
},
"template": "|{{> partial }}|"
}
]
}

0 comments on commit ea36948

Please sign in to comment.