forked from osteele/liquid
-
Notifications
You must be signed in to change notification settings - Fork 1
/
blocks.go
117 lines (101 loc) · 3.42 KB
/
blocks.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
package render
import (
"io"
"sort"
"github.com/osteele/liquid/parser"
)
// BlockCompiler builds a renderer for the tag instance.
type BlockCompiler func(BlockNode) (func(io.Writer, Context) error, error)
// blockSyntax tells the parser how to parse a control tag.
type blockSyntax struct {
name string
isClauseTag, isEndTag bool
startName string // for an end tag, the name of the correspondign start tag
parents map[string]bool // if non-nil, must be an immediate clause of one of these
parser BlockCompiler
}
func (s *blockSyntax) CanHaveParent(parent parser.BlockSyntax) bool {
switch {
case s.isClauseTag:
return parent != nil && s.parents[parent.TagName()]
case s.isEndTag:
return parent != nil && parent.TagName() == s.startName
default:
return true
}
}
func (s *blockSyntax) IsBlock() bool { return true }
func (s *blockSyntax) IsBlockEnd() bool { return s.isEndTag }
func (s *blockSyntax) IsBlockStart() bool { return !s.isClauseTag && !s.isEndTag }
func (s *blockSyntax) IsClause() bool { return s.isClauseTag }
func (s *blockSyntax) RequiresParent() bool { return s.isClauseTag || s.isEndTag }
func (s *blockSyntax) ParentTags() (parents []string) {
for k := range s.parents {
parents = append(parents, k)
}
sort.Strings(parents)
return
}
func (s *blockSyntax) TagName() string { return s.name }
func (g grammar) addBlockDef(ct *blockSyntax) {
if g.blockDefs[ct.name] != nil {
panic("duplicate definition of " + ct.name)
}
g.blockDefs[ct.name] = ct
}
func (g grammar) findBlockDef(name string) (*blockSyntax, bool) {
ct, found := g.blockDefs[name]
return ct, found
}
// BlockSyntax is part of the Grammar interface.
func (g grammar) BlockSyntax(name string) (parser.BlockSyntax, bool) {
ct, found := g.blockDefs[name]
return ct, found
}
type blockDefBuilder struct {
grammar
tag *blockSyntax
}
// AddBlock defines a control tag and its matching end tag.
func (g grammar) AddBlock(name string) blockDefBuilder { // nolint: golint
ct := &blockSyntax{name: name}
g.addBlockDef(ct)
g.addBlockDef(&blockSyntax{name: "end" + name, isEndTag: true, startName: name})
return blockDefBuilder{g, ct}
}
// Clause tells the parser that the named tag can appear immediately between this tag and its end tag,
// so long as it is not nested within any other control tag.
func (b blockDefBuilder) Clause(name string) blockDefBuilder {
if b.blockDefs[name] == nil {
b.addBlockDef(&blockSyntax{name: name, isClauseTag: true})
}
c := b.blockDefs[name]
if !c.isClauseTag {
panic(name + " has already been defined as a non-clause")
}
if len(c.parents) == 0 {
c.parents = make(map[string]bool)
}
c.parents[b.tag.name] = true
return b
}
// SameSyntaxAs tells the parser that this tag has the same syntax as the named tag.
// func (b blockDefBuilder) SameSyntaxAs(name string) blockDefBuilder {
// rt := b.blockDefs[name]
// if rt == nil {
// panic(fmt.Errorf("undefined: %s", name))
// }
// b.tag.syntaxModel = rt
// return b
// }
// Compiler sets the parser for a control tag definition.
func (b blockDefBuilder) Compiler(fn BlockCompiler) {
b.tag.parser = fn
}
// Renderer sets the render action for a control tag definition.
func (b blockDefBuilder) Renderer(fn func(io.Writer, Context) error) {
b.tag.parser = func(node BlockNode) (func(io.Writer, Context) error, error) {
// TODO syntax error if there are arguments?
return fn, nil
}
}