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.
- 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/lib/undies/element.rb
CHANGED
@@ -1,88 +1,48 @@
|
|
1
|
-
require 'undies/node'
|
2
|
-
|
3
1
|
module Undies
|
4
|
-
class Element < Node
|
5
2
|
|
6
|
-
|
7
|
-
|
8
|
-
|
3
|
+
|
4
|
+
|
5
|
+
module Element
|
9
6
|
|
10
7
|
def self.hash_attrs(attrs="", ns=nil)
|
11
8
|
return attrs.to_s if !attrs.kind_of?(::Hash)
|
12
9
|
|
13
|
-
|
14
|
-
|
15
|
-
end.sort.
|
16
|
-
|
10
|
+
attrs.collect do |k_v|
|
11
|
+
[ns ? "#{ns}_#{k_v.first}" : k_v.first.to_s, k_v.last]
|
12
|
+
end.sort.collect do |k_v|
|
13
|
+
if k_v.last.kind_of?(::Hash)
|
17
14
|
hash_attrs(k_v.last, k_v.first)
|
15
|
+
elsif k_v.last.kind_of?(::Array)
|
16
|
+
" #{k_v.first}=\"#{escape_attr_value(k_v.last.join(' '))}\""
|
18
17
|
else
|
19
18
|
" #{k_v.first}=\"#{escape_attr_value(k_v.last)}\""
|
20
19
|
end
|
21
|
-
end
|
20
|
+
end.join
|
22
21
|
end
|
23
22
|
|
23
|
+
ESCAPE_ATTRS = {
|
24
|
+
"&" => "&",
|
25
|
+
"<" => "<",
|
26
|
+
'"' => """
|
27
|
+
}
|
28
|
+
ESCAPE_ATTRS_PATTERN = Regexp.union(*ESCAPE_ATTRS.keys)
|
24
29
|
def self.escape_attr_value(value)
|
25
|
-
value.
|
26
|
-
to_s.
|
27
|
-
gsub('&', '&').
|
28
|
-
gsub('<', '<').
|
29
|
-
gsub('"', '"')
|
30
|
-
end
|
31
|
-
|
32
|
-
def self.set_children(element, value)
|
33
|
-
element.instance_variable_set("@children", value)
|
34
|
-
end
|
35
|
-
|
36
|
-
def self.children(element)
|
37
|
-
element.instance_variable_get("@children")
|
38
|
-
end
|
39
|
-
|
40
|
-
def self.prefix(element, meth, level, indent)
|
41
|
-
"".tap do |value|
|
42
|
-
if indent > 0
|
43
|
-
if meth == 'start_tag'
|
44
|
-
value << "#{level > 0 ? "\n": ''}#{' '*level*indent}"
|
45
|
-
elsif meth == 'end_tag'
|
46
|
-
value << "\n#{' '*level*indent}" if children(element)
|
47
|
-
end
|
48
|
-
end
|
49
|
-
end
|
30
|
+
value.to_s.gsub(ESCAPE_ATTRS_PATTERN){|c| ESCAPE_ATTRS[c] }
|
50
31
|
end
|
51
32
|
|
52
|
-
def self.
|
53
|
-
|
54
|
-
"@start_tag",
|
55
|
-
"<#{node_name(element)}#{hash_attrs(attrs(element))}" + (builds(element).size > 0 ? ">" : " />")
|
56
|
-
)
|
33
|
+
def self.open(*args, &build)
|
34
|
+
Open.new(*args, &build)
|
57
35
|
end
|
58
36
|
|
59
|
-
def self.
|
60
|
-
|
61
|
-
"@end_tag",
|
62
|
-
builds(element).size > 0 ? "</#{node_name(element)}>" : nil
|
63
|
-
)
|
37
|
+
def self.closed(*args, &build)
|
38
|
+
Closed.new(*args, &build)
|
64
39
|
end
|
65
40
|
|
66
|
-
|
67
|
-
super(nil)
|
68
|
-
@start_tag = ""
|
69
|
-
@end_tag = ""
|
70
|
-
@content = nil
|
71
|
-
@builds = []
|
72
|
-
@children = false
|
41
|
+
end
|
73
42
|
|
74
|
-
if !attrs.kind_of?(::Hash)
|
75
|
-
raise ArgumentError, "#{name.inspect} attrs must be provided as a Hash."
|
76
|
-
end
|
77
43
|
|
78
|
-
@name = name.to_s
|
79
|
-
@attrs = attrs
|
80
|
-
@builds << build if build
|
81
44
|
|
82
|
-
|
83
|
-
self.class.set_start_tag(self)
|
84
|
-
self.class.set_end_tag(self)
|
85
|
-
end
|
45
|
+
module CSSProxy
|
86
46
|
|
87
47
|
# CSS proxy methods ============================================
|
88
48
|
ID_METH_REGEX = /^([^_].+)!$/
|
@@ -90,13 +50,11 @@ module Undies
|
|
90
50
|
|
91
51
|
def method_missing(meth, *args, &block)
|
92
52
|
if meth.to_s =~ ID_METH_REGEX
|
93
|
-
|
94
|
-
|
95
|
-
end
|
53
|
+
@attrs[:id] = $1
|
54
|
+
proxy(args, block)
|
96
55
|
elsif meth.to_s =~ CLASS_METH_REGEX
|
97
|
-
|
98
|
-
|
99
|
-
end
|
56
|
+
@attrs[:class] = [@attrs[:class], $1].compact.join(' ')
|
57
|
+
proxy(args, block)
|
100
58
|
else
|
101
59
|
super
|
102
60
|
end
|
@@ -111,6 +69,133 @@ module Undies
|
|
111
69
|
end
|
112
70
|
# ==============================================================
|
113
71
|
|
72
|
+
end
|
73
|
+
|
74
|
+
|
75
|
+
|
76
|
+
module MergeAttrs
|
77
|
+
|
78
|
+
def __attrs(attrs_hash=nil)
|
79
|
+
return @attrs if attrs_hash.nil?
|
80
|
+
@attrs.merge!(attrs_hash)
|
81
|
+
end
|
82
|
+
|
83
|
+
end
|
84
|
+
|
85
|
+
|
86
|
+
|
87
|
+
class Raw < ::String
|
88
|
+
|
89
|
+
# A Raw string is one that is impervious to String#gsub and returns itself
|
90
|
+
# when `to_s` is called.
|
91
|
+
|
92
|
+
def gsub(*args, &block)
|
93
|
+
self
|
94
|
+
end
|
95
|
+
|
96
|
+
def gsub!(*args, &block)
|
97
|
+
nil
|
98
|
+
end
|
99
|
+
|
100
|
+
def to_s
|
101
|
+
self
|
102
|
+
end
|
103
|
+
|
104
|
+
end
|
105
|
+
|
106
|
+
|
107
|
+
|
108
|
+
class Element::Open
|
109
|
+
include CSSProxy
|
110
|
+
include MergeAttrs
|
111
|
+
|
112
|
+
def initialize(name, *args, &build)
|
113
|
+
@name = name.to_s
|
114
|
+
@attrs = {}
|
115
|
+
@content = []
|
116
|
+
@build = nil
|
117
|
+
|
118
|
+
proxy(args, build)
|
119
|
+
end
|
120
|
+
|
121
|
+
def __start_tag
|
122
|
+
"<#{@name}#{Element.hash_attrs(@attrs)}>"
|
123
|
+
end
|
124
|
+
|
125
|
+
def __content
|
126
|
+
@content.collect{ |c| Template.escape_html(c) }.join
|
127
|
+
end
|
128
|
+
|
129
|
+
def __build
|
130
|
+
@build.call if @build
|
131
|
+
end
|
132
|
+
|
133
|
+
def __end_tag
|
134
|
+
"</#{@name}>"
|
135
|
+
end
|
136
|
+
|
137
|
+
def to_s
|
138
|
+
"#{__start_tag}#{__content}#{__end_tag}"
|
139
|
+
end
|
140
|
+
|
141
|
+
def ==(other)
|
142
|
+
other.instance_variable_get("@name") == @name &&
|
143
|
+
other.instance_variable_get("@attrs") == @attrs &&
|
144
|
+
other.instance_variable_get("@content") == @content
|
145
|
+
end
|
146
|
+
|
147
|
+
# overriding this because the base Node class defines a 'to_s' method that
|
148
|
+
# needs to be honored
|
149
|
+
def to_str(*args)
|
150
|
+
"Undies::Element::Open:#{self.object_id} " +
|
151
|
+
"@name=#{@name.inspect}, @attrs=#{@attrs.inspect}, @content=#{@content.inspect}"
|
152
|
+
end
|
153
|
+
alias_method :inspect, :to_str
|
154
|
+
|
155
|
+
private
|
156
|
+
|
157
|
+
def proxy(args, build)
|
158
|
+
if args.last.kind_of?(Hash)
|
159
|
+
@attrs.merge!(args.pop)
|
160
|
+
end
|
161
|
+
|
162
|
+
@content.push *args
|
163
|
+
@build = build
|
164
|
+
|
165
|
+
self
|
166
|
+
end
|
167
|
+
|
168
|
+
end
|
169
|
+
|
170
|
+
|
171
|
+
|
172
|
+
class Element::Closed
|
173
|
+
include CSSProxy
|
174
|
+
include MergeAttrs
|
175
|
+
|
176
|
+
def initialize(name, attrs={})
|
177
|
+
@name = name.to_s
|
178
|
+
@attrs = {}
|
179
|
+
proxy([attrs])
|
180
|
+
end
|
181
|
+
|
182
|
+
def __start_tag
|
183
|
+
"<#{@name}#{Element.hash_attrs(@attrs)} />"
|
184
|
+
end
|
185
|
+
|
186
|
+
# closed elements have no content
|
187
|
+
def __content; ''; end
|
188
|
+
|
189
|
+
# closed elements should have no build so do nothing
|
190
|
+
def __build; end
|
191
|
+
|
192
|
+
# closed elements have no end tag
|
193
|
+
def __end_tag; ''; end
|
194
|
+
|
195
|
+
def to_s
|
196
|
+
"#{__start_tag}"
|
197
|
+
end
|
198
|
+
|
114
199
|
def ==(other)
|
115
200
|
other.instance_variable_get("@name") == @name &&
|
116
201
|
other.instance_variable_get("@attrs") == @attrs
|
@@ -119,25 +204,20 @@ module Undies
|
|
119
204
|
# overriding this because the base Node class defines a 'to_s' method that
|
120
205
|
# needs to be honored
|
121
206
|
def to_str(*args)
|
122
|
-
"Undies::Element:#{self.object_id} " +
|
207
|
+
"Undies::Element::Closed:#{self.object_id} " +
|
123
208
|
"@name=#{@name.inspect}, @attrs=#{@attrs.inspect}"
|
124
209
|
end
|
125
210
|
alias_method :inspect, :to_str
|
126
211
|
|
127
212
|
private
|
128
213
|
|
129
|
-
def proxy(
|
130
|
-
|
131
|
-
@attrs.merge!(attrs)
|
132
|
-
@builds << build if build
|
133
|
-
|
134
|
-
# cache in an instance variable for fast access with flush and pop
|
135
|
-
self.class.set_start_tag(self)
|
136
|
-
self.class.set_end_tag(self)
|
137
|
-
|
138
|
-
# return self so you can chain proxy method calls
|
214
|
+
def proxy(args, build=nil)
|
215
|
+
@attrs.merge!(args.last || {})
|
139
216
|
self
|
140
217
|
end
|
141
218
|
|
142
219
|
end
|
220
|
+
|
221
|
+
|
222
|
+
|
143
223
|
end
|
@@ -0,0 +1,116 @@
|
|
1
|
+
module Undies
|
2
|
+
|
3
|
+
class ElementAPIError < RuntimeError; end
|
4
|
+
|
5
|
+
class ElementNode
|
6
|
+
|
7
|
+
# Used internally to implement the markup tree nodes. Each node caches and
|
8
|
+
# processes nested markup and elements. At each node level in the markup
|
9
|
+
# tree, nodes/markup are cached until the next sibling node or raw markup
|
10
|
+
# is defined, or until the node is flushed. This keeps nodes from bloating
|
11
|
+
# memory on large documents and allows for output streaming.
|
12
|
+
|
13
|
+
# ElementNode is specifically used to handle nested element markup.
|
14
|
+
|
15
|
+
attr_reader :io, :element, :cached
|
16
|
+
|
17
|
+
def initialize(io, element)
|
18
|
+
@io = io
|
19
|
+
@cached = nil
|
20
|
+
@element = element
|
21
|
+
|
22
|
+
@start_tag_written = false
|
23
|
+
end
|
24
|
+
|
25
|
+
def attrs(*args, &block)
|
26
|
+
@element.__attrs(*args, &block)
|
27
|
+
end
|
28
|
+
|
29
|
+
def text(raw)
|
30
|
+
raise ElementAPIError, "can't insert text markup in an element build block - pass in as a content argument instead"
|
31
|
+
end
|
32
|
+
|
33
|
+
def element_node(element_node)
|
34
|
+
if !@start_tag_written
|
35
|
+
# with newline
|
36
|
+
# -1 level offset b/c we are operating on the element build one deep
|
37
|
+
write_start_tag(@io.newline, -1)
|
38
|
+
end
|
39
|
+
write_cached
|
40
|
+
@cached = element_node
|
41
|
+
end
|
42
|
+
|
43
|
+
def partial(partial)
|
44
|
+
element_node(partial)
|
45
|
+
end
|
46
|
+
|
47
|
+
def flush
|
48
|
+
write_cached
|
49
|
+
@cached = nil
|
50
|
+
self
|
51
|
+
end
|
52
|
+
|
53
|
+
def push
|
54
|
+
@io.push(@cached)
|
55
|
+
@cached = nil
|
56
|
+
end
|
57
|
+
|
58
|
+
def pop
|
59
|
+
flush
|
60
|
+
@io.pop
|
61
|
+
write_end_tag
|
62
|
+
end
|
63
|
+
|
64
|
+
def to_s
|
65
|
+
@io.push(self)
|
66
|
+
|
67
|
+
@element.__build
|
68
|
+
flush
|
69
|
+
|
70
|
+
@io.pop
|
71
|
+
write_end_tag
|
72
|
+
|
73
|
+
# needed so the `write_cached` calls on ElementNode and RootNode won't add
|
74
|
+
# anything else to the IO
|
75
|
+
return ""
|
76
|
+
end
|
77
|
+
|
78
|
+
def ==(other)
|
79
|
+
other.instance_variable_get("@io") == @io &&
|
80
|
+
other.instance_variable_get("@element") == @element
|
81
|
+
end
|
82
|
+
|
83
|
+
# overriding this because the base Node class defines a 'to_s' method that
|
84
|
+
# needs to be honored
|
85
|
+
def to_str(*args)
|
86
|
+
"Undies::ElementNode:#{self.object_id} @element=#{@element.inspect}"
|
87
|
+
end
|
88
|
+
alias_method :inspect, :to_str
|
89
|
+
|
90
|
+
private
|
91
|
+
|
92
|
+
def write_cached
|
93
|
+
@io << @cached.to_s
|
94
|
+
end
|
95
|
+
|
96
|
+
def write_start_tag(newline='', level_offset=0)
|
97
|
+
@io << "#{@io.line_indent(level_offset)}#{@element.__start_tag}#{newline}"
|
98
|
+
@start_tag_written = true
|
99
|
+
end
|
100
|
+
|
101
|
+
def write_content
|
102
|
+
@io << @element.__content
|
103
|
+
end
|
104
|
+
|
105
|
+
def write_end_tag(level_offset=0)
|
106
|
+
if !@start_tag_written
|
107
|
+
write_start_tag('', level_offset)
|
108
|
+
write_content
|
109
|
+
@io << "#{@element.__end_tag}#{@io.newline}"
|
110
|
+
else
|
111
|
+
@io << "#{@io.line_indent(level_offset)}#{@element.__end_tag}#{@io.newline}"
|
112
|
+
end
|
113
|
+
end
|
114
|
+
|
115
|
+
end
|
116
|
+
end
|
data/lib/undies/io.rb
ADDED
@@ -0,0 +1,43 @@
|
|
1
|
+
module Undies
|
2
|
+
class IO
|
3
|
+
|
4
|
+
# the IO class wraps a stream (anything that responds to '<<' and
|
5
|
+
# gathers streaming options options. handles writing markup to the
|
6
|
+
# stream.
|
7
|
+
|
8
|
+
attr_reader :stream, :indent, :newline, :node_stack
|
9
|
+
attr_accessor :level
|
10
|
+
|
11
|
+
def initialize(stream, opts={})
|
12
|
+
@stream = stream
|
13
|
+
@node_stack = []
|
14
|
+
self.options = opts
|
15
|
+
end
|
16
|
+
|
17
|
+
def options=(opts)
|
18
|
+
if !opts.kind_of?(::Hash)
|
19
|
+
raise ArgumentError, "please provide a hash to set IO options"
|
20
|
+
end
|
21
|
+
|
22
|
+
@indent = opts[:pp] || 0
|
23
|
+
@newline = opts[:pp].nil? ? "" : "\n"
|
24
|
+
@level = opts[:level] || 0
|
25
|
+
end
|
26
|
+
|
27
|
+
def line_indent(relative_level=0)
|
28
|
+
"#{' '*(@level+relative_level)*@indent}"
|
29
|
+
end
|
30
|
+
|
31
|
+
# TODO: threaded/forked writing for performance improvement
|
32
|
+
def <<(markup)
|
33
|
+
@stream << markup
|
34
|
+
end
|
35
|
+
|
36
|
+
def push(scope); @level += 1; push!(scope); end
|
37
|
+
def push!(scope); @node_stack.push(scope); end
|
38
|
+
def pop; @level -= 1; @node_stack.pop; end
|
39
|
+
def current; @node_stack.last; end
|
40
|
+
def empty?; @node_stack.empty? end
|
41
|
+
|
42
|
+
end
|
43
|
+
end
|
@@ -0,0 +1,62 @@
|
|
1
|
+
module Undies
|
2
|
+
|
3
|
+
class RootAPIError < RuntimeError; end
|
4
|
+
|
5
|
+
class RootNode
|
6
|
+
|
7
|
+
# Used internally to implement the markup tree nodes. Each node caches and
|
8
|
+
# processes nested markup and elements. At each node level in the markup
|
9
|
+
# tree, nodes/markup are cached until the next sibling node or raw markup
|
10
|
+
# is defined, or until the node is flushed. This keeps nodes from bloating
|
11
|
+
# memory on large documents and allows for output streaming.
|
12
|
+
|
13
|
+
# RootNode is specifically used to handle root document markup.
|
14
|
+
|
15
|
+
attr_reader :io, :cached
|
16
|
+
|
17
|
+
def initialize(io)
|
18
|
+
@io = io
|
19
|
+
@cached = nil
|
20
|
+
end
|
21
|
+
|
22
|
+
def attrs(*args, &block)
|
23
|
+
raise RootAPIError, "can't call '__attrs' at the root node level"
|
24
|
+
end
|
25
|
+
|
26
|
+
def text(raw)
|
27
|
+
write_cached
|
28
|
+
@cached = "#{@io.line_indent}#{raw.to_s}#{@io.newline}"
|
29
|
+
end
|
30
|
+
|
31
|
+
def element_node(element_node)
|
32
|
+
write_cached
|
33
|
+
@cached = element_node
|
34
|
+
end
|
35
|
+
|
36
|
+
def partial(partial)
|
37
|
+
text(partial)
|
38
|
+
end
|
39
|
+
|
40
|
+
def flush
|
41
|
+
write_cached
|
42
|
+
@cached = nil
|
43
|
+
self
|
44
|
+
end
|
45
|
+
|
46
|
+
def push
|
47
|
+
@io.push(@cached)
|
48
|
+
@cached = nil
|
49
|
+
end
|
50
|
+
|
51
|
+
def pop
|
52
|
+
flush
|
53
|
+
end
|
54
|
+
|
55
|
+
private
|
56
|
+
|
57
|
+
def write_cached
|
58
|
+
@io << @cached.to_s
|
59
|
+
end
|
60
|
+
|
61
|
+
end
|
62
|
+
end
|
data/lib/undies/source.rb
CHANGED
@@ -1,6 +1,7 @@
|
|
1
|
-
require 'undies/named_source'
|
2
|
-
|
3
1
|
module Undies
|
2
|
+
|
3
|
+
|
4
|
+
|
4
5
|
class Source
|
5
6
|
|
6
7
|
attr_reader :source, :data, :layout
|
@@ -70,4 +71,79 @@ module Undies
|
|
70
71
|
end
|
71
72
|
|
72
73
|
end
|
74
|
+
|
75
|
+
|
76
|
+
|
77
|
+
class NamedSource
|
78
|
+
|
79
|
+
attr_accessor :file, :opts, :proc
|
80
|
+
|
81
|
+
def initialize(*args, &block)
|
82
|
+
args << block if block
|
83
|
+
self.args = args
|
84
|
+
end
|
85
|
+
|
86
|
+
def ==(other_named_source)
|
87
|
+
self.file == other_named_source.file &&
|
88
|
+
self.opts == other_named_source.opts &&
|
89
|
+
self.proc == other_named_source.proc
|
90
|
+
end
|
91
|
+
|
92
|
+
def args=(values)
|
93
|
+
self.proc, self.opts, self.file = [
|
94
|
+
values.last.kind_of?(::Proc) ? values.pop : nil,
|
95
|
+
values.last.kind_of?(::Hash) ? values.pop : {},
|
96
|
+
values.last.kind_of?(::String) ? values.pop : nil
|
97
|
+
]
|
98
|
+
end
|
99
|
+
|
100
|
+
def args
|
101
|
+
[self.file, self.opts, self.proc]
|
102
|
+
end
|
103
|
+
|
104
|
+
end
|
105
|
+
|
106
|
+
# singleton accessors for named sources
|
107
|
+
|
108
|
+
def self.named_sources
|
109
|
+
@@sources ||= {}
|
110
|
+
end
|
111
|
+
|
112
|
+
def self.named_source(name, *args, &block)
|
113
|
+
if args.empty? && block.nil?
|
114
|
+
self.named_sources[name]
|
115
|
+
else
|
116
|
+
self.named_sources[name] = Undies::NamedSource.new(*args, &block)
|
117
|
+
end
|
118
|
+
end
|
119
|
+
|
120
|
+
def self.source(name)
|
121
|
+
if ns = self.named_source(name)
|
122
|
+
Undies::Source.new(ns)
|
123
|
+
end
|
124
|
+
end
|
125
|
+
|
126
|
+
|
127
|
+
|
128
|
+
class SourceStack < ::Array
|
129
|
+
|
130
|
+
# a source stack is used to manage which sources and any deeply nested
|
131
|
+
# layouts they are in. initialize this object with a content source obj
|
132
|
+
# and get a stack where the the top source is the outer most layout and
|
133
|
+
# the bottom source is the source used to initialize the stack (the content
|
134
|
+
# source). naturally any sources in between are the intermediate layouts
|
135
|
+
# for the content source
|
136
|
+
|
137
|
+
def initialize(source)
|
138
|
+
super([source, source.layouts].flatten.compact)
|
139
|
+
end
|
140
|
+
|
141
|
+
def pop
|
142
|
+
super
|
143
|
+
end
|
144
|
+
|
145
|
+
end
|
146
|
+
|
147
|
+
|
148
|
+
|
73
149
|
end
|