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.
@@ -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
@@ -0,0 +1,14 @@
1
+ module Radius
2
+ module Version
3
+ Major = '1'
4
+ Minor = '0'
5
+ Tiny = '0'
6
+
7
+ class << self
8
+ def to_s
9
+ [Major, Minor, Tiny].join('.')
10
+ end
11
+ alias :to_str :to_s
12
+ end
13
+ end
14
+ end
@@ -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
@@ -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