bkerley-radius 0.6.1

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.
@@ -0,0 +1,122 @@
1
+ %%{
2
+ machine parser;
3
+
4
+
5
+ action _prefix { mark_pfx = p }
6
+ action prefix {
7
+ if data[mark_pfx..p-1] != @prefix
8
+ fnext Closeout;
9
+ end
10
+ }
11
+ action _starttag { mark_stg = p }
12
+ action starttag { @starttag = data[mark_stg..p-1] }
13
+ action _attr { mark_attr = p }
14
+ action attr {
15
+ @attrs[@nat] = @vat
16
+ }
17
+
18
+ action prematch {
19
+ @prematch_end = p
20
+ @prematch = data[0..p] if p > 0
21
+ }
22
+
23
+ action _nameattr { mark_nat = p }
24
+ action nameattr { @nat = data[mark_nat..p-1] }
25
+ action _valattr { mark_vat = p }
26
+ action valattr { @vat = data[mark_vat..p-1] }
27
+
28
+ action opentag { @flavor = :open }
29
+ action selftag { @flavor = :self }
30
+ action closetag { @flavor = :close }
31
+
32
+ action stopparse {
33
+ @cursor = p;
34
+ fbreak;
35
+ }
36
+
37
+
38
+ Closeout := empty;
39
+
40
+ # words
41
+ PrefixChar = [\-A-Za-z0-9._?] ;
42
+ NameChar = [\-A-Za-z0-9._:?] ;
43
+ TagName = NameChar+ >_starttag %starttag;
44
+ Prefix = PrefixChar+ >_prefix %prefix;
45
+
46
+ Name = Prefix ":" TagName;
47
+
48
+ NameAttr = NameChar+ >_nameattr %nameattr;
49
+ Q1Char = ( "\\\'" | [^'] ) ;
50
+ Q1Attr = Q1Char* >_valattr %valattr;
51
+ Q2Char = ( "\\\"" | [^"] ) ;
52
+ Q2Attr = Q2Char* >_valattr %valattr;
53
+
54
+ Attr = NameAttr space* "=" space* ('"' Q2Attr '"' | "'" Q1Attr "'") space* >_attr %attr;
55
+ Attrs = (space+ Attr* | empty);
56
+
57
+ CloseTrailer = "/>" %selftag;
58
+ OpenTrailer = ">" %opentag;
59
+
60
+ Trailer = (OpenTrailer | CloseTrailer);
61
+
62
+ OpenOrSelfTag = Name Attrs? Trailer;
63
+ CloseTag = "/" Name space* ">" %closetag;
64
+
65
+ SomeTag = '<' (OpenOrSelfTag | CloseTag);
66
+
67
+ main := |*
68
+ SomeTag => {
69
+ tag = {:prefix=>@prefix, :name=>@starttag, :flavor => @flavor, :attrs => @attrs}
70
+ @prefix = nil
71
+ @name = nil
72
+ @flavor = :tasteless
73
+ @attrs = {}
74
+ @nodes << tag << ''
75
+ fbreak;
76
+ };
77
+ any => {
78
+ @nodes.last << data[p]
79
+ @tagstart = p
80
+ };
81
+ *|;
82
+ }%%
83
+
84
+ module Radius
85
+ class Scanner
86
+ def self.operate(prefix, data)
87
+ buf = ""
88
+ csel = ""
89
+ @prematch = ''
90
+ @starttag = nil
91
+ @attrs = {}
92
+ @flavor = :tasteless
93
+ @cursor = 0
94
+ @tagstart = 0
95
+ @nodes = ['']
96
+ remainder = data.dup
97
+
98
+ until remainder.length == 0
99
+ p = perform_parse(prefix, remainder)
100
+ remainder = remainder[p..-1]
101
+ end
102
+
103
+ return @nodes
104
+ end
105
+
106
+ private
107
+ def self.perform_parse(prefix, data)
108
+ stack = []
109
+ p = 0
110
+ ts = 0
111
+ te = 0
112
+ act = 0
113
+ eof = data.length
114
+
115
+ @prefix = prefix
116
+ %% write data;
117
+ %% write init;
118
+ %% write exec;
119
+ return p
120
+ end
121
+ end
122
+ end
@@ -0,0 +1,24 @@
1
+ module Radius
2
+ class ParseTag # :nodoc:
3
+ def initialize(&b)
4
+ @block = b
5
+ end
6
+
7
+ def on_parse(&b)
8
+ @block = b
9
+ end
10
+
11
+ def to_s
12
+ @block.call(self)
13
+ end
14
+ end
15
+
16
+ class ParseContainerTag < ParseTag # :nodoc:
17
+ attr_accessor :name, :attributes, :contents
18
+
19
+ def initialize(name = "", attributes = {}, contents = [], &b)
20
+ @name, @attributes, @contents = name, attributes, contents
21
+ super(&b)
22
+ end
23
+ end
24
+ 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 = Util.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 Util # :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 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_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,278 @@
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
+ protected
241
+
242
+ def assert_parse_output(output, input, message = nil)
243
+ r = @parser.parse(input)
244
+ assert_equal(output, r, message)
245
+ end
246
+
247
+ def assert_parsed_is_unchanged(something)
248
+ assert_parse_output something, something
249
+ end
250
+
251
+ class User
252
+ attr_accessor :name, :age, :email, :friend
253
+ def initialize(name, age, email)
254
+ @name, @age, @email = name, age, email
255
+ end
256
+ def <=>(other)
257
+ name <=> other.name
258
+ end
259
+ end
260
+
261
+ class UserWithAttributes < User
262
+ def attributes
263
+ { :name => name, :age => age, :email => email }
264
+ end
265
+ end
266
+
267
+ def users
268
+ [
269
+ User.new('John', 25, 'test@johnwlong.com'),
270
+ User.new('James', 27, 'test@jameslong.com')
271
+ ]
272
+ end
273
+
274
+ def user_with_attributes
275
+ UserWithAttributes.new('John', 25, 'test@johnwlong.com')
276
+ end
277
+
278
+ end