Repository: tlienart/Franklin.jl (julia)

Issue:
Allow {{ var }} as short  for {{ fill  var }}
But with  least priority in case of ambiguity with a function name that would take no parameters (not many of those though: `toc`, and the `if`....`end`)

Pull request:
This allows the following

`somefile.md`:
```md
@def var = "hello"
{{var}}
```

Leading to

```html
<p>hello</p>
```

I.e. two things:

1. templating blocks can directly be used from Markdown as well as HTML
2. there is now the shortcut `{{var}}` for `{{fill var}}`, note that if `var` is the name of an internal `hfun` like `fill` or `toc`, these functions will take precedence. (closes #355) 

### Secondary modifications

- Some additional work to make Regexes easier to read + test them more. 
- Dropped the  `HBLOCK_TOC_PAT` in favour of just `HBLOCK_FUN_PAT` with zero parameters

Patch:
diff --git a/src/converter/html/functions.jl b/src/converter/html/functions.jl
index 193c01f54..ea1c1743a 100644
--- a/src/converter/html/functions.jl
+++ b/src/converter/html/functions.jl
@@ -8,6 +8,16 @@ function convert_html_fblock(β::HFun)::String
     # try to find a function `hfun_...`
     fun = Symbol("hfun_" * lowercase(β.fname))
     isdefined(Franklin, fun) && return eval(:($fun($β.params)))
+
+    # if zero parameters, see if can fill
+    if isempty(β.params) && !isnothing(locvar(β.fname))
+        return hfun_fill([β.fname])
+    end
+
+    # XXX Future
+    # XXX see if there's an externally defined hfun
+    # XXX isdefined(Utils, fun) && Utils.eval(:($fun($β.params)))
+
     # if we get here, then the function name is unknown, warn and ignore
     @warn "I found a function block '{{$(β.fname) ...}}' but I don't " *
           "recognise the function name. Ignoring."
diff --git a/src/converter/markdown/blocks.jl b/src/converter/markdown/blocks.jl
index 7754d2227..19444a2fa 100644
--- a/src/converter/markdown/blocks.jl
+++ b/src/converter/markdown/blocks.jl
@@ -22,6 +22,8 @@ function convert_block(β::AbstractBlock, lxdefs::Vector{LxDef})::AS
     βn == :FOOTNOTE_DEF    && return convert_footnote_def(β, lxdefs)
     βn == :LINK_DEF        && return ""
 
+    βn == :DOUBLE_BRACE    && return β.ss # let HTML converter deal with it
+
     # Math block --> needs to call further processing to resolve possible latex
     βn ∈ MATH_BLOCKS_NAMES && return convert_math_block(β, lxdefs)
 
diff --git a/src/converter/markdown/utils.jl b/src/converter/markdown/utils.jl
index 428af0640..62a6f5657 100644
--- a/src/converter/markdown/utils.jl
+++ b/src/converter/markdown/utils.jl
@@ -11,6 +11,9 @@ function md2html(ss::AS; stripp::Bool=false)::AS
     isempty(ss) && return ss
     # Use Julia's Markdown parser followed by Julia's MD->HTML conversion
     partial = ss |> fix_inserts |> Markdown.parse |> Markdown.html
+    # Markdown.html transforms {{ with HTML entities but we don't want that
+    partial = replace(partial, r"&#123;&#123;" => "{{")
+    partial = replace(partial, r"&#125;&#125;" => "}}")
     # In some cases, base converter adds <p>...</p>\n which we might not want
     stripp || return partial
     startswith(partial, "<p>")    && (partial = chop(partial, head=3))
diff --git a/src/parser/html/blocks.jl b/src/parser/html/blocks.jl
index 1385deb30..1a230adee 100644
--- a/src/parser/html/blocks.jl
+++ b/src/parser/html/blocks.jl
@@ -48,12 +48,16 @@ function qualify_html_hblocks(blocks::Vector{OCBlock})::Vector{AbstractBlock}
         # ---
         # function block {{ fname v1 v2 ... }}
         m = match(HBLOCK_FUN_PAT, β.ss)
-        isnothing(m) || (qb[i] = HFun(β.ss, m.captures[1], split(m.captures[2])); continue)
+        if !isnothing(m)
+            if isnothing(m.captures[2]) || isempty(strip(m.captures[2]))
+                ps = String[]
+            else
+                ps = split(m.captures[2])
+            end
+            qb[i] = HFun(β.ss, m.captures[1], ps)
+            continue
+        end
         # ---
-        # function toc {{toc}}
-        m = match(HBLOCK_TOC_PAT, β.ss)
-        isnothing(m) || (qb[i] = HFun(β.ss, "toc", String[]); continue)
-
         throw(HTMLBlockError("I found a HBlock that did not match anything, " *
                              "verify '$(β.ss)'"))
     end
diff --git a/src/parser/html/tokens.jl b/src/parser/html/tokens.jl
index 06a16354a..35593f739 100644
--- a/src/parser/html/tokens.jl
+++ b/src/parser/html/tokens.jl
@@ -168,20 +168,6 @@ end
 FUNCTION BLOCKS
 =============== =#
 
-"""
-HBLOCK_FUN_PAT
-
-Regex to match `{{ fname param₁ param₂ }}` where `fname` is a html processing
-function and `paramᵢ` should refer to appropriate variables in the current
-scope.
-
-Available functions are:
-    * `{{ fill vname }}`: to plug a variable (e.g.: a date, author name)
-    * `{{ insert fpath }}`: to plug in a file referred to by the `fpath` (e.g.: a html header)
-"""
-const HBLOCK_FUN_PAT = r"{{\s*([a-z]\S+)\s+((.|\n)+?)}}"
-
-
 """
 $(TYPEDEF)
 
diff --git a/src/regexes.jl b/src/regexes.jl
index 6ba9b9f87..1c915156b 100644
--- a/src/regexes.jl
+++ b/src/regexes.jl
@@ -9,22 +9,47 @@ const ESC_LINK_PAT = r"(&#33;)?&#91;(.*?)&#93;(?!:)(?:&#91;(.*?)&#93;)?"
 
 #= =====================================================
 HBLOCK patterns, see html blocks
+NOTE: the &#123 is { and 125 is }, this is because
+Markdown.html converts { => html entity but we want to
+recognise those double as {{ so that they can be used
+within markdown as well.
 NOTE: the for block needs verification (matching parens)
 ===================================================== =#
+const HBO = raw"(?:{{|&#123;&#123;)\s*"
+const HBC = raw"\s*(?:}}|&#125;&#125;)"
+const VAR = raw"([a-zA-Z_]\S*)"
+const ANY = raw"((.|\n)+?)"
 
-const HBLOCK_IF_PAT        = r"{{\s*if\s+([a-zA-Z_]\S*)\s*}}"
-const HBLOCK_ELSE_PAT      = r"{{\s*else\s*}}"
-const HBLOCK_ELSEIF_PAT    = r"{{\s*else\s*if\s+([a-zA-Z_]\S*)\s*}}"
-const HBLOCK_END_PAT       = r"{{\s*end\s*}}"
+const HBLOCK_IF_PAT     = Regex(HBO * raw"if\s+" * VAR * HBC)
+const HBLOCK_ELSE_PAT   = Regex(HBO * "else" * HBC)
+const HBLOCK_ELSEIF_PAT = Regex(HBO * raw"else\s*if\s+" * VAR * HBC)
+const HBLOCK_END_PAT    = Regex(HBO * "end" * HBC)
 
-const HBLOCK_ISDEF_PAT     = r"{{\s*i(?:s|f)def\s+([a-zA-Z_]\S*)\s*}}"
-const HBLOCK_ISNOTDEF_PAT  = r"{{\s*i(?:s|f)n(?:ot)?def\s+([a-zA-Z_]\S*)\s*}}"
-const HBLOCK_ISPAGE_PAT    = r"{{\s*ispage\s+((.|\n)+?)}}"
-const HBLOCK_ISNOTPAGE_PAT = r"{{\s*isnotpage\s+((.|\n)+?)}}"
+const HBLOCK_ISDEF_PAT    = Regex(HBO * raw"i(?:s|f)def\s+" * VAR * HBC)
+const HBLOCK_ISNOTDEF_PAT = Regex(HBO * raw"i(?:s|f)n(?:ot)?def\s+" * VAR * HBC)
 
-const HBLOCK_FOR_PAT = r"{{\s*for\s+(\(?(?:\s*[a-zA-Z_][^\r\n\t\f\v,]*,\s*)*[a-zA-Z_]\S*\s*\)?)\s+in\s+([a-zA-Z_]\S*)\s*}}"
+const HBLOCK_ISPAGE_PAT    = Regex(HBO * raw"ispage\s+" * ANY * HBC)
+const HBLOCK_ISNOTPAGE_PAT = Regex(HBO * raw"isnotpage\s+" * ANY * HBC)
 
-const HBLOCK_TOC_PAT = r"{{\s*toc\s*}}"
+"""
+    HBLOCK_FOR_PAT
+
+Regex to match `{{ for v in iterate }}` or {{ for (v1, v2) in iterate}} etc
+where `iterate` is an iterator
+"""
+const HBLOCK_FOR_PAT = Regex(
+        HBO * raw"for\s+" *
+        raw"(\(?(?:\s*[a-zA-Z_][^\r\n\t\f\v,]*,\s*)*[a-zA-Z_]\S*\s*\)?)" *
+        raw"\s+in\s+" * VAR * HBC)
+
+"""
+HBLOCK_FUN_PAT
+
+Regex to match `{{ fname param₁ param₂ }}` where `fname` is a html processing
+function and `paramᵢ` should refer to appropriate variables in the current
+scope.
+"""
+const HBLOCK_FUN_PAT = Regex(HBO * VAR * raw"(\s+((.|\n)*?))?" * HBC)
 
 #= =====================================================
 Pattern checkers


Test patch:
diff --git a/test/parser/markdown-extra.jl b/test/parser/markdown-extra.jl
index a6ebd5c15..609e838f4 100644
--- a/test/parser/markdown-extra.jl
+++ b/test/parser/markdown-extra.jl
@@ -111,3 +111,11 @@ end
     """ |> fd2html_td
     @test isapproxstr(h, "good lord")
 end
+
+@testset "Double brace" begin
+    s = """
+        @def title = "hello"
+        {{title}}{{title}}
+        """ |> fd2html_td
+    @test isapproxstr(s, "<p>hellohello</p>")
+end
diff --git a/test/regexes.jl b/test/regexes.jl
index fedaa87db..fe864e2c4 100644
--- a/test/regexes.jl
+++ b/test/regexes.jl
@@ -31,6 +31,7 @@ end
             "{{if var1}}",
             "{{if  var1 }}",
             "{{ if  var1 }}",
+            "&#123;&#123; if var1 &#125;&#125;"
         )
         m = match(F.HBLOCK_IF_PAT, s)
         @test m.captures[1] == "var1"
@@ -48,6 +49,7 @@ end
             "{{else}}",
             "{{ else}}",
             "{{  else   }}",
+            "&#123;&#123; else &#125;&#125;"
         )
         m = match(F.HBLOCK_ELSE_PAT, s)
         @test !isnothing(m)
@@ -58,6 +60,7 @@ end
             "{{elseif var1}}",
             "{{else if  var1 }}",
             "{{ elseif  var1 }}",
+            "&#123;&#123; elseif var1 &#125;&#125;"
         )
         m = match(F.HBLOCK_ELSEIF_PAT, s)
         @test m.captures[1] == "var1"
@@ -75,6 +78,7 @@ end
             "{{end}}",
             "{{ end}}",
             "{{  end   }}",
+            "&#123;&#123; end &#125;&#125;"
         )
         m = match(F.HBLOCK_END_PAT, s)
         @test !isnothing(m)
@@ -86,6 +90,7 @@ end
             "{{ isdef  var1 }}",
             "{{ isdef  var1 }}",
             "{{ ifdef  var1 }}",
+            "&#123;&#123; ifdef var1 &#125;&#125;"
         )
         m = match(F.HBLOCK_ISDEF_PAT, s)
         @test m.captures[1] == "var1"
@@ -105,6 +110,7 @@ end
             "{{ isndef  var1 }}",
             "{{ ifndef  var1 }}",
             "{{ ifnotdef  var1 }}",
+            "&#123;&#123; ifndef var1 &#125;&#125;"
         )
         m = match(F.HBLOCK_ISNOTDEF_PAT, s)
         @test m.captures[1] == "var1"
@@ -121,6 +127,7 @@ end
 @testset "hb-ispage" begin
     for s in (
             "{{ispage var1 var2}}",
+            "&#123;&#123; ispage var1 var2 &#125;&#125;"
         )
         m = match(F.HBLOCK_ISPAGE_PAT, s)
         @test m.captures[1] == "var1 var2"
@@ -129,6 +136,7 @@ end
 @testset "hb-isnotpage" begin
     for s in (
             "{{isnotpage var1 var2}}",
+            "&#123;&#123; isnotpage var1 var2 &#125;&#125;"
         )
         m = match(F.HBLOCK_ISNOTPAGE_PAT, s)
         @test m.captures[1] == "var1 var2"
@@ -139,7 +147,8 @@ end
             "{{for (v1,v2,v3) in iterate}}",
             "{{for (v1, v2,v3) in iterate}}",
             "{{for ( v1, v2, v3) in iterate}}",
-            "{{for ( v1  , v2 , v3 ) in iterate}}"
+            "{{for ( v1  , v2 , v3 ) in iterate}}",
+            "&#123;&#123; for (v1, v2, v3) in iterate &#125;&#125;"
         )
         m = match(F.HBLOCK_FOR_PAT, s)
         @test isapproxstr(m.captures[1], "(v1, v2, v3)")
@@ -159,13 +168,36 @@ end
     m = match(F.HBLOCK_FOR_PAT, s)
     @test isapproxstr(m.captures[1], "v1,v2)")
 end
-@testset "hb-toc" begin
+@testset "hb-fun-0" begin
     for s in (
             "{{toc}}",
-            "{{ toc }}"
+            "{{ toc }}",
+            "&#123;&#123; toc &#125;&#125;"
             )
-        m = match(F.HBLOCK_TOC_PAT, s)
-        @test !isnothing(m)
+        m = match(F.HBLOCK_FUN_PAT, s)
+        @test m.captures[1] == "toc"
+        @test m.captures[2] === nothing || isempty(strip(m.captures[2]))
+    end
+end
+@testset "hb-fun-1" begin
+    for s in (
+            "{{fun p1}}",
+            "{{ fun  p1 }}",
+            "&#123;&#123; fun p1 &#125;&#125;"
+            )
+        m = match(F.HBLOCK_FUN_PAT, s)
+        @test m.captures[1] == "fun"
+        @test strip(m.captures[2]) == "p1"
+    end
+end
+@testset "hb-fun-2" begin
+    for s in (
+            "{{fun p1 p2}}",
+            "{{ fun  p1 p2 }}"
+            )
+        m = match(F.HBLOCK_FUN_PAT, s)
+        @test m.captures[1] == "fun"
+        @test strip(m.captures[2]) == "p1 p2"
     end
 end