undies 2.2.1 → 3.0.0.rc.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (54) hide show
  1. data/ARCH.md +116 -0
  2. data/Gemfile +3 -0
  3. data/Gemfile.lock +20 -4
  4. data/LICENSE +22 -0
  5. data/README.md +343 -0
  6. data/Rakefile +25 -17
  7. data/bench/bench_runner.rb +132 -12
  8. data/bench/large.html.erb +9 -13
  9. data/bench/large.html.haml +11 -0
  10. data/bench/large.html.rb +8 -12
  11. data/bench/profiler +1 -1
  12. data/bench/profiler_runner.rb +2 -5
  13. data/bench/small.html.erb +9 -13
  14. data/bench/small.html.haml +11 -0
  15. data/bench/small.html.rb +8 -12
  16. data/bench/verylarge.html.erb +9 -13
  17. data/bench/verylarge.html.haml +11 -0
  18. data/bench/verylarge.html.rb +8 -12
  19. data/lib/undies/api.rb +163 -0
  20. data/lib/undies/element.rb +160 -80
  21. data/lib/undies/element_node.rb +116 -0
  22. data/lib/undies/io.rb +43 -0
  23. data/lib/undies/root_node.rb +62 -0
  24. data/lib/undies/source.rb +78 -2
  25. data/lib/undies/template.rb +17 -131
  26. data/lib/undies/version.rb +1 -1
  27. data/lib/undies.rb +3 -2
  28. data/test/element_closed_test.rb +69 -0
  29. data/test/element_node_test.rb +274 -0
  30. data/test/element_open_test.rb +101 -0
  31. data/test/element_test.rb +23 -196
  32. data/test/fixtures/write_thing.rb +4 -4
  33. data/test/helper.rb +84 -0
  34. data/test/io_test.rb +104 -0
  35. data/test/named_source_test.rb +1 -1
  36. data/test/raw_test.rb +25 -0
  37. data/test/root_node_test.rb +108 -0
  38. data/test/source_stack_test.rb +1 -1
  39. data/test/template_builder_render_test.rb +4 -9
  40. data/test/template_source_render_test.rb +16 -20
  41. data/test/template_test.rb +87 -80
  42. data/test/templates/content.html.rb +1 -1
  43. data/test/templates/test.html.rb +1 -1
  44. data/undies.gemspec +1 -0
  45. metadata +52 -23
  46. data/README.rdoc +0 -203
  47. data/lib/undies/named_source.rb +0 -54
  48. data/lib/undies/node.rb +0 -87
  49. data/lib/undies/node_stack.rb +0 -111
  50. data/lib/undies/output.rb +0 -31
  51. data/lib/undies/source_stack.rb +0 -22
  52. data/test/node_stack_test.rb +0 -109
  53. data/test/node_test.rb +0 -91
  54. data/test/output_test.rb +0 -69
@@ -1,27 +1,19 @@
1
- require 'undies/source_stack'
2
- require 'undies/node_stack'
3
- require 'undies/output'
4
-
5
- require 'undies/node'
6
- require 'undies/element'
1
+ require 'undies/io'
2
+ require 'undies/source'
3
+ require 'undies/root_node'
4
+ require 'undies/api'
7
5
 
8
6
  module Undies
9
7
  class Template
10
8
 
9
+ include API
10
+
11
11
  # have as many methods on the class level as possible to keep from
12
12
  # polluting the public instance methods, the instance scope, and to
13
13
  # maximize the effectiveness of the Template#method_missing logic
14
14
 
15
- def self.source_stack(template)
16
- template.instance_variable_get("@_undies_source_stack")
17
- end
18
-
19
- def self.node_stack(template)
20
- template.instance_variable_get("@_undies_node_stack")
21
- end
22
-
23
15
  def self.flush(template)
24
- node_stack(template).flush
16
+ template.__flush
25
17
  end
26
18
 
27
19
  # Ripped from Rack v1.3.0 ======================================
@@ -43,136 +35,30 @@ module Undies
43
35
 
44
36
  def initialize(*args)
45
37
  # setup a node stack with the given output obj
46
- output = if args.last.kind_of?(NodeStack) || args.last.kind_of?(Output)
38
+ @_undies_io = if args.last.kind_of?(Undies::IO)
47
39
  args.pop
48
40
  else
49
- raise ArgumentError, "please provide an Output object"
41
+ raise ArgumentError, "please provide an IO object"
50
42
  end
51
- @_undies_node_stack = NodeStack.create(output)
52
43
 
53
- # apply any given data to template scope
54
- data = args.last.kind_of?(::Hash) ? args.pop : {}
55
- if (data.keys.map(&:to_s) & self.public_methods.map(&:to_s)).size > 0
56
- raise ArgumentError, "data conflicts with template public methods."
44
+ # apply any given data to template scope as instance variables
45
+ (args.last.kind_of?(::Hash) ? args.pop : {}).each do |k, v|
46
+ self.instance_variable_set("@#{k}", v)
57
47
  end
58
- metaclass = class << self; self; end
59
- data.each {|key, value| metaclass.class_eval { define_method(key){value} }}
60
48
 
61
49
  # setup a source stack with the given source
62
50
  source = args.last.kind_of?(Source) ? args.pop : Source.new(Proc.new {})
63
51
  @_undies_source_stack = SourceStack.new(source)
64
52
 
53
+ # push a root node onto the IO
54
+ @_undies_io.push!(RootNode.new(@_undies_io)) if @_undies_io.empty?
55
+
65
56
  # yield to recursivley render the source stack
66
- self.__yield
57
+ __yield
67
58
 
68
59
  # flush any elements that need to be built
69
- self.class.flush(self)
70
- end
71
-
72
- # call this to modify element attrs inside a build block. Once content
73
- # or child elements have been added, any '__attr' directives will
74
- # be ignored b/c the elements start_tag has already been flushed
75
- # to the output
76
- def __attrs(attrs_hash={})
77
- self.class.node_stack(self).current.tap do |node|
78
- if node
79
- node.class.merge_attrs(node, attrs_hash)
80
- node.class.set_start_tag(node)
81
- end
82
- end
83
- end
84
-
85
- # call this method to manually push the currently cached node onto the
86
- # node stack
87
- # - implicitly flushes the cache
88
- # - changes the context of template method calls to operate on that node
89
- def __push
90
- ns = self.class.node_stack(self)
91
- node, ns.cached_node = ns.cached_node, nil
92
- if node
93
- # add an empty build block to generate a non-closing start tag
94
- # and a closing end tag
95
- node.class.add_build(node, Proc.new {})
96
- node.class.set_start_tag(node)
97
- node.class.set_end_tag(node)
98
-
99
- ns.push(node)
100
- end
101
- end
102
-
103
- # call this method to manually pop the current scoped node from the node stack
104
- # - flushes the cache
105
- # - changes the context of template method calls to operate on the parent node
106
- def __pop
107
- ns = self.class.node_stack(self)
108
- ns.clear_cached
109
- ns.pop
110
- end
111
-
112
- # call this to manually flush a template
113
- def __flush
114
- self.class.flush(self)
115
- end
116
-
117
- # call this to render template source
118
- # use this method in layouts to insert a layout's content source
119
- def __yield
120
- return if self.class.node_stack(self).nil? || (source = self.class.source_stack(self).pop).nil?
121
- if source.file?
122
- instance_eval(source.data, source.source, 1)
123
- else
124
- instance_eval(&source.data)
125
- end
126
- end
127
-
128
- # call this to render partial source embedded in a template
129
- # partial source is rendered with its own scope/data but shares
130
- # its parent template's output object
131
- def __partial(source, data={})
132
- if source.kind_of?(Source)
133
- Undies::Template.new(source, data, self.class.node_stack(self))
134
- else
135
- self.__ source.to_s, :partial
136
- end
137
- end
138
-
139
- # Add a text node (data escaped) to the nodes of the current node
140
- def _(data="", mode=:inline)
141
- self.__ self.class.escape_html(data.to_s), mode
142
- end
143
-
144
- # Add a text node with the data un-escaped
145
- def __(data="", mode=:inline)
146
- Node.new(data.to_s, mode).tap do |node|
147
- self.class.node_stack(self).node(node)
148
- end
149
- end
150
-
151
- # Add an element to the node stack
152
- def element(*args, &build)
153
- Element.new(*args, &build).tap do |element|
154
- self.class.node_stack(self).node(element)
155
- end
156
- end
157
- alias_method :tag, :element
158
-
159
- # Element proxy methods ('_<element>'') ========================
160
- ELEM_METH_REGEX = /^_(.+)$/
161
- def method_missing(meth, *args, &block)
162
- if meth.to_s =~ ELEM_METH_REGEX
163
- element($1, *args, &block)
164
- else
165
- super
166
- end
167
- end
168
- def respond_to?(*args)
169
- if args.first.to_s =~ ELEM_METH_REGEX
170
- true
171
- else
172
- super
173
- end
60
+ __flush
174
61
  end
175
- # ==============================================================
176
62
 
177
63
  end
178
64
  end
@@ -1,3 +1,3 @@
1
1
  module Undies
2
- VERSION = "2.2.1"
2
+ VERSION = "3.0.0.rc.1"
3
3
  end
data/lib/undies.rb CHANGED
@@ -1,3 +1,4 @@
1
- require 'undies/template'
2
-
3
1
  module Undies; end
2
+
3
+ require 'undies/io'
4
+ require 'undies/template'
@@ -0,0 +1,69 @@
1
+ require "assert"
2
+ require 'undies/io'
3
+ require "undies/element"
4
+
5
+
6
+ module Undies::Element
7
+
8
+ class ClosedBasicTests < Assert::Context
9
+ desc 'a closed element'
10
+ before do
11
+ @ec = Undies::Element::Closed.new(:br)
12
+ end
13
+ subject { @ec }
14
+
15
+ should have_instance_methods :__start_tag, :__content, :__build, :__end_tag
16
+ should have_instance_methods :to_s
17
+
18
+ should "know its name and store it as a string" do
19
+ assert_equal "br", subject.instance_variable_get("@name")
20
+ end
21
+
22
+ should "have no attrs by default" do
23
+ assert_empty subject.instance_variable_get("@attrs")
24
+ end
25
+
26
+ end
27
+
28
+
29
+
30
+ class ClosedCSSProxyTests < ClosedBasicTests
31
+ extend CSSProxyMacro
32
+
33
+ should proxy_css_methods
34
+ end
35
+
36
+
37
+
38
+ class ClosedSerializeTests < ClosedBasicTests
39
+
40
+ should "serialize with no attrs" do
41
+ elem = Undies::Element::Closed.new(:br)
42
+ assert_equal "<br />", elem.to_s
43
+ end
44
+
45
+ should "serialize with attrs" do
46
+ elem = Undies::Element::Closed.new(:br, :class => 'big')
47
+ assert_equal "<br class=\"big\" />", elem.to_s
48
+ end
49
+
50
+ should "serialize with attrs that have double-quotes" do
51
+ elem = Undies::Element::Closed.new(:br, :class => '"this" is double-quoted')
52
+ assert_equal "<br class=\"&quot;this&quot; is double-quoted\" />", elem.to_s
53
+ end
54
+
55
+ should "serialize element proxy id call" do
56
+ elem = Undies::Element::Closed.new(:br).thing1!
57
+ assert_equal "<br id=\"thing1\" />", elem.to_s
58
+ end
59
+
60
+ should "serialize element proxy class call" do
61
+ # calling a private method as public to test private methods not
62
+ # polluting public method_missing scope
63
+ elem = Undies::Element::Closed.new(:br).proxy
64
+ assert_equal "<br class=\"proxy\" />", elem.to_s
65
+ end
66
+
67
+ end
68
+
69
+ end
@@ -0,0 +1,274 @@
1
+ require "assert"
2
+ require 'undies/io'
3
+ require 'undies/element_node'
4
+ require 'undies/element'
5
+
6
+
7
+ class Undies::ElementNode
8
+
9
+ class BasicTests < Assert::Context
10
+ desc 'an element node'
11
+ before do
12
+ # io test with :pp 1 so we can test newline insertion
13
+ # io test with level 1 so we can test element start tag writing
14
+ @io = Undies::IO.new(@out = "", :pp => 1, :level => 1)
15
+ @e = Undies::Element.open(:div, "hi")
16
+ @en = Undies::ElementNode.new(@io, @e)
17
+ end
18
+ subject { @en }
19
+
20
+ should have_readers :io, :element, :cached
21
+ should have_instance_methods :attrs, :text, :element_node
22
+ should have_instance_methods :partial, :flush, :push, :pop, :to_s
23
+
24
+ should "know its IO" do
25
+ assert_equal @io, subject.io
26
+ end
27
+
28
+ should "know its element" do
29
+ assert_equal @e, subject.element
30
+ end
31
+
32
+ should "have nothing cached by default" do
33
+ assert_nil subject.cached
34
+ end
35
+
36
+ should "complain if trying to add text in a build" do
37
+ assert_raises Undies::ElementAPIError do
38
+ subject.text 'blah'
39
+ end
40
+ end
41
+
42
+ end
43
+
44
+
45
+
46
+ class AddContentTests < BasicTests
47
+ desc "adding content"
48
+ before do
49
+ @text1 = "some raw markup"
50
+ @text2 = "more raw markup"
51
+ @elem1 = Undies::Element::Open.new(:strong, "blah")
52
+ @elem2 = Undies::Element::Closed.new(:br).meh
53
+ @en1 = Undies::ElementNode.new(@io, @elem1)
54
+ @en2 = Undies::ElementNode.new(@io, @elem2)
55
+ end
56
+
57
+ end
58
+
59
+ class AddContentStartTagWrittenTests < AddContentTests
60
+ desc "(the start tag has already been written)"
61
+ before do
62
+ subject.instance_variable_set("@start_tag_written", true)
63
+ end
64
+
65
+ end
66
+
67
+ class ElementStartTagWrittenTests < AddContentStartTagWrittenTests
68
+ desc "using the element_node meth"
69
+
70
+ should "cache any element node given" do
71
+ subject.element_node(@en1)
72
+ assert_equal @en1, subject.cached
73
+ end
74
+
75
+ should "return the element node" do
76
+ assert_equal @en1, subject.element_node(@en1)
77
+ end
78
+
79
+ should "write out any cached value when a new element is given" do
80
+ subject.element_node(@en1)
81
+ assert_empty @out
82
+
83
+ subject.element_node(@en2)
84
+ assert_equal "#{@io.line_indent}<strong>blah</strong>#{@io.newline}", @out
85
+ end
86
+
87
+ should "write out any cached value when flushed" do
88
+ subject.flush
89
+ assert_empty @out
90
+
91
+ subject.element_node(@en1)
92
+ subject.flush
93
+ assert_equal "#{@io.line_indent}<strong>blah</strong>#{@io.newline}", @out
94
+ end
95
+
96
+ end
97
+
98
+ class PartialStartTagWrittenTests < AddContentStartTagWrittenTests
99
+ desc "using the partial meth"
100
+
101
+ should "cache any partial markup given" do
102
+ subject.partial("partial markup")
103
+ assert_equal "partial markup", subject.cached
104
+ end
105
+
106
+ should "write out any cached partial values" do
107
+ subject.partial("partial markup")
108
+ assert_empty @out
109
+
110
+ subject.element_node(@en2)
111
+ assert_equal "partial markup", @out
112
+ end
113
+
114
+ should "write out any cached value when flushed" do
115
+ subject.flush
116
+ assert_empty @out
117
+
118
+ subject.partial("partial markup")
119
+ subject.flush
120
+ assert_equal "partial markup", @out
121
+ end
122
+
123
+ end
124
+
125
+
126
+
127
+ class AddContentStartTagNotWrittenTests < AddContentTests
128
+ desc "(the start tag has not been written)"
129
+ before do
130
+ subject.instance_variable_set("@start_tag_written", false)
131
+ end
132
+
133
+ end
134
+
135
+ class ElementStartTagNotWrittenTests < AddContentStartTagNotWrittenTests
136
+ desc "using the element_node meth"
137
+
138
+ should "cache any element_node given" do
139
+ subject.element_node(@en1)
140
+ assert_equal @en1, subject.cached
141
+ end
142
+
143
+ should "return the element node" do
144
+ assert_equal @en1, subject.element_node(@en1)
145
+ end
146
+
147
+ should "write out the start tag with IO#newline when an element is given" do
148
+ subject.element_node(@en1)
149
+ assert_equal "<div>#{@io.newline}", @out
150
+ end
151
+
152
+ should "write out any cached content and cache new markup when given" do
153
+ subject.element_node @en1
154
+ subject.element_node @en2
155
+ assert_equal "<div>#{@io.newline}#{@io.line_indent}<strong>blah</strong>#{@io.newline}", @out
156
+ assert_equal @en2, subject.cached
157
+ end
158
+
159
+ should "write out the end tag with IO#newline indented when popped" do
160
+ subject.element_node(@en1)
161
+ subject.pop
162
+ assert_equal "<div>#{@io.newline} <strong>blah</strong>#{@io.newline}</div>#{@io.newline}", @out
163
+ end
164
+
165
+ end
166
+
167
+ class PartialStartTagNotWrittenTests < AddContentStartTagNotWrittenTests
168
+ desc "using the partial meth"
169
+
170
+ should "cache any partial markup given" do
171
+ subject.partial("partial markup")
172
+ assert_equal "partial markup", subject.cached
173
+ end
174
+
175
+ should "write out the start tag with IO#newline when a partial is given" do
176
+ subject.partial("partial markup")
177
+ assert_equal "<div>#{@io.newline}", @out
178
+ end
179
+
180
+ should "write out the end tag with IO#newline indented when a partial is given" do
181
+ subject.partial(" partial markup\n")
182
+ subject.pop
183
+ assert_equal "<div>#{@io.newline} partial markup\n</div>#{@io.newline}", @out
184
+ end
185
+
186
+ end
187
+
188
+ class AttrsTests < AddContentStartTagNotWrittenTests
189
+ desc "using the attrs meth"
190
+
191
+ should "modify the parent element's tag attributes" do
192
+ subject.attrs(:test => 'value')
193
+ subject.element_node(@en1)
194
+
195
+ assert_equal "<div test=\"value\">#{@io.newline}", @out
196
+ end
197
+
198
+ should "not effect the start tag once child elements have been written" do
199
+ subject.attrs(:test => 'value')
200
+ subject.element_node(@en1)
201
+ subject.attrs(:another => 'val')
202
+
203
+ assert_equal "<div test=\"value\">#{@io.newline}", @out
204
+ end
205
+
206
+ end
207
+
208
+
209
+
210
+ class SerializeTests < BasicTests
211
+
212
+ should "serialize nested elements with pp and only honor the last build block" do
213
+ io = Undies::IO.new(@out = "", :pp => 1, :level => 0)
214
+
215
+ e1a = Undies::Element::Open.new :span, 'Content!'
216
+ en1a = Undies::ElementNode.new(io, e1a)
217
+
218
+ e1b = Undies::Element::Open.new :span, 'More content'
219
+ en1b = Undies::ElementNode.new(io, e1b)
220
+
221
+ e1c = Undies::Element::Open.new :div
222
+ en1c = Undies::ElementNode.new(io, e1c)
223
+
224
+ e2a = Undies::Element::Open.new :p, 'a'
225
+ en2a = Undies::ElementNode.new(io, e2a)
226
+
227
+ e2b = Undies::Element::Open.new :p, 'b'
228
+ en2b = Undies::ElementNode.new(io, e2b)
229
+
230
+ e_root = Undies::Element::Open.new :div
231
+ en_root = Undies::ElementNode.new(io, e_root)
232
+ e_root.test do
233
+ en_root.attrs :class => 'root'
234
+ en_root.element_node(en1a)
235
+ en_root.element_node(en1b)
236
+ en_root.element_node(en1c)
237
+ end
238
+
239
+ # any build trumps any content provided in the args
240
+ e1b.test {
241
+ en1b.element_node(en2a)
242
+ }
243
+
244
+ # the last build specified is the only one used
245
+ e1c.
246
+ proxy {
247
+ # this will be ignored
248
+ en1c.element_node(en1a)
249
+ }.
250
+ start_tag.
251
+ you! {
252
+ # this will be the build for e1c
253
+ # only builds on the last proxy call
254
+ en1c.element_node(en2b)
255
+ }
256
+
257
+
258
+ en_root.to_s
259
+
260
+ assert_equal "<div class=\"root\">
261
+ <span>Content!</span>
262
+ <span class=\"test\">
263
+ <p>a</p>
264
+ </span>
265
+ <div class=\"proxy start_tag\" id=\"you\">
266
+ <p>b</p>
267
+ </div>
268
+ </div>
269
+ ", @out
270
+ end
271
+
272
+ end
273
+
274
+ end
@@ -0,0 +1,101 @@
1
+ require "assert"
2
+ require "undies/element"
3
+
4
+
5
+ module Undies::Element
6
+
7
+ class OpenBasicTests < Assert::Context
8
+ desc 'an open element'
9
+ before do
10
+ @e = Undies::Element::Open.new(:div)
11
+ end
12
+ subject { @e }
13
+
14
+ should have_instance_methods :__start_tag, :__content, :__build, :__end_tag
15
+ should have_instance_methods :to_s
16
+
17
+ should "know its name and store it as a string" do
18
+ assert_equal "div", subject.instance_variable_get("@name")
19
+ end
20
+
21
+ should "have no attrs by default" do
22
+ assert_empty subject.instance_variable_get("@attrs")
23
+ end
24
+
25
+ should "have no content by default" do
26
+ assert_empty subject.instance_variable_get("@content")
27
+ end
28
+
29
+ should "have no build by default" do
30
+ assert_nil subject.instance_variable_get("@build")
31
+ end
32
+
33
+ end
34
+
35
+
36
+
37
+ class OpenCSSProxyTests < OpenBasicTests
38
+ extend CSSProxyMacro
39
+
40
+ should proxy_css_methods
41
+ end
42
+
43
+
44
+
45
+ class OpenSerializeTests < OpenBasicTests
46
+
47
+ should "serialize with no attrs, content, or build" do
48
+ elem = Undies::Element::Open.new(:div)
49
+ assert_equal "<div></div>", elem.to_s
50
+ end
51
+
52
+ should "serialize with attrs" do
53
+ elem = Undies::Element::Open.new(:div, :class => 'big')
54
+ assert_equal "<div class=\"big\"></div>", elem.to_s
55
+ end
56
+
57
+ should "serialize with escaped attrs content" do
58
+ elem = Undies::Element::Open.new(:div, :class => '"this" is double-quoted')
59
+ assert_equal "<div class=\"&quot;this&quot; is double-quoted\"></div>", elem.to_s
60
+ end
61
+
62
+ should "serialize with a single piece of content" do
63
+ elem = Undies::Element::Open.new(:div, "hi")
64
+ assert_equal "<div>hi</div>", elem.to_s
65
+ end
66
+
67
+ should "serialize with multiple pieces of content joined" do
68
+ elem = Undies::Element::Open.new(:div, "hi", ' there', ' you')
69
+ assert_equal "<div>hi there you</div>", elem.to_s
70
+ end
71
+
72
+ should "serialize with escaped content" do
73
+ elem = Undies::Element::Open.new(:div, "stuff & <em>more stuff</em>")
74
+ assert_equal "<div>stuff &amp; &lt;em&gt;more stuff&lt;&#x2F;em&gt;</div>", elem.to_s
75
+ end
76
+
77
+ should "serialize with raw content" do
78
+ elem = Undies::Element::Open.new(:div, Undies::Raw.new("stuff & <em>more stuff</em>"))
79
+ assert_equal "<div>stuff & <em>more stuff</em></div>", elem.to_s
80
+ end
81
+
82
+ should "serialize element proxy id call" do
83
+ elem = Undies::Element::Open.new(:div, 'stuff').thing1!
84
+ assert_equal "<div id=\"thing1\">stuff</div>", elem.to_s
85
+ end
86
+
87
+ should "serialize element proxy id call with content" do
88
+ elem = Undies::Element::Open.new(:div).thing1! 'proxy stuff'
89
+ assert_equal "<div id=\"thing1\">proxy stuff</div>", elem.to_s
90
+ end
91
+
92
+ should "serialize element proxy class call" do
93
+ # calling a private method as public to test private methods not
94
+ # polluting public method_missing scope
95
+ elem = Undies::Element::Open.new(:div, 'stuff').proxy
96
+ assert_equal "<div class=\"proxy\">stuff</div>", elem.to_s
97
+ end
98
+
99
+ end
100
+
101
+ end