undies 2.2.1 → 3.0.0.rc.1

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