utopia 1.9.11 → 2.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.codeclimate.yml +3 -2
- data/.gitignore +4 -1
- data/.rspec +1 -0
- data/.travis.yml +4 -0
- data/.yardopts +2 -0
- data/Gemfile +8 -1
- data/README.md +2 -2
- data/Rakefile +10 -10
- data/benchmarks/call_vs_check.rb +36 -0
- data/benchmarks/const_vs_hash.rb +33 -0
- data/documentation/Gemfile +5 -0
- data/documentation/Guardfile +20 -0
- data/documentation/config.ru +6 -13
- data/documentation/config/puma.rb +20 -0
- data/documentation/pages/_editor.xnode +64 -0
- data/documentation/pages/_heading.xnode +2 -2
- data/documentation/pages/_page.xnode +1 -2
- data/documentation/pages/errors/exception.xnode +3 -3
- data/documentation/pages/errors/file-not-found.xnode +3 -3
- data/documentation/pages/wiki/bower-integration/content.md +1 -1
- data/documentation/pages/wiki/content.md +6 -8
- data/documentation/pages/wiki/controller.rb +3 -3
- data/documentation/pages/wiki/edit.xnode +7 -19
- data/documentation/pages/wiki/middleware/content/content.md +4 -10
- data/documentation/pages/wiki/{controller → middleware/controller}/actions/content.md +0 -0
- data/documentation/pages/wiki/{controller → middleware/controller}/links.yaml +0 -0
- data/documentation/pages/wiki/{controller → middleware/controller}/rewrite/content.md +3 -3
- data/documentation/pages/wiki/show.xnode +4 -6
- data/documentation/pages/wiki/updating-utopia/content.md +55 -0
- data/documentation/pages/wiki/your-first-page/content.md +5 -3
- data/documentation/public/materials +1 -0
- data/lib/utopia.rb +3 -4
- data/lib/utopia/command.rb +4 -284
- data/lib/utopia/command/server.rb +115 -0
- data/lib/utopia/command/setup.rb +78 -0
- data/lib/utopia/command/site.rb +183 -0
- data/lib/utopia/content.rb +83 -59
- data/lib/utopia/content/{transaction.rb → document.rb} +116 -110
- data/lib/utopia/content/link.rb +7 -2
- data/lib/utopia/content/links.rb +2 -1
- data/lib/utopia/content/markup.rb +7 -2
- data/lib/utopia/{tags/deferred.rb → content/namespace.rb} +25 -6
- data/lib/utopia/content/node.rb +74 -76
- data/lib/utopia/content/response.rb +22 -3
- data/lib/utopia/content/tags.rb +66 -0
- data/lib/utopia/controller.rb +10 -18
- data/lib/utopia/controller/actions.rb +10 -0
- data/lib/utopia/controller/base.rb +2 -1
- data/lib/utopia/controller/respond.rb +1 -1
- data/lib/utopia/controller/rewrite.rb +8 -4
- data/lib/utopia/exceptions.rb +1 -0
- data/lib/utopia/exceptions/handler.rb +7 -2
- data/lib/utopia/exceptions/mailer.rb +33 -12
- data/lib/utopia/{tags/node.rb → extensions/array_split.rb} +11 -9
- data/lib/utopia/{tags/environment.rb → extensions/date_comparisons.rb} +24 -14
- data/lib/utopia/http.rb +2 -0
- data/lib/utopia/locale.rb +1 -0
- data/lib/utopia/localization.rb +37 -28
- data/lib/utopia/logger.rb +1 -0
- data/lib/utopia/logger/compact_formatter.rb +1 -0
- data/lib/utopia/middleware.rb +11 -1
- data/lib/utopia/path.rb +1 -0
- data/lib/utopia/path/matcher.rb +14 -2
- data/lib/utopia/redirection.rb +13 -16
- data/lib/utopia/session.rb +14 -6
- data/lib/utopia/setup.rb +3 -1
- data/lib/utopia/static.rb +11 -12
- data/lib/utopia/version.rb +1 -1
- data/setup/server/git/hooks/post-receive +0 -4
- data/setup/site/.gitignore +9 -0
- data/setup/site/.rspec +1 -0
- data/setup/site/Gemfile +4 -0
- data/setup/site/Guardfile +17 -0
- data/setup/site/Rakefile +2 -2
- data/setup/site/config.ru +5 -12
- data/setup/site/pages/_heading.xnode +2 -2
- data/setup/site/pages/_page.xnode +1 -1
- data/setup/site/pages/errors/exception.xnode +3 -3
- data/setup/site/pages/errors/file-not-found.xnode +3 -3
- data/setup/site/pages/welcome/index.xnode +3 -3
- data/setup/site/public/_static/site.css +4 -0
- data/setup/site/spec/spec_helper.rb +29 -0
- data/setup/site/tasks/deploy.rake +13 -0
- data/setup/site/tasks/development.rake +34 -0
- data/setup/site/tasks/environment.rake +17 -0
- data/spec/mock_node.rb +15 -0
- data/spec/spec_helper.rb +29 -0
- data/{lib/utopia/extensions/date.rb → spec/utopia/content/document_spec.rb} +31 -21
- data/spec/utopia/content/markup_spec.rb +2 -2
- data/spec/utopia/content/{tag_spec.rb → namespace_spec.rb} +17 -10
- data/spec/utopia/content/tags_spec.rb +80 -0
- data/spec/utopia/content_spec.rb +1 -1
- data/spec/utopia/content_spec.ru +1 -6
- data/spec/utopia/content_spec/_heading.xnode +1 -1
- data/spec/utopia/content_spec/content/test-partial.xnode +1 -1
- data/spec/utopia/content_spec/index.xnode +1 -1
- data/spec/utopia/controller/middleware_spec.ru +1 -3
- data/spec/utopia/controller/respond_spec.rb +2 -22
- data/spec/utopia/controller/respond_spec.ru +1 -5
- data/spec/utopia/controller/respond_spec/errors/file-not-found.xnode +7 -6
- data/spec/utopia/exceptions/handler_spec.ru +1 -2
- data/spec/utopia/exceptions/mailer_spec.ru +1 -2
- data/spec/utopia/extensions_spec.rb +2 -2
- data/spec/utopia/localization_spec.ru +1 -2
- data/spec/utopia/performance_spec.rb +2 -6
- data/spec/utopia/performance_spec/config.ru +5 -12
- data/spec/utopia/performance_spec/pages/_heading.xnode +2 -2
- data/spec/utopia/performance_spec/pages/_page.xnode +1 -1
- data/spec/utopia/performance_spec/pages/errors/exception.xnode +3 -3
- data/spec/utopia/performance_spec/pages/errors/file-not-found.xnode +3 -3
- data/spec/utopia/performance_spec/pages/welcome/index.xnode +3 -3
- data/spec/utopia/setup_spec.rb +79 -15
- data/utopia.gemspec +3 -3
- metadata +41 -27
- data/.simplecov +0 -9
- data/documentation/pages/welcome/index.xnode +0 -41
- data/lib/utopia/content/tag.rb +0 -90
- data/lib/utopia/extensions/array.rb +0 -29
- data/lib/utopia/tags/override.rb +0 -33
- data/setup/site/.simplecov +0 -9
- data/setup/site/tasks/test.rake +0 -10
- data/setup/site/tasks/utopia.rake +0 -41
- data/spec/utopia/controller/respond_spec/rewrite/controller.rb +0 -12
@@ -19,8 +19,8 @@
|
|
19
19
|
# THE SOFTWARE.
|
20
20
|
|
21
21
|
require_relative 'links'
|
22
|
-
|
23
22
|
require_relative 'response'
|
23
|
+
require_relative 'markup'
|
24
24
|
|
25
25
|
module Utopia
|
26
26
|
class Content
|
@@ -35,26 +35,38 @@ module Utopia
|
|
35
35
|
attr :tag
|
36
36
|
end
|
37
37
|
|
38
|
-
DEFERRED_TAG_NAME = "deferred".freeze
|
39
|
-
CONTENT_TAG_NAME = "content".freeze
|
40
|
-
|
41
38
|
# A single request through content middleware. We use a struct to hide instance varibles since we instance_exec within this context.
|
42
|
-
class
|
39
|
+
class Document < Response
|
40
|
+
def self.render(node, request, attributes)
|
41
|
+
self.new(request, attributes).render!(node, attributes)
|
42
|
+
end
|
43
|
+
|
43
44
|
def initialize(request, attributes = {})
|
44
45
|
@request = request
|
45
46
|
|
46
47
|
@attributes = attributes
|
47
48
|
|
48
|
-
@
|
49
|
+
@first = nil
|
50
|
+
@current = nil
|
49
51
|
@end_tags = []
|
50
52
|
|
51
|
-
# TODO: Provide way to set encoding and content type.
|
52
|
-
#@encoding = Encoding::UTF_8
|
53
|
-
#self.content_type = "text/html; charset=#{@encoding.name}"
|
54
|
-
|
55
53
|
super()
|
56
54
|
end
|
57
55
|
|
56
|
+
def [] key
|
57
|
+
@attributes[key]
|
58
|
+
end
|
59
|
+
|
60
|
+
def []= key, value
|
61
|
+
@attributes[key] = value
|
62
|
+
end
|
63
|
+
|
64
|
+
def render!(node, attributes)
|
65
|
+
@body << render_node(node, attributes)
|
66
|
+
|
67
|
+
return self
|
68
|
+
end
|
69
|
+
|
58
70
|
# A helper method for accessing controller variables from view:
|
59
71
|
def controller
|
60
72
|
@controller ||= Utopia::Controller[request]
|
@@ -68,134 +80,137 @@ module Utopia
|
|
68
80
|
MarkupParser.parse(markup, self)
|
69
81
|
end
|
70
82
|
|
71
|
-
# The Rack::Request for this
|
83
|
+
# The Rack::Request for this document.
|
72
84
|
attr :request
|
73
85
|
|
74
|
-
# Per-
|
86
|
+
# Per-document global attributes.
|
75
87
|
attr :attributes
|
76
88
|
|
77
|
-
#
|
78
|
-
# At any point in parsing markup,
|
79
|
-
# then the next outer tag, etc.
|
80
|
-
attr :
|
89
|
+
# The current state, represents a list from outer to inner most tag by traversing {State#parent}.
|
90
|
+
# At any point in parsing markup, this is a list of the inner most tag,
|
91
|
+
# then the next outer tag, etc.
|
92
|
+
attr :current
|
93
|
+
|
94
|
+
# The first {State} generated by rendering this document. It contains useful information
|
95
|
+
# regarding the node and uri used to access the resource.
|
96
|
+
attr :first
|
81
97
|
|
82
98
|
# End tags represents a list of execution order. This is the order that end tags
|
83
99
|
# have appeared when evaluating nodes.
|
84
100
|
attr :end_tags
|
85
101
|
|
86
|
-
def tag(name, attributes = {}
|
102
|
+
def tag(name, attributes = {})
|
87
103
|
# If we provide a block which can give inner data, we are not self-closing.
|
88
104
|
tag = Tag.new(name, !block_given?, attributes)
|
89
105
|
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
106
|
+
if block_given?
|
107
|
+
node = tag_begin(tag)
|
108
|
+
yield node
|
109
|
+
tag_end(tag)
|
110
|
+
else
|
111
|
+
tag_complete(tag, node)
|
112
|
+
end
|
95
113
|
end
|
96
114
|
|
97
115
|
def tag_complete(tag, node = nil)
|
98
|
-
|
99
|
-
current.write(content)
|
100
|
-
else
|
101
|
-
node ||= lookup(tag)
|
116
|
+
node ||= lookup_tag(tag)
|
102
117
|
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
end
|
118
|
+
if node
|
119
|
+
tag_begin(tag, node)
|
120
|
+
tag_end(tag)
|
121
|
+
else
|
122
|
+
@current.tag_complete(tag)
|
109
123
|
end
|
110
124
|
end
|
111
125
|
|
112
126
|
def tag_begin(tag, node = nil)
|
113
|
-
node ||=
|
127
|
+
node ||= lookup_tag(tag)
|
114
128
|
|
115
129
|
if node
|
116
|
-
|
117
|
-
self.begin_tags << state
|
130
|
+
@current = State.new(@current, tag, node)
|
118
131
|
|
119
|
-
if node.respond_to?
|
120
|
-
node.tag_begin(self, state)
|
121
|
-
end
|
132
|
+
node.tag_begin(self, state) if node.respond_to?(:tag_begin)
|
122
133
|
|
123
134
|
return node
|
124
135
|
end
|
125
136
|
|
126
|
-
|
137
|
+
# raise ArgumentError.new("tag_begin: #{tag} is tag.self_closed?") if tag.self_closed?
|
138
|
+
|
139
|
+
@current.tag_begin(tag)
|
127
140
|
|
128
141
|
return nil
|
129
142
|
end
|
130
143
|
|
131
144
|
def write(string)
|
132
|
-
current.write(string)
|
145
|
+
@current.write(string)
|
133
146
|
end
|
134
147
|
|
135
148
|
alias cdata write
|
136
149
|
|
137
150
|
def text(string)
|
138
|
-
current.text(string)
|
151
|
+
@current.text(string)
|
139
152
|
end
|
140
153
|
|
141
154
|
def tag_end(tag = nil)
|
142
|
-
#
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
if top.node.respond_to? :tag_end
|
147
|
-
top.node.tag_end(self, top)
|
155
|
+
# Determine if the current state contains tags that need to be completed, or if the state itself is finished.
|
156
|
+
if @current.empty?
|
157
|
+
if node = @current.node
|
158
|
+
node.tag_end(self, @current) if node.respond_to?(:tag_end)
|
148
159
|
end
|
149
160
|
|
150
|
-
|
151
|
-
buffer =
|
161
|
+
@end_tags << @current
|
162
|
+
buffer = @current.call(self)
|
152
163
|
|
153
|
-
|
154
|
-
|
164
|
+
@current = @current.parent
|
165
|
+
@end_tags.pop
|
155
166
|
|
156
|
-
if current
|
157
|
-
current.write(buffer)
|
158
|
-
end
|
167
|
+
@current.write(buffer) if @current
|
159
168
|
|
160
169
|
return buffer
|
161
170
|
else
|
162
|
-
|
171
|
+
# raise ArgumentError.new("tag_begin: #{tag} is tag.self_closed?") if tag.self_closed?
|
172
|
+
@current.tag_end(tag)
|
163
173
|
end
|
164
174
|
|
165
175
|
return nil
|
166
176
|
end
|
167
|
-
|
168
|
-
def render_node(node, attributes = {})
|
169
|
-
self.begin_tags << State.new(attributes, node)
|
170
177
|
|
178
|
+
def render_node(node, attributes = {})
|
179
|
+
@current = State.new(@current, nil, node, attributes)
|
180
|
+
|
181
|
+
# We keep track of the first thing rendered by this document.
|
182
|
+
@first ||= @current
|
183
|
+
|
184
|
+
# This returns the content of rendering the tag:
|
171
185
|
return tag_end
|
172
186
|
end
|
173
187
|
|
174
|
-
#
|
175
|
-
|
176
|
-
|
177
|
-
|
178
|
-
|
179
|
-
|
180
|
-
|
181
|
-
|
182
|
-
|
183
|
-
|
184
|
-
|
185
|
-
|
186
|
-
|
187
|
-
|
188
|
-
return state.node.
|
188
|
+
# Maps a tag to a node instance by asking the current node to lookup the tag name. This function is called for each tag and thus heavily affects performance.
|
189
|
+
# @return [Node] The node for the given tag.
|
190
|
+
def lookup_tag(tag)
|
191
|
+
# result = tag
|
192
|
+
#
|
193
|
+
# # This loop works from inner to outer tags, and updates the tag we are currently searching for based on any overrides:
|
194
|
+
# @begin_tags.reverse_each do |state|
|
195
|
+
# result = state.lookup(result)
|
196
|
+
#
|
197
|
+
# return result if result.is_a?(Node)
|
198
|
+
# end
|
199
|
+
|
200
|
+
# This loop looks up a tag by asking the most embedded node to look it up based on tag name. This almost always only evaluates the top state:
|
201
|
+
@end_tags.reverse_each do |state|
|
202
|
+
return state.node.lookup_tag(tag) if state.node.respond_to?(:lookup_tag)
|
189
203
|
end
|
190
|
-
|
204
|
+
|
191
205
|
return nil
|
192
206
|
end
|
193
207
|
|
194
|
-
#
|
195
|
-
|
196
|
-
|
208
|
+
# Lookup a node with the given path relative to the current node.
|
209
|
+
# @return [Node] The node if could be found.
|
210
|
+
def lookup_node(path)
|
211
|
+
@current.node.lookup_node(path)
|
197
212
|
end
|
198
|
-
|
213
|
+
|
199
214
|
# The content of the node
|
200
215
|
def content
|
201
216
|
@end_tags.last.content
|
@@ -204,32 +219,30 @@ module Utopia
|
|
204
219
|
def parent
|
205
220
|
@end_tags[-2]
|
206
221
|
end
|
207
|
-
|
208
|
-
def first
|
209
|
-
@begin_tags.first
|
210
|
-
end
|
211
222
|
end
|
212
223
|
|
213
|
-
# The state of a single tag being rendered within a
|
214
|
-
class
|
215
|
-
def initialize(tag, node, attributes = tag.to_hash)
|
224
|
+
# The state of a single tag being rendered within a document instance.
|
225
|
+
class Document::State
|
226
|
+
def initialize(parent, tag, node, attributes = tag.to_hash)
|
227
|
+
@parent = parent
|
228
|
+
@tag = tag
|
216
229
|
@node = node
|
230
|
+
@attributes = attributes
|
217
231
|
|
218
232
|
@buffer = Trenni::MarkupString.new.force_encoding(Encoding::UTF_8)
|
233
|
+
@content = nil
|
219
234
|
|
220
|
-
@
|
235
|
+
@deferred = []
|
221
236
|
|
222
237
|
@tags = []
|
223
|
-
@attributes = attributes
|
224
|
-
|
225
|
-
@content = nil
|
226
|
-
@deferred = []
|
227
238
|
end
|
228
239
|
|
240
|
+
attr :parent
|
229
241
|
attr :attributes
|
230
|
-
attr :overrides
|
231
242
|
attr :content
|
232
243
|
attr :node
|
244
|
+
|
245
|
+
# A list of all tags in order of rendering them, which have not been finished yet.
|
233
246
|
attr :tags
|
234
247
|
|
235
248
|
attr :deferred
|
@@ -244,28 +257,14 @@ module Utopia
|
|
244
257
|
@attributes[key]
|
245
258
|
end
|
246
259
|
|
247
|
-
def
|
248
|
-
if override = @overrides[tag.name]
|
249
|
-
if override.respond_to? :call
|
250
|
-
return override.call(tag)
|
251
|
-
elsif String === override
|
252
|
-
return Tag.new(override, tag.attributes)
|
253
|
-
else
|
254
|
-
return override
|
255
|
-
end
|
256
|
-
else
|
257
|
-
return tag
|
258
|
-
end
|
259
|
-
end
|
260
|
-
|
261
|
-
def call(transaction)
|
260
|
+
def call(document)
|
262
261
|
@content = @buffer
|
263
262
|
@buffer = Trenni::MarkupString.new.force_encoding(Encoding::UTF_8)
|
264
263
|
|
265
264
|
if node.respond_to? :call
|
266
|
-
node.call(
|
265
|
+
node.call(document, self)
|
267
266
|
else
|
268
|
-
|
267
|
+
document.parse_markup(@content)
|
269
268
|
end
|
270
269
|
|
271
270
|
return @buffer
|
@@ -276,20 +275,27 @@ module Utopia
|
|
276
275
|
end
|
277
276
|
|
278
277
|
def text(string)
|
279
|
-
@buffer
|
278
|
+
Trenni::Markup.append(@buffer, string)
|
280
279
|
end
|
281
280
|
|
282
281
|
def tag_complete(tag)
|
283
282
|
tag.write(@buffer)
|
284
283
|
end
|
284
|
+
|
285
|
+
# Whether this state has any nested tags.
|
286
|
+
def empty?
|
287
|
+
@tags.empty?
|
288
|
+
end
|
285
289
|
|
286
290
|
def tag_begin(tag)
|
291
|
+
# raise ArgumentError.new("tag_begin: #{tag} is tag.self_closed?") if tag.self_closed?
|
292
|
+
|
287
293
|
@tags << tag
|
288
294
|
tag.write_opening_tag(@buffer)
|
289
295
|
end
|
290
296
|
|
291
297
|
def tag_end(tag)
|
292
|
-
raise UnbalancedTagError(tag) unless @tags.pop.name == tag.name
|
298
|
+
raise UnbalancedTagError.new(tag) unless @tags.pop.name == tag.name
|
293
299
|
tag.write_closing_tag(@buffer)
|
294
300
|
end
|
295
301
|
end
|
data/lib/utopia/content/link.rb
CHANGED
@@ -28,6 +28,7 @@ require_relative '../locale'
|
|
28
28
|
|
29
29
|
module Utopia
|
30
30
|
class Content
|
31
|
+
# Represents a link to some content with associated metadata.
|
31
32
|
class Link
|
32
33
|
def initialize(kind, path, info = nil)
|
33
34
|
path = Path.create(path)
|
@@ -91,9 +92,13 @@ module Utopia
|
|
91
92
|
def to_anchor(**options)
|
92
93
|
Trenni::Builder.fragment(options[:builder]) do |builder|
|
93
94
|
if href?
|
94
|
-
|
95
|
+
a_attributes = {
|
96
|
+
:class => options.fetch(:class, 'link'),
|
97
|
+
:href => relative_href(options[:base]),
|
98
|
+
:target => options.fetch(:target, @info[:target])
|
99
|
+
}
|
95
100
|
|
96
|
-
builder.inline('a',
|
101
|
+
builder.inline('a', a_attributes) do
|
97
102
|
builder.text(options[:content] || title)
|
98
103
|
end
|
99
104
|
else
|
data/lib/utopia/content/links.rb
CHANGED
@@ -22,9 +22,10 @@ require_relative 'link'
|
|
22
22
|
|
23
23
|
module Utopia
|
24
24
|
class Content
|
25
|
+
# The file extension for markup nodes on disk.
|
25
26
|
XNODE_EXTENSION = '.xnode'.freeze
|
26
27
|
|
27
|
-
#
|
28
|
+
# Represents a list of {Link} instances relating to the structure of the content. They are formed from the `links.yaml` file and the actual directory structure on disk.
|
28
29
|
class Links
|
29
30
|
def self.for(root, path, locale = nil)
|
30
31
|
links = self.new(root, path.dirname)
|
@@ -21,11 +21,13 @@
|
|
21
21
|
require 'trenni/parsers'
|
22
22
|
require 'trenni/entities'
|
23
23
|
require 'trenni/strings'
|
24
|
-
|
25
|
-
require_relative 'tag'
|
24
|
+
require 'trenni/tag'
|
26
25
|
|
27
26
|
module Utopia
|
28
27
|
class Content
|
28
|
+
Tag = Trenni::Tag
|
29
|
+
|
30
|
+
# A hash which forces all keys to be symbols and fails with KeyError when strings are used.
|
29
31
|
class SymbolicHash < Hash
|
30
32
|
def [] key
|
31
33
|
raise KeyError.new("attribute #{key} is a string, prefer a symbol") if key.is_a? String
|
@@ -49,7 +51,9 @@ module Utopia
|
|
49
51
|
end
|
50
52
|
end
|
51
53
|
|
54
|
+
# Provides a high level interface for parsing markup.
|
52
55
|
class MarkupParser
|
56
|
+
# A tag generated by parsing markup.
|
53
57
|
class ParsedTag
|
54
58
|
def initialize(name, offset)
|
55
59
|
@offset = offset
|
@@ -64,6 +68,7 @@ module Utopia
|
|
64
68
|
end
|
65
69
|
end
|
66
70
|
|
71
|
+
# The name of a closing tag fails to match up with the corresponding opening tag.
|
67
72
|
class UnbalancedTagError < StandardError
|
68
73
|
def initialize(buffer, opening_tag, closing_tag = nil)
|
69
74
|
@buffer = buffer
|
@@ -19,14 +19,33 @@
|
|
19
19
|
# THE SOFTWARE.
|
20
20
|
|
21
21
|
module Utopia
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
22
|
+
class Content
|
23
|
+
# A namespace which contains tags which can be rendered within a {Document}.
|
24
|
+
module Namespace
|
25
|
+
def self.extended(other)
|
26
|
+
other.class_exec do
|
27
|
+
@named = {}
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
attr :named
|
32
|
+
|
33
|
+
def freeze
|
34
|
+
return self if frozen?
|
26
35
|
|
27
|
-
|
36
|
+
@named.freeze
|
37
|
+
@named.values.each(&:freeze)
|
28
38
|
|
29
|
-
|
39
|
+
super
|
40
|
+
end
|
41
|
+
|
42
|
+
def tag(name, klass = nil, &block)
|
43
|
+
@named[name] = klass || block
|
44
|
+
end
|
45
|
+
|
46
|
+
# @return [Node] The node which should be used to render the named tag.
|
47
|
+
def call(name, node)
|
48
|
+
@named[name]
|
30
49
|
end
|
31
50
|
end
|
32
51
|
end
|