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.
- data/ARCH.md +116 -0
- data/Gemfile +3 -0
- data/Gemfile.lock +20 -4
- data/LICENSE +22 -0
- data/README.md +343 -0
- data/Rakefile +25 -17
- data/bench/bench_runner.rb +132 -12
- data/bench/large.html.erb +9 -13
- data/bench/large.html.haml +11 -0
- data/bench/large.html.rb +8 -12
- data/bench/profiler +1 -1
- data/bench/profiler_runner.rb +2 -5
- data/bench/small.html.erb +9 -13
- data/bench/small.html.haml +11 -0
- data/bench/small.html.rb +8 -12
- data/bench/verylarge.html.erb +9 -13
- data/bench/verylarge.html.haml +11 -0
- data/bench/verylarge.html.rb +8 -12
- data/lib/undies/api.rb +163 -0
- data/lib/undies/element.rb +160 -80
- data/lib/undies/element_node.rb +116 -0
- data/lib/undies/io.rb +43 -0
- data/lib/undies/root_node.rb +62 -0
- data/lib/undies/source.rb +78 -2
- data/lib/undies/template.rb +17 -131
- data/lib/undies/version.rb +1 -1
- data/lib/undies.rb +3 -2
- data/test/element_closed_test.rb +69 -0
- data/test/element_node_test.rb +274 -0
- data/test/element_open_test.rb +101 -0
- data/test/element_test.rb +23 -196
- data/test/fixtures/write_thing.rb +4 -4
- data/test/helper.rb +84 -0
- data/test/io_test.rb +104 -0
- data/test/named_source_test.rb +1 -1
- data/test/raw_test.rb +25 -0
- data/test/root_node_test.rb +108 -0
- data/test/source_stack_test.rb +1 -1
- data/test/template_builder_render_test.rb +4 -9
- data/test/template_source_render_test.rb +16 -20
- data/test/template_test.rb +87 -80
- data/test/templates/content.html.rb +1 -1
- data/test/templates/test.html.rb +1 -1
- data/undies.gemspec +1 -0
- metadata +52 -23
- data/README.rdoc +0 -203
- data/lib/undies/named_source.rb +0 -54
- data/lib/undies/node.rb +0 -87
- data/lib/undies/node_stack.rb +0 -111
- data/lib/undies/output.rb +0 -31
- data/lib/undies/source_stack.rb +0 -22
- data/test/node_stack_test.rb +0 -109
- data/test/node_test.rb +0 -91
- data/test/output_test.rb +0 -69
data/README.rdoc
DELETED
@@ -1,203 +0,0 @@
|
|
1
|
-
= Undies
|
2
|
-
A pure-Ruby DSL for streaming templated HTML, XML, or plain text. Named for its gratuitous use of the underscore.
|
3
|
-
|
4
|
-
== Installation
|
5
|
-
$ gem install undies
|
6
|
-
|
7
|
-
== DSL
|
8
|
-
|
9
|
-
Undies uses an underscore-based DSL for rendering content. It can render plain text, html-escaped text, xml, and html.
|
10
|
-
|
11
|
-
=== Plain text
|
12
|
-
|
13
|
-
Escaped (xml / html) text:
|
14
|
-
|
15
|
-
_ "this will be escaped & streamed"
|
16
|
-
# => "this will be escaped & streamed"
|
17
|
-
|
18
|
-
Raw (un-escaped) text:
|
19
|
-
__ "this will <em>not</em> be escaped"
|
20
|
-
# => "this will <em>not</em> be escaped"
|
21
|
-
|
22
|
-
=== XML
|
23
|
-
|
24
|
-
Empty node:
|
25
|
-
_thing # => "<thing />"
|
26
|
-
|
27
|
-
Node with content:
|
28
|
-
_thing {
|
29
|
-
_ "Some Content"
|
30
|
-
_ "More Content "
|
31
|
-
} # => "<thing>Some ContentMore Content</thing>"
|
32
|
-
|
33
|
-
Node with attributes:
|
34
|
-
_thing(:one => 1, 'a' => "Aye")
|
35
|
-
# => "<thing one=\"1\" a=\"Aye\" />"
|
36
|
-
|
37
|
-
Nested nodes:
|
38
|
-
_thing {
|
39
|
-
_Something {
|
40
|
-
_ "Some Content"
|
41
|
-
}
|
42
|
-
} # => "<thing><Something>Some Content</Something></thing>"
|
43
|
-
|
44
|
-
Namespaces / Doctype stuff:
|
45
|
-
__ "<?xml version="1.0" encoding="UTF-8"?>"
|
46
|
-
|
47
|
-
_thing('xmlns:ss="urn:some-cool-namespace"') {
|
48
|
-
send("_ss:Value", {'ss:some_attr' => "some attr value"}) {
|
49
|
-
_ "Some Content"
|
50
|
-
}
|
51
|
-
} # => "<thing xmlns:ss=\"urn:some-cool-namespace\"><ss:Value ss:some_attr=\"some attr value\">Some Content</ss:Value>"
|
52
|
-
|
53
|
-
Comments:
|
54
|
-
__ "<!-- some comment -->"
|
55
|
-
# => "<!-- some comment -->"
|
56
|
-
|
57
|
-
=== HTML
|
58
|
-
|
59
|
-
Same stuff applies:
|
60
|
-
__ "<!DOCTYPE html>"
|
61
|
-
|
62
|
-
_div(:style => "border-top: 1px") {
|
63
|
-
_h1 { _ "Hello World" }
|
64
|
-
_p { _ "blah blah" }
|
65
|
-
} # => "<div style=\"border-top: 1px\"><h1>Hello World</h1><p>blah blah</p></div>"
|
66
|
-
|
67
|
-
id attribute helper:
|
68
|
-
_h1.header!
|
69
|
-
# => "<h1 id=\"header\" />"
|
70
|
-
|
71
|
-
_h1.header!.title!
|
72
|
-
# => "<h1 id=\"title\" />"
|
73
|
-
|
74
|
-
class attributes helper:
|
75
|
-
_h1.header.awesome
|
76
|
-
# => "<h1 class=\"header awesome\" />"
|
77
|
-
|
78
|
-
use in combo:
|
79
|
-
_h1.header!.awesome
|
80
|
-
# => "<h1 class=\"awesome\" id=\"header\" />
|
81
|
-
|
82
|
-
== Streamed Output
|
83
|
-
|
84
|
-
Undies works by streaming content defined by using its DSL to a given output stream. Any call to Template#_, Template#__, or Tempate#_<node> will stream its output (or you can write helpers you mix in that use these base api methods). This has a number of advantages:
|
85
|
-
|
86
|
-
* content is written out immediately
|
87
|
-
* maintain a relatively low memory profile while rendering
|
88
|
-
* can process large templates with linear performance impact
|
89
|
-
|
90
|
-
However, because content is streamed then forgotten as it is being rendered, Undies templates cannot be self-referrential. No one element may refer to other previously rendered elements.
|
91
|
-
|
92
|
-
== Rendering
|
93
|
-
|
94
|
-
To render using Undies, create a Template instance, providing the template source, data, and output information.
|
95
|
-
|
96
|
-
source = Undies::Source.new("/path/to/sourcefile")
|
97
|
-
data = { :two_plus_two => 4 }
|
98
|
-
output = Undies::Output.new(@some_io_stream)
|
99
|
-
|
100
|
-
Undies::Template.new(source, {}, output)
|
101
|
-
|
102
|
-
=== Source
|
103
|
-
|
104
|
-
You specify Undies source using the Undies::Source object. You can create source either form a block or a file. Source content (either block or file) will be evaluated in context of the template.
|
105
|
-
|
106
|
-
=== Data
|
107
|
-
|
108
|
-
Undies renders source content in isolated scope (the context of the template). This means that content has access to only the data it is given or the Undies API itself. When you define a template for rendering, you provide not only the template source, but any data that source should be rendered with. Data is given in the form of a Hash. The string form of the hash keys are exposed as local methods that return their corresponding values.
|
109
|
-
|
110
|
-
=== Output
|
111
|
-
|
112
|
-
As said before, Undies streams to a given output stream. You specify a Template's output by creating an Undies::Output object. These objects take an io stream and a hash of options:
|
113
|
-
|
114
|
-
* :pp (pretty-print) : set to a Fixnum to tab-space indent pretty print the output.
|
115
|
-
|
116
|
-
=== Examples
|
117
|
-
|
118
|
-
# file source, no local data, no pretty printing
|
119
|
-
source = Undies::Source.new("/path/to/source")
|
120
|
-
Undies::Template.new(source, {}, Undies::Output.new(@io))
|
121
|
-
|
122
|
-
|
123
|
-
# proc source, simple local data, no pretty printing
|
124
|
-
source = Undies::Source.new(Proc.new do
|
125
|
-
_div {
|
126
|
-
_ content.to_s
|
127
|
-
}
|
128
|
-
end)
|
129
|
-
Undies::Template.new(source, {:content => "Some Content!!" }, Undies::Output.new(@io))
|
130
|
-
|
131
|
-
|
132
|
-
# pretty printing (4 space tab indentation)
|
133
|
-
source = Undies::Source.new("/path/to/source")
|
134
|
-
Undies::Template.new(source, {}, Undies::Output.new(@io, :pp => 4))
|
135
|
-
|
136
|
-
=== Builder approach
|
137
|
-
|
138
|
-
The above examples use the "source rendering" approach. This works great when you know your source content before render time and create a source object from it (ie rendering a view template). However, in some cases, you may not know the source until render time and/or want to use a more declarative style to specify render output. Undies content can be specified programmatically using the "builder rendering" approach.
|
139
|
-
|
140
|
-
To render using this approach, create a Template instance passing it data and output info as above. However, don't pass in any source info, only pass in any local data if you like, and save off the created template:
|
141
|
-
|
142
|
-
# choosing not to use any local data in this example
|
143
|
-
template = Undies::Template.new(Undies::Output.new(@io))
|
144
|
-
|
145
|
-
Now just interact with the Undies API directly.
|
146
|
-
|
147
|
-
# notice that it becomes less important to bind any local data to the Template using this approach
|
148
|
-
something = "Some Thing!"
|
149
|
-
template._div.something! {
|
150
|
-
template._ something.to_s
|
151
|
-
}
|
152
|
-
|
153
|
-
*Note:* there is one extra caveat to be aware of using this approach. You need to be sure and flush the template when content processing is complete. Just pass the template to the Undies::Template#flush method:
|
154
|
-
|
155
|
-
# ensures all content is streamed to the template's output stream
|
156
|
-
# this is necessary when not using the source approach above
|
157
|
-
Undies::Template.flush(template)
|
158
|
-
|
159
|
-
=== Manual approach
|
160
|
-
|
161
|
-
There is another method you can use to render output: the Manual approach. Like the Builder approach, this method is ideal when you don't know the source until render time. The key difference is that blocks are not used to imply nesting relationships. Using this approach, you manually 'push' and 'pop' to move up and down nesting relationship contexts. So a push on an element would move the template context to the element pushed. A pop would move back to the current context's parent element. As you would expect, pop'ing on the root of a template has no effect on the context and pushing a non-element node has no effect on the context.
|
162
|
-
|
163
|
-
To render using this approach, create a Template as you would with the Builder approach. Interact with the Undies API directly. Use the Template#push and Template#pop class methods to change the template scope.
|
164
|
-
|
165
|
-
# this is the equivalent to the Builder approach example above
|
166
|
-
|
167
|
-
template = Undies::Template.new(Undies::Output.new(@io))
|
168
|
-
something = "Some Thing!"
|
169
|
-
current = template._div.something!
|
170
|
-
|
171
|
-
template.__push(current)
|
172
|
-
template._ something.to_s
|
173
|
-
template.__pop
|
174
|
-
|
175
|
-
# alternate method for flushing a template
|
176
|
-
template.__flush
|
177
|
-
|
178
|
-
*Note:* as with the Builder approach, you must flush the template when content processing is complete.
|
179
|
-
|
180
|
-
== License
|
181
|
-
|
182
|
-
Copyright (c) 2011-Present Kelly Redding
|
183
|
-
|
184
|
-
Permission is hereby granted, free of charge, to any person
|
185
|
-
obtaining a copy of this software and associated documentation
|
186
|
-
files (the "Software"), to deal in the Software without
|
187
|
-
restriction, including without limitation the rights to use,
|
188
|
-
copy, modify, merge, publish, distribute, sublicense, and/or sell
|
189
|
-
copies of the Software, and to permit persons to whom the
|
190
|
-
Software is furnished to do so, subject to the following
|
191
|
-
conditions:
|
192
|
-
|
193
|
-
The above copyright notice and this permission notice shall be
|
194
|
-
included in all copies or substantial portions of the Software.
|
195
|
-
|
196
|
-
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
197
|
-
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
|
198
|
-
OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
199
|
-
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
|
200
|
-
HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
201
|
-
WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
202
|
-
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
|
203
|
-
OTHER DEALINGS IN THE SOFTWARE.
|
data/lib/undies/named_source.rb
DELETED
@@ -1,54 +0,0 @@
|
|
1
|
-
module Undies
|
2
|
-
|
3
|
-
class Source; end
|
4
|
-
|
5
|
-
class NamedSource
|
6
|
-
|
7
|
-
attr_accessor :file, :opts, :proc
|
8
|
-
|
9
|
-
def initialize(*args, &block)
|
10
|
-
args << block if block
|
11
|
-
self.args = args
|
12
|
-
end
|
13
|
-
|
14
|
-
def ==(other_named_source)
|
15
|
-
self.file == other_named_source.file &&
|
16
|
-
self.opts == other_named_source.opts &&
|
17
|
-
self.proc == other_named_source.proc
|
18
|
-
end
|
19
|
-
|
20
|
-
def args=(values)
|
21
|
-
self.proc, self.opts, self.file = [
|
22
|
-
values.last.kind_of?(::Proc) ? values.pop : nil,
|
23
|
-
values.last.kind_of?(::Hash) ? values.pop : {},
|
24
|
-
values.last.kind_of?(::String) ? values.pop : nil
|
25
|
-
]
|
26
|
-
end
|
27
|
-
|
28
|
-
def args
|
29
|
-
[self.file, self.opts, self.proc]
|
30
|
-
end
|
31
|
-
|
32
|
-
end
|
33
|
-
|
34
|
-
# singleton accessors for named sources
|
35
|
-
|
36
|
-
def self.named_sources
|
37
|
-
@@sources ||= {}
|
38
|
-
end
|
39
|
-
|
40
|
-
def self.named_source(name, *args, &block)
|
41
|
-
if args.empty? && block.nil?
|
42
|
-
self.named_sources[name]
|
43
|
-
else
|
44
|
-
self.named_sources[name] = Undies::NamedSource.new(*args, &block)
|
45
|
-
end
|
46
|
-
end
|
47
|
-
|
48
|
-
def self.source(name)
|
49
|
-
if ns = self.named_source(name)
|
50
|
-
Undies::Source.new(ns)
|
51
|
-
end
|
52
|
-
end
|
53
|
-
|
54
|
-
end
|
data/lib/undies/node.rb
DELETED
@@ -1,87 +0,0 @@
|
|
1
|
-
module Undies
|
2
|
-
|
3
|
-
class Node
|
4
|
-
|
5
|
-
# have as many methods to the class level as possible to keep from
|
6
|
-
# polluting the public instance methods and to maximize the effectiveness
|
7
|
-
# of the Element#method_missing logic
|
8
|
-
|
9
|
-
def self.start_tag(node)
|
10
|
-
node.instance_variable_get("@start_tag") || ""
|
11
|
-
end
|
12
|
-
|
13
|
-
def self.end_tag(node)
|
14
|
-
node.instance_variable_get("@end_tag") || ""
|
15
|
-
end
|
16
|
-
|
17
|
-
def self.set_start_tag(node); end
|
18
|
-
def self.set_end_tag(node); end
|
19
|
-
|
20
|
-
def self.builds(node)
|
21
|
-
node.instance_variable_get("@builds") || []
|
22
|
-
end
|
23
|
-
|
24
|
-
def self.add_build(node, build_block)
|
25
|
-
node.instance_variable_set("@builds", builds(node) + [build_block])
|
26
|
-
end
|
27
|
-
|
28
|
-
def self.children(node)
|
29
|
-
node.instance_variable_get("@children")
|
30
|
-
end
|
31
|
-
|
32
|
-
def self.set_children(node, value)
|
33
|
-
node.instance_variable_set("@children", value)
|
34
|
-
end
|
35
|
-
|
36
|
-
def self.attrs(element)
|
37
|
-
element.instance_variable_get("@attrs")
|
38
|
-
end
|
39
|
-
|
40
|
-
def self.merge_attrs(element, value={})
|
41
|
-
attrs(element).merge(value).tap do |a|
|
42
|
-
element.instance_variable_set("@attrs", a)
|
43
|
-
end
|
44
|
-
end
|
45
|
-
|
46
|
-
def self.node_name(node)
|
47
|
-
node.instance_variable_get("@name") || ""
|
48
|
-
end
|
49
|
-
|
50
|
-
def self.content(node)
|
51
|
-
node.instance_variable_get("@content") || ""
|
52
|
-
end
|
53
|
-
|
54
|
-
def self.mode(node)
|
55
|
-
node.instance_variable_get("@mode") || :inline
|
56
|
-
end
|
57
|
-
|
58
|
-
def self.prefix(node, meth, level, indent)
|
59
|
-
"".tap do |value|
|
60
|
-
if mode(node) != :inline && indent > 0
|
61
|
-
if meth == 'start_tag'
|
62
|
-
value << "#{level > 0 ? "\n": ''}#{' '*level*indent}"
|
63
|
-
elsif meth == 'end_tag'
|
64
|
-
value << "\n#{' '*(level > 0 ? level-1 : level)*indent}"
|
65
|
-
end
|
66
|
-
end
|
67
|
-
end
|
68
|
-
end
|
69
|
-
|
70
|
-
def initialize(content, mode=:inline)
|
71
|
-
@start_tag = nil
|
72
|
-
@end_tag = nil
|
73
|
-
@content = content
|
74
|
-
@builds = []
|
75
|
-
@attrs = {}
|
76
|
-
@children = false
|
77
|
-
@mode = mode
|
78
|
-
end
|
79
|
-
|
80
|
-
def ==(other_node)
|
81
|
-
self.class.content(self) == other_node.class.content(other_node) &&
|
82
|
-
self.instance_variable_get("@start_tag") == other_node.instance_variable_get("@start_tag") &&
|
83
|
-
self.instance_variable_get("@end_tag") == other_node.instance_variable_get("@end_tag")
|
84
|
-
end
|
85
|
-
|
86
|
-
end
|
87
|
-
end
|
data/lib/undies/node_stack.rb
DELETED
@@ -1,111 +0,0 @@
|
|
1
|
-
module Undies
|
2
|
-
class NodeStack
|
3
|
-
|
4
|
-
# the node stack handles caching nodes for processing, running node
|
5
|
-
# builds, and buffering node output for writing. I want to treat this
|
6
|
-
# as a stack of nodes for the template API to reference. I need to push
|
7
|
-
# a node onto the stack, reference it using the 'current' method,
|
8
|
-
# and pop it off the stack when I'm done.
|
9
|
-
|
10
|
-
# the stack first caches new nodes before pushing them onto the stack
|
11
|
-
# and node output is buffered as new nodes are pushed
|
12
|
-
|
13
|
-
def self.create(output)
|
14
|
-
output.kind_of?(NodeStack) ? output : NodeStack.new(output)
|
15
|
-
end
|
16
|
-
|
17
|
-
attr_reader :stack, :output, :buffer
|
18
|
-
attr_accessor :cached_node
|
19
|
-
|
20
|
-
def initialize(output)
|
21
|
-
@stack = []
|
22
|
-
@cached_node = nil
|
23
|
-
@output = output
|
24
|
-
@written_level = 0
|
25
|
-
end
|
26
|
-
|
27
|
-
def empty?; @stack.empty?; end
|
28
|
-
def size; @stack.size; end
|
29
|
-
def first; @stack.first; end
|
30
|
-
def last; @stack.last; end
|
31
|
-
|
32
|
-
alias_method :current, :last
|
33
|
-
alias_method :level, :size
|
34
|
-
|
35
|
-
def push(node)
|
36
|
-
if current
|
37
|
-
current.class.set_children(current, node.kind_of?(Element))
|
38
|
-
end
|
39
|
-
if @written_level < level
|
40
|
-
open(current)
|
41
|
-
end
|
42
|
-
@stack.push(node)
|
43
|
-
end
|
44
|
-
|
45
|
-
def pop(*args)
|
46
|
-
if !empty?
|
47
|
-
node = if @written_level < level
|
48
|
-
@stack.pop.tap { |node| write(node) }
|
49
|
-
else
|
50
|
-
@stack.pop.tap { |node| close(node) }
|
51
|
-
end
|
52
|
-
end
|
53
|
-
end
|
54
|
-
|
55
|
-
def node(node)
|
56
|
-
clear_cached
|
57
|
-
self.cached_node = node
|
58
|
-
end
|
59
|
-
|
60
|
-
def flush
|
61
|
-
clear_cached
|
62
|
-
end
|
63
|
-
|
64
|
-
def clear_cached
|
65
|
-
node_to_push, self.cached_node = self.cached_node, nil
|
66
|
-
if node_to_push
|
67
|
-
push(node_to_push)
|
68
|
-
node_to_push.class.builds(node_to_push).each { |build| build.call }
|
69
|
-
clear_cached
|
70
|
-
pop
|
71
|
-
end
|
72
|
-
end
|
73
|
-
|
74
|
-
private
|
75
|
-
|
76
|
-
def open(node)
|
77
|
-
start_tag(node, @written_level)
|
78
|
-
@written_level += 1
|
79
|
-
content(node, @written_level)
|
80
|
-
end
|
81
|
-
|
82
|
-
def write(node)
|
83
|
-
start_tag(node, @written_level)
|
84
|
-
content(node, @written_level+1)
|
85
|
-
end_tag(node, @written_level)
|
86
|
-
end
|
87
|
-
|
88
|
-
def close(node)
|
89
|
-
@written_level -= 1
|
90
|
-
end_tag(node, @written_level)
|
91
|
-
end
|
92
|
-
|
93
|
-
# TODO: Fill up an output write buffer with arg arrays
|
94
|
-
# then empty it
|
95
|
-
|
96
|
-
def start_tag(node, level)
|
97
|
-
@output.write(node, 'start_tag', level)
|
98
|
-
end
|
99
|
-
|
100
|
-
def content(node, level)
|
101
|
-
@output.write(node, 'content', level)
|
102
|
-
end
|
103
|
-
|
104
|
-
def end_tag(node, level)
|
105
|
-
@output.write(node, 'end_tag', level)
|
106
|
-
end
|
107
|
-
|
108
|
-
end
|
109
|
-
|
110
|
-
|
111
|
-
end
|
data/lib/undies/output.rb
DELETED
@@ -1,31 +0,0 @@
|
|
1
|
-
module Undies
|
2
|
-
class Output
|
3
|
-
|
4
|
-
# the output class wraps an IO stream, gathers pretty printing options,
|
5
|
-
# and handles writing out buffered node stack items
|
6
|
-
|
7
|
-
attr_reader :io, :pp
|
8
|
-
attr_accessor :pp_level
|
9
|
-
|
10
|
-
def initialize(io, opts={})
|
11
|
-
@io = io
|
12
|
-
self.options = opts
|
13
|
-
end
|
14
|
-
|
15
|
-
def options=(opts)
|
16
|
-
if !opts.kind_of?(::Hash)
|
17
|
-
raise ArgumentError, "please provide a hash to set options with"
|
18
|
-
end
|
19
|
-
|
20
|
-
# setup any pretty printing
|
21
|
-
@pp = opts[:pp] || 0
|
22
|
-
@pp_level = opts[:pp_level] || 0
|
23
|
-
end
|
24
|
-
|
25
|
-
def write(node, meth, level)
|
26
|
-
@io << node.class.prefix(node, meth, level+@pp_level, @pp)
|
27
|
-
@io << node.class.send(meth, node)
|
28
|
-
end
|
29
|
-
|
30
|
-
end
|
31
|
-
end
|
data/lib/undies/source_stack.rb
DELETED
@@ -1,22 +0,0 @@
|
|
1
|
-
require "undies/source"
|
2
|
-
|
3
|
-
module Undies
|
4
|
-
class SourceStack < ::Array
|
5
|
-
|
6
|
-
# a source stack is used to manage which sources and any deeply nested
|
7
|
-
# layouts they are in. initialize this object with a content source obj
|
8
|
-
# and get a stack where the the top source is the outer most layout and
|
9
|
-
# the bottom source is the source used to initialize the stack (the content
|
10
|
-
# source). naturally any sources in between are the intermediate layouts
|
11
|
-
# for the content source
|
12
|
-
|
13
|
-
def initialize(source)
|
14
|
-
super([source, source.layouts].flatten.compact)
|
15
|
-
end
|
16
|
-
|
17
|
-
def pop
|
18
|
-
super
|
19
|
-
end
|
20
|
-
|
21
|
-
end
|
22
|
-
end
|
data/test/node_stack_test.rb
DELETED
@@ -1,109 +0,0 @@
|
|
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
|