diff --git a/lib/radius/parser/mixed_scanner.rb b/lib/radius/parser/mixed_scanner.rb new file mode 100644 index 0000000..d157316 --- /dev/null +++ b/lib/radius/parser/mixed_scanner.rb @@ -0,0 +1,56 @@ +module Radius + class MixedScanner < Radius::Scanner + + def scanner_regex(prefix = nil) + # allow for {prefix:tag}{/prefix:tag} and {tag} style syntax + %r{<#{prefix}:([\w:]+?)(\s+(?:\w+\s*=\s*(?:"[^"]*?"|'[^']*?')\s*)*|)(\/?)>|<\/#{prefix}:([\w:]+?)\s*>|\{#{prefix}:([\w:]+?)(\s+(?:\w+\s*=\s*(?:"[^"]*?"|'[^']*?')\s*)*|)(\/?)\}|\{\/#{prefix}:([\w:]+?)\s*\}|\{\s*([\w:]+?)(\s+(?:\w+\s*=\s*(?:"[^"]*?"|'[^']*?')\s*)*|)\}} + end + + def operate(prefix, data) + data = Radius::OrdString.new data + @nodes = [''] + + re = scanner_regex(prefix) + if md = re.match(data) + remainder = '' + while md + start_tag, attributes, self_enclosed, end_tag = $1, $2, $3, $4 + + flavor = self_enclosed == '/' ? :self : (start_tag ? :open : :close) + + # if {prefix:tag}..{/prefix:tag} style syntax + if $5 or $8 + start_tag, attributes, self_enclosed, end_tag = $5, $6, $7, $8 + flavor = self_enclosed == '/' ? :self : (start_tag ? :open : :close) + end + + # if {tag} style syntax without prefix and end tags + if $9 + start_tag = $9 + attributes = $10 + flavor = :self + end + + # save the part before the current match as a string node + @nodes << md.pre_match + + # save the tag that was found as a tag hash node + @nodes << {:prefix=>prefix, :name=>(start_tag || end_tag), :flavor => flavor, :attrs => parse_attributes(attributes)} + + # remember the part after the current match + remainder = md.post_match + + # see if we find another tag in the remaining string + md = re.match(md.post_match) + end + + # add the last remaining string after the last tag that was found as a string node + @nodes << remainder + else + @nodes << data + end + + return @nodes + end + end +end \ No newline at end of file diff --git a/test/mixed_test.rb b/test/mixed_test.rb new file mode 100644 index 0000000..5e4d3c3 --- /dev/null +++ b/test/mixed_test.rb @@ -0,0 +1,324 @@ +require File.expand_path(File.dirname(__FILE__) + '/test_helper') +require 'radius/parser/mixed_scanner' + +class RadiusMixedTest < Test::Unit::TestCase + include RadiusTestHelper + + def setup + @context = new_context + @parser = Radius::Parser.new(@context, :tag_prefix => 'r',:scanner => Radius::MixedScanner.new) + end + + def test_initialize + @parser = Radius::Parser.new + assert_kind_of Radius::Context, @parser.context + end + + def test_sane_scanner_default + assert !Radius::Parser.new.scanner.is_a?(Radius::MixedScanner) + end + + def test_initialize_with_params + @parser = Radius::Parser.new(:scanner => Radius::MixedScanner.new) + assert_kind_of Radius::MixedScanner, @parser.scanner + end + + + def test_parse_individual_tags_and_parameters + define_tag "add" do |tag| + tag.attr["param1"].to_i + tag.attr["param2"].to_i + end + assert_parse_output "<3>", %{<>} + assert_parse_output "{3}", %[{{r:add param1="1" param2='2'/}}] + assert_parse_output "{3}", %[{{add param1="1" param2='2'}}] + end + + def test_parse_attributes + attributes = %{{"a"=>"1", "b"=>"2", "c"=>"3", "d"=>"'"}} + assert_parse_output attributes, %{} + assert_parse_output attributes, %{} + assert_parse_output attributes, %[{r:attr a="1" b='2'c="3"d="'" /}] + assert_parse_output attributes, %[{r:attr a="1" b='2'c="3"d="'"}{/r:attr}] + assert_parse_output attributes, %[{attr a="1" b='2'c="3"d="'"}] + end + + def test_parse_attributes_with_slashes_or_angle_brackets + slash = %{{"slash"=>"/"}} + angle = %{{"angle"=>">"}} + assert_parse_output slash, %{} + assert_parse_output slash, %{} + assert_parse_output angle, %{} + + assert_parse_output slash, %[{r:attr slash="/"}{/r:attr}] + assert_parse_output slash, %[{r:attr slash="/"}{r:attr /}{/r:attr}] + assert_parse_output angle, %[{r:attr angle=">"}{/r:attr}] + + assert_parse_output slash, %[{attr slash="/"}] + assert_parse_output slash, %[{attr slash="/"}] + assert_parse_output angle, %[{attr angle=">"}] + end + + def test_parse_quotes + assert_parse_output "test []", %{ } + assert_parse_output "test []", %[{r:echo value="test" /} {r:wrap attr="test"}{/r:wrap}] + assert_parse_output "test []", %[{echo value="test"} {wrap attr="test"}] + end + + def test_things_that_should_be_left_alone + [ + %{ test="2"="4" }, + %{="2" } + ].each do |middle| + assert_parsed_is_unchanged "" + assert_parsed_is_unchanged "" + assert_parsed_is_unchanged "{r:attr#{middle}/}" + assert_parsed_is_unchanged "{r:attr#{middle}}" + assert_parsed_is_unchanged "{attr#{middle}}" + end + end + + def test_tags_inside_html_tags + assert_parse_output %{
tags in yo tags
},%{
tags in yo tags
} + assert_parse_output %{
tags in yo tags
},%{
tags in yo tags
} + end + + def test_parse_result_is_always_a_string + define_tag("twelve") { 12 } + assert_parse_output "12", "" + assert_parse_output "12", "{r:twelve /}" + assert_parse_output "12", "{twelve}" + end + + def test_parse_double_tags + assert_parse_output "test".reverse, "test" + assert_parse_output "tset TEST", "test test" + + assert_parse_output "test".reverse, "{r:reverse}test{/r:reverse}" + assert_parse_output "tset TEST", "{r:reverse}test{/r:reverse} {r:capitalize}test{/r:capitalize}" + + end + + + def test_parse_tag_nesting + define_tag("parent", :for => '') + define_tag("parent:child", :for => '') + define_tag("extra", :for => '') + define_tag("nesting") { |tag| tag.nesting } + define_tag("extra:nesting") { |tag| tag.nesting.gsub(':', ' > ') } + define_tag("parent:child:nesting") { |tag| tag.nesting.gsub(':', ' * ') } + assert_parse_output "nesting", "" + assert_parse_output "parent:nesting", "" + assert_parse_output "extra > nesting", "" + assert_parse_output "parent * child * nesting", "" + assert_parse_output "parent > extra > nesting", "" + assert_parse_output "parent > child > extra > nesting", "" + assert_parse_output "parent * extra * child * nesting", "" + assert_parse_output "parent > extra > child > extra > nesting", "" + assert_parse_output "parent > extra > child > extra > nesting", "" + assert_parse_output "extra * parent * child * nesting", "" + assert_parse_output "extra > parent > nesting", "" + assert_parse_output "extra * parent * child * nesting", "" + assert_raises(Radius::UndefinedTagError) { @parser.parse("") } + end + def test_parse_tag_nesting_2 + define_tag("parent", :for => '') + define_tag("parent:child", :for => '') + define_tag("content") { |tag| tag.nesting } + assert_parse_output 'parent:child:content', '' + end + + def test_parse_tag__binding_do_missing + define_tag 'test' do |tag| + tag.missing! + end + e = assert_raises(Radius::UndefinedTagError) { @parser.parse("") } + assert_equal "undefined tag `test'", e.message + end + + def test_parse_chirpy_bird + # :> chirp chirp + assert_parse_output "<:", "<:" + end + + def test_parse_tag__binding_render_tag + define_tag('test') { |tag| "Hello #{tag.attr['name']}!" } + define_tag('hello') { |tag| tag.render('test', tag.attr) } + assert_parse_output 'Hello John!', '' + end + + def test_accessing_tag_attributes_through_tag_indexer + define_tag('test') { |tag| "Hello #{tag['name']}!" } + assert_parse_output 'Hello John!', '' + end + + def test_parse_tag__binding_render_tag_with_block + define_tag('test') { |tag| "Hello #{tag.expand}!" } + define_tag('hello') { |tag| tag.render('test') { tag.expand } } + assert_parse_output 'Hello John!', 'John' + end + + def test_tag_locals + define_tag "outer" do |tag| + tag.locals.var = 'outer' + tag.expand + end + define_tag "outer:inner" do |tag| + tag.locals.var = 'inner' + tag.expand + end + define_tag "outer:var" do |tag| + tag.locals.var + end + assert_parse_output 'outer', "" + assert_parse_output 'outer:inner:outer', "::" + assert_parse_output 'outer:inner:outer:inner:outer', "::::" + assert_parse_output 'outer', "" + end + + def test_tag_globals + define_tag "set" do |tag| + tag.globals.var = tag.attr['value'] + '' + end + define_tag "var" do |tag| + tag.globals.var + end + assert_parse_output " true false", %{ } + end + + def test_parse_loops + @item = nil + define_tag "each" do |tag| + result = [] + ["Larry", "Moe", "Curly"].each do |item| + tag.locals.item = item + result << tag.expand + end + result.join(tag.attr["between"] || "") + end + define_tag "each:item" do |tag| + tag.locals.item + end + assert_parse_output %{Three Stooges: "Larry", "Moe", "Curly"}, %{Three Stooges: ""} + end + + def test_parse_speed + define_tag "set" do |tag| + tag.globals.var = tag.attr['value'] + '' + end + define_tag "var" do |tag| + tag.globals.var + end + parts = %w{decima nobis augue at facer processus commodo legentis odio lectorum dolore nulla esse lius qui nonummy ullamcorper erat ii notare} + multiplier = parts.map{|p| "#{p}=\"#{rand}\""}.join(' ') + assert_nothing_raised do + Timeout.timeout(10) do + assert_parse_output " false", %{ } + end + end + end + + def test_tag_option_for + define_tag 'fun', :for => 'just for kicks' + assert_parse_output 'just for kicks', '' + end + + def test_tag_expose_option + define_tag 'user', :for => users.first, :expose => ['name', :age] + assert_parse_output 'John', '' + assert_parse_output '25', '' + e = assert_raises(Radius::UndefinedTagError) { @parser.parse "" } + assert_equal "undefined tag `email'", e.message + end + + def test_tag_expose_attributes_option_on_by_default + define_tag 'user', :for => user_with_attributes + assert_parse_output 'John', '' + end + def test_tag_expose_attributes_set_to_false + define_tag 'user_without_attributes', :for => user_with_attributes, :attributes => false + assert_raises(Radius::UndefinedTagError) { @parser.parse "" } + end + + def test_tag_options_must_contain_a_for_option_if_methods_are_exposed + e = assert_raises(ArgumentError) { define_tag('fun', :expose => :today) { 'test' } } + assert_equal "tag definition must contain a :for option when used with the :expose option", e.message + end + + def test_parse_fail_on_missing_end_tag + assert_raises(Radius::MissingEndTagError) { @parser.parse("") } + end + + def test_parse_fail_on_wrong_end_tag + assert_raises(Radius::WrongEndTagError) { @parser.parse("") } + end + + def test_parse_with_default_tag_prefix + @parser = Radius::Parser.new(@context) + define_tag("hello") { |tag| "Hello world!" } + assert_equal "

Hello world!

", @parser.parse('

') + end + + def test_parse_with_other_radius_like_tags + @parser = Radius::Parser.new(@context, :tag_prefix => "ralph") + define_tag('hello') { "hello" } + assert_equal "", @parser.parse("") + end + + def test_copyin_global_values + @context.globals.foo = 'bar' + assert_equal 'bar', Radius::Parser.new(@context).context.globals.foo + end + + def test_does_not_pollute_copied_globals + @context.globals.foo = 'bar' + parser = Radius::Parser.new(@context) + parser.context.globals.foo = '[baz]' + assert_equal 'bar', @context.globals.foo + end + + def test_parse_with_other_namespaces + @parser = Radius::Parser.new(@context, :tag_prefix => 'r') + assert_equal "hello world", @parser.parse("hello world") + end + + protected + + def assert_parse_output(output, input, message = nil) + r = @parser.parse(input) + assert_equal(output, r, message) + end + + def assert_parsed_is_unchanged(something) + assert_parse_output something, something + end + + class User + attr_accessor :name, :age, :email, :friend + def initialize(name, age, email) + @name, @age, @email = name, age, email + end + def <=>(other) + name <=> other.name + end + end + + class UserWithAttributes < User + def attributes + { :name => name, :age => age, :email => email } + end + end + + def users + [ + User.new('John', 25, 'test@johnwlong.com'), + User.new('James', 27, 'test@jameslong.com') + ] + end + + def user_with_attributes + UserWithAttributes.new('John', 25, 'test@johnwlong.com') + end + +end