radius19-radiant 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- data/CHANGELOG +7 -0
- data/QUICKSTART +323 -0
- data/README +5 -0
- data/ROADMAP +12 -0
- data/Rakefile +56 -0
- data/VERSION +1 -0
- data/lib/radius/context.rb +139 -0
- data/lib/radius/dostruct.rb +31 -0
- data/lib/radius/errors.rb +24 -0
- data/lib/radius/parser.rb +95 -0
- data/lib/radius/parsetag.rb +23 -0
- data/lib/radius/tagbinding.rb +66 -0
- data/lib/radius/tagdefs.rb +78 -0
- data/lib/radius/util.rb +34 -0
- data/lib/radius19.rb +24 -0
- data/test/context_test.rb +61 -0
- data/test/dostruct_test.rb +53 -0
- data/test/parser_test.rb +242 -0
- data/test/tagbinding_test.rb +50 -0
- data/test/tagdefs_test.rb +61 -0
- data/test/test_helper.rb +25 -0
- data/test/util_test.rb +25 -0
- metadata +91 -0
data/CHANGELOG
ADDED
data/QUICKSTART
ADDED
@@ -0,0 +1,323 @@
|
|
1
|
+
= Radius Quick Start
|
2
|
+
|
3
|
+
|
4
|
+
== Defining Tags
|
5
|
+
|
6
|
+
Before you can parse a template with Radius you need to create a Context object which defines
|
7
|
+
the tags that will be used in the template. This is actually quite simple:
|
8
|
+
|
9
|
+
require 'radius19'
|
10
|
+
|
11
|
+
context = Context.new
|
12
|
+
context.define_tag "hello" do |tag|
|
13
|
+
"Hello #{tag.attr['name'] || 'World'}!"
|
14
|
+
end
|
15
|
+
|
16
|
+
Once you have defined a context you can easily create a Parser:
|
17
|
+
|
18
|
+
parser = Radius::Parser.new(context)
|
19
|
+
puts parser.parse('<p><radius:hello /></p>')
|
20
|
+
puts parser.parse('<p><radius:hello name="John" /></p>')
|
21
|
+
|
22
|
+
This code will output:
|
23
|
+
|
24
|
+
<p>Hello World!</p>
|
25
|
+
<p>Hello John!</p>
|
26
|
+
|
27
|
+
Note how you can pass attributes from the template to the context using the attributes hash.
|
28
|
+
Above, the first tag that was parsed didn't have a name attribute so the code in the +hello+
|
29
|
+
tag definition uses "World" instead. The second time the tag is parsed the name attribute is
|
30
|
+
set to "John" which is used to create the string "Hello John!". Tags that do not follow this
|
31
|
+
rule will be treated as if they were undefined (like normal methods).
|
32
|
+
|
33
|
+
|
34
|
+
== Container Tags
|
35
|
+
|
36
|
+
Radius also allows you to define "container" tags. That is, tags that contain content and
|
37
|
+
that may optionally manipulate it in some way. For example, if you have RedCloth installed
|
38
|
+
you could define another tag to parse and create Textile output:
|
39
|
+
|
40
|
+
require 'redcloth'
|
41
|
+
|
42
|
+
context.define_tag "textile" do |tag|
|
43
|
+
contents = tag.expand
|
44
|
+
RedCloth.new(contents).to_html
|
45
|
+
end
|
46
|
+
|
47
|
+
(The code <tt>tag.expand</tt> above returns the contents of the template between the start and end
|
48
|
+
tags.)
|
49
|
+
|
50
|
+
With the code above your parser can easily handle Textile:
|
51
|
+
|
52
|
+
parser.parse('<radius:textile>h1. Hello **World**!</radius:textile>')
|
53
|
+
|
54
|
+
This code will output:
|
55
|
+
|
56
|
+
<h1>Hello <strong>World</strong>!</h1>
|
57
|
+
|
58
|
+
|
59
|
+
== Nested Tags
|
60
|
+
|
61
|
+
But wait!--it gets better. Because container tags can manipulate the content they contain
|
62
|
+
you can use them to iterate over collections:
|
63
|
+
|
64
|
+
context = Context.new
|
65
|
+
|
66
|
+
context.define_tag "stooge" do |tag|
|
67
|
+
content = ''
|
68
|
+
["Larry", "Moe", "Curly"].each do |name|
|
69
|
+
tag.locals.name = name
|
70
|
+
content << tag.expand
|
71
|
+
end
|
72
|
+
content
|
73
|
+
end
|
74
|
+
|
75
|
+
context.define_tag "stooge:name" do
|
76
|
+
tag.locals.name
|
77
|
+
end
|
78
|
+
|
79
|
+
parser = Radius::Parser.new(context)
|
80
|
+
|
81
|
+
template = <<-TEMPLATE
|
82
|
+
<ul>
|
83
|
+
<radius:stooge>
|
84
|
+
<li><radius:name /></li>
|
85
|
+
</radius:stooge>
|
86
|
+
</ul>
|
87
|
+
TEMPLATE
|
88
|
+
|
89
|
+
puts parser.parse(template)
|
90
|
+
|
91
|
+
This code will output:
|
92
|
+
|
93
|
+
<ul>
|
94
|
+
|
95
|
+
<li>Larry</li>
|
96
|
+
|
97
|
+
<li>Moe</li>
|
98
|
+
|
99
|
+
<li>Curly</li>
|
100
|
+
|
101
|
+
</ul>
|
102
|
+
|
103
|
+
Note how the definition for the +name+ tag is defined. Because "name" is prefixed
|
104
|
+
with "stooge:" the +name+ tag cannot appear outside the +stooge+ tag. Had it been defined
|
105
|
+
simply as "name" it would be valid anywhere, even outside the +stooge+ tag (which was
|
106
|
+
not what we wanted). Using the colon operator you can define tags with any amount of
|
107
|
+
nesting.
|
108
|
+
|
109
|
+
|
110
|
+
== Exposing Objects to Templates
|
111
|
+
|
112
|
+
During normal operation, you will often want to expose certain objects to your templates.
|
113
|
+
Writing the tags to do this all by hand would be cumbersome of Radius did not provide
|
114
|
+
several mechanisms to make this easier. The first is a way of exposing objects as tags
|
115
|
+
on the context object. To expose an object simply call the +define_tag+
|
116
|
+
method with the +for+ option:
|
117
|
+
|
118
|
+
context.define_tag "count", :for => 1
|
119
|
+
|
120
|
+
This would expose the object <tt>1</tt> to the template as the +count+ tag. It's basically the
|
121
|
+
equivalent of writing:
|
122
|
+
|
123
|
+
context.define_tag("count") { 1 }
|
124
|
+
|
125
|
+
So far this doesn't save you a whole lot of typing, but suppose you want to expose certain
|
126
|
+
methods that are on that object? You could do this:
|
127
|
+
|
128
|
+
context.define_tag "user", :for => user, :expose => [ :name, :age, :email ]
|
129
|
+
|
130
|
+
This will add a total of four tags to the context. One for the <tt>user</tt> variable, and
|
131
|
+
one for each of the three methods listed in the +expose+ clause. You could now get the user's
|
132
|
+
name inside your template like this:
|
133
|
+
|
134
|
+
<radius:user><radius:name /></radius:user>
|
135
|
+
|
136
|
+
If "John" was the value stored in <tt>user.name</tt> the template would render as "John".
|
137
|
+
|
138
|
+
|
139
|
+
== Tag Shorthand
|
140
|
+
|
141
|
+
In the example above we made reference to <tt>user.name</tt> in our template by using the
|
142
|
+
following code:
|
143
|
+
|
144
|
+
<radius:user><radius:name /></radius:user>
|
145
|
+
|
146
|
+
There is a much easer way to refer to the <tt>user.name</tt> variable. Use the colon operator
|
147
|
+
to "scope" the reference to <tt>name</tt>:
|
148
|
+
|
149
|
+
<radius:user:name />
|
150
|
+
|
151
|
+
Radius allows you to use this shortcut for all tags.
|
152
|
+
|
153
|
+
|
154
|
+
== Changing the Tag Prefix
|
155
|
+
|
156
|
+
By default, all Radius tags must begin with "radius". You can change this by altering the
|
157
|
+
tag_prefix attribute on a Parser. For example:
|
158
|
+
|
159
|
+
parser = Radius::Parser.new(context, :tag_prefix => 'r')
|
160
|
+
|
161
|
+
Now, when parsing templates with +parser+, Radius will require that every tag begin with "r"
|
162
|
+
instead of "radius".
|
163
|
+
|
164
|
+
|
165
|
+
== Custom Behavior for Undefined Tags
|
166
|
+
|
167
|
+
Context#tag_missing behaves much like Object#method_missing only it allows you to define
|
168
|
+
specific behavior for when a tag is not defined on a Context. For example:
|
169
|
+
|
170
|
+
class LazyContext < Radius::Context
|
171
|
+
def tag_missing(tag, attr, &block)
|
172
|
+
"<strong>ERROR: Undefined tag `#{tag}' with attributes #{attr.inspect}</strong>"
|
173
|
+
end
|
174
|
+
end
|
175
|
+
|
176
|
+
parser = Radius::Parser.new(LazyContext.new, :tag_prefix => 'lazy')
|
177
|
+
puts parser.parse('<lazy:weird value="true" />')
|
178
|
+
|
179
|
+
This will output:
|
180
|
+
|
181
|
+
<strong>ERROR: Undefined tag `weird' with attributes {"value"=>"true"}</strong>
|
182
|
+
|
183
|
+
Normally, when the Radius Parser encounters an undefined tag for a Context it raises an
|
184
|
+
UndefinedTagError, but since we have defined #tag_missing on LazyContext the Parser now
|
185
|
+
outputs a nicely formated error message when we parse a string that does not contain a
|
186
|
+
valid tag.
|
187
|
+
|
188
|
+
|
189
|
+
== Tag Bindings
|
190
|
+
|
191
|
+
Radius passes a TagBinding into the block of the Context#define_tag method. The tag
|
192
|
+
binding is useful for a number of tasks. A tag binding has an #expand instance method
|
193
|
+
which processes a tag's contents and returns the result. It also has a #attr method
|
194
|
+
which returns a hash of the attributes that were passed into the tag. TagBinding also
|
195
|
+
contains the TagBinding#single? and TagBinding#double? methods which return true or false
|
196
|
+
based on wether the tag is a container tag or not. More about the methods which are
|
197
|
+
available on tag bindings can be found on the Radius::TagBinding documentation page.
|
198
|
+
|
199
|
+
|
200
|
+
== Tag Binding Locals, Globals, and Context Sensitive Tags
|
201
|
+
|
202
|
+
A TagBinding also contains two OpenStruct-like objects which are useful when developing
|
203
|
+
tags. TagBinding#globals is useful for storing variables which you would like to be
|
204
|
+
accessible to all tags:
|
205
|
+
|
206
|
+
context.define_tag "inc" do |tag|
|
207
|
+
tag.globals.count ||= 0
|
208
|
+
tag.globals.count += 1
|
209
|
+
end
|
210
|
+
|
211
|
+
context.define_tag "count" do |tag|
|
212
|
+
tag.globals.count || 0
|
213
|
+
end
|
214
|
+
|
215
|
+
TagBinding#locals mirrors the variables that are in TagBinding#globals, but allows child
|
216
|
+
tags to redefine variables. This is valuable when defining context sensitive tags:
|
217
|
+
|
218
|
+
require 'radius'
|
219
|
+
|
220
|
+
class Person
|
221
|
+
attr_accessor :name, :friend
|
222
|
+
def initialize(name)
|
223
|
+
@name = name
|
224
|
+
end
|
225
|
+
end
|
226
|
+
|
227
|
+
jack = Person.new('Jack')
|
228
|
+
jill = Person.new('Jill')
|
229
|
+
jack.friend = jill
|
230
|
+
jill.friend = jack
|
231
|
+
|
232
|
+
context = Radius::Context.new do |c|
|
233
|
+
c.define_tag "jack" do |tag|
|
234
|
+
tag.locals.person = jack
|
235
|
+
tag.expand
|
236
|
+
end
|
237
|
+
c.define_tag "jill" do |tag|
|
238
|
+
tag.locals.person = jill
|
239
|
+
tag.expand
|
240
|
+
end
|
241
|
+
c.define_tag "name" do |tag|
|
242
|
+
tag.locals.person.name rescue tag.missing!
|
243
|
+
end
|
244
|
+
c.define_tag "friend" do |tag|
|
245
|
+
tag.locals.person = tag.locals.person.friend rescue tag.missing!
|
246
|
+
tag.expand
|
247
|
+
end
|
248
|
+
end
|
249
|
+
|
250
|
+
parser = Radius::Parser.new(context, :tag_prefix => 'r')
|
251
|
+
|
252
|
+
parser.parse('<r:jack:name />') #=> "Jack"
|
253
|
+
parser.parse('<r:jill:name />') #=> "Jill"
|
254
|
+
parser.parse('<r:jill:friend:name />') #=> "Jack"
|
255
|
+
parser.parse('<r:jill:friend:friend:name />') #=> "Jack"
|
256
|
+
parser.parse('<r:jill><r:friend:name /> and <r:name /></r:jill>') #=> "Jack and Jill"
|
257
|
+
parser.parse('<r:name />') # raises an UndefinedTagError exception
|
258
|
+
|
259
|
+
Notice how TagBinding#locals enables intelligent nesting. "<r:jill:name />" evaluates to
|
260
|
+
"Jill", but "<r:jill:friend:name />" evaluates to "Jack". Locals loose scope as soon as
|
261
|
+
the tag they were defined in closes. Globals on the other hand, never loose scope.
|
262
|
+
|
263
|
+
The final line in the example above demonstrates that calling "<r:name />" raises a
|
264
|
+
TagMissing error. This is because of the way the name tag was defined:
|
265
|
+
|
266
|
+
tag.locals.person.name rescue tag.missing!
|
267
|
+
|
268
|
+
If person is not defined on locals it will return nil. Calling #name on nil would normally
|
269
|
+
raise a NoMethodError exception, but because of the 'rescue' clause the TagBinding#missing!
|
270
|
+
method is called which fires off Context#tag_missing. By default Context#tag_missing raises
|
271
|
+
a UndefinedTagError exception. The 'rescue tag.missing!' idiom is extremly useful for adding
|
272
|
+
simple error checking to context sensitive tags.
|
273
|
+
|
274
|
+
|
275
|
+
== Tag Specificity
|
276
|
+
|
277
|
+
When Radius is presented with two tags that have the same name, but different nesting
|
278
|
+
Radius uses an algorithm similar to the way winning rules are calculated in Cascading Style
|
279
|
+
Sheets (CSS) to determine which definition should be used. Each time a tag is encountered
|
280
|
+
in a template potential tags are assigned specificity values and the tag with the highest
|
281
|
+
specificity wins.
|
282
|
+
|
283
|
+
For example, given the following tag definitions:
|
284
|
+
|
285
|
+
nesting
|
286
|
+
extra:nesting
|
287
|
+
parent:child:nesting
|
288
|
+
|
289
|
+
And template:
|
290
|
+
|
291
|
+
<r:parent:extra:child:nesting />
|
292
|
+
|
293
|
+
Radius will calculate specificity values like this:
|
294
|
+
|
295
|
+
nesting => 1.0.0.0
|
296
|
+
extra:nesting => 1.0.1.0
|
297
|
+
parent:child:nesting => 1.1.0.1
|
298
|
+
|
299
|
+
Meaning that parent:child:nesting will win. If a template contained:
|
300
|
+
|
301
|
+
<r:parent:child:extra:nesting />
|
302
|
+
|
303
|
+
The following specificity values would be assigned to each of the tag definitions:
|
304
|
+
|
305
|
+
nesting => 1.0.0.0
|
306
|
+
extra:nesting => 1.1.0.0
|
307
|
+
parent:child:nesting => 1.0.1.1
|
308
|
+
|
309
|
+
Meaning that extra:nesting would win because it is more "specific".
|
310
|
+
|
311
|
+
Values are assigned by assigning points to each of the tags from right to left.
|
312
|
+
Given a tag found in a template with nesting four levels deep, the maximum
|
313
|
+
specificity a tag could be assigned would be:
|
314
|
+
|
315
|
+
1.1.1.1
|
316
|
+
|
317
|
+
One point for each of the levels.
|
318
|
+
|
319
|
+
A deep understanding of tag specificity is not necessary to be effective with
|
320
|
+
Radius. For the most part you will find that Radius resolves tags precisely the
|
321
|
+
way that you would expect. If you find this section confusing forget about it and
|
322
|
+
refer back to it if you find that tags are resolving differently from the way that
|
323
|
+
you expected.
|
data/README
ADDED
data/ROADMAP
ADDED
@@ -0,0 +1,12 @@
|
|
1
|
+
= Roadmap
|
2
|
+
|
3
|
+
This is a prioritized roadmap for future releases:
|
4
|
+
|
5
|
+
1. Clean up the current code base. [Done]
|
6
|
+
|
7
|
+
2. Add support for multi-level contexts: tags should be able to be
|
8
|
+
defined to only be valid within other sets of tags. [Done]
|
9
|
+
|
10
|
+
3. Create a simple DSL for defining contexts. [Done]
|
11
|
+
|
12
|
+
4. Optimize for speed. Incorporate strscan?
|
data/Rakefile
ADDED
@@ -0,0 +1,56 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'rake'
|
3
|
+
|
4
|
+
begin
|
5
|
+
require 'jeweler'
|
6
|
+
Jeweler::Tasks.new do |gem|
|
7
|
+
gem.name = "radius19"
|
8
|
+
gem.summary = %Q{A small, but powerful tag-based template language for Ruby modeled after the ones used in MovableType and TextPattern. It has tags similar to XML, but can be used to generate any form of plain text (HTML, e-mail, etc...) adapted to work on Ruby 1.9.}
|
9
|
+
gem.email = "aemadrid@gmail.com"
|
10
|
+
gem.homepage = "http://github.com/aemadrid/radius19"
|
11
|
+
gem.authors = ["Adrian Madrid", "John W. Long"]
|
12
|
+
# gem is a Gem::Specification... see http://www.rubygems.org/read/chapter/20 for additional settings
|
13
|
+
end
|
14
|
+
|
15
|
+
rescue LoadError
|
16
|
+
puts "Jeweler (or a dependency) not available. Install it with: sudo gem install jeweler"
|
17
|
+
end
|
18
|
+
|
19
|
+
require 'rake/testtask'
|
20
|
+
Rake::TestTask.new(:test) do |test|
|
21
|
+
test.libs << 'lib' << 'test'
|
22
|
+
test.pattern = 'test/**/*_test.rb'
|
23
|
+
test.verbose = true
|
24
|
+
end
|
25
|
+
|
26
|
+
begin
|
27
|
+
require 'rcov/rcovtask'
|
28
|
+
Rcov::RcovTask.new do |test|
|
29
|
+
test.libs << 'test'
|
30
|
+
test.pattern = 'test/**/*_test.rb'
|
31
|
+
test.verbose = true
|
32
|
+
end
|
33
|
+
rescue LoadError
|
34
|
+
task :rcov do
|
35
|
+
abort "RCov is not available. In order to run rcov, you must: sudo gem install spicycode-rcov"
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
|
40
|
+
task :default => :test
|
41
|
+
|
42
|
+
require 'rake/rdoctask'
|
43
|
+
Rake::RDocTask.new do |rdoc|
|
44
|
+
if File.exist?('VERSION.yml')
|
45
|
+
config = YAML.load(File.read('VERSION.yml'))
|
46
|
+
version = "#{config[:major]}.#{config[:minor]}.#{config[:patch]}"
|
47
|
+
else
|
48
|
+
version = ""
|
49
|
+
end
|
50
|
+
|
51
|
+
rdoc.rdoc_dir = 'rdoc'
|
52
|
+
rdoc.title = "radius19 #{version}"
|
53
|
+
rdoc.rdoc_files.include('README*')
|
54
|
+
rdoc.rdoc_files.include('lib/**/*.rb')
|
55
|
+
end
|
56
|
+
|
data/VERSION
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
0.5.3
|
@@ -0,0 +1,139 @@
|
|
1
|
+
module Radius
|
2
|
+
#
|
3
|
+
# A context contains the tag definitions which are available for use in a template.
|
4
|
+
# See the QUICKSTART[link:files/QUICKSTART.html] for a detailed explaination its
|
5
|
+
# usage.
|
6
|
+
#
|
7
|
+
class Context
|
8
|
+
# A hash of tag definition blocks that define tags accessible on a Context.
|
9
|
+
attr_accessor :definitions # :nodoc:
|
10
|
+
attr_accessor :globals # :nodoc:
|
11
|
+
|
12
|
+
# Creates a new Context object.
|
13
|
+
def initialize(&block)
|
14
|
+
@definitions = {}
|
15
|
+
@tag_binding_stack = []
|
16
|
+
@globals = DelegatingOpenStruct.new
|
17
|
+
with(&block) if block_given?
|
18
|
+
end
|
19
|
+
|
20
|
+
# Yeild an instance of self for tag definitions:
|
21
|
+
#
|
22
|
+
# context.with do |c|
|
23
|
+
# c.define_tag 'test' do
|
24
|
+
# 'test'
|
25
|
+
# end
|
26
|
+
# end
|
27
|
+
#
|
28
|
+
def with
|
29
|
+
yield self
|
30
|
+
self
|
31
|
+
end
|
32
|
+
|
33
|
+
# Creates a tag definition on a context. Several options are available to you
|
34
|
+
# when creating a tag:
|
35
|
+
#
|
36
|
+
# +for+:: Specifies an object that the tag is in reference to. This is
|
37
|
+
# applicable when a block is not passed to the tag, or when the
|
38
|
+
# +expose+ option is also used.
|
39
|
+
#
|
40
|
+
# +expose+:: Specifies that child tags should be set for each of the methods
|
41
|
+
# contained in this option. May be either a single symbol/string or
|
42
|
+
# an array of symbols/strings.
|
43
|
+
#
|
44
|
+
# +attributes+:: Specifies whether or not attributes should be exposed
|
45
|
+
# automatically. Useful for ActiveRecord objects. Boolean. Defaults
|
46
|
+
# to +true+.
|
47
|
+
#
|
48
|
+
def define_tag(name, options = {}, &block)
|
49
|
+
type = Util.impartial_hash_delete(options, :type).to_s
|
50
|
+
klass = Util.constantize('Radius::TagDefinitions::' + Util.camelize(type) + 'TagFactory') rescue raise(ArgumentError.new("Undefined type `#{type}' in options hash"))
|
51
|
+
klass.new(self).define_tag(name, options, &block)
|
52
|
+
end
|
53
|
+
|
54
|
+
# Returns the value of a rendered tag. Used internally by Parser#parse.
|
55
|
+
def render_tag(name, attributes = {}, &block)
|
56
|
+
if name =~ /^(.+?):(.+)$/
|
57
|
+
render_tag($1) { render_tag($2, attributes, &block) }
|
58
|
+
else
|
59
|
+
tag_definition_block = @definitions[qualified_tag_name(name.to_s)]
|
60
|
+
if tag_definition_block
|
61
|
+
stack(name, attributes, block) do |tag|
|
62
|
+
tag_definition_block.call(tag).to_s
|
63
|
+
end
|
64
|
+
else
|
65
|
+
tag_missing(name, attributes, &block)
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
# Like method_missing for objects, but fired when a tag is undefined.
|
71
|
+
# Override in your own Context to change what happens when a tag is
|
72
|
+
# undefined. By default this method raises an UndefinedTagError.
|
73
|
+
def tag_missing(name, attributes, &block)
|
74
|
+
raise UndefinedTagError.new(name)
|
75
|
+
end
|
76
|
+
|
77
|
+
# Returns the state of the current render stack. Useful from inside
|
78
|
+
# a tag definition. Normally just use TagBinding#nesting.
|
79
|
+
def current_nesting
|
80
|
+
@tag_binding_stack.collect { |tag| tag.name }.join(':')
|
81
|
+
end
|
82
|
+
|
83
|
+
private
|
84
|
+
|
85
|
+
# A convienence method for managing the various parts of the
|
86
|
+
# tag binding stack.
|
87
|
+
def stack(name, attributes, block)
|
88
|
+
previous = @tag_binding_stack.last
|
89
|
+
previous_locals = previous.nil? ? @globals : previous.locals
|
90
|
+
locals = DelegatingOpenStruct.new(previous_locals)
|
91
|
+
binding = TagBinding.new(self, locals, name, attributes, block)
|
92
|
+
@tag_binding_stack.push(binding)
|
93
|
+
result = yield(binding)
|
94
|
+
@tag_binding_stack.pop
|
95
|
+
result
|
96
|
+
end
|
97
|
+
|
98
|
+
# Returns a fully qualified tag name based on state of the
|
99
|
+
# tag binding stack.
|
100
|
+
def qualified_tag_name(name)
|
101
|
+
nesting_parts = @tag_binding_stack.collect { |tag| tag.name }
|
102
|
+
nesting_parts << name unless nesting_parts.last == name
|
103
|
+
specific_name = nesting_parts.join(':') # specific_name always has the highest specificity
|
104
|
+
unless @definitions.has_key? specific_name
|
105
|
+
possible_matches = @definitions.keys.grep(/(^|:)#{name}$/)
|
106
|
+
specificity = possible_matches.inject({}) { |hash, tag| hash[numeric_specificity(tag, nesting_parts)] = tag; hash }
|
107
|
+
max = specificity.keys.max
|
108
|
+
if max != 0
|
109
|
+
specificity[max]
|
110
|
+
else
|
111
|
+
name
|
112
|
+
end
|
113
|
+
else
|
114
|
+
specific_name
|
115
|
+
end
|
116
|
+
end
|
117
|
+
|
118
|
+
# Returns the specificity for +tag_name+ at nesting defined
|
119
|
+
# by +nesting_parts+ as a number.
|
120
|
+
def numeric_specificity(tag_name, nesting_parts)
|
121
|
+
nesting_parts = nesting_parts.dup
|
122
|
+
name_parts = tag_name.split(':')
|
123
|
+
specificity = 0
|
124
|
+
value = 1
|
125
|
+
if nesting_parts.last == name_parts.last
|
126
|
+
while nesting_parts.size > 0
|
127
|
+
if nesting_parts.last == name_parts.last
|
128
|
+
specificity += value
|
129
|
+
name_parts.pop
|
130
|
+
end
|
131
|
+
nesting_parts.pop
|
132
|
+
value *= 0.1
|
133
|
+
end
|
134
|
+
specificity = 0 if (name_parts.size > 0)
|
135
|
+
end
|
136
|
+
specificity
|
137
|
+
end
|
138
|
+
end
|
139
|
+
end
|