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