temple 0.2.0 → 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (54) hide show
  1. data/.yardopts +2 -1
  2. data/CHANGES +63 -0
  3. data/EXPRESSIONS.md +250 -0
  4. data/README.md +24 -12
  5. data/lib/temple.rb +34 -18
  6. data/lib/temple/engine.rb +11 -7
  7. data/lib/temple/erb/engine.rb +5 -3
  8. data/lib/temple/erb/parser.rb +5 -2
  9. data/lib/temple/erb/template.rb +11 -0
  10. data/lib/temple/erb/trimming.rb +9 -1
  11. data/lib/temple/filter.rb +2 -0
  12. data/lib/temple/filters/control_flow.rb +43 -0
  13. data/lib/temple/filters/dynamic_inliner.rb +29 -32
  14. data/lib/temple/filters/eraser.rb +22 -0
  15. data/lib/temple/filters/escapable.rb +39 -0
  16. data/lib/temple/filters/multi_flattener.rb +4 -1
  17. data/lib/temple/filters/static_merger.rb +11 -10
  18. data/lib/temple/filters/validator.rb +15 -0
  19. data/lib/temple/generators.rb +41 -100
  20. data/lib/temple/grammar.rb +56 -0
  21. data/lib/temple/hash.rb +48 -0
  22. data/lib/temple/html/dispatcher.rb +10 -4
  23. data/lib/temple/html/fast.rb +50 -38
  24. data/lib/temple/html/filter.rb +8 -0
  25. data/lib/temple/html/pretty.rb +25 -14
  26. data/lib/temple/mixins/dispatcher.rb +103 -0
  27. data/lib/temple/{mixins.rb → mixins/engine_dsl.rb} +10 -95
  28. data/lib/temple/mixins/grammar_dsl.rb +166 -0
  29. data/lib/temple/mixins/options.rb +28 -0
  30. data/lib/temple/mixins/template.rb +25 -0
  31. data/lib/temple/templates.rb +2 -0
  32. data/lib/temple/utils.rb +11 -57
  33. data/lib/temple/version.rb +1 -1
  34. data/test/filters/test_control_flow.rb +92 -0
  35. data/test/filters/test_dynamic_inliner.rb +7 -7
  36. data/test/filters/test_eraser.rb +55 -0
  37. data/test/filters/{test_escape_html.rb → test_escapable.rb} +13 -6
  38. data/test/filters/test_multi_flattener.rb +1 -1
  39. data/test/filters/test_static_merger.rb +3 -3
  40. data/test/helper.rb +8 -0
  41. data/test/html/test_fast.rb +42 -57
  42. data/test/html/test_pretty.rb +10 -7
  43. data/test/mixins/test_dispatcher.rb +31 -0
  44. data/test/mixins/test_grammar_dsl.rb +86 -0
  45. data/test/test_engine.rb +73 -57
  46. data/test/test_erb.rb +0 -7
  47. data/test/test_filter.rb +26 -0
  48. data/test/test_generator.rb +57 -36
  49. data/test/test_grammar.rb +52 -0
  50. data/test/test_hash.rb +39 -0
  51. data/test/test_utils.rb +11 -38
  52. metadata +34 -10
  53. data/lib/temple/filters/debugger.rb +0 -26
  54. data/lib/temple/filters/escape_html.rb +0 -33
@@ -1,12 +1,18 @@
1
1
  module Temple
2
2
  module Mixins
3
+ # @api private
3
4
  module EngineDSL
5
+ def chain_modified!
6
+ end
7
+
4
8
  def append(*args, &block)
5
9
  chain << element(args, block)
10
+ chain_modified!
6
11
  end
7
12
 
8
13
  def prepend(*args, &block)
9
14
  chain.unshift(element(args, block))
15
+ chain_modified!
10
16
  end
11
17
 
12
18
  def remove(name)
@@ -17,6 +23,7 @@ module Temple
17
23
  equal
18
24
  end
19
25
  raise "#{name} not found" unless found
26
+ chain_modified!
20
27
  end
21
28
 
22
29
  alias use append
@@ -36,6 +43,7 @@ module Temple
36
43
  end
37
44
  end
38
45
  raise "#{name} not found" unless found
46
+ chain_modified!
39
47
  end
40
48
 
41
49
  def after(name, *args, &block)
@@ -52,6 +60,7 @@ module Temple
52
60
  i += 1
53
61
  end
54
62
  raise "#{name} not found" unless found
63
+ chain_modified!
55
64
  end
56
65
 
57
66
  def replace(name, *args, &block)
@@ -66,6 +75,7 @@ module Temple
66
75
  end
67
76
  end
68
77
  raise "#{name} not found" unless found
78
+ chain_modified!
69
79
  end
70
80
 
71
81
  def filter(name, *options, &block)
@@ -122,100 +132,5 @@ module Temple
122
132
  end
123
133
  end
124
134
  end
125
-
126
- module CoreDispatcher
127
- def on_multi(*exps)
128
- [:multi, *exps.map {|exp| compile(exp) }]
129
- end
130
-
131
- def on_capture(name, exp)
132
- [:capture, name, compile(exp)]
133
- end
134
-
135
- def on_escape(flag, exp)
136
- [:escape, flag, compile(exp)]
137
- end
138
- end
139
-
140
- module Dispatcher
141
- include CoreDispatcher
142
-
143
- def self.included(base)
144
- base.class_eval { extend ClassMethods }
145
- end
146
-
147
- def call(exp)
148
- compile(exp)
149
- end
150
-
151
- def compile(exp)
152
- type, *args = exp
153
- if respond_to?("on_#{type}")
154
- send("on_#{type}", *args)
155
- else
156
- exp
157
- end
158
- end
159
-
160
- module ClassMethods
161
- def temple_dispatch(*bases)
162
- bases.each do |base|
163
- class_eval %{def on_#{base}(type, *args)
164
- if respond_to?("on_" #{base.to_s.inspect} "_\#{type}")
165
- send("on_" #{base.to_s.inspect} "_\#{type}", *args)
166
- else
167
- [:#{base}, type, *args]
168
- end
169
- end}
170
- end
171
- end
172
- end
173
- end
174
-
175
- module DefaultOptions
176
- def set_default_options(options)
177
- default_options.update(options)
178
- end
179
-
180
- def default_options
181
- @default_options ||= Utils::MutableHash.new(superclass.respond_to?(:default_options) ?
182
- superclass.default_options : nil)
183
- end
184
- end
185
-
186
- module Options
187
- def self.included(base)
188
- base.class_eval { extend DefaultOptions }
189
- end
190
-
191
- attr_reader :options
192
-
193
- def initialize(options = {})
194
- @options = Utils::ImmutableHash.new(options, self.class.default_options)
195
- end
196
- end
197
-
198
- module Template
199
- include DefaultOptions
200
-
201
- def engine(engine = nil)
202
- default_options[:engine] = engine if engine
203
- default_options[:engine]
204
- end
205
-
206
- def build_engine(*options)
207
- raise 'No engine configured' unless engine
208
- options << default_options
209
- engine.new(Utils::ImmutableHash.new(*options)) do |e|
210
- chain.each {|block| e.instance_eval(&block) }
211
- end
212
- end
213
-
214
- def chain(&block)
215
- chain = (default_options[:chain] ||= [])
216
- chain << block if block
217
- chain
218
- end
219
- end
220
135
  end
221
136
  end
@@ -0,0 +1,166 @@
1
+ module Temple
2
+ module Mixins
3
+ # @api private
4
+ module GrammarDSL
5
+ class Rule
6
+ def initialize(grammar)
7
+ @grammar = grammar
8
+ end
9
+
10
+ def |(rule)
11
+ Or.new(@grammar, self, rule)
12
+ end
13
+
14
+ def copy_to(grammar)
15
+ copy = dup.instance_eval { @grammar = grammar; self }
16
+ copy.after_copy(self) if copy.respond_to?(:after_copy)
17
+ copy
18
+ end
19
+ end
20
+
21
+ class Or < Rule
22
+ def initialize(grammar, *children)
23
+ super(grammar)
24
+ @children = children.map {|rule| @grammar.Rule(rule) }
25
+ end
26
+
27
+ def <<(rule)
28
+ @children << @grammar.Rule(rule)
29
+ self
30
+ end
31
+
32
+ alias | <<
33
+
34
+ def match(exp, unmatched)
35
+ tmp = []
36
+ @children.any? {|rule| rule.match(exp, tmp) } || (unmatched.push(*tmp) && false)
37
+ end
38
+
39
+ def after_copy(source)
40
+ @children = @children.map {|child| child.copy_to(@grammar) }
41
+ end
42
+ end
43
+
44
+ class Root < Or
45
+ def initialize(grammar, name)
46
+ super(grammar)
47
+ @name = name.to_sym
48
+ end
49
+
50
+ def match(exp, unmatched)
51
+ success = super
52
+ unmatched << [@name, exp] unless success
53
+ success
54
+ end
55
+
56
+ def match?(exp)
57
+ match(exp, [])
58
+ end
59
+
60
+ def validate!(exp)
61
+ unmatched = []
62
+ unless match(exp, unmatched)
63
+ require 'pp'
64
+ rule, exp = unmatched.sort_by {|r,e| [*e].flatten.size }.first
65
+ raise(InvalidExpression, PP.pp(exp, "#{@grammar}::#{rule} did not match\n"))
66
+ end
67
+ end
68
+
69
+ def copy_to(grammar)
70
+ grammar.const_defined?(@name) ? grammar.const_get(@name) : super
71
+ end
72
+
73
+ def after_copy(source)
74
+ @grammar.const_set(@name, self)
75
+ super
76
+ end
77
+ end
78
+
79
+ class Element < Or
80
+ def initialize(grammar, rule)
81
+ super(grammar)
82
+ @rule = grammar.Rule(rule)
83
+ end
84
+
85
+ def match(exp, unmatched)
86
+ return false unless Array === exp && !exp.empty?
87
+ head, *tail = exp
88
+ @rule.match(head, unmatched) && super(tail, unmatched)
89
+ end
90
+
91
+ def after_copy(source)
92
+ @children = @children.map do |child|
93
+ child == source ? self : child.copy_to(@grammar)
94
+ end
95
+ @rule = @rule.copy_to(@grammar)
96
+ end
97
+ end
98
+
99
+ class Value < Rule
100
+ def initialize(grammar, value)
101
+ super(grammar)
102
+ @value = value
103
+ end
104
+
105
+ def match(exp, unmatched)
106
+ @value === exp
107
+ end
108
+ end
109
+
110
+ def extended(mod)
111
+ mod.extend GrammarDSL
112
+ constants.each do |name|
113
+ const_get(name).copy_to(mod) if Rule === const_get(name)
114
+ end
115
+ end
116
+
117
+ def match?(exp)
118
+ const_get(:Expression).match?(exp)
119
+ end
120
+
121
+ def validate!(exp)
122
+ const_get(:Expression).validate!(exp)
123
+ end
124
+
125
+ alias === match?
126
+ alias =~ match?
127
+
128
+ def Value(value)
129
+ Value.new(self, value)
130
+ end
131
+
132
+ def Rule(rule)
133
+ case rule
134
+ when Rule
135
+ rule
136
+ when Symbol, Class, true, false, nil
137
+ Value(rule)
138
+ when Array
139
+ start = Or.new(self)
140
+ curr = [start]
141
+ rule.each do |elem|
142
+ if elem =~ /^(.*)(\*|\?|\+)$/
143
+ elem = Element.new(self, const_get($1))
144
+ curr.each {|c| c << elem }
145
+ elem << elem if $2 != '?'
146
+ curr = $2 == '+' ? [elem] : (curr << elem)
147
+ else
148
+ elem = Element.new(self, elem)
149
+ curr.each {|c| c << elem }
150
+ curr = [elem]
151
+ end
152
+ end
153
+ elem = Value([])
154
+ curr.each {|c| c << elem }
155
+ start
156
+ else
157
+ raise "Invalid grammar rule '#{rule.inspect}'"
158
+ end
159
+ end
160
+
161
+ def const_missing(name)
162
+ const_set(name, Root.new(self, name))
163
+ end
164
+ end
165
+ end
166
+ end
@@ -0,0 +1,28 @@
1
+ module Temple
2
+ module Mixins
3
+ # @api public
4
+ module DefaultOptions
5
+ def set_default_options(options)
6
+ default_options.update(options)
7
+ end
8
+
9
+ def default_options
10
+ @default_options ||= MutableHash.new(superclass.respond_to?(:default_options) ?
11
+ superclass.default_options : nil)
12
+ end
13
+ end
14
+
15
+ # @api public
16
+ module Options
17
+ def self.included(base)
18
+ base.class_eval { extend DefaultOptions }
19
+ end
20
+
21
+ attr_reader :options
22
+
23
+ def initialize(options = {})
24
+ @options = ImmutableHash.new(options, self.class.default_options)
25
+ end
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,25 @@
1
+ module Temple
2
+ module Mixins
3
+ # @api private
4
+ module Template
5
+ include DefaultOptions
6
+
7
+ def engine(engine = nil)
8
+ default_options[:engine] = engine if engine
9
+ default_options[:engine]
10
+ end
11
+
12
+ def build_engine(*options)
13
+ raise 'No engine configured' unless engine
14
+ options << default_options
15
+ engine.new(ImmutableHash.new(*options))
16
+ end
17
+
18
+ def chain(&block)
19
+ chain = (default_options[:chain] ||= [])
20
+ chain << block if block
21
+ chain
22
+ end
23
+ end
24
+ end
25
+ end
@@ -1,4 +1,5 @@
1
1
  module Temple
2
+ # @api public
2
3
  module Templates
3
4
  autoload :Tilt, 'temple/templates/tilt'
4
5
  autoload :Rails, 'temple/templates/rails'
@@ -7,6 +8,7 @@ module Temple
7
8
  template = Class.new(const_get(name))
8
9
  template.engine(engine)
9
10
  template.register_as(options[:register_as]) if options[:register_as]
11
+ template.default_options.update(options)
10
12
  template
11
13
  end
12
14
  end
data/lib/temple/utils.rb CHANGED
@@ -1,62 +1,13 @@
1
1
  module Temple
2
+ # @api public
2
3
  module Utils
3
4
  extend self
4
5
 
5
- class ImmutableHash
6
- include Enumerable
7
-
8
- def initialize(*hash)
9
- @hash = hash.compact
10
- end
11
-
12
- def include?(key)
13
- @hash.any? {|h| h.include?(key) }
14
- end
15
-
16
- def [](key)
17
- @hash.each {|h| return h[key] if h.include?(key) }
18
- nil
19
- end
20
-
21
- def each
22
- keys.each {|k| yield(k, self[k]) }
23
- end
24
-
25
- def keys
26
- @hash.inject([]) {|keys, h| keys += h.keys }.uniq
27
- end
28
-
29
- def values
30
- keys.map {|k| self[k] }
31
- end
32
- end
33
-
34
- class MutableHash < ImmutableHash
35
- def initialize(*hash)
36
- super({}, *hash)
37
- end
38
-
39
- def []=(key, value)
40
- @hash.first[key] = value
41
- end
42
-
43
- def update(hash)
44
- @hash.first.update(hash)
45
- end
46
- end
47
-
48
- def indent(text, indent, pre_tags)
49
- text = text.to_s
50
- text.gsub!("\n", indent) if pre_tags !~ text
51
- text
52
- end
53
-
54
6
  # Returns an escaped copy of `html`.
55
7
  # Strings which are declared as html_safe are not escaped.
56
8
  #
57
9
  # @param html [String] The string to escape
58
10
  # @return [String] The escaped string
59
- # @api public
60
11
  def escape_html_safe(html)
61
12
  html.html_safe? ? html : escape_html(html)
62
13
  end
@@ -66,7 +17,6 @@ module Temple
66
17
  #
67
18
  # @param html [String] The string to escape
68
19
  # @return [String] The escaped string
69
- # @api public
70
20
  def escape_html(html)
71
21
  EscapeUtils.escape_html(html.to_s)
72
22
  end
@@ -85,7 +35,6 @@ module Temple
85
35
  #
86
36
  # @param html [String] The string to escape
87
37
  # @return [String] The escaped string
88
- # @api public
89
38
  def escape_html(html)
90
39
  html.to_s.gsub(/[&\"<>\/]/, ESCAPE_HTML)
91
40
  end
@@ -94,20 +43,25 @@ module Temple
94
43
  #
95
44
  # @param html [String] The string to escape
96
45
  # @return [String] The escaped string
97
- # @api public
98
46
  def escape_html(html)
99
47
  html.to_s.gsub(/&/n, '&amp;').gsub(/\"/n, '&quot;').gsub(/>/n, '&gt;').gsub(/</n, '&lt;').gsub(/\//, '&#47;')
100
48
  end
101
49
  end
102
50
 
103
- # Generate unique temporary variable name
51
+ # Generate unique variable name
104
52
  #
53
+ # @param prefix [String] Variable name prefix
105
54
  # @return [String] Variable name
106
- def tmp_var(prefix)
107
- @tmp_var ||= 0
108
- "_temple_#{prefix}#{@tmp_var += 1}"
55
+ def unique_name(prefix = nil)
56
+ @unique_name ||= 0
57
+ prefix ||= (@unique_prefix ||= self.class.name.gsub('::', '_').downcase)
58
+ "_#{prefix}#{@unique_name += 1}"
109
59
  end
110
60
 
61
+ # Check if expression is empty
62
+ #
63
+ # @param exp [Array] Temple expression
64
+ # @return true if expression is empty
111
65
  def empty_exp?(exp)
112
66
  case exp[0]
113
67
  when :multi