temple 0.2.0 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/.yardopts +2 -1
- data/CHANGES +63 -0
- data/EXPRESSIONS.md +250 -0
- data/README.md +24 -12
- data/lib/temple.rb +34 -18
- data/lib/temple/engine.rb +11 -7
- data/lib/temple/erb/engine.rb +5 -3
- data/lib/temple/erb/parser.rb +5 -2
- data/lib/temple/erb/template.rb +11 -0
- data/lib/temple/erb/trimming.rb +9 -1
- data/lib/temple/filter.rb +2 -0
- data/lib/temple/filters/control_flow.rb +43 -0
- data/lib/temple/filters/dynamic_inliner.rb +29 -32
- data/lib/temple/filters/eraser.rb +22 -0
- data/lib/temple/filters/escapable.rb +39 -0
- data/lib/temple/filters/multi_flattener.rb +4 -1
- data/lib/temple/filters/static_merger.rb +11 -10
- data/lib/temple/filters/validator.rb +15 -0
- data/lib/temple/generators.rb +41 -100
- data/lib/temple/grammar.rb +56 -0
- data/lib/temple/hash.rb +48 -0
- data/lib/temple/html/dispatcher.rb +10 -4
- data/lib/temple/html/fast.rb +50 -38
- data/lib/temple/html/filter.rb +8 -0
- data/lib/temple/html/pretty.rb +25 -14
- data/lib/temple/mixins/dispatcher.rb +103 -0
- data/lib/temple/{mixins.rb → mixins/engine_dsl.rb} +10 -95
- data/lib/temple/mixins/grammar_dsl.rb +166 -0
- data/lib/temple/mixins/options.rb +28 -0
- data/lib/temple/mixins/template.rb +25 -0
- data/lib/temple/templates.rb +2 -0
- data/lib/temple/utils.rb +11 -57
- data/lib/temple/version.rb +1 -1
- data/test/filters/test_control_flow.rb +92 -0
- data/test/filters/test_dynamic_inliner.rb +7 -7
- data/test/filters/test_eraser.rb +55 -0
- data/test/filters/{test_escape_html.rb → test_escapable.rb} +13 -6
- data/test/filters/test_multi_flattener.rb +1 -1
- data/test/filters/test_static_merger.rb +3 -3
- data/test/helper.rb +8 -0
- data/test/html/test_fast.rb +42 -57
- data/test/html/test_pretty.rb +10 -7
- data/test/mixins/test_dispatcher.rb +31 -0
- data/test/mixins/test_grammar_dsl.rb +86 -0
- data/test/test_engine.rb +73 -57
- data/test/test_erb.rb +0 -7
- data/test/test_filter.rb +26 -0
- data/test/test_generator.rb +57 -36
- data/test/test_grammar.rb +52 -0
- data/test/test_hash.rb +39 -0
- data/test/test_utils.rb +11 -38
- metadata +34 -10
- data/lib/temple/filters/debugger.rb +0 -26
- data/lib/temple/filters/escape_html.rb +0 -33
@@ -0,0 +1,56 @@
|
|
1
|
+
module Temple
|
2
|
+
# Temple expression grammar which can be used to validate Temple expressions.
|
3
|
+
#
|
4
|
+
# Example:
|
5
|
+
# Temple::Grammar.match? [:static, 'Valid Temple Expression']
|
6
|
+
# Temple::Grammar.validate! [:multi, 'Invalid Temple Expression']
|
7
|
+
#
|
8
|
+
# See {file:EXPRESSIONS.md Expression documentation}.
|
9
|
+
#
|
10
|
+
# @api public
|
11
|
+
module Grammar
|
12
|
+
extend Mixins::GrammarDSL
|
13
|
+
|
14
|
+
Expression <<
|
15
|
+
# Core abstraction
|
16
|
+
[:multi, 'Expression*'] |
|
17
|
+
[:static, String] |
|
18
|
+
[:dynamic, String] |
|
19
|
+
[:code, String] |
|
20
|
+
[:capture, String, Expression] |
|
21
|
+
[:newline] |
|
22
|
+
# Control flow abstraction
|
23
|
+
[:if, String, Expression, 'Expression?'] |
|
24
|
+
[:block, String, Expression] |
|
25
|
+
[:case, String, 'Case*'] |
|
26
|
+
[:cond, 'Case*'] |
|
27
|
+
# Escape abstraction
|
28
|
+
[:escape, Bool, Expression] |
|
29
|
+
# HTML abstraction
|
30
|
+
[:html, :doctype, String] |
|
31
|
+
[:html, :comment, Expression] |
|
32
|
+
[:html, :tag, HTMLIdentifier, HTMLAttrs, 'Expression?']
|
33
|
+
|
34
|
+
EmptyExp <<
|
35
|
+
[:newline] | [:multi, 'EmptyExp*']
|
36
|
+
|
37
|
+
HTMLAttrs <<
|
38
|
+
Expression | [:html, :attrs, 'HTMLAttr*']
|
39
|
+
|
40
|
+
HTMLAttr <<
|
41
|
+
[:html, :attr, HTMLIdentifier, Expression]
|
42
|
+
|
43
|
+
HTMLIdentifier <<
|
44
|
+
Symbol | String
|
45
|
+
|
46
|
+
Case <<
|
47
|
+
[Condition, 'Expression*']
|
48
|
+
|
49
|
+
Condition <<
|
50
|
+
String | :else
|
51
|
+
|
52
|
+
Bool <<
|
53
|
+
true | false
|
54
|
+
|
55
|
+
end
|
56
|
+
end
|
data/lib/temple/hash.rb
ADDED
@@ -0,0 +1,48 @@
|
|
1
|
+
module Temple
|
2
|
+
# Immutable hash class which supports hash merging
|
3
|
+
# @api public
|
4
|
+
class ImmutableHash
|
5
|
+
include Enumerable
|
6
|
+
|
7
|
+
def initialize(*hash)
|
8
|
+
@hash = hash.compact
|
9
|
+
end
|
10
|
+
|
11
|
+
def include?(key)
|
12
|
+
@hash.any? {|h| h.include?(key) }
|
13
|
+
end
|
14
|
+
|
15
|
+
def [](key)
|
16
|
+
@hash.each {|h| return h[key] if h.include?(key) }
|
17
|
+
nil
|
18
|
+
end
|
19
|
+
|
20
|
+
def each
|
21
|
+
keys.each {|k| yield(k, self[k]) }
|
22
|
+
end
|
23
|
+
|
24
|
+
def keys
|
25
|
+
@hash.inject([]) {|keys, h| keys += h.keys }.uniq
|
26
|
+
end
|
27
|
+
|
28
|
+
def values
|
29
|
+
keys.map {|k| self[k] }
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
# Mutable hash class which supports hash merging
|
34
|
+
# @api public
|
35
|
+
class MutableHash < ImmutableHash
|
36
|
+
def initialize(*hash)
|
37
|
+
super({}, *hash)
|
38
|
+
end
|
39
|
+
|
40
|
+
def []=(key, value)
|
41
|
+
@hash.first[key] = value
|
42
|
+
end
|
43
|
+
|
44
|
+
def update(hash)
|
45
|
+
@hash.first.update(hash)
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
@@ -1,16 +1,22 @@
|
|
1
1
|
module Temple
|
2
2
|
module HTML
|
3
|
+
# @api private
|
3
4
|
module Dispatcher
|
4
|
-
def
|
5
|
-
[:html, :
|
5
|
+
def on_html_attrs(*attrs)
|
6
|
+
[:html, :attrs, *attrs.map {|a| compile(a) }]
|
7
|
+
end
|
8
|
+
|
9
|
+
def on_html_attr(name, content)
|
10
|
+
[:html, :attr, name, compile(content)]
|
6
11
|
end
|
7
12
|
|
8
13
|
def on_html_comment(content)
|
9
14
|
[:html, :comment, compile(content)]
|
10
15
|
end
|
11
16
|
|
12
|
-
def on_html_tag(name, attrs,
|
13
|
-
[:html, :tag, name, compile(attrs)
|
17
|
+
def on_html_tag(name, attrs, content = nil)
|
18
|
+
result = [:html, :tag, name, compile(attrs)]
|
19
|
+
content ? (result << compile(content)) : result
|
14
20
|
end
|
15
21
|
end
|
16
22
|
end
|
data/lib/temple/html/fast.rb
CHANGED
@@ -1,8 +1,7 @@
|
|
1
1
|
module Temple
|
2
2
|
module HTML
|
3
|
+
# @api public
|
3
4
|
class Fast < Filter
|
4
|
-
temple_dispatch :html
|
5
|
-
|
6
5
|
XHTML_DOCTYPES = {
|
7
6
|
'1.1' => '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">',
|
8
7
|
'5' => '<!DOCTYPE html>',
|
@@ -23,14 +22,14 @@ module Temple
|
|
23
22
|
set_default_options :format => :xhtml,
|
24
23
|
:attr_wrapper => "'",
|
25
24
|
:autoclose => %w[meta img link br hr input area param col base],
|
26
|
-
:
|
25
|
+
:attr_delimiter => {'id' => '_', 'class' => ' '}
|
27
26
|
|
28
|
-
def initialize(
|
27
|
+
def initialize(opts = {})
|
29
28
|
super
|
30
29
|
# html5 is now called html only
|
31
|
-
|
32
|
-
unless [:xhtml, :html4, :html5].include?(
|
33
|
-
raise "Invalid format #{
|
30
|
+
options[:format] = :html5 if options[:format] == :html
|
31
|
+
unless [:xhtml, :html4, :html5].include?(options[:format])
|
32
|
+
raise "Invalid format #{options[:format].inspect}"
|
34
33
|
end
|
35
34
|
end
|
36
35
|
|
@@ -73,58 +72,69 @@ module Temple
|
|
73
72
|
[:static, '-->']]
|
74
73
|
end
|
75
74
|
|
76
|
-
def on_html_tag(name, attrs,
|
77
|
-
|
78
|
-
|
75
|
+
def on_html_tag(name, attrs, content = nil)
|
76
|
+
name = name.to_s
|
77
|
+
closed = !content || (empty_exp?(content) && options[:autoclose].include?(name))
|
79
78
|
result = [:multi, [:static, "<#{name}"], compile(attrs)]
|
80
|
-
result << [:static, ' /'
|
81
|
-
result <<
|
79
|
+
result << [:static, (closed && xhtml? ? ' /' : '') + '>']
|
80
|
+
result << compile(content) if content
|
82
81
|
result << [:static, "</#{name}>"] if !closed
|
83
82
|
result
|
84
83
|
end
|
85
84
|
|
86
|
-
def
|
85
|
+
def on_html_attrs(*attrs)
|
87
86
|
result = {}
|
88
|
-
attrs.each do |
|
89
|
-
if
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
87
|
+
attrs.each do |attr|
|
88
|
+
raise(InvalidExpression, 'Attribute is not a html attr') if attr[0] != :html || attr[1] != :attr
|
89
|
+
name, value = attr[2].to_s, attr[3]
|
90
|
+
next if empty_exp?(value)
|
91
|
+
if result[name]
|
92
|
+
delimiter = options[:attr_delimiter][name]
|
93
|
+
raise "Multiple #{name} attributes specified" unless delimiter
|
94
|
+
if contains_static?(value)
|
95
|
+
result[name] = [:html, :attr, name,
|
96
|
+
[:multi,
|
97
|
+
result[name][3],
|
98
|
+
[:static, delimiter],
|
99
|
+
value]]
|
100
|
+
else
|
101
|
+
tmp = unique_name
|
102
|
+
result[name] = [:html, :attr, name,
|
103
|
+
[:multi,
|
104
|
+
result[name][3],
|
105
|
+
[:capture, tmp, value],
|
106
|
+
[:if, "!#{tmp}.empty?",
|
107
|
+
[:multi,
|
108
|
+
[:static, delimiter],
|
109
|
+
[:dynamic, tmp]]]]]
|
110
|
+
end
|
95
111
|
else
|
96
|
-
result[name] =
|
112
|
+
result[name] = attr
|
97
113
|
end
|
98
114
|
end
|
99
|
-
result.sort.
|
100
|
-
list << compile_attribute(name, value)
|
101
|
-
end
|
115
|
+
[:multi, *result.sort.map {|name,attr| compile(attr) }]
|
102
116
|
end
|
103
117
|
|
104
|
-
|
105
|
-
|
106
|
-
def compile_attribute(name, value)
|
118
|
+
def on_html_attr(name, value)
|
107
119
|
if empty_exp?(value)
|
108
|
-
|
120
|
+
compile(value)
|
109
121
|
elsif contains_static?(value)
|
110
122
|
attribute(name, value)
|
111
123
|
else
|
112
|
-
tmp =
|
124
|
+
tmp = unique_name
|
113
125
|
[:multi,
|
114
|
-
[:capture, tmp, value],
|
115
|
-
[:
|
116
|
-
|
117
|
-
[:block, 'end']]
|
126
|
+
[:capture, tmp, compile(value)],
|
127
|
+
[:if, "!#{tmp}.empty?",
|
128
|
+
attribute(name, [:dynamic, tmp])]]
|
118
129
|
end
|
119
130
|
end
|
120
131
|
|
132
|
+
protected
|
133
|
+
|
121
134
|
def attribute(name, value)
|
122
135
|
[:multi,
|
123
|
-
[:static,
|
124
|
-
|
125
|
-
[:static, '='],
|
126
|
-
[:static, options[:attr_wrapper]],
|
127
|
-
value,
|
136
|
+
[:static, " #{name}=#{options[:attr_wrapper]}"],
|
137
|
+
compile(value),
|
128
138
|
[:static, options[:attr_wrapper]]]
|
129
139
|
end
|
130
140
|
|
@@ -132,6 +142,8 @@ module Temple
|
|
132
142
|
case exp[0]
|
133
143
|
when :multi
|
134
144
|
exp[1..-1].any? {|e| contains_static?(e) }
|
145
|
+
when :escape
|
146
|
+
contains_static?(exp[2])
|
135
147
|
when :static
|
136
148
|
true
|
137
149
|
else
|
data/lib/temple/html/pretty.rb
CHANGED
@@ -1,12 +1,13 @@
|
|
1
1
|
module Temple
|
2
2
|
module HTML
|
3
|
+
# @api public
|
3
4
|
class Pretty < Fast
|
4
5
|
set_default_options :indent => ' ',
|
5
6
|
:pretty => true,
|
6
7
|
:indent_tags => %w(base body dd div dl dt fieldset form head h1 h2 h3
|
7
8
|
h4 h5 h6 hr html img input li link meta ol p script
|
8
9
|
style table tbody td tfoot th thead title tr ul).freeze,
|
9
|
-
:pre_tags => %w(pre textarea).freeze
|
10
|
+
:pre_tags => %w(code pre textarea).freeze
|
10
11
|
|
11
12
|
def initialize(opts = {})
|
12
13
|
super
|
@@ -22,15 +23,23 @@ module Temple
|
|
22
23
|
|
23
24
|
def on_static(content)
|
24
25
|
if @pretty
|
25
|
-
content
|
26
|
+
content.gsub!("\n", indent) if @pre_tags !~ content
|
26
27
|
@last = content.sub!(/\r?\n\s*$/, ' ') ? nil : :noindent
|
27
28
|
end
|
28
29
|
[:static, content]
|
29
30
|
end
|
30
31
|
|
31
|
-
def on_dynamic(
|
32
|
-
@
|
33
|
-
|
32
|
+
def on_dynamic(code)
|
33
|
+
if @pretty
|
34
|
+
@last = :noindent
|
35
|
+
tmp = unique_name
|
36
|
+
[:multi,
|
37
|
+
[:code, "#{tmp} = (#{code}).to_s"],
|
38
|
+
[:code, "#{tmp}.gsub!(\"\\n\", #{indent.inspect}) if #{@pre_tags_name} !~ #{tmp}"],
|
39
|
+
[:dynamic, tmp]]
|
40
|
+
else
|
41
|
+
[:dynamic, code]
|
42
|
+
end
|
34
43
|
end
|
35
44
|
|
36
45
|
def on_html_doctype(type)
|
@@ -44,21 +53,22 @@ module Temple
|
|
44
53
|
[:multi, [:static, indent], super]
|
45
54
|
end
|
46
55
|
|
47
|
-
def on_html_tag(name, attrs,
|
56
|
+
def on_html_tag(name, attrs, content = nil)
|
48
57
|
return super unless @pretty
|
49
58
|
|
50
|
-
|
51
|
-
|
59
|
+
name = name.to_s
|
60
|
+
closed = !content || (empty_exp?(content) && options[:autoclose].include?(name))
|
52
61
|
|
53
62
|
@pretty = false
|
54
63
|
result = [:multi, [:static, "#{tag_indent(name)}<#{name}"], compile(attrs)]
|
55
|
-
result << [:static, ' /'
|
56
|
-
result << [:static, '>']
|
64
|
+
result << [:static, (closed && xhtml? ? ' /' : '') + '>']
|
57
65
|
|
58
66
|
@pretty = !options[:pre_tags].include?(name)
|
59
|
-
|
60
|
-
|
61
|
-
|
67
|
+
if content
|
68
|
+
@indent += 1
|
69
|
+
result << compile(content)
|
70
|
+
@indent -= 1
|
71
|
+
end
|
62
72
|
|
63
73
|
result << [:static, "#{tag_indent(name)}</#{name}>"] if !closed
|
64
74
|
@pretty = true
|
@@ -68,7 +78,8 @@ module Temple
|
|
68
78
|
protected
|
69
79
|
|
70
80
|
def preamble
|
71
|
-
|
81
|
+
@pre_tags_name = unique_name
|
82
|
+
[:code, "#{@pre_tags_name} = /#{@pre_tags.source}/"]
|
72
83
|
end
|
73
84
|
|
74
85
|
# Return indentation if not in pre tag
|
@@ -0,0 +1,103 @@
|
|
1
|
+
module Temple
|
2
|
+
module Mixins
|
3
|
+
# @api private
|
4
|
+
module CoreDispatcher
|
5
|
+
def on_multi(*exps)
|
6
|
+
multi = [:multi]
|
7
|
+
exps.each {|exp| multi << compile(exp) }
|
8
|
+
multi
|
9
|
+
end
|
10
|
+
|
11
|
+
def on_capture(name, exp)
|
12
|
+
[:capture, name, compile(exp)]
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
# @api private
|
17
|
+
module EscapeDispatcher
|
18
|
+
def on_escape(flag, exp)
|
19
|
+
[:escape, flag, compile(exp)]
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
# @api private
|
24
|
+
module ControlFlowDispatcher
|
25
|
+
def on_if(condition, *cases)
|
26
|
+
[:if, condition, *cases.compact.map {|e| compile(e) }]
|
27
|
+
end
|
28
|
+
|
29
|
+
def on_case(arg, *cases)
|
30
|
+
[:case, arg, *cases.map {|condition, exp| [condition, compile(exp)] }]
|
31
|
+
end
|
32
|
+
|
33
|
+
def on_block(code, content)
|
34
|
+
[:block, code, compile(content)]
|
35
|
+
end
|
36
|
+
|
37
|
+
def on_cond(*cases)
|
38
|
+
[:cond, *cases.map {|condition, exp| [condition, compile(exp)] }]
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
# @api private
|
43
|
+
module CompiledDispatcher
|
44
|
+
def call(exp)
|
45
|
+
compile(exp)
|
46
|
+
end
|
47
|
+
|
48
|
+
def compile(exp)
|
49
|
+
dispatcher(exp)
|
50
|
+
end
|
51
|
+
|
52
|
+
private
|
53
|
+
|
54
|
+
def case_statement(types)
|
55
|
+
code = "type, *args = args\ncase type\n"
|
56
|
+
types.each do |name, method|
|
57
|
+
code << "when #{name.to_sym.inspect}\n" <<
|
58
|
+
(Hash === method ? case_statement(method) : "#{method}(*args)\n")
|
59
|
+
end
|
60
|
+
code << "else\nexp\nend\n"
|
61
|
+
end
|
62
|
+
|
63
|
+
def dispatcher(exp)
|
64
|
+
replace_dispatcher(exp)
|
65
|
+
end
|
66
|
+
|
67
|
+
def replace_dispatcher(exp)
|
68
|
+
types = {}
|
69
|
+
self.class.instance_methods.each do |method|
|
70
|
+
next if method.to_s !~ /^on_(.*)$/
|
71
|
+
method_types = $1.split('_')
|
72
|
+
(0...method_types.size).inject(types) do |tmp, i|
|
73
|
+
raise "Invalid temple dispatcher #{method}" unless Hash === tmp
|
74
|
+
if i == method_types.size - 1
|
75
|
+
tmp[method_types[i]] = method
|
76
|
+
else
|
77
|
+
tmp[method_types[i]] ||= {}
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
81
|
+
self.class.class_eval %{
|
82
|
+
def dispatcher(exp)
|
83
|
+
if self.class == #{self.class}
|
84
|
+
args = exp
|
85
|
+
#{case_statement(types)}
|
86
|
+
else
|
87
|
+
replace_dispatcher(exp)
|
88
|
+
end
|
89
|
+
end
|
90
|
+
}
|
91
|
+
dispatcher(exp)
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
95
|
+
# @api private
|
96
|
+
module Dispatcher
|
97
|
+
include CompiledDispatcher
|
98
|
+
include CoreDispatcher
|
99
|
+
include EscapeDispatcher
|
100
|
+
include ControlFlowDispatcher
|
101
|
+
end
|
102
|
+
end
|
103
|
+
end
|