undies 2.2.0 → 2.2.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.
@@ -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