temple 0.2.0 → 0.3.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|