radius-ts 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/CHANGELOG +37 -0
- data/Manifest.txt +21 -0
- data/QUICKSTART.rdoc +322 -0
- data/README.rdoc +118 -0
- data/Rakefile +34 -0
- data/lib/radius.rb +10 -0
- data/lib/radius/context.rb +147 -0
- data/lib/radius/delegating_open_struct.rb +37 -0
- data/lib/radius/error.rb +43 -0
- data/lib/radius/parse_tag.rb +24 -0
- data/lib/radius/parser.rb +64 -0
- data/lib/radius/parser/scan.rb +1194 -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/multithreaded_test.rb +29 -0
- data/test/parser_test.rb +302 -0
- data/test/quickstart_test.rb +151 -0
- data/test/test_helper.rb +32 -0
- metadata +104 -0
@@ -0,0 +1,123 @@
|
|
1
|
+
%%{
|
2
|
+
machine parser;
|
3
|
+
|
4
|
+
|
5
|
+
action _prefix { mark_pfx = p }
|
6
|
+
action prefix {
|
7
|
+
if data[mark_pfx..p-1] != @prefix
|
8
|
+
@nodes.last << data[mark_pfx-1..p]
|
9
|
+
fbreak;
|
10
|
+
end
|
11
|
+
}
|
12
|
+
action _starttag { mark_stg = p }
|
13
|
+
action starttag { @starttag = data[mark_stg..p-1] }
|
14
|
+
action _attr { mark_attr = p }
|
15
|
+
action attr {
|
16
|
+
@attrs[@nat] = @vat
|
17
|
+
}
|
18
|
+
|
19
|
+
action prematch {
|
20
|
+
@prematch_end = p
|
21
|
+
@prematch = data[0..p] if p > 0
|
22
|
+
}
|
23
|
+
|
24
|
+
action _nameattr { mark_nat = p }
|
25
|
+
action nameattr { @nat = data[mark_nat..p-1] }
|
26
|
+
action _valattr { mark_vat = p }
|
27
|
+
action valattr { @vat = data[mark_vat..p-1] }
|
28
|
+
|
29
|
+
action opentag { @flavor = :open }
|
30
|
+
action selftag { @flavor = :self }
|
31
|
+
action closetag { @flavor = :close }
|
32
|
+
|
33
|
+
action stopparse {
|
34
|
+
@cursor = p;
|
35
|
+
fbreak;
|
36
|
+
}
|
37
|
+
|
38
|
+
|
39
|
+
Closeout := empty;
|
40
|
+
|
41
|
+
# words
|
42
|
+
PrefixChar = [\-A-Za-z0-9._?] ;
|
43
|
+
NameChar = [\-A-Za-z0-9._:?] ;
|
44
|
+
TagName = NameChar+ >_starttag %starttag;
|
45
|
+
Prefix = PrefixChar+ >_prefix %prefix;
|
46
|
+
|
47
|
+
Name = Prefix ":" TagName;
|
48
|
+
|
49
|
+
NameAttr = NameChar+ >_nameattr %nameattr;
|
50
|
+
Q1Char = ( "\\\'" | [^'] ) ;
|
51
|
+
Q1Attr = Q1Char* >_valattr %valattr;
|
52
|
+
Q2Char = ( "\\\"" | [^"] ) ;
|
53
|
+
Q2Attr = Q2Char* >_valattr %valattr;
|
54
|
+
|
55
|
+
Attr = NameAttr space* "=" space* ('"' Q2Attr '"' | "'" Q1Attr "'") space* >_attr %attr;
|
56
|
+
Attrs = (space+ Attr* | empty);
|
57
|
+
|
58
|
+
CloseTrailer = "/>" %selftag;
|
59
|
+
OpenTrailer = ">" %opentag;
|
60
|
+
|
61
|
+
Trailer = (OpenTrailer | CloseTrailer);
|
62
|
+
|
63
|
+
OpenOrSelfTag = Name Attrs? Trailer;
|
64
|
+
CloseTag = "/" Name space* ">" %closetag;
|
65
|
+
|
66
|
+
SomeTag = '<' (OpenOrSelfTag | CloseTag);
|
67
|
+
|
68
|
+
main := |*
|
69
|
+
SomeTag => {
|
70
|
+
tag = {:prefix=>@prefix, :name=>@starttag, :flavor => @flavor, :attrs => @attrs}
|
71
|
+
@prefix = nil
|
72
|
+
@name = nil
|
73
|
+
@flavor = :tasteless
|
74
|
+
@attrs = {}
|
75
|
+
@nodes << tag << ''
|
76
|
+
fbreak;
|
77
|
+
};
|
78
|
+
any => {
|
79
|
+
@nodes.last << data[p]
|
80
|
+
@tagstart = p
|
81
|
+
};
|
82
|
+
*|;
|
83
|
+
}%%
|
84
|
+
|
85
|
+
module Radius
|
86
|
+
class Scanner
|
87
|
+
def operate(prefix, data)
|
88
|
+
buf = ""
|
89
|
+
csel = ""
|
90
|
+
@prematch = ''
|
91
|
+
@starttag = nil
|
92
|
+
@attrs = {}
|
93
|
+
@flavor = :tasteless
|
94
|
+
@cursor = 0
|
95
|
+
@tagstart = 0
|
96
|
+
@nodes = ['']
|
97
|
+
remainder = data.dup
|
98
|
+
|
99
|
+
until remainder.length == 0
|
100
|
+
p = perform_parse(prefix, remainder)
|
101
|
+
remainder = remainder[p..-1]
|
102
|
+
end
|
103
|
+
|
104
|
+
return @nodes
|
105
|
+
end
|
106
|
+
|
107
|
+
private
|
108
|
+
def perform_parse(prefix, data)
|
109
|
+
stack = []
|
110
|
+
p = 0
|
111
|
+
ts = 0
|
112
|
+
te = 0
|
113
|
+
act = 0
|
114
|
+
eof = data.length
|
115
|
+
|
116
|
+
@prefix = prefix
|
117
|
+
%% write data;
|
118
|
+
%% write init;
|
119
|
+
%% write exec;
|
120
|
+
return p
|
121
|
+
end
|
122
|
+
end
|
123
|
+
end
|
@@ -0,0 +1,71 @@
|
|
1
|
+
module Radius
|
2
|
+
#
|
3
|
+
# A tag binding is passed into each tag definition and contains helper methods for working
|
4
|
+
# with tags. Use it to gain access to the attributes that were passed to the tag, to
|
5
|
+
# render the tag contents, and to do other tasks.
|
6
|
+
#
|
7
|
+
class TagBinding
|
8
|
+
# The Context that the TagBinding is associated with. Used internally. Try not to use
|
9
|
+
# this object directly.
|
10
|
+
attr_reader :context
|
11
|
+
|
12
|
+
# The locals object for the current tag.
|
13
|
+
attr_reader :locals
|
14
|
+
|
15
|
+
# The name of the tag (as used in a template string).
|
16
|
+
attr_reader :name
|
17
|
+
|
18
|
+
# The attributes of the tag. Also aliased as TagBinding#attr.
|
19
|
+
attr_reader :attributes
|
20
|
+
alias :attr :attributes
|
21
|
+
|
22
|
+
# The render block. When called expands the contents of the tag. Use TagBinding#expand
|
23
|
+
# instead.
|
24
|
+
attr_reader :block
|
25
|
+
|
26
|
+
# Creates a new TagBinding object.
|
27
|
+
def initialize(context, locals, name, attributes, block)
|
28
|
+
@context, @locals, @name, @attributes, @block = context, locals, name, attributes, block
|
29
|
+
end
|
30
|
+
|
31
|
+
# Evaluates the current tag and returns the rendered contents.
|
32
|
+
def expand
|
33
|
+
double? ? block.call : ''
|
34
|
+
end
|
35
|
+
|
36
|
+
# Returns true if the current tag is a single tag.
|
37
|
+
def single?
|
38
|
+
block.nil?
|
39
|
+
end
|
40
|
+
|
41
|
+
# Returns true if the current tag is a container tag.
|
42
|
+
def double?
|
43
|
+
not single?
|
44
|
+
end
|
45
|
+
|
46
|
+
# The globals object from which all locals objects ultimately inherit their values.
|
47
|
+
def globals
|
48
|
+
@context.globals
|
49
|
+
end
|
50
|
+
|
51
|
+
# Returns a list of the way tags are nested around the current tag as a string.
|
52
|
+
def nesting
|
53
|
+
@context.current_nesting
|
54
|
+
end
|
55
|
+
|
56
|
+
# Fires off Context#tag_missing for the current tag.
|
57
|
+
def missing!
|
58
|
+
@context.tag_missing(name, attributes, &block)
|
59
|
+
end
|
60
|
+
|
61
|
+
# Renders the tag using the current context .
|
62
|
+
def render(tag, attributes = {}, &block)
|
63
|
+
@context.render_tag(tag, attributes, &block)
|
64
|
+
end
|
65
|
+
|
66
|
+
# Shortcut for accessing tag.attr[key]
|
67
|
+
def [](key)
|
68
|
+
attr[key]
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
@@ -0,0 +1,78 @@
|
|
1
|
+
module Radius
|
2
|
+
module TagDefinitions # :nodoc:
|
3
|
+
class TagFactory # :nodoc:
|
4
|
+
def initialize(context)
|
5
|
+
@context = context
|
6
|
+
end
|
7
|
+
|
8
|
+
def define_tag(name, options, &block)
|
9
|
+
options = prepare_options(name, options)
|
10
|
+
validate_params(name, options, &block)
|
11
|
+
construct_tag_set(name, options, &block)
|
12
|
+
expose_methods_as_tags(name, options)
|
13
|
+
end
|
14
|
+
|
15
|
+
protected
|
16
|
+
|
17
|
+
# Adds the tag definition to the context. Override in subclasses to add additional tags
|
18
|
+
# (child tags) when the tag is created.
|
19
|
+
def construct_tag_set(name, options, &block)
|
20
|
+
if block
|
21
|
+
@context.definitions[name.to_s] = block
|
22
|
+
else
|
23
|
+
lp = last_part(name)
|
24
|
+
@context.define_tag(name) do |tag|
|
25
|
+
if tag.single?
|
26
|
+
options[:for]
|
27
|
+
else
|
28
|
+
tag.locals.send("#{ lp }=", options[:for]) unless options[:for].nil?
|
29
|
+
tag.expand
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
# Normalizes options pased to tag definition. Override in decendants to preform
|
36
|
+
# additional normalization.
|
37
|
+
def prepare_options(name, options)
|
38
|
+
options = Utility.symbolize_keys(options)
|
39
|
+
options[:expose] = expand_array_option(options[:expose])
|
40
|
+
object = options[:for]
|
41
|
+
options[:attributes] = object.respond_to?(:attributes) unless options.has_key? :attributes
|
42
|
+
options[:expose] += object.attributes.keys if options[:attributes]
|
43
|
+
options
|
44
|
+
end
|
45
|
+
|
46
|
+
# Validates parameters passed to tag definition. Override in decendants to add custom
|
47
|
+
# validations.
|
48
|
+
def validate_params(name, options, &block)
|
49
|
+
unless options.has_key? :for
|
50
|
+
raise ArgumentError.new("tag definition must contain a :for option or a block") unless block
|
51
|
+
raise ArgumentError.new("tag definition must contain a :for option when used with the :expose option") unless options[:expose].empty?
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
# Exposes the methods of an object as child tags.
|
56
|
+
def expose_methods_as_tags(name, options)
|
57
|
+
options[:expose].each do |method|
|
58
|
+
tag_name = "#{name}:#{method}"
|
59
|
+
lp = last_part(name)
|
60
|
+
@context.define_tag(tag_name) do |tag|
|
61
|
+
object = tag.locals.send(lp)
|
62
|
+
object.send(method)
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
protected
|
68
|
+
|
69
|
+
def expand_array_option(value)
|
70
|
+
[*value].compact.map { |m| m.to_s.intern }
|
71
|
+
end
|
72
|
+
|
73
|
+
def last_part(name)
|
74
|
+
name.split(':').last
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
module Radius
|
2
|
+
module Utility # :nodoc:
|
3
|
+
def self.symbolize_keys(hash)
|
4
|
+
new_hash = {}
|
5
|
+
hash.keys.each do |k|
|
6
|
+
new_hash[k.to_s.intern] = hash[k]
|
7
|
+
end
|
8
|
+
new_hash
|
9
|
+
end
|
10
|
+
|
11
|
+
def self.impartial_hash_delete(hash, key)
|
12
|
+
string = key.to_s
|
13
|
+
symbol = string.intern
|
14
|
+
value1 = hash.delete(symbol)
|
15
|
+
value2 = hash.delete(string)
|
16
|
+
value1 || value2
|
17
|
+
end
|
18
|
+
|
19
|
+
def self.constantize(camelized_string)
|
20
|
+
raise "invalid constant name `#{camelized_string}'" unless camelized_string.split('::').all? { |part| part =~ /^[A-Za-z]+$/ }
|
21
|
+
Object.module_eval(camelized_string)
|
22
|
+
end
|
23
|
+
|
24
|
+
def self.camelize(underscored_string)
|
25
|
+
string = ''
|
26
|
+
underscored_string.split('_').each { |part| string << part.capitalize }
|
27
|
+
string
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
data/tasks/scan.rake
ADDED
@@ -0,0 +1,27 @@
|
|
1
|
+
namespace :scan do
|
2
|
+
desc 'Generate the parser'
|
3
|
+
task 'build' => ['lib/radius/parser/scan.rb']
|
4
|
+
|
5
|
+
desc 'Generate a PDF state graph from the parser'
|
6
|
+
task 'graph' => ['doc/scan.pdf']
|
7
|
+
|
8
|
+
desc 'turn the scan.rl file into a ruby file'
|
9
|
+
file 'lib/radius/parser/scan.rb' => ['lib/radius/parser/scan.rl'] do |t|
|
10
|
+
cd 'lib/radius/parser' do
|
11
|
+
sh "ragel -R -F1 scan.rl"
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
desc 'pdf of the ragel scanner'
|
16
|
+
file 'doc/scan.pdf' => 'lib/radius/parser/scan.dot' do |t|
|
17
|
+
cd 'lib/radius/parser' do
|
18
|
+
sh "dot -Tpdf -o ../../../doc/scan.pdf scan.dot"
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
file 'lib/radius/parser/scan.dot' => ['lib/radius/parser/scan.rl'] do |t|
|
23
|
+
cd 'lib/radius/parser' do
|
24
|
+
sh "ragel -Vp scan.rl > scan.dot"
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,61 @@
|
|
1
|
+
require File.join(File.dirname(__FILE__), 'test_helper')
|
2
|
+
|
3
|
+
class RadiusContextTest < Test::Unit::TestCase
|
4
|
+
include RadiusTestHelper
|
5
|
+
|
6
|
+
def setup
|
7
|
+
@context = new_context
|
8
|
+
end
|
9
|
+
|
10
|
+
def test_initialize
|
11
|
+
@context = Radius::Context.new
|
12
|
+
end
|
13
|
+
|
14
|
+
def test_initialize_with_block
|
15
|
+
@context = Radius::Context.new do |c|
|
16
|
+
assert_kind_of Radius::Context, c
|
17
|
+
c.define_tag('test') { 'just a test' }
|
18
|
+
end
|
19
|
+
assert_not_equal Hash.new, @context.definitions
|
20
|
+
end
|
21
|
+
|
22
|
+
def test_with
|
23
|
+
got = @context.with do |c|
|
24
|
+
assert_equal @context, c
|
25
|
+
end
|
26
|
+
assert_equal @context, got
|
27
|
+
end
|
28
|
+
|
29
|
+
def test_render_tag
|
30
|
+
define_global_tag "hello" do |tag|
|
31
|
+
"Hello #{tag.attr['name'] || 'World'}!"
|
32
|
+
end
|
33
|
+
assert_render_tag_output 'Hello World!', 'hello'
|
34
|
+
assert_render_tag_output 'Hello John!', 'hello', 'name' => 'John'
|
35
|
+
end
|
36
|
+
|
37
|
+
def test_render_tag__undefined_tag
|
38
|
+
e = assert_raises(Radius::UndefinedTagError) { @context.render_tag('undefined_tag') }
|
39
|
+
assert_equal "undefined tag `undefined_tag'", e.message
|
40
|
+
end
|
41
|
+
|
42
|
+
def test_tag_missing
|
43
|
+
class << @context
|
44
|
+
def tag_missing(tag, attr, &block)
|
45
|
+
"undefined tag `#{tag}' with attributes #{attr.inspect}"
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
text = ''
|
50
|
+
expected = %{undefined tag `undefined_tag' with attributes {"cool"=>"beans"}}
|
51
|
+
assert_nothing_raised { text = @context.render_tag('undefined_tag', 'cool' => 'beans') }
|
52
|
+
assert_equal expected, text
|
53
|
+
end
|
54
|
+
|
55
|
+
private
|
56
|
+
|
57
|
+
def assert_render_tag_output(output, *render_tag_params)
|
58
|
+
assert_equal output, @context.render_tag(*render_tag_params)
|
59
|
+
end
|
60
|
+
|
61
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
require 'test/unit'
|
2
|
+
require 'radius'
|
3
|
+
|
4
|
+
class MultithreadTest < Test::Unit::TestCase
|
5
|
+
|
6
|
+
def setup
|
7
|
+
Thread.abort_on_exception
|
8
|
+
@context = Radius::Context.new do |c|
|
9
|
+
c.define_tag('thread') do |tag|
|
10
|
+
"#{tag.locals.thread_id} / #{tag.globals.object_id}"
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
def test_runs_multithreaded
|
16
|
+
threads = []
|
17
|
+
1000.times do |t|
|
18
|
+
threads << Thread.new do
|
19
|
+
parser = Radius::Parser.new(@context, :tag_prefix => 'r')
|
20
|
+
parser.context.globals.thread_id = Thread.current.object_id
|
21
|
+
expected = "#{Thread.current.object_id} / #{parser.context.globals.object_id}"
|
22
|
+
actual = parser.parse('<r:thread />')
|
23
|
+
assert_equal expected, actual
|
24
|
+
end
|
25
|
+
end
|
26
|
+
threads.each{|t| t.join }
|
27
|
+
end
|
28
|
+
|
29
|
+
end
|
data/test/parser_test.rb
ADDED
@@ -0,0 +1,302 @@
|
|
1
|
+
require File.join(File.dirname(__FILE__), 'test_helper')
|
2
|
+
|
3
|
+
class RadiusParserTest < Test::Unit::TestCase
|
4
|
+
include RadiusTestHelper
|
5
|
+
|
6
|
+
def setup
|
7
|
+
@context = new_context
|
8
|
+
@parser = Radius::Parser.new(@context, :tag_prefix => 'r')
|
9
|
+
end
|
10
|
+
|
11
|
+
def test_initialize
|
12
|
+
@parser = Radius::Parser.new
|
13
|
+
assert_kind_of Radius::Context, @parser.context
|
14
|
+
end
|
15
|
+
|
16
|
+
def test_initialize_with_params
|
17
|
+
@parser = Radius::Parser.new(TestContext.new)
|
18
|
+
assert_kind_of TestContext, @parser.context
|
19
|
+
|
20
|
+
@parser = Radius::Parser.new(:context => TestContext.new)
|
21
|
+
assert_kind_of TestContext, @parser.context
|
22
|
+
|
23
|
+
@parser = Radius::Parser.new('context' => TestContext.new)
|
24
|
+
assert_kind_of TestContext, @parser.context
|
25
|
+
|
26
|
+
@parser = Radius::Parser.new(:tag_prefix => 'r')
|
27
|
+
assert_kind_of Radius::Context, @parser.context
|
28
|
+
assert_equal 'r', @parser.tag_prefix
|
29
|
+
|
30
|
+
@parser = Radius::Parser.new(TestContext.new, :tag_prefix => 'r')
|
31
|
+
assert_kind_of TestContext, @parser.context
|
32
|
+
assert_equal 'r', @parser.tag_prefix
|
33
|
+
end
|
34
|
+
|
35
|
+
def test_parse_individual_tags_and_parameters
|
36
|
+
define_tag "add" do |tag|
|
37
|
+
tag.attr["param1"].to_i + tag.attr["param2"].to_i
|
38
|
+
end
|
39
|
+
assert_parse_output "<3>", %{<<r:add param1="1" param2='2'/>>}
|
40
|
+
end
|
41
|
+
|
42
|
+
def test_parse_attributes
|
43
|
+
attributes = %{{"a"=>"1", "b"=>"2", "c"=>"3", "d"=>"'"}}
|
44
|
+
assert_parse_output attributes, %{<r:attr a="1" b='2'c="3"d="'" />}
|
45
|
+
assert_parse_output attributes, %{<r:attr a="1" b='2'c="3"d="'"></r:attr>}
|
46
|
+
end
|
47
|
+
|
48
|
+
def test_parse_attributes_with_slashes_or_angle_brackets
|
49
|
+
slash = %{{"slash"=>"/"}}
|
50
|
+
angle = %{{"angle"=>">"}}
|
51
|
+
assert_parse_output slash, %{<r:attr slash="/"></r:attr>}
|
52
|
+
assert_parse_output slash, %{<r:attr slash="/"><r:attr /></r:attr>}
|
53
|
+
assert_parse_output angle, %{<r:attr angle=">"></r:attr>}
|
54
|
+
end
|
55
|
+
|
56
|
+
def test_parse_quotes
|
57
|
+
assert_parse_output "test []", %{<r:echo value="test" /> <r:wrap attr="test"></r:wrap>}
|
58
|
+
end
|
59
|
+
|
60
|
+
def test_things_that_should_be_left_alone
|
61
|
+
[
|
62
|
+
%{ test="2"="4" },
|
63
|
+
%{="2" }
|
64
|
+
].each do |middle|
|
65
|
+
assert_parsed_is_unchanged "<r:attr#{middle}/>"
|
66
|
+
assert_parsed_is_unchanged "<r:attr#{middle}>"
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
def test_tags_inside_html_tags
|
71
|
+
assert_parse_output %{<div class="xzibit">tags in yo tags</div>},
|
72
|
+
%{<div class="<r:reverse>tibizx</r:reverse>">tags in yo tags</div>}
|
73
|
+
end
|
74
|
+
|
75
|
+
def test_parse_result_is_always_a_string
|
76
|
+
define_tag("twelve") { 12 }
|
77
|
+
assert_parse_output "12", "<r:twelve />"
|
78
|
+
end
|
79
|
+
|
80
|
+
def test_parse_double_tags
|
81
|
+
assert_parse_output "test".reverse, "<r:reverse>test</r:reverse>"
|
82
|
+
assert_parse_output "tset TEST", "<r:reverse>test</r:reverse> <r:capitalize>test</r:capitalize>"
|
83
|
+
end
|
84
|
+
|
85
|
+
def test_parse_tag_nesting
|
86
|
+
define_tag("parent", :for => '')
|
87
|
+
define_tag("parent:child", :for => '')
|
88
|
+
define_tag("extra", :for => '')
|
89
|
+
define_tag("nesting") { |tag| tag.nesting }
|
90
|
+
define_tag("extra:nesting") { |tag| tag.nesting.gsub(':', ' > ') }
|
91
|
+
define_tag("parent:child:nesting") { |tag| tag.nesting.gsub(':', ' * ') }
|
92
|
+
assert_parse_output "nesting", "<r:nesting />"
|
93
|
+
assert_parse_output "parent:nesting", "<r:parent:nesting />"
|
94
|
+
assert_parse_output "extra > nesting", "<r:extra:nesting />"
|
95
|
+
assert_parse_output "parent * child * nesting", "<r:parent:child:nesting />"
|
96
|
+
assert_parse_output "parent > extra > nesting", "<r:parent:extra:nesting />"
|
97
|
+
assert_parse_output "parent > child > extra > nesting", "<r:parent:child:extra:nesting />"
|
98
|
+
assert_parse_output "parent * extra * child * nesting", "<r:parent:extra:child:nesting />"
|
99
|
+
assert_parse_output "parent > extra > child > extra > nesting", "<r:parent:extra:child:extra:nesting />"
|
100
|
+
assert_parse_output "parent > extra > child > extra > nesting", "<r:parent><r:extra><r:child><r:extra><r:nesting /></r:extra></r:child></r:extra></r:parent>"
|
101
|
+
assert_parse_output "extra * parent * child * nesting", "<r:extra:parent:child:nesting />"
|
102
|
+
assert_parse_output "extra > parent > nesting", "<r:extra><r:parent:nesting /></r:extra>"
|
103
|
+
assert_parse_output "extra * parent * child * nesting", "<r:extra:parent><r:child:nesting /></r:extra:parent>"
|
104
|
+
assert_raises(Radius::UndefinedTagError) { @parser.parse("<r:child />") }
|
105
|
+
end
|
106
|
+
def test_parse_tag_nesting_2
|
107
|
+
define_tag("parent", :for => '')
|
108
|
+
define_tag("parent:child", :for => '')
|
109
|
+
define_tag("content") { |tag| tag.nesting }
|
110
|
+
assert_parse_output 'parent:child:content', '<r:parent><r:child:content /></r:parent>'
|
111
|
+
end
|
112
|
+
|
113
|
+
def test_parse_tag__binding_do_missing
|
114
|
+
define_tag 'test' do |tag|
|
115
|
+
tag.missing!
|
116
|
+
end
|
117
|
+
e = assert_raises(Radius::UndefinedTagError) { @parser.parse("<r:test />") }
|
118
|
+
assert_equal "undefined tag `test'", e.message
|
119
|
+
end
|
120
|
+
|
121
|
+
def test_parse_chirpy_bird
|
122
|
+
# :> chirp chirp
|
123
|
+
assert_parse_output "<:", "<:"
|
124
|
+
end
|
125
|
+
|
126
|
+
def test_parse_tag__binding_render_tag
|
127
|
+
define_tag('test') { |tag| "Hello #{tag.attr['name']}!" }
|
128
|
+
define_tag('hello') { |tag| tag.render('test', tag.attr) }
|
129
|
+
assert_parse_output 'Hello John!', '<r:hello name="John" />'
|
130
|
+
end
|
131
|
+
|
132
|
+
def test_accessing_tag_attributes_through_tag_indexer
|
133
|
+
define_tag('test') { |tag| "Hello #{tag['name']}!" }
|
134
|
+
assert_parse_output 'Hello John!', '<r:test name="John" />'
|
135
|
+
end
|
136
|
+
|
137
|
+
def test_parse_tag__binding_render_tag_with_block
|
138
|
+
define_tag('test') { |tag| "Hello #{tag.expand}!" }
|
139
|
+
define_tag('hello') { |tag| tag.render('test') { tag.expand } }
|
140
|
+
assert_parse_output 'Hello John!', '<r:hello>John</r:hello>'
|
141
|
+
end
|
142
|
+
|
143
|
+
def test_tag_locals
|
144
|
+
define_tag "outer" do |tag|
|
145
|
+
tag.locals.var = 'outer'
|
146
|
+
tag.expand
|
147
|
+
end
|
148
|
+
define_tag "outer:inner" do |tag|
|
149
|
+
tag.locals.var = 'inner'
|
150
|
+
tag.expand
|
151
|
+
end
|
152
|
+
define_tag "outer:var" do |tag|
|
153
|
+
tag.locals.var
|
154
|
+
end
|
155
|
+
assert_parse_output 'outer', "<r:outer><r:var /></r:outer>"
|
156
|
+
assert_parse_output 'outer:inner:outer', "<r:outer><r:var />:<r:inner><r:var /></r:inner>:<r:var /></r:outer>"
|
157
|
+
assert_parse_output 'outer:inner:outer:inner:outer', "<r:outer><r:var />:<r:inner><r:var />:<r:outer><r:var /></r:outer>:<r:var /></r:inner>:<r:var /></r:outer>"
|
158
|
+
assert_parse_output 'outer', "<r:outer:var />"
|
159
|
+
end
|
160
|
+
|
161
|
+
def test_tag_globals
|
162
|
+
define_tag "set" do |tag|
|
163
|
+
tag.globals.var = tag.attr['value']
|
164
|
+
''
|
165
|
+
end
|
166
|
+
define_tag "var" do |tag|
|
167
|
+
tag.globals.var
|
168
|
+
end
|
169
|
+
assert_parse_output " true false", %{<r:var /> <r:set value="true" /> <r:var /> <r:set value="false" /> <r:var />}
|
170
|
+
end
|
171
|
+
|
172
|
+
def test_parse_loops
|
173
|
+
@item = nil
|
174
|
+
define_tag "each" do |tag|
|
175
|
+
result = []
|
176
|
+
["Larry", "Moe", "Curly"].each do |item|
|
177
|
+
tag.locals.item = item
|
178
|
+
result << tag.expand
|
179
|
+
end
|
180
|
+
result.join(tag.attr["between"] || "")
|
181
|
+
end
|
182
|
+
define_tag "each:item" do |tag|
|
183
|
+
tag.locals.item
|
184
|
+
end
|
185
|
+
assert_parse_output %{Three Stooges: "Larry", "Moe", "Curly"}, %{Three Stooges: <r:each between=", ">"<r:item />"</r:each>}
|
186
|
+
end
|
187
|
+
|
188
|
+
def test_parse_speed
|
189
|
+
define_tag "set" do |tag|
|
190
|
+
tag.globals.var = tag.attr['value']
|
191
|
+
''
|
192
|
+
end
|
193
|
+
define_tag "var" do |tag|
|
194
|
+
tag.globals.var
|
195
|
+
end
|
196
|
+
parts = %w{decima nobis augue at facer processus commodo legentis odio lectorum dolore nulla esse lius qui nonummy ullamcorper erat ii notare}
|
197
|
+
multiplier = parts.map{|p| "#{p}=\"#{rand}\""}.join(' ')
|
198
|
+
assert_nothing_raised do
|
199
|
+
Timeout.timeout(10) do
|
200
|
+
assert_parse_output " false", %{<r:set value="false" #{multiplier} /> <r:var />}
|
201
|
+
end
|
202
|
+
end
|
203
|
+
end
|
204
|
+
|
205
|
+
def test_tag_option_for
|
206
|
+
define_tag 'fun', :for => 'just for kicks'
|
207
|
+
assert_parse_output 'just for kicks', '<r:fun />'
|
208
|
+
end
|
209
|
+
|
210
|
+
def test_tag_expose_option
|
211
|
+
define_tag 'user', :for => users.first, :expose => ['name', :age]
|
212
|
+
assert_parse_output 'John', '<r:user:name />'
|
213
|
+
assert_parse_output '25', '<r:user><r:age /></r:user>'
|
214
|
+
e = assert_raises(Radius::UndefinedTagError) { @parser.parse "<r:user:email />" }
|
215
|
+
assert_equal "undefined tag `email'", e.message
|
216
|
+
end
|
217
|
+
|
218
|
+
def test_tag_expose_attributes_option_on_by_default
|
219
|
+
define_tag 'user', :for => user_with_attributes
|
220
|
+
assert_parse_output 'John', '<r:user:name />'
|
221
|
+
end
|
222
|
+
def test_tag_expose_attributes_set_to_false
|
223
|
+
define_tag 'user_without_attributes', :for => user_with_attributes, :attributes => false
|
224
|
+
assert_raises(Radius::UndefinedTagError) { @parser.parse "<r:user_without_attributes:name />" }
|
225
|
+
end
|
226
|
+
|
227
|
+
def test_tag_options_must_contain_a_for_option_if_methods_are_exposed
|
228
|
+
e = assert_raises(ArgumentError) { define_tag('fun', :expose => :today) { 'test' } }
|
229
|
+
assert_equal "tag definition must contain a :for option when used with the :expose option", e.message
|
230
|
+
end
|
231
|
+
|
232
|
+
def test_parse_fail_on_missing_end_tag
|
233
|
+
assert_raises(Radius::MissingEndTagError) { @parser.parse("<r:reverse>") }
|
234
|
+
end
|
235
|
+
|
236
|
+
def test_parse_fail_on_wrong_end_tag
|
237
|
+
assert_raises(Radius::WrongEndTagError) { @parser.parse("<r:reverse><r:capitalize></r:reverse>") }
|
238
|
+
end
|
239
|
+
|
240
|
+
def test_parse_with_default_tag_prefix
|
241
|
+
@parser = Radius::Parser.new(@context)
|
242
|
+
define_tag("hello") { |tag| "Hello world!" }
|
243
|
+
assert_equal "<p>Hello world!</p>", @parser.parse('<p><radius:hello /></p>')
|
244
|
+
end
|
245
|
+
|
246
|
+
def test_parse_with_other_radius_like_tags
|
247
|
+
@parser = Radius::Parser.new(@context, :tag_prefix => "ralph")
|
248
|
+
define_tag('hello') { "hello" }
|
249
|
+
assert_equal "<r:ralph:hello />", @parser.parse("<r:ralph:hello />")
|
250
|
+
end
|
251
|
+
|
252
|
+
def test_copyin_global_values
|
253
|
+
@context.globals.foo = 'bar'
|
254
|
+
assert_equal 'bar', Radius::Parser.new(@context).context.globals.foo
|
255
|
+
end
|
256
|
+
|
257
|
+
def test_does_not_pollute_copied_globals
|
258
|
+
@context.globals.foo = 'bar'
|
259
|
+
parser = Radius::Parser.new(@context)
|
260
|
+
parser.context.globals.foo = '[baz]'
|
261
|
+
assert_equal 'bar', @context.globals.foo
|
262
|
+
end
|
263
|
+
|
264
|
+
protected
|
265
|
+
|
266
|
+
def assert_parse_output(output, input, message = nil)
|
267
|
+
r = @parser.parse(input)
|
268
|
+
assert_equal(output, r, message)
|
269
|
+
end
|
270
|
+
|
271
|
+
def assert_parsed_is_unchanged(something)
|
272
|
+
assert_parse_output something, something
|
273
|
+
end
|
274
|
+
|
275
|
+
class User
|
276
|
+
attr_accessor :name, :age, :email, :friend
|
277
|
+
def initialize(name, age, email)
|
278
|
+
@name, @age, @email = name, age, email
|
279
|
+
end
|
280
|
+
def <=>(other)
|
281
|
+
name <=> other.name
|
282
|
+
end
|
283
|
+
end
|
284
|
+
|
285
|
+
class UserWithAttributes < User
|
286
|
+
def attributes
|
287
|
+
{ :name => name, :age => age, :email => email }
|
288
|
+
end
|
289
|
+
end
|
290
|
+
|
291
|
+
def users
|
292
|
+
[
|
293
|
+
User.new('John', 25, 'test@johnwlong.com'),
|
294
|
+
User.new('James', 27, 'test@jameslong.com')
|
295
|
+
]
|
296
|
+
end
|
297
|
+
|
298
|
+
def user_with_attributes
|
299
|
+
UserWithAttributes.new('John', 25, 'test@johnwlong.com')
|
300
|
+
end
|
301
|
+
|
302
|
+
end
|