Skip to content

Commit

Permalink
introduce vector index notation
Browse files Browse the repository at this point in the history
  • Loading branch information
jverzani committed Jan 3, 2017
1 parent d98fa09 commit b90adf3
Show file tree
Hide file tree
Showing 6 changed files with 127 additions and 18 deletions.
40 changes: 40 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -223,6 +223,46 @@ end

(A string is used -- and not a `mt` macro above -- so that string interpolation can happen.)

### Iterating over vectors

Iterating over an unnamed vector uses `{{.}}` to refer to the item:

```
tpl = "{{#:vec}}{{.}} {{/:vec}}"
render(tpl, vec = ["A1", "B2", "C3"]) # "A1 B2 C3 "
```

Not the extra space after `C3`. There is *experimental* support for
indexing with the iteration of a vector that allows on to work around
this. The syntax `.[ind]` refers to the value `vec[ind]`.

To print commas one can use this pattern:

```
tpl = "{{#:vec}}{{.}}{{^.[end]}}, {{/.[end]}}{{/:vec}}"
render(tpl, vec = ["A1", "B2", "C3"]) # "A1, B2, C3"
```

To put the first value in bold, but no others, say:

```
tpl = """
{{#:vec}}
{{#.[1]}}<bold>{{.}}</bold>{{/.[1]}}
{{^.[1]}}{{.}}{{/.[1]}}
{{/:vec}}
"""
render(tpl, vec = ["A1", "B2", "C3"]) # "A1, B2, C3"
```

This was inspired by
[this](http://stackoverflow.com/questions/11147373/only-show-the-first-item-in-list-using-mustache)
question, but the syntax chosen was more Julian. This syntax-- as
implemented for now -- does not nest. That is, it won't work with the
outer vector in a vector of vectors, say. The parent `vec` should be a
vector of replacement values only.


### Partials

Partials are used to include partial templates into a template.
Expand Down
26 changes: 21 additions & 5 deletions src/context.jl
Original file line number Diff line number Diff line change
@@ -1,15 +1,14 @@
## context


## A context stores objects where named values are looked up.
type Context
view ## of what? a Dict, Module, CompositeKind, DataFrame
view ## of what? a Dict, Module, CompositeKind, DataFrame.parent.
parent ## a context or nothing
_cache::Dict
end
function Context(view, parent=nothing)
d = Dict()
d["."] = view
d["."] = isa(view, AnIndex) ? view.value : view
Context(view, parent, d)
end

Expand All @@ -20,6 +19,7 @@ end

## Lookup value by key in the context
function lookup(ctx::Context, key)

if haskey(ctx._cache, key)
value = ctx._cache[key]
else
Expand All @@ -29,14 +29,29 @@ function lookup(ctx::Context, key)
## does name have a .?
if ismatch(r"\.", key)
## do something with "."
error("Not implemented. Can use Composite Kinds in the view.")
## we use .[ind] to refer to value in parent of given index;
m = match(r"^\.\[(.*)\]$", key)
m == nothing && error("Not implemented. Can use Composite Kinds in the view.")

idx = m.captures[1]
vals = context.parent.view

if isa(vals, Vector)
if idx == "end"
value = AnIndex(:end, vals[end])
else
ind = Base.parse(Int, idx[1:end])
value = AnIndex(ind, vals[ind])
end
break
end
else
## strip leading, trailing whitespace in key
value = lookup_in_view(context.view, stripWhitepace(key))
end

context = context.parent


end

## cache
Expand Down Expand Up @@ -67,6 +82,7 @@ function lookup_in_view(view, key)
end
end


function _lookup_in_view(view::Dict, key)

## is it a symbol?
Expand Down
43 changes: 36 additions & 7 deletions src/tokens.jl
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,12 @@ type MustacheTokens
tokens
end

type AnIndex
ind
value
end
Base.string(ind::AnIndex) = string(ind.value)

## after parsing off to squash, nest and render the tokens

## Make the intial set of tokens before squashing and nesting
Expand Down Expand Up @@ -94,10 +100,8 @@ function make_tokens(template, tags)
error("Unclosed tag at " * string(scanner.pos))
end


token = Any[_type, value, start, scanner.pos]


push!(tokens, token)

if _type == "#" || _type == "^"
Expand Down Expand Up @@ -224,6 +228,23 @@ end
## end
## end

## what to do with an index value `.[ind]`?
## We have `.[ind]` being of a leaf type (values are not pushed onto a Context) so of simple usage
function _renderTokensByValue(value::AnIndex, io, token, writer, context, template)
if token[1] == "#"
# print if match
if value.value == context.view
renderTokens(io, token[5], writer, context, template)
end
elseif token[1] == "^"
# print if *not* a match
if value.value != context.view
renderTokens(io, token[5], writer, context, template)
end
else
renderTokens(io, token[5], writer, ctx_push(context, value.value), template)
end
end


function _renderTokensByValue(value::Function, io, token, writer, context, template)
Expand Down Expand Up @@ -265,8 +286,9 @@ function renderTokens(io, tokens, writer, context, template)
## iterate over value if Dict, Array or DataFrame,
## or display conditionally
value = lookup(context, tokenValue)

context = Context(value, context) # <<<
if !isa(value, AnIndex)
context = Context(value, context)
end
renderTokensByValue(value, io, token, writer, context, template)
## ## many things based on value of value
## if isa(value, Dict)
Expand Down Expand Up @@ -303,11 +325,18 @@ function renderTokens(io, tokens, writer, context, template)

elseif token[1] == "^"
## display if falsy, unlike #

value = lookup(context, tokenValue)

if falsy(value)
renderTokens(io, token[5], writer, context, template)
if !isa(value, AnIndex)
context = Context(value, context)
end
#context = Context(value, context) # <<<
renderTokensByValue(value, io, token, writer, context, template)

# if falsy(value)
# renderTokens(io, token[5], writer, context, template)
# end


elseif token[1] == ">"
Expand Down Expand Up @@ -347,7 +376,7 @@ function renderTokens(io, tokens, writer, context, template)
end

elseif token[1] == "text"
print(io, tokenValue)
print(io, string(tokenValue))
end

end
Expand Down
21 changes: 15 additions & 6 deletions test/examples.jl
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ mtrender = render
tpl = mt"the value of x is {{x}} and that of y is {{y}}"

## a dict
out = mtrender(tpl, {"x"=>1, "y"=>2})
out = mtrender(tpl, Dict("x"=>1, "y"=>2))
println(out)

## A module
Expand All @@ -31,7 +31,7 @@ mtrender(tpl, Beta(1, 2))
## conditional text
using Mustache
tpl = "{{#b}}this doesn't show{{/b}}{{#a}}this does show{{/a}}"
mtrender(tpl, {"a" => 1})
mtrender(tpl, Dict("a" => 1))



Expand All @@ -54,7 +54,7 @@ for s in sort(map(string, names(m)))
end

using DataFrames
d = DataFrame({"names" => _names, "summs" => _summaries})
d = DataFrame(names = _names, summs = _summaries)

tpl = "
<html>
Expand Down Expand Up @@ -90,8 +90,8 @@ mtrender(tpl, d)
## array of Dicts
using Mustache

A = [{"a" => "eh", "b" => "bee"},
{"a" => "ah", "b" => "buh"}]
A = [Dict("a" => "eh", "b" => "bee"),
Dict("a" => "ah", "b" => "buh")]

## Contrast to data frame:
# D = DataFrame(quote
Expand All @@ -101,4 +101,13 @@ A = [{"a" => "eh", "b" => "bee"},

tpl = mt"{{#A}} pronounce a as {{a}} and b as {{b}}.{{/A}}"

mtrender(tpl, {"A" => A})
mtrender(tpl, Dict("A" => A))


## use .[ind] to index within a vector:

tpl = mt"{{#:vec}}{{.[1]}}{{/:vec}}" # just first one
mtrender(tpl, vec=["A1","B2","C3"])

tpl = mt"{{#:vec}}{{.}}{{^.[end]}}, {{/.[end]}}{{/:vec}}" # serial commas
mtrender(tpl, vec=["A1","B2","C3"])
1 change: 1 addition & 0 deletions test/runtests.jl
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
include("Mustache_test.jl")
include("multiple_nested_sections.jl")
include("test_index.jl")
#include("data-frame-test.jl")
14 changes: 14 additions & 0 deletions test/test_index.jl
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
## Experimental syntax for indexing within vectors:
using Base.Test

## use # to include
tpl = mt"{{#:vec}}{{#.[1]}}{{.}}{{/.[1]}}{{/:vec}}"
out = Mustache.render(tpl, vec=["A1","B2","C3"])
@test out == "A1"

## use ^ to exclude
tpl = mt"{{#:vec}}{{.}}{{^.[end]}}, {{/.[end]}}{{/:vec}}"
out = Mustache.render(tpl, vec=["A1","B2","C3"])
@test out == "A1, B2, C3"


0 comments on commit b90adf3

Please sign in to comment.