radius 0.5.1 → 0.6.1
Sign up to get free protection for your applications and to get access to all the features.
- data.tar.gz.sig +0 -0
- data/CHANGELOG +8 -0
- data/Manifest.txt +21 -0
- data/{QUICKSTART → QUICKSTART.rdoc} +11 -12
- data/{pkg/radius-0.5.0/README → README.rdoc} +38 -17
- data/Rakefile +31 -86
- data/lib/radius.rb +10 -497
- data/lib/radius/context.rb +139 -0
- data/lib/radius/delegating_open_struct.rb +31 -0
- data/lib/radius/error.rb +43 -0
- data/lib/radius/parse_tag.rb +24 -0
- data/lib/radius/parser.rb +65 -0
- data/lib/radius/parser/scan.rb +700 -0
- data/lib/radius/parser/scan.rl +123 -0
- data/lib/radius/tag_binding.rb +71 -0
- data/lib/radius/tag_definitions.rb +78 -0
- data/lib/radius/utility.rb +30 -0
- data/lib/radius/version.rb +14 -0
- data/tasks/scan.rake +27 -0
- data/test/context_test.rb +61 -0
- data/test/{radius_test.rb → parser_test.rb} +51 -82
- data/test/quickstart_test.rb +153 -0
- data/test/test_helper.rb +28 -0
- metadata +109 -61
- metadata.gz.sig +0 -0
- data/README +0 -97
- data/ROADMAP +0 -12
- data/pkg/radius-0.5.0.gem +0 -0
- data/pkg/radius-0.5.0.tgz +0 -0
- data/pkg/radius-0.5.0.zip +0 -0
- data/pkg/radius-0.5.0/CHANGELOG +0 -25
- data/pkg/radius-0.5.0/QUICKSTART +0 -323
- data/pkg/radius-0.5.0/ROADMAP +0 -12
- data/pkg/radius-0.5.0/Rakefile +0 -86
- data/pkg/radius-0.5.0/lib/radius.rb +0 -497
- data/pkg/radius-0.5.0/test/radius_test.rb +0 -321
data.tar.gz.sig
ADDED
Binary file
|
data/CHANGELOG
CHANGED
@@ -1,4 +1,12 @@
|
|
1
1
|
= Change Log
|
2
|
+
=== 0.6.1
|
3
|
+
* Fixed a problem with non-tags that have no prefix or tagname (see test_parse_chirpy_bird)
|
4
|
+
|
5
|
+
=== 0.6.0
|
6
|
+
* Split radius.rb into multiple files.
|
7
|
+
* Port the really hairy regexes from Radius::Parser to a single Ragel machine.
|
8
|
+
* Added and refactored tests.
|
9
|
+
* Refactored Rakefile and other administrativia.
|
2
10
|
|
3
11
|
=== 0.5.1
|
4
12
|
* Fixed a problem with parsing quotes where a single tag preceding a double tag would consume the start
|
data/Manifest.txt
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
CHANGELOG
|
2
|
+
Manifest.txt
|
3
|
+
QUICKSTART.rdoc
|
4
|
+
README.rdoc
|
5
|
+
Rakefile
|
6
|
+
lib/radius.rb
|
7
|
+
lib/radius/context.rb
|
8
|
+
lib/radius/delegating_open_struct.rb
|
9
|
+
lib/radius/error.rb
|
10
|
+
lib/radius/parse_tag.rb
|
11
|
+
lib/radius/parser.rb
|
12
|
+
lib/radius/parser/scan.rb
|
13
|
+
lib/radius/parser/scan.rl
|
14
|
+
lib/radius/tag_binding.rb
|
15
|
+
lib/radius/tag_definitions.rb
|
16
|
+
lib/radius/utility.rb
|
17
|
+
lib/radius/version.rb
|
18
|
+
tasks/scan.rake
|
19
|
+
test/context_test.rb
|
20
|
+
test/parser_test.rb
|
21
|
+
test/test_helper.rb
|
@@ -8,7 +8,7 @@ the tags that will be used in the template. This is actually quite simple:
|
|
8
8
|
|
9
9
|
require 'radius'
|
10
10
|
|
11
|
-
context = Context.new
|
11
|
+
context = Radius::Context.new
|
12
12
|
context.define_tag "hello" do |tag|
|
13
13
|
"Hello #{tag.attr['name'] || 'World'}!"
|
14
14
|
end
|
@@ -53,7 +53,7 @@ With the code above your parser can easily handle Textile:
|
|
53
53
|
|
54
54
|
This code will output:
|
55
55
|
|
56
|
-
<h1>Hello <
|
56
|
+
<h1>Hello <b>World</b>!</h1>
|
57
57
|
|
58
58
|
|
59
59
|
== Nested Tags
|
@@ -61,7 +61,7 @@ This code will output:
|
|
61
61
|
But wait!--it gets better. Because container tags can manipulate the content they contain
|
62
62
|
you can use them to iterate over collections:
|
63
63
|
|
64
|
-
context = Context.new
|
64
|
+
context = Radius::Context.new
|
65
65
|
|
66
66
|
context.define_tag "stooge" do |tag|
|
67
67
|
content = ''
|
@@ -72,7 +72,7 @@ you can use them to iterate over collections:
|
|
72
72
|
content
|
73
73
|
end
|
74
74
|
|
75
|
-
context.define_tag "stooge:name" do
|
75
|
+
context.define_tag "stooge:name" do |tag|
|
76
76
|
tag.locals.name
|
77
77
|
end
|
78
78
|
|
@@ -126,7 +126,7 @@ So far this doesn't save you a whole lot of typing, but suppose you want to expo
|
|
126
126
|
methods that are on that object? You could do this:
|
127
127
|
|
128
128
|
context.define_tag "user", :for => user, :expose => [ :name, :age, :email ]
|
129
|
-
|
129
|
+
|
130
130
|
This will add a total of four tags to the context. One for the <tt>user</tt> variable, and
|
131
131
|
one for each of the three methods listed in the +expose+ clause. You could now get the user's
|
132
132
|
name inside your template like this:
|
@@ -206,6 +206,7 @@ accessible to all tags:
|
|
206
206
|
context.define_tag "inc" do |tag|
|
207
207
|
tag.globals.count ||= 0
|
208
208
|
tag.globals.count += 1
|
209
|
+
""
|
209
210
|
end
|
210
211
|
|
211
212
|
context.define_tag "count" do |tag|
|
@@ -215,20 +216,18 @@ accessible to all tags:
|
|
215
216
|
TagBinding#locals mirrors the variables that are in TagBinding#globals, but allows child
|
216
217
|
tags to redefine variables. This is valuable when defining context sensitive tags:
|
217
218
|
|
218
|
-
require 'radius'
|
219
|
-
|
220
219
|
class Person
|
221
220
|
attr_accessor :name, :friend
|
222
221
|
def initialize(name)
|
223
222
|
@name = name
|
224
223
|
end
|
225
224
|
end
|
226
|
-
|
225
|
+
|
227
226
|
jack = Person.new('Jack')
|
228
227
|
jill = Person.new('Jill')
|
229
228
|
jack.friend = jill
|
230
229
|
jill.friend = jack
|
231
|
-
|
230
|
+
|
232
231
|
context = Radius::Context.new do |c|
|
233
232
|
c.define_tag "jack" do |tag|
|
234
233
|
tag.locals.person = jack
|
@@ -252,9 +251,9 @@ tags to redefine variables. This is valuable when defining context sensitive tag
|
|
252
251
|
parser.parse('<r:jack:name />') #=> "Jack"
|
253
252
|
parser.parse('<r:jill:name />') #=> "Jill"
|
254
253
|
parser.parse('<r:jill:friend:name />') #=> "Jack"
|
255
|
-
parser.parse('<r:
|
254
|
+
parser.parse('<r:jack:friend:friend:name />') #=> "Jack"
|
256
255
|
parser.parse('<r:jill><r:friend:name /> and <r:name /></r:jill>') #=> "Jack and Jill"
|
257
|
-
parser.parse('<r:name />') # raises
|
256
|
+
parser.parse('<r:name />') # raises a Radius::UndefinedTagError exception
|
258
257
|
|
259
258
|
Notice how TagBinding#locals enables intelligent nesting. "<r:jill:name />" evaluates to
|
260
259
|
"Jill", but "<r:jill:friend:name />" evaluates to "Jack". Locals loose scope as soon as
|
@@ -264,7 +263,7 @@ The final line in the example above demonstrates that calling "<r:name />" raise
|
|
264
263
|
TagMissing error. This is because of the way the name tag was defined:
|
265
264
|
|
266
265
|
tag.locals.person.name rescue tag.missing!
|
267
|
-
|
266
|
+
|
268
267
|
If person is not defined on locals it will return nil. Calling #name on nil would normally
|
269
268
|
raise a NoMethodError exception, but because of the 'rescue' clause the TagBinding#missing!
|
270
269
|
method is called which fires off Context#tag_missing. By default Context#tag_missing raises
|
@@ -5,13 +5,13 @@ used in MovableType[http://www.movabletype.org] and TextPattern[http://www.textp
|
|
5
5
|
It uses tags similar to XML, but can be used to generate any form of plain text (HTML, e-mail,
|
6
6
|
etc...).
|
7
7
|
|
8
|
-
==
|
8
|
+
== Usage
|
9
9
|
|
10
10
|
With Radius, it is extremely easy to create custom tags and parse them. Here's a small
|
11
11
|
example:
|
12
12
|
|
13
13
|
require 'radius'
|
14
|
-
|
14
|
+
|
15
15
|
# Define tags on a context that will be available to a template:
|
16
16
|
context = Radius::Context.new do |c|
|
17
17
|
c.define_tag 'hello' do
|
@@ -30,7 +30,7 @@ example:
|
|
30
30
|
|
31
31
|
# Parse tags and output the result
|
32
32
|
puts parser.parse(%{A small example:\n<r:repeat times="3">* <r:hello />!\n</r:repeat>})
|
33
|
-
|
33
|
+
|
34
34
|
Output:
|
35
35
|
|
36
36
|
A small example:
|
@@ -39,16 +39,19 @@ Output:
|
|
39
39
|
* Hello world!
|
40
40
|
|
41
41
|
|
42
|
-
|
42
|
+
== Quick Start
|
43
43
|
|
44
|
-
Read the QUICKSTART
|
44
|
+
Read the QUICKSTART file to get up and running with Radius.
|
45
45
|
|
46
46
|
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
47
|
+
= Requirements
|
48
|
+
|
49
|
+
Radius does not have any external requirements for using the library in your
|
50
|
+
own programs.
|
51
|
+
|
52
|
+
Ragel is required to create the ruby parser from the Ragel specification,
|
53
|
+
and both Ragel and Graphviz are required to draw the state graph for the
|
54
|
+
parser.
|
52
55
|
|
53
56
|
|
54
57
|
== Installation
|
@@ -64,7 +67,7 @@ You can also install Radius by copying lib/radius.rb into the Ruby load path.
|
|
64
67
|
|
65
68
|
Radius is free software and may be redistributed under the terms of the MIT-LICENSE:
|
66
69
|
|
67
|
-
Copyright (c) 2006, John W. Long
|
70
|
+
Copyright (c) 2006-2009, John W. Long
|
68
71
|
|
69
72
|
Permission is hereby granted, free of charge, to any person obtaining a copy of this
|
70
73
|
software and associated documentation files (the "Software"), to deal in the Software
|
@@ -83,15 +86,33 @@ OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
|
|
83
86
|
DEALINGS IN THE SOFTWARE.
|
84
87
|
|
85
88
|
|
86
|
-
==
|
89
|
+
== Roadmap
|
90
|
+
|
91
|
+
This is a prioritized roadmap for future releases:
|
92
|
+
|
93
|
+
1. Clean up the current code base. [Done]
|
94
|
+
|
95
|
+
2. Add support for multi-level contexts: tags should be able to be
|
96
|
+
defined to only be valid within other sets of tags. [Done]
|
97
|
+
|
98
|
+
3. Create a simple DSL for defining contexts. [Done]
|
99
|
+
|
100
|
+
4. Optimize for speed, modify scan.rl to emit C.
|
101
|
+
|
102
|
+
|
103
|
+
== Development
|
104
|
+
|
105
|
+
The latest version of Radius can be found on RubyForge:
|
106
|
+
|
107
|
+
http://rubyforge.org/projects/radius
|
87
108
|
|
88
|
-
|
89
|
-
|
109
|
+
Experimental and development versions of Radius can be found on Github:
|
110
|
+
|
111
|
+
http://github.com/jlong/radius
|
90
112
|
|
91
|
-
If you are interested in helping with the development of Radiant,
|
113
|
+
If you are interested in helping with the development of Radiant, feel free to
|
114
|
+
fork the project on GitHub and send me a pull request.
|
92
115
|
|
93
|
-
Enjoy!
|
94
116
|
|
95
|
-
--
|
96
117
|
John Long ::
|
97
118
|
http://wiseheartdesign.com
|
data/Rakefile
CHANGED
@@ -1,86 +1,31 @@
|
|
1
|
-
require
|
2
|
-
|
3
|
-
require '
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
spec = Gem::Specification.new do |s|
|
34
|
-
s.name = PKG_NAME
|
35
|
-
s.version = PKG_VERSION
|
36
|
-
s.summary = 'Powerful tag-based template system.'
|
37
|
-
s.description = "Radius is a small, but powerful tag-based template language for Ruby\nsimilar to the ones used in MovableType and TextPattern. It has tags\nsimilar to HTML or XML, but can be used to generate any form of plain\ntext (not just HTML)."
|
38
|
-
s.homepage = 'http://radius.rubyforge.org'
|
39
|
-
s.rubyforge_project = RUBY_FORGE_PROJECT
|
40
|
-
s.platform = Gem::Platform::RUBY
|
41
|
-
s.requirements << 'none'
|
42
|
-
s.require_path = 'lib'
|
43
|
-
s.autorequire = 'radius'
|
44
|
-
s.has_rdoc = true
|
45
|
-
s.rdoc_options << '--title' << RDOC_TITLE << '--line-numbers' << '--main' << 'README'
|
46
|
-
s.extra_rdoc_files = RDOC_EXTRAS
|
47
|
-
files = FileList['**/*']
|
48
|
-
files.exclude 'doc'
|
49
|
-
files.exclude '**/._*'
|
50
|
-
s.files = files.to_a
|
51
|
-
end
|
52
|
-
|
53
|
-
Rake::GemPackageTask.new(spec) do |pkg|
|
54
|
-
pkg.need_zip = true
|
55
|
-
pkg.need_tar = true
|
56
|
-
end
|
57
|
-
|
58
|
-
desc "Uninstall Gem"
|
59
|
-
task :uninstall_gem do
|
60
|
-
sh "gem uninstall radius" rescue nil
|
61
|
-
end
|
62
|
-
|
63
|
-
desc "Build and install Gem from source"
|
64
|
-
task :install_gem => [:package, :uninstall_gem] do
|
65
|
-
dir = File.join(File.dirname(__FILE__), 'pkg')
|
66
|
-
chdir(dir) do
|
67
|
-
latest = Dir['radius-*.gem'].last
|
68
|
-
sh "gem install #{latest}"
|
69
|
-
end
|
70
|
-
end
|
71
|
-
|
72
|
-
# --- Ruby forge release manager by florian gross -------------------------------------------------
|
73
|
-
#
|
74
|
-
# task found in Tobias Luetke's library 'liquid'
|
75
|
-
#
|
76
|
-
|
77
|
-
desc "Publish the release files to RubyForge."
|
78
|
-
task :release => [:gem, :package] do
|
79
|
-
files = ["gem", "tgz", "zip"].map { |ext| "pkg/#{PKG_FILE_NAME}.#{ext}" }
|
80
|
-
|
81
|
-
system("rubyforge login --username #{RUBY_FORGE_USER}")
|
82
|
-
|
83
|
-
files.each do |file|
|
84
|
-
system("rubyforge add_release #{RUBY_FORGE_GROUPID} #{RUBY_FORGE_PACKAGEID} \"#{RELEASE_NAME}\" #{file}")
|
85
|
-
end
|
86
|
-
end
|
1
|
+
%w(rubygems rake rake/clean fileutils newgem rubigen hoe).each { |f| require f }
|
2
|
+
|
3
|
+
require 'lib/radius/version'
|
4
|
+
|
5
|
+
# Generate all the Rake tasks
|
6
|
+
# Run 'rake -T' to see list of generated tasks (from gem root directory)
|
7
|
+
Hoe.spec 'radius' do |p|
|
8
|
+
p.version = Radius::Version.to_s
|
9
|
+
p.url = "http://radius.rubyforge.org"
|
10
|
+
p.developer('John W. Long', 'me@johnwlong.com')
|
11
|
+
p.author = [
|
12
|
+
"John W. Long (me@johnwlong.com)",
|
13
|
+
"David Chelimsky (dchelimsky@gmail.com)",
|
14
|
+
"Bryce Kerley (bkerley@brycekerley.net)"
|
15
|
+
]
|
16
|
+
p.changes = p.paragraphs_of("CHANGELOG", 1..2).join("\n\n")
|
17
|
+
p.rubyforge_name = p.name
|
18
|
+
p.extra_dev_deps = [
|
19
|
+
['newgem', ">= #{::Newgem::VERSION}"]
|
20
|
+
]
|
21
|
+
p.readme_file = 'README.rdoc'
|
22
|
+
p.extra_rdoc_files |= %w(README.rdoc QUICKSTART.rdoc)
|
23
|
+
p.clean_globs |= %w(**/.DS_Store tmp *.log) # Remove these files on "rake clean"
|
24
|
+
path = (p.rubyforge_name == p.name) ? p.rubyforge_name : "\#{p.rubyforge_name}/\#{p.name}"
|
25
|
+
p.remote_rdoc_dir = File.join(path.gsub(/^#{p.rubyforge_name}\/?/,''), 'rdoc')
|
26
|
+
p.rsync_args = '-av --delete --ignore-errors'
|
27
|
+
p.test_globs = "test/**/*_test.rb"
|
28
|
+
end
|
29
|
+
|
30
|
+
require 'newgem/tasks' # load /tasks/*.rake
|
31
|
+
Dir['tasks/**/*.rake'].each { |t| load t }
|
data/lib/radius.rb
CHANGED
@@ -1,497 +1,10 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
# The above copyright notice and this permission notice shall be included in all copies
|
12
|
-
# or substantial portions of the Software.
|
13
|
-
#
|
14
|
-
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
|
15
|
-
# INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
|
16
|
-
# PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
|
17
|
-
# HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF
|
18
|
-
# CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE
|
19
|
-
# OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
20
|
-
#++
|
21
|
-
module Radius
|
22
|
-
# Abstract base class for all parsing errors.
|
23
|
-
class ParseError < StandardError
|
24
|
-
end
|
25
|
-
|
26
|
-
# Occurs when Parser cannot find an end tag for a given tag in a template or when
|
27
|
-
# tags are miss-matched in a template.
|
28
|
-
class MissingEndTagError < ParseError
|
29
|
-
# Create a new MissingEndTagError object for +tag_name+.
|
30
|
-
def initialize(tag_name)
|
31
|
-
super("end tag not found for start tag `#{tag_name}'")
|
32
|
-
end
|
33
|
-
end
|
34
|
-
|
35
|
-
# Occurs when Context#render_tag cannot find the specified tag on a Context.
|
36
|
-
class UndefinedTagError < ParseError
|
37
|
-
# Create a new UndefinedTagError object for +tag_name+.
|
38
|
-
def initialize(tag_name)
|
39
|
-
super("undefined tag `#{tag_name}'")
|
40
|
-
end
|
41
|
-
end
|
42
|
-
|
43
|
-
module TagDefinitions # :nodoc:
|
44
|
-
class TagFactory # :nodoc:
|
45
|
-
def initialize(context)
|
46
|
-
@context = context
|
47
|
-
end
|
48
|
-
|
49
|
-
def define_tag(name, options, &block)
|
50
|
-
options = prepare_options(name, options)
|
51
|
-
validate_params(name, options, &block)
|
52
|
-
construct_tag_set(name, options, &block)
|
53
|
-
expose_methods_as_tags(name, options)
|
54
|
-
end
|
55
|
-
|
56
|
-
protected
|
57
|
-
|
58
|
-
# Adds the tag definition to the context. Override in subclasses to add additional tags
|
59
|
-
# (child tags) when the tag is created.
|
60
|
-
def construct_tag_set(name, options, &block)
|
61
|
-
if block
|
62
|
-
@context.definitions[name.to_s] = block
|
63
|
-
else
|
64
|
-
lp = last_part(name)
|
65
|
-
@context.define_tag(name) do |tag|
|
66
|
-
if tag.single?
|
67
|
-
options[:for]
|
68
|
-
else
|
69
|
-
tag.locals.send("#{ lp }=", options[:for]) unless options[:for].nil?
|
70
|
-
tag.expand
|
71
|
-
end
|
72
|
-
end
|
73
|
-
end
|
74
|
-
end
|
75
|
-
|
76
|
-
# Normalizes options pased to tag definition. Override in decendants to preform
|
77
|
-
# additional normalization.
|
78
|
-
def prepare_options(name, options)
|
79
|
-
options = Util.symbolize_keys(options)
|
80
|
-
options[:expose] = expand_array_option(options[:expose])
|
81
|
-
object = options[:for]
|
82
|
-
options[:attributes] = object.respond_to?(:attributes) unless options.has_key? :attributes
|
83
|
-
options[:expose] += object.attributes.keys if options[:attributes]
|
84
|
-
options
|
85
|
-
end
|
86
|
-
|
87
|
-
# Validates parameters passed to tag definition. Override in decendants to add custom
|
88
|
-
# validations.
|
89
|
-
def validate_params(name, options, &block)
|
90
|
-
unless options.has_key? :for
|
91
|
-
raise ArgumentError.new("tag definition must contain a :for option or a block") unless block
|
92
|
-
raise ArgumentError.new("tag definition must contain a :for option when used with the :expose option") unless options[:expose].empty?
|
93
|
-
end
|
94
|
-
end
|
95
|
-
|
96
|
-
# Exposes the methods of an object as child tags.
|
97
|
-
def expose_methods_as_tags(name, options)
|
98
|
-
options[:expose].each do |method|
|
99
|
-
tag_name = "#{name}:#{method}"
|
100
|
-
lp = last_part(name)
|
101
|
-
@context.define_tag(tag_name) do |tag|
|
102
|
-
object = tag.locals.send(lp)
|
103
|
-
object.send(method)
|
104
|
-
end
|
105
|
-
end
|
106
|
-
end
|
107
|
-
|
108
|
-
protected
|
109
|
-
|
110
|
-
def expand_array_option(value)
|
111
|
-
[*value].compact.map { |m| m.to_s.intern }
|
112
|
-
end
|
113
|
-
|
114
|
-
def last_part(name)
|
115
|
-
name.split(':').last
|
116
|
-
end
|
117
|
-
end
|
118
|
-
end
|
119
|
-
|
120
|
-
class DelegatingOpenStruct # :nodoc:
|
121
|
-
attr_accessor :object
|
122
|
-
|
123
|
-
def initialize(object = nil)
|
124
|
-
@object = object
|
125
|
-
@hash = {}
|
126
|
-
end
|
127
|
-
|
128
|
-
def method_missing(method, *args, &block)
|
129
|
-
symbol = (method.to_s =~ /^(.*?)=$/) ? $1.intern : method
|
130
|
-
if (0..1).include?(args.size)
|
131
|
-
if args.size == 1
|
132
|
-
@hash[symbol] = args.first
|
133
|
-
else
|
134
|
-
if @hash.has_key?(symbol)
|
135
|
-
@hash[symbol]
|
136
|
-
else
|
137
|
-
unless object.nil?
|
138
|
-
@object.send(method, *args, &block)
|
139
|
-
else
|
140
|
-
nil
|
141
|
-
end
|
142
|
-
end
|
143
|
-
end
|
144
|
-
else
|
145
|
-
super
|
146
|
-
end
|
147
|
-
end
|
148
|
-
end
|
149
|
-
|
150
|
-
#
|
151
|
-
# A tag binding is passed into each tag definition and contains helper methods for working
|
152
|
-
# with tags. Use it to gain access to the attributes that were passed to the tag, to
|
153
|
-
# render the tag contents, and to do other tasks.
|
154
|
-
#
|
155
|
-
class TagBinding
|
156
|
-
# The Context that the TagBinding is associated with. Used internally. Try not to use
|
157
|
-
# this object directly.
|
158
|
-
attr_reader :context
|
159
|
-
|
160
|
-
# The locals object for the current tag.
|
161
|
-
attr_reader :locals
|
162
|
-
|
163
|
-
# The name of the tag (as used in a template string).
|
164
|
-
attr_reader :name
|
165
|
-
|
166
|
-
# The attributes of the tag. Also aliased as TagBinding#attr.
|
167
|
-
attr_reader :attributes
|
168
|
-
alias :attr :attributes
|
169
|
-
|
170
|
-
# The render block. When called expands the contents of the tag. Use TagBinding#expand
|
171
|
-
# instead.
|
172
|
-
attr_reader :block
|
173
|
-
|
174
|
-
# Creates a new TagBinding object.
|
175
|
-
def initialize(context, locals, name, attributes, block)
|
176
|
-
@context, @locals, @name, @attributes, @block = context, locals, name, attributes, block
|
177
|
-
end
|
178
|
-
|
179
|
-
# Evaluates the current tag and returns the rendered contents.
|
180
|
-
def expand
|
181
|
-
double? ? block.call : ''
|
182
|
-
end
|
183
|
-
|
184
|
-
# Returns true if the current tag is a single tag.
|
185
|
-
def single?
|
186
|
-
block.nil?
|
187
|
-
end
|
188
|
-
|
189
|
-
# Returns true if the current tag is a container tag.
|
190
|
-
def double?
|
191
|
-
not single?
|
192
|
-
end
|
193
|
-
|
194
|
-
# The globals object from which all locals objects ultimately inherit their values.
|
195
|
-
def globals
|
196
|
-
@context.globals
|
197
|
-
end
|
198
|
-
|
199
|
-
# Returns a list of the way tags are nested around the current tag as a string.
|
200
|
-
def nesting
|
201
|
-
@context.current_nesting
|
202
|
-
end
|
203
|
-
|
204
|
-
# Fires off Context#tag_missing for the current tag.
|
205
|
-
def missing!
|
206
|
-
@context.tag_missing(name, attributes, &block)
|
207
|
-
end
|
208
|
-
|
209
|
-
# Renders the tag using the current context .
|
210
|
-
def render(tag, attributes = {}, &block)
|
211
|
-
@context.render_tag(tag, attributes, &block)
|
212
|
-
end
|
213
|
-
end
|
214
|
-
|
215
|
-
#
|
216
|
-
# A context contains the tag definitions which are available for use in a template.
|
217
|
-
# See the QUICKSTART[link:files/QUICKSTART.html] for a detailed explaination its
|
218
|
-
# usage.
|
219
|
-
#
|
220
|
-
class Context
|
221
|
-
# A hash of tag definition blocks that define tags accessible on a Context.
|
222
|
-
attr_accessor :definitions # :nodoc:
|
223
|
-
attr_accessor :globals # :nodoc:
|
224
|
-
|
225
|
-
# Creates a new Context object.
|
226
|
-
def initialize(&block)
|
227
|
-
@definitions = {}
|
228
|
-
@tag_binding_stack = []
|
229
|
-
@globals = DelegatingOpenStruct.new
|
230
|
-
with(&block) if block_given?
|
231
|
-
end
|
232
|
-
|
233
|
-
# Yeild an instance of self for tag definitions:
|
234
|
-
#
|
235
|
-
# context.with do |c|
|
236
|
-
# c.define_tag 'test' do
|
237
|
-
# 'test'
|
238
|
-
# end
|
239
|
-
# end
|
240
|
-
#
|
241
|
-
def with
|
242
|
-
yield self
|
243
|
-
self
|
244
|
-
end
|
245
|
-
|
246
|
-
# Creates a tag definition on a context. Several options are available to you
|
247
|
-
# when creating a tag:
|
248
|
-
#
|
249
|
-
# +for+:: Specifies an object that the tag is in reference to. This is
|
250
|
-
# applicable when a block is not passed to the tag, or when the
|
251
|
-
# +expose+ option is also used.
|
252
|
-
#
|
253
|
-
# +expose+:: Specifies that child tags should be set for each of the methods
|
254
|
-
# contained in this option. May be either a single symbol/string or
|
255
|
-
# an array of symbols/strings.
|
256
|
-
#
|
257
|
-
# +attributes+:: Specifies whether or not attributes should be exposed
|
258
|
-
# automatically. Useful for ActiveRecord objects. Boolean. Defaults
|
259
|
-
# to +true+.
|
260
|
-
#
|
261
|
-
def define_tag(name, options = {}, &block)
|
262
|
-
type = Util.impartial_hash_delete(options, :type).to_s
|
263
|
-
klass = Util.constantize('Radius::TagDefinitions::' + Util.camelize(type) + 'TagFactory') rescue raise(ArgumentError.new("Undefined type `#{type}' in options hash"))
|
264
|
-
klass.new(self).define_tag(name, options, &block)
|
265
|
-
end
|
266
|
-
|
267
|
-
# Returns the value of a rendered tag. Used internally by Parser#parse.
|
268
|
-
def render_tag(name, attributes = {}, &block)
|
269
|
-
if name =~ /^(.+?):(.+)$/
|
270
|
-
render_tag($1) { render_tag($2, attributes, &block) }
|
271
|
-
else
|
272
|
-
tag_definition_block = @definitions[qualified_tag_name(name.to_s)]
|
273
|
-
if tag_definition_block
|
274
|
-
stack(name, attributes, block) do |tag|
|
275
|
-
tag_definition_block.call(tag).to_s
|
276
|
-
end
|
277
|
-
else
|
278
|
-
tag_missing(name, attributes, &block)
|
279
|
-
end
|
280
|
-
end
|
281
|
-
end
|
282
|
-
|
283
|
-
# Like method_missing for objects, but fired when a tag is undefined.
|
284
|
-
# Override in your own Context to change what happens when a tag is
|
285
|
-
# undefined. By default this method raises an UndefinedTagError.
|
286
|
-
def tag_missing(name, attributes, &block)
|
287
|
-
raise UndefinedTagError.new(name)
|
288
|
-
end
|
289
|
-
|
290
|
-
# Returns the state of the current render stack. Useful from inside
|
291
|
-
# a tag definition. Normally just use TagBinding#nesting.
|
292
|
-
def current_nesting
|
293
|
-
@tag_binding_stack.collect { |tag| tag.name }.join(':')
|
294
|
-
end
|
295
|
-
|
296
|
-
private
|
297
|
-
|
298
|
-
# A convienence method for managing the various parts of the
|
299
|
-
# tag binding stack.
|
300
|
-
def stack(name, attributes, block)
|
301
|
-
previous = @tag_binding_stack.last
|
302
|
-
previous_locals = previous.nil? ? @globals : previous.locals
|
303
|
-
locals = DelegatingOpenStruct.new(previous_locals)
|
304
|
-
binding = TagBinding.new(self, locals, name, attributes, block)
|
305
|
-
@tag_binding_stack.push(binding)
|
306
|
-
result = yield(binding)
|
307
|
-
@tag_binding_stack.pop
|
308
|
-
result
|
309
|
-
end
|
310
|
-
|
311
|
-
# Returns a fully qualified tag name based on state of the
|
312
|
-
# tag binding stack.
|
313
|
-
def qualified_tag_name(name)
|
314
|
-
nesting_parts = @tag_binding_stack.collect { |tag| tag.name }
|
315
|
-
nesting_parts << name unless nesting_parts.last == name
|
316
|
-
specific_name = nesting_parts.join(':') # specific_name always has the highest specificity
|
317
|
-
unless @definitions.has_key? specific_name
|
318
|
-
possible_matches = @definitions.keys.grep(/(^|:)#{name}$/)
|
319
|
-
specificity = possible_matches.inject({}) { |hash, tag| hash[numeric_specificity(tag, nesting_parts)] = tag; hash }
|
320
|
-
max = specificity.keys.max
|
321
|
-
if max != 0
|
322
|
-
specificity[max]
|
323
|
-
else
|
324
|
-
name
|
325
|
-
end
|
326
|
-
else
|
327
|
-
specific_name
|
328
|
-
end
|
329
|
-
end
|
330
|
-
|
331
|
-
# Returns the specificity for +tag_name+ at nesting defined
|
332
|
-
# by +nesting_parts+ as a number.
|
333
|
-
def numeric_specificity(tag_name, nesting_parts)
|
334
|
-
nesting_parts = nesting_parts.dup
|
335
|
-
name_parts = tag_name.split(':')
|
336
|
-
specificity = 0
|
337
|
-
value = 1
|
338
|
-
if nesting_parts.last == name_parts.last
|
339
|
-
while nesting_parts.size > 0
|
340
|
-
if nesting_parts.last == name_parts.last
|
341
|
-
specificity += value
|
342
|
-
name_parts.pop
|
343
|
-
end
|
344
|
-
nesting_parts.pop
|
345
|
-
value *= 0.1
|
346
|
-
end
|
347
|
-
specificity = 0 if (name_parts.size > 0)
|
348
|
-
end
|
349
|
-
specificity
|
350
|
-
end
|
351
|
-
end
|
352
|
-
|
353
|
-
class ParseTag # :nodoc:
|
354
|
-
def initialize(&b)
|
355
|
-
@block = b
|
356
|
-
end
|
357
|
-
|
358
|
-
def on_parse(&b)
|
359
|
-
@block = b
|
360
|
-
end
|
361
|
-
|
362
|
-
def to_s
|
363
|
-
@block.call(self)
|
364
|
-
end
|
365
|
-
end
|
366
|
-
|
367
|
-
class ParseContainerTag < ParseTag # :nodoc:
|
368
|
-
attr_accessor :name, :attributes, :contents
|
369
|
-
|
370
|
-
def initialize(name = "", attributes = {}, contents = [], &b)
|
371
|
-
@name, @attributes, @contents = name, attributes, contents
|
372
|
-
super(&b)
|
373
|
-
end
|
374
|
-
end
|
375
|
-
|
376
|
-
#
|
377
|
-
# The Radius parser. Initialize a parser with a Context object that
|
378
|
-
# defines how tags should be expanded. See the QUICKSTART[link:files/QUICKSTART.html]
|
379
|
-
# for a detailed explaination of its usage.
|
380
|
-
#
|
381
|
-
class Parser
|
382
|
-
# The Context object used to expand template tags.
|
383
|
-
attr_accessor :context
|
384
|
-
|
385
|
-
# The string that prefixes all tags that are expanded by a parser
|
386
|
-
# (the part in the tag name before the first colon).
|
387
|
-
attr_accessor :tag_prefix
|
388
|
-
|
389
|
-
# Creates a new parser object initialized with a Context.
|
390
|
-
def initialize(context = Context.new, options = {})
|
391
|
-
if context.kind_of?(Hash) and options.empty?
|
392
|
-
options = context
|
393
|
-
context = options[:context] || options['context'] || Context.new
|
394
|
-
end
|
395
|
-
options = Util.symbolize_keys(options)
|
396
|
-
@context = context
|
397
|
-
@tag_prefix = options[:tag_prefix]
|
398
|
-
end
|
399
|
-
|
400
|
-
# Parses string for tags, expands them, and returns the result.
|
401
|
-
def parse(string)
|
402
|
-
@stack = [ParseContainerTag.new { |t| t.contents.to_s }]
|
403
|
-
pre_parse(string)
|
404
|
-
@stack.last.to_s
|
405
|
-
end
|
406
|
-
|
407
|
-
protected
|
408
|
-
|
409
|
-
def pre_parse(text) # :nodoc:
|
410
|
-
re = %r{<#{@tag_prefix}:([\w:]+?)(\s+(?:\w+\s*=\s*(?:"[^"]*?"|'[^']*?')\s*)*|)>|</#{@tag_prefix}:([\w:]+?)\s*>}
|
411
|
-
if md = re.match(text)
|
412
|
-
start_tag, attr, end_tag = $1, $2, $3
|
413
|
-
@stack.last.contents << ParseTag.new { parse_individual(md.pre_match) }
|
414
|
-
remaining = md.post_match
|
415
|
-
if start_tag
|
416
|
-
parse_start_tag(start_tag, attr, remaining)
|
417
|
-
else
|
418
|
-
parse_end_tag(end_tag, remaining)
|
419
|
-
end
|
420
|
-
else
|
421
|
-
if @stack.length == 1
|
422
|
-
@stack.last.contents << ParseTag.new { parse_individual(text) }
|
423
|
-
else
|
424
|
-
raise MissingEndTagError.new(@stack.last.name)
|
425
|
-
end
|
426
|
-
end
|
427
|
-
end
|
428
|
-
|
429
|
-
def parse_start_tag(start_tag, attr, remaining) # :nodoc:
|
430
|
-
@stack.push(ParseContainerTag.new(start_tag, parse_attributes(attr)))
|
431
|
-
pre_parse(remaining)
|
432
|
-
end
|
433
|
-
|
434
|
-
def parse_end_tag(end_tag, remaining) # :nodoc:
|
435
|
-
popped = @stack.pop
|
436
|
-
if popped.name == end_tag
|
437
|
-
popped.on_parse { |t| @context.render_tag(popped.name, popped.attributes) { t.contents.to_s } }
|
438
|
-
tag = @stack.last
|
439
|
-
tag.contents << popped
|
440
|
-
pre_parse(remaining)
|
441
|
-
else
|
442
|
-
raise MissingEndTagError.new(popped.name)
|
443
|
-
end
|
444
|
-
end
|
445
|
-
|
446
|
-
def parse_individual(text) # :nodoc:
|
447
|
-
re = %r{<#{@tag_prefix}:([\w:]+?)(\s+(?:\w+\s*=\s*(?:"[^"]*?"|'[^']*?')\s*)*|)/>}
|
448
|
-
if md = re.match(text)
|
449
|
-
attr = parse_attributes($2)
|
450
|
-
replace = @context.render_tag($1, attr)
|
451
|
-
md.pre_match + replace + parse_individual(md.post_match)
|
452
|
-
else
|
453
|
-
text || ''
|
454
|
-
end
|
455
|
-
end
|
456
|
-
|
457
|
-
def parse_attributes(text) # :nodoc:
|
458
|
-
attr = {}
|
459
|
-
re = /(\w+?)\s*=\s*('|")(.*?)\2/
|
460
|
-
while md = re.match(text)
|
461
|
-
attr[$1] = $3
|
462
|
-
text = md.post_match
|
463
|
-
end
|
464
|
-
attr
|
465
|
-
end
|
466
|
-
end
|
467
|
-
|
468
|
-
module Util # :nodoc:
|
469
|
-
def self.symbolize_keys(hash)
|
470
|
-
new_hash = {}
|
471
|
-
hash.keys.each do |k|
|
472
|
-
new_hash[k.to_s.intern] = hash[k]
|
473
|
-
end
|
474
|
-
new_hash
|
475
|
-
end
|
476
|
-
|
477
|
-
def self.impartial_hash_delete(hash, key)
|
478
|
-
string = key.to_s
|
479
|
-
symbol = string.intern
|
480
|
-
value1 = hash.delete(symbol)
|
481
|
-
value2 = hash.delete(string)
|
482
|
-
value1 || value2
|
483
|
-
end
|
484
|
-
|
485
|
-
def self.constantize(camelized_string)
|
486
|
-
raise "invalid constant name `#{camelized_string}'" unless camelized_string.split('::').all? { |part| part =~ /^[A-Za-z]+$/ }
|
487
|
-
Object.module_eval(camelized_string)
|
488
|
-
end
|
489
|
-
|
490
|
-
def self.camelize(underscored_string)
|
491
|
-
string = ''
|
492
|
-
underscored_string.split('_').each { |part| string << part.capitalize }
|
493
|
-
string
|
494
|
-
end
|
495
|
-
end
|
496
|
-
|
497
|
-
end
|
1
|
+
require 'radius/version'
|
2
|
+
require 'radius/error'
|
3
|
+
require 'radius/tag_definitions'
|
4
|
+
require 'radius/delegating_open_struct'
|
5
|
+
require 'radius/tag_binding'
|
6
|
+
require 'radius/context'
|
7
|
+
require 'radius/parse_tag'
|
8
|
+
require 'radius/parser/scan'
|
9
|
+
require 'radius/parser'
|
10
|
+
require 'radius/utility'
|