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.
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