undies 2.2.0 → 2.2.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,9 +1,10 @@
1
1
  require 'undies/source_stack'
2
+ require 'undies/node_stack'
2
3
  require 'undies/output'
4
+
3
5
  require 'undies/node'
4
6
  require 'undies/element'
5
7
 
6
-
7
8
  module Undies
8
9
  class Template
9
10
 
@@ -11,12 +12,16 @@ module Undies
11
12
  # polluting the public instance methods, the instance scope, and to
12
13
  # maximize the effectiveness of the Template#method_missing logic
13
14
 
14
- def self.output(template)
15
- template.instance_variable_get("@_undies_output")
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")
16
21
  end
17
22
 
18
23
  def self.flush(template)
19
- template.instance_variable_get("@_undies_output").flush
24
+ node_stack(template).flush
20
25
  end
21
26
 
22
27
  # Ripped from Rack v1.3.0 ======================================
@@ -37,38 +42,82 @@ module Undies
37
42
  # end Rip from Rack v1.3.0 =====================================
38
43
 
39
44
  def initialize(*args)
40
- output = if args.last.kind_of?(Output)
45
+ # setup a node stack with the given output obj
46
+ output = if args.last.kind_of?(NodeStack) || args.last.kind_of?(Output)
41
47
  args.pop
42
48
  else
43
49
  raise ArgumentError, "please provide an Output object"
44
50
  end
45
- data = args.last.kind_of?(::Hash) ? args.pop : {}
46
- source = args.last.kind_of?(Source) ? args.pop : Source.new(Proc.new {})
47
-
48
- # setup the source stack and output objects
49
- @_undies_source_stack = SourceStack.new(source)
51
+ @_undies_node_stack = NodeStack.create(output)
50
52
 
51
- # apply data to template scope
53
+ # apply any given data to template scope
54
+ data = args.last.kind_of?(::Hash) ? args.pop : {}
52
55
  if (data.keys.map(&:to_s) & self.public_methods.map(&:to_s)).size > 0
53
56
  raise ArgumentError, "data conflicts with template public methods."
54
57
  end
55
58
  metaclass = class << self; self; end
56
59
  data.each {|key, value| metaclass.class_eval { define_method(key){value} }}
57
60
 
58
- # save off the output obj for streaming
59
- @_undies_output = output
61
+ # setup a source stack with the given source
62
+ source = args.last.kind_of?(Source) ? args.pop : Source.new(Proc.new {})
63
+ @_undies_source_stack = SourceStack.new(source)
60
64
 
61
65
  # yield to recursivley render the source stack
62
66
  self.__yield
63
67
 
64
- # flush any remaining output to the stream
68
+ # 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
65
114
  self.class.flush(self)
66
115
  end
67
116
 
68
117
  # call this to render template source
69
118
  # use this method in layouts to insert a layout's content source
70
119
  def __yield
71
- return if @_undies_source_stack.nil? || (source = @_undies_source_stack.pop).nil?
120
+ return if self.class.node_stack(self).nil? || (source = self.class.source_stack(self).pop).nil?
72
121
  if source.file?
73
122
  instance_eval(source.data, source.source, 1)
74
123
  else
@@ -79,23 +128,32 @@ module Undies
79
128
  # call this to render partial source embedded in a template
80
129
  # partial source is rendered with its own scope/data but shares
81
130
  # its parent template's output object
82
- def __partial(source, data)
83
- Undies::Template.new(source, data, @_undies_output)
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
84
137
  end
85
138
 
86
139
  # Add a text node (data escaped) to the nodes of the current node
87
- def _(data=""); self.__ self.class.escape_html(data.to_s); end
140
+ def _(data="", mode=:inline)
141
+ self.__ self.class.escape_html(data.to_s), mode
142
+ end
88
143
 
89
144
  # Add a text node with the data un-escaped
90
- def __(data=""); @_undies_output.node(Node.new(data.to_s)); end
91
-
92
- # Add a text node with the data un-escaped, forcing pp output
93
- def ___(data="")
94
- @_undies_output.node(Node.new(data.to_s, :force_pp => true))
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
95
149
  end
96
150
 
97
- # Add an element to the nodes of the current node
98
- def element(*args, &block); @_undies_output.node(Element.new(*args, &block)); end
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
99
157
  alias_method :tag, :element
100
158
 
101
159
  # Element proxy methods ('_<element>'') ========================
@@ -1,3 +1,3 @@
1
1
  module Undies
2
- VERSION = "2.2.0"
2
+ VERSION = "2.2.1"
3
3
  end
data/test/element_test.rb CHANGED
@@ -1,8 +1,12 @@
1
1
  require "assert"
2
2
 
3
- require "undies/element"
3
+ require 'undies/node'
4
+ require "undies/output"
4
5
  require "undies/template"
5
6
 
7
+ require "undies/element"
8
+
9
+
6
10
  class Undies::Element
7
11
 
8
12
  class BasicTests < Assert::Context
@@ -12,19 +16,37 @@ class Undies::Element
12
16
  end
13
17
  subject { @e }
14
18
 
15
- should have_class_methods :hash_attrs, :content, :flush
16
- should have_instance_method :to_str
19
+ should have_class_methods :hash_attrs
20
+ should have_instance_method :prefix, :to_str
17
21
 
18
22
  should "be a Node" do
19
23
  assert_kind_of Undies::Node, subject
20
24
  end
21
25
 
22
- should "store it's name as a string" do
26
+ should "store its name as a string" do
23
27
  assert_equal "div", subject.instance_variable_get("@name")
24
28
  end
25
29
 
30
+ should "know its name" do
31
+ assert_equal "div", subject.class.node_name(subject)
32
+ end
33
+
34
+ should "know its start/end tags" do
35
+ assert_equal "<div />", subject.class.start_tag(subject)
36
+ assert_empty subject.class.end_tag(subject)
37
+ end
38
+
26
39
  should "have no content itself" do
27
- assert_nil subject.class.content(subject)
40
+ assert_empty subject.class.content(subject)
41
+ end
42
+
43
+ should "have no builds by default" do
44
+ assert_empty subject.class.builds(subject)
45
+ assert_empty subject.class.builds(nil)
46
+ end
47
+
48
+ should "have no children by default" do
49
+ assert_equal false, subject.class.children(subject)
28
50
  end
29
51
 
30
52
  end
@@ -73,6 +95,7 @@ class Undies::Element
73
95
  end
74
96
  end
75
97
 
98
+
76
99
  class CSSProxyTests < BasicTests
77
100
 
78
101
  should "respond to any method ending in '!' as an id proxy" do
@@ -82,68 +105,62 @@ class Undies::Element
82
105
  should "proxy id attr with methods ending in '!'" do
83
106
  assert_equal({
84
107
  :id => 'thing1'
85
- }, subject.thing1!.instance_variable_get("@attrs"))
108
+ }, subject.class.attrs(subject.thing1!))
86
109
  end
87
110
 
88
111
  should "proxy id attr with last method call ending in '!'" do
89
112
  assert_equal({
90
113
  :id => 'thing2'
91
- }, subject.thing1!.thing2!.instance_variable_get("@attrs"))
114
+ }, subject.class.attrs(subject.thing1!.thing2!))
92
115
  end
93
116
 
94
117
  should "set id attr to explicit if called last " do
95
118
  assert_equal({
96
119
  :id => 'thing3'
97
- }, subject.thing1!.thing2!(:id => 'thing3').instance_variable_get("@attrs"))
120
+ }, subject.class.attrs(subject.thing1!.thing2!(:id => 'thing3')))
98
121
  end
99
122
 
100
123
  should "set id attr to proxy if called last" do
101
124
  assert_equal({
102
125
  :id => 'thing1'
103
- }, subject.thing2!(:id => 'thing3').thing1!.instance_variable_get("@attrs"))
126
+ }, subject.class.attrs(subject.thing2!(:id => 'thing3').thing1!))
104
127
  end
105
128
 
106
129
  should "respond to any other method as a class proxy" do
107
- assert subject.respond_to?(:asdgasdg)
130
+ assert_respond_to :asdgasdg, subject
108
131
  end
109
132
 
110
133
  should "proxy single html class attr" do
111
134
  assert_equal({
112
135
  :class => 'thing'
113
- }, subject.thing.instance_variable_get("@attrs"))
136
+ }, subject.class.attrs(subject.thing))
114
137
  end
115
138
 
116
139
  should "proxy multi html class attrs" do
117
140
  assert_equal({
118
141
  :class => 'list thing awesome'
119
- }, subject.list.thing.awesome.instance_variable_get("@attrs"))
142
+ }, subject.class.attrs(subject.list.thing.awesome))
120
143
  end
121
144
 
122
145
  should "set class attr with explicit if called last " do
123
146
  assert_equal({
124
147
  :class => 'list'
125
- }, subject.thing.awesome(:class => "list").instance_variable_get("@attrs"))
148
+ }, subject.class.attrs(subject.thing.awesome(:class => "list")))
126
149
  end
127
150
 
128
151
  should "update class attr with proxy if called last" do
129
152
  assert_equal({
130
153
  :class => 'list is good'
131
- }, subject.thing.awesome(:class => "list is").good.instance_variable_get("@attrs"))
154
+ }, subject.class.attrs(subject.thing.awesome(:class => "list is").good))
132
155
  end
133
156
 
134
157
  should "proxy mixed class and id selector attrs" do
158
+ subject.thing1!.awesome({:class => "list is", :id => "thing2"}).good.thing3!
159
+
135
160
  assert_equal({
136
161
  :class => 'list is good',
137
162
  :id => "thing3"
138
- }, subject.thing1!.awesome({
139
- :class => "list is",
140
- :id => "thing2"
141
- }).good.thing3!.instance_variable_get("@attrs"))
142
- end
143
-
144
- should "not proxy if private methods are called" do
145
- assert_equal "<div />", subject.send(:start_tag)
146
- assert_equal nil, subject.send(:end_tag)
163
+ }, subject.class.attrs(subject))
147
164
  end
148
165
 
149
166
  end
@@ -154,58 +171,58 @@ class Undies::Element
154
171
  end
155
172
 
156
173
  should "serialize with no child elements" do
157
- src = Undies::Source.new do
174
+ Undies::Template.new(Undies::Source.new do
158
175
  element(:br)
159
- end
160
- templ = Undies::Template.new(src, {}, @output)
176
+ end, {}, @output)
177
+
161
178
  assert_equal "<br />", @out
162
179
  end
163
180
 
164
181
  should "serialize with attrs" do
165
- src = Undies::Source.new do
182
+ Undies::Template.new(Undies::Source.new do
166
183
  element(:br, :class => 'big')
167
- end
168
- templ = Undies::Template.new(src, {}, @output)
184
+ end, {}, @output)
185
+
169
186
  assert_equal '<br class="big" />', @out
170
187
  end
171
188
 
172
189
  should "serialize with attrs that have double-quotes" do
173
- src = Undies::Source.new do
190
+ Undies::Template.new(Undies::Source.new do
174
191
  element(:br, :class => '"this" is double-quoted')
175
- end
176
- templ = Undies::Template.new(src, {}, @output)
192
+ end, {}, @output)
193
+
177
194
  assert_equal '<br class="&quot;this&quot; is double-quoted" />', @out
178
195
  end
179
196
 
180
197
  should "serialize with attrs and content" do
181
- src = Undies::Source.new do
198
+ Undies::Template.new(Undies::Source.new do
182
199
  element(:strong, {:class => 'big'}) { __ "Loud Noises!" }
183
- end
184
- templ = Undies::Template.new(src, {}, @output)
200
+ end, {}, @output)
201
+
185
202
  assert_equal '<strong class="big">Loud Noises!</strong>', @out
186
203
  end
187
204
 
188
205
  should "serialize element proxy id call" do
189
- src = Undies::Source.new do
206
+ Undies::Template.new(Undies::Source.new do
190
207
  element(:div).thing1! { _ "stuff" }
191
- end
192
- templ = Undies::Template.new(src, {}, @output)
208
+ end, {}, @output)
209
+
193
210
  assert_equal "<div id=\"thing1\">stuff</div>", @out
194
211
  end
195
212
 
196
213
  should "serialize element proxy class call" do
197
- src = Undies::Source.new do
214
+ Undies::Template.new(Undies::Source.new do
198
215
  element(:div).thing { _ "stuff" }
199
- end
200
- templ = Undies::Template.new(src, {}, @output)
216
+ end, {}, @output)
217
+
201
218
  assert_equal "<div class=\"thing\">stuff</div>", @out
202
219
  end
203
220
 
204
221
  should "serialize content from separate content blocks" do
205
- src = Undies::Source.new do
222
+ Undies::Template.new(Undies::Source.new do
206
223
  element(:div){ _ "stuff" }.thing1!{ _ " and more stuff" }
207
- end
208
- templ = Undies::Template.new(src, {}, @output)
224
+ end, {}, @output)
225
+
209
226
  assert_equal "<div id=\"thing1\">stuff and more stuff</div>", @out
210
227
  end
211
228
 
@@ -216,14 +233,18 @@ class Undies::Element
216
233
  element(:span) { _ "Content!" }
217
234
  __ "Raw"
218
235
  element(:span) { _ "More content" }
236
+ element(:div).hi {
237
+ _ "first build"
238
+ }.there.you! {
239
+ _ "second build"
240
+ }
219
241
  }
220
242
  end
221
243
  templ = Undies::Template.new(src, {}, output)
222
- assert_equal "
223
- <div>
224
- <span>Content!</span>
225
- Raw
244
+ assert_equal "<div>
245
+ <span>Content!</span>Raw
226
246
  <span>More content</span>
247
+ <div class=\"hi there\" id=\"you\">first buildsecond build</div>
227
248
  </div>", @out
228
249
  end
229
250
 
@@ -0,0 +1,21 @@
1
+ class WriteThing
2
+
3
+ # this is used in testing the write buffer
4
+
5
+ def self.hi(thing)
6
+ 'hi'
7
+ end
8
+
9
+ def self.hello(thing)
10
+ 'hello'
11
+ end
12
+
13
+ def self.hithere(thing)
14
+ 'hithere'
15
+ end
16
+
17
+ def self.prefix(thing, meth, level, indent)
18
+ "#{level > 0 ? "\n": ''}#{' '*level*indent}"
19
+ end
20
+
21
+ end
@@ -0,0 +1,109 @@
1
+ require 'assert'
2
+
3
+ require 'undies/node'
4
+ require 'undies/element'
5
+ require 'test/fixtures/write_thing'
6
+
7
+ require 'undies/node_stack'
8
+
9
+ module Undies
10
+
11
+ class NodeStackTests < Assert::Context
12
+ desc "a NodeStack"
13
+ before do
14
+ @hello = Node.new "hello"
15
+ @something = Node.new "something else"
16
+ @hi = Node.new("hi")
17
+ @br = Element.new :br
18
+ @div = Element.new(:div) {}
19
+
20
+ @outstream = StringIO.new(@out = "")
21
+ @stream_test_output = Output.new(@outstream)
22
+ @ns = NodeStack.new @stream_test_output
23
+ end
24
+ subject { @ns }
25
+
26
+ should have_class_method :create
27
+ should have_readers :stack, :cached_node, :output
28
+ should have_instance_methods :current, :size, :level, :empty?, :first, :last
29
+ should have_instance_methods :push, :pop, :node, :flush
30
+ should have_instance_methods :clear_cached
31
+
32
+ should "be empty by default" do
33
+ assert_empty subject
34
+ end
35
+
36
+ should "have an empty cache by default" do
37
+ assert_nil subject.cached_node
38
+ end
39
+
40
+ end
41
+
42
+
43
+ class StackTests < NodeStackTests
44
+
45
+ should "push nodes onto the stack" do
46
+ assert_nothing_raised do
47
+ subject.push(@hello)
48
+ subject.push(@something)
49
+ end
50
+
51
+ assert_equal 2, subject.size
52
+ end
53
+
54
+ should "know its level (should be one less than the array's size)" do
55
+ assert_equal 0, subject.level
56
+ subject.push(@hello)
57
+ assert_equal 1, subject.level
58
+ end
59
+
60
+ should "fetch the last item in the array with the current method" do
61
+ subject.push(@hello)
62
+ subject.push(@something)
63
+
64
+ assert_same @something, subject.current
65
+ end
66
+
67
+ should "remove the last item in the array with the pop method" do
68
+ subject.push(@hello)
69
+ subject.push(@something)
70
+
71
+ assert_equal 2, subject.size
72
+
73
+ node = subject.pop
74
+ assert_same @something, node
75
+ assert_equal 1, subject.size
76
+ end
77
+
78
+ end
79
+
80
+
81
+ class CacheTests < NodeStackTests
82
+
83
+ should "add nodes to its cache using the #node method" do
84
+ subject.node(@hi)
85
+ assert_equal @hi, subject.cached_node
86
+ end
87
+
88
+ should "push the current cached node onto the stack when caching a new node" do
89
+ subject.node(@br)
90
+ assert_equal @br, subject.cached_node
91
+ assert_equal 0, subject.size
92
+
93
+ subject.node(@div)
94
+ assert_equal @div, subject.cached_node
95
+ end
96
+
97
+ should "call the node's builds when flushed from cache" do
98
+ build = "build"
99
+ subject.node(Element.new(:div) { build += " called" })
100
+ subject.flush
101
+
102
+ assert_equal "build called", build
103
+ end
104
+
105
+ end
106
+
107
+
108
+
109
+ end
data/test/node_test.rb CHANGED
@@ -1,39 +1,89 @@
1
1
  require "assert"
2
2
 
3
+ require "undies/output"
3
4
  require "undies/node"
4
- require "undies/node_buffer"
5
5
 
6
6
  class Undies::Node
7
7
 
8
8
  class BasicTests < Assert::Context
9
9
  desc 'a node'
10
- before { @n = Undies::Node.new("a text node here") }
10
+ before do
11
+ @output = Undies::Output.new(StringIO.new(@out = ""))
12
+ @n = Undies::Node.new("a text node here")
13
+ end
11
14
  subject { @n }
12
15
 
13
- should have_class_methods :content, :flush
16
+ should have_class_methods :start_tag, :end_tag, :set_start_tag, :set_end_tag
17
+ should have_class_methods :builds, :add_build
18
+ should have_class_methods :children, :set_children
19
+ should have_class_methods :attrs, :merge_attrs
20
+ should have_class_methods :node_name, :content, :mode, :prefix
21
+
22
+ should "have no start/end tags" do
23
+ assert_empty subject.class.start_tag(subject)
24
+ assert_empty subject.class.end_tag(subject)
25
+ end
14
26
 
15
- should "know it's content" do
27
+ should "have content" do
16
28
  assert_equal "a text node here", subject.class.content(subject)
17
29
  end
18
30
 
19
- should "know it's start/end tags" do
20
- assert_nil subject.instance_variable_get("@start_tag")
21
- assert_nil subject.instance_variable_get("@end_tag")
31
+ should "have no builds" do
32
+ assert_empty subject.class.builds(subject)
33
+ end
34
+
35
+ should "be :inline mode by default" do
36
+ assert_equal :inline, subject.class.mode(subject)
37
+ end
38
+
39
+ should "have no prefix if :inline mode" do
40
+ assert_empty subject.class.prefix(subject, 'start_tag', 2, 2)
41
+ assert_empty subject.class.prefix(subject, 'end_tag', 1, 2)
42
+ end
43
+
44
+ should "have a pp prefix if not :inline mode" do
45
+ node = Undies::Node.new("a non inline node", :partial)
46
+ assert_equal "\n ", node.class.prefix(node, 'start_tag', 2, 2)
47
+ assert_equal "\n ", node.class.prefix(node, 'end_tag', 2, 2)
48
+ assert_equal "\n ", node.class.prefix(node, 'start_tag', 1, 2)
49
+ assert_equal "\n", node.class.prefix(node, 'end_tag', 1, 2)
50
+ assert_equal "", node.class.prefix(node, 'start_tag', 0, 2)
51
+ assert_equal "\n", node.class.prefix(node, 'end_tag', 0, 2)
22
52
  end
23
53
 
24
- should "output its content when flushed" do
25
- output = Undies::Output.new(StringIO.new(out = ""))
26
- subject.class.flush(output, subject)
54
+ should "have no children by default" do
55
+ assert_equal false, subject.class.children(subject)
56
+ end
57
+
58
+ should "have no attrs by default" do
59
+ assert_empty subject.class.attrs(subject)
60
+ end
61
+
62
+ should "have no name by default" do
63
+ assert_empty subject.class.node_name(subject)
64
+ end
65
+
66
+ should "add a build if given a block" do
67
+ subject.class.add_build(subject, Proc.new {})
68
+ assert_equal [Proc.new {}], subject.class.builds(subject)
69
+
70
+ subject.class.add_build(subject, Proc.new {})
71
+ assert_equal [Proc.new {}, Proc.new {}], subject.class.builds(subject)
72
+ end
27
73
 
28
- assert_equal "a text node here", out
74
+ should "set children if given a value" do
75
+ subject.class.set_children(subject, true)
76
+ assert_equal true, subject.class.children(subject)
29
77
  end
30
78
 
31
- should "force pp when flushed if directed" do
32
- output = Undies::Output.new(StringIO.new(out = ""), :pp => 2, :pp_level => 1)
33
- output.pp_use_indent = false
34
- Undies::Node.flush(output, Undies::Node.new("should be pp", {:force_pp => true}))
79
+ should "merge attrs if given an attrs hash" do
80
+ attrs_hash = {:same => 'value', :new => 'a new value'}
81
+ subject.class.merge_attrs(subject, attrs_hash)
82
+ assert_equal attrs_hash, subject.class.attrs(subject)
35
83
 
36
- assert_equal "\n should be pp", out
84
+ attrs_hash = {:same => 'new same', :new => 'a new value'}
85
+ subject.class.merge_attrs(subject, attrs_hash)
86
+ assert_equal attrs_hash, subject.class.attrs(subject)
37
87
  end
38
88
 
39
89
  end