radius19-radiant 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,31 @@
1
+ module Radius
2
+ class DelegatingOpenStruct # :nodoc:
3
+ attr_accessor :object
4
+
5
+ def initialize(object = nil)
6
+ @object = object
7
+ @hash = {}
8
+ end
9
+
10
+ def method_missing(method, *args, &block)
11
+ symbol = (method.to_s =~ /^(.*?)=$/) ? $1.intern : method
12
+ if (0..1).include?(args.size)
13
+ if args.size == 1
14
+ @hash[symbol] = args.first
15
+ else
16
+ if @hash.has_key?(symbol)
17
+ @hash[symbol]
18
+ else
19
+ unless object.nil?
20
+ @object.send(method, *args, &block)
21
+ else
22
+ nil
23
+ end
24
+ end
25
+ end
26
+ else
27
+ super
28
+ end
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,24 @@
1
+ module Radius
2
+
3
+ # Abstract base class for all parsing errors.
4
+ class ParseError < StandardError
5
+ end
6
+
7
+ # Occurs when Parser cannot find an end tag for a given tag in a template or when
8
+ # tags are miss-matched in a template.
9
+ class MissingEndTagError < ParseError
10
+ # Create a new MissingEndTagError object for +tag_name+.
11
+ def initialize(tag_name)
12
+ super("end tag not found for start tag `#{tag_name}'")
13
+ end
14
+ end
15
+
16
+ # Occurs when Context#render_tag cannot find the specified tag on a Context.
17
+ class UndefinedTagError < ParseError
18
+ # Create a new UndefinedTagError object for +tag_name+.
19
+ def initialize(tag_name)
20
+ super("undefined tag `#{tag_name}'")
21
+ end
22
+ end
23
+
24
+ end
@@ -0,0 +1,95 @@
1
+ module Radius
2
+ #
3
+ # The Radius parser. Initialize a parser with a Context object that
4
+ # defines how tags should be expanded. See the QUICKSTART[link:files/QUICKSTART.html]
5
+ # for a detailed explaination of its usage.
6
+ #
7
+ class Parser
8
+ # The Context object used to expand template tags.
9
+ attr_accessor :context
10
+
11
+ # The string that prefixes all tags that are expanded by a parser
12
+ # (the part in the tag name before the first colon).
13
+ attr_accessor :tag_prefix
14
+
15
+ # Creates a new parser object initialized with a Context.
16
+ def initialize(context = Context.new, options = {})
17
+ if context.kind_of?(Hash) and options.empty?
18
+ options = context
19
+ context = options[:context] || options['context'] || Context.new
20
+ end
21
+ options = Util.symbolize_keys(options)
22
+ @context = context
23
+ @tag_prefix = options[:tag_prefix]
24
+ end
25
+
26
+ # Parses string for tags, expands them, and returns the result.
27
+ def parse(string)
28
+ @stack = [ParseContainerTag.new { |t| Util.recurring_array_to_s(t.contents) }]
29
+ pre_parse(string)
30
+ @stack.last.to_s
31
+ end
32
+
33
+ protected
34
+
35
+ def pre_parse(text) # :nodoc:
36
+ re = %r{<#{@tag_prefix}:([\w:]+?)(\s+(?:\w+\s*=\s*(?:"[^"]*?"|'[^']*?')\s*)*|)>|</#{@tag_prefix}:([\w:]+?)\s*>}
37
+ if md = re.match(text)
38
+ start_tag, attr, end_tag = $1, $2, $3
39
+ @stack.last.contents << ParseTag.new { parse_individual(md.pre_match) }
40
+ remaining = md.post_match
41
+ if start_tag
42
+ parse_start_tag(start_tag, attr, remaining)
43
+ else
44
+ parse_end_tag(end_tag, remaining)
45
+ end
46
+ else
47
+ if @stack.length == 1
48
+ @stack.last.contents << ParseTag.new { parse_individual(text) }
49
+ else
50
+ raise MissingEndTagError.new(@stack.last.name)
51
+ end
52
+ end
53
+ end
54
+
55
+ def parse_start_tag(start_tag, attr, remaining) # :nodoc:
56
+ @stack.push(ParseContainerTag.new(start_tag, parse_attributes(attr)))
57
+ pre_parse(remaining)
58
+ end
59
+
60
+ def parse_end_tag(end_tag, remaining) # :nodoc:
61
+ popped = @stack.pop
62
+ if popped.name == end_tag
63
+ popped.on_parse do |t|
64
+ @context.render_tag(popped.name, popped.attributes) { Util.recurring_array_to_s(t.contents) }
65
+ end
66
+ tag = @stack.last
67
+ tag.contents << popped
68
+ pre_parse(remaining)
69
+ else
70
+ raise MissingEndTagError.new(popped.name)
71
+ end
72
+ end
73
+
74
+ def parse_individual(text) # :nodoc:
75
+ re = %r{<#{@tag_prefix}:([\w:]+?)(\s+(?:\w+\s*=\s*(?:"[^"]*?"|'[^']*?')\s*)*|)/>}
76
+ if md = re.match(text)
77
+ attr = parse_attributes($2)
78
+ replace = @context.render_tag($1, attr)
79
+ md.pre_match + replace + parse_individual(md.post_match)
80
+ else
81
+ text || ''
82
+ end
83
+ end
84
+
85
+ def parse_attributes(text) # :nodoc:
86
+ attr = {}
87
+ re = /(\w+?)\s*=\s*('|")(.*?)\2/
88
+ while md = re.match(text)
89
+ attr[$1] = $3
90
+ text = md.post_match
91
+ end
92
+ attr
93
+ end
94
+ end
95
+ end
@@ -0,0 +1,23 @@
1
+ class ParseTag # :nodoc:
2
+ def initialize(&b)
3
+ @block = b
4
+ end
5
+
6
+ def on_parse(&b)
7
+ @block = b
8
+ end
9
+
10
+ def to_s
11
+ @block.call(self)
12
+ end
13
+ end
14
+
15
+ class ParseContainerTag < ParseTag # :nodoc:
16
+ attr_accessor :name, :attributes, :contents
17
+
18
+ def initialize(name = "", attributes = {}, contents = [], &b)
19
+ @name, @attributes, @contents = name, attributes, contents
20
+ super(&b)
21
+ end
22
+ end
23
+
@@ -0,0 +1,66 @@
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
+ end
66
+ 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
+ # Normalizes options pased to tag definition. Override in decendants to preform
18
+ # additional normalization.
19
+ def prepare_options(name, options)
20
+ options = Util.symbolize_keys(options)
21
+ options[:expose] = expand_array_option(options[:expose])
22
+ object = options[:for]
23
+ options[:attributes] = object.respond_to?(:attributes) unless options.has_key? :attributes
24
+ options[:expose] += object.attributes.keys if options[:attributes]
25
+ options
26
+ end
27
+
28
+ # Validates parameters passed to tag definition. Override in decendants to add custom
29
+ # validations.
30
+ def validate_params(name, options, &block)
31
+ unless options.has_key? :for
32
+ raise ArgumentError.new("tag definition must contain a :for option or a block") unless block
33
+ raise ArgumentError.new("tag definition must contain a :for option when used with the :expose option") unless options[:expose].empty?
34
+ end
35
+ end
36
+
37
+ # Adds the tag definition to the context. Override in subclasses to add additional tags
38
+ # (child tags) when the tag is created.
39
+ def construct_tag_set(name, options, &block)
40
+ if block
41
+ @context.definitions[name.to_s] = block
42
+ else
43
+ lp = last_part(name)
44
+ @context.define_tag(name) do |tag|
45
+ if tag.single?
46
+ options[:for]
47
+ else
48
+ tag.locals.send("#{ lp }=", options[:for]) unless options[:for].nil?
49
+ tag.expand
50
+ end
51
+ end
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,34 @@
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
+
30
+ def self.recurring_array_to_s(ary)
31
+ ary.map{|x| x.is_a?(Array) ? recurring_array_to_s(x) : x.to_s }.join
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,24 @@
1
+ #--
2
+ # Copyright (c) 2006, John W. Long
3
+ #
4
+ # Permission is hereby granted, free of charge, to any person obtaining a copy of this
5
+ # software and associated documentation files (the "Software"), to deal in the Software
6
+ # without restriction, including without limitation the rights to use, copy, modify,
7
+ # merge, publish, distribute, sublicense, and/or sell copies of the Software, and to
8
+ # permit persons to whom the Software is furnished to do so, subject to the following
9
+ # conditions:
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
+
22
+ dir = File.join(File.dirname(__FILE__), 'radius')
23
+ require_files = %w{errors tagdefs dostruct tagbinding context parsetag parser util}
24
+ require_files.each {|f| require File.join(dir, f)}
@@ -0,0 +1,61 @@
1
+ require 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