Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Mixed syntax scanner #11

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
56 changes: 56 additions & 0 deletions lib/radius/parser/mixed_scanner.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
module Radius
class MixedScanner < Radius::Scanner

def scanner_regex(prefix = nil)
# allow for <prefix:tag></prefix:tag> {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
324 changes: 324 additions & 0 deletions test/mixed_test.rb
Original file line number Diff line number Diff line change
@@ -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>", %{<<r:add param1="1" param2='2'/>>}
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, %{<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, %[{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, %{<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, %[{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 []", %{<r:echo value="test" /> <r:wrap attr="test"></r:wrap>}
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 "<r:attr#{middle}/>"
assert_parsed_is_unchanged "<r:attr#{middle}>"
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 %{<div class="xzibit">tags in yo tags</div>},%{<div class="<r:reverse>tibizx</r:reverse>">tags in yo tags</div>}
assert_parse_output %{<div class="xzibit">tags in yo tags</div>},%{<div class="{r:reverse}tibizx{/r:reverse}">tags in yo tags</div>}
end

def test_parse_result_is_always_a_string
define_tag("twelve") { 12 }
assert_parse_output "12", "<r:twelve />"
assert_parse_output "12", "{r:twelve /}"
assert_parse_output "12", "{twelve}"
end

def test_parse_double_tags
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>"

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", "<r:nesting />"
assert_parse_output "parent:nesting", "<r:parent:nesting />"
assert_parse_output "extra > nesting", "<r:extra:nesting />"
assert_parse_output "parent * child * nesting", "<r:parent:child:nesting />"
assert_parse_output "parent > extra > nesting", "<r:parent:extra:nesting />"
assert_parse_output "parent > child > extra > nesting", "<r:parent:child:extra:nesting />"
assert_parse_output "parent * extra * child * nesting", "<r:parent:extra:child:nesting />"
assert_parse_output "parent > extra > child > extra > nesting", "<r:parent:extra:child:extra:nesting />"
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>"
assert_parse_output "extra * parent * child * nesting", "<r:extra:parent:child:nesting />"
assert_parse_output "extra > parent > nesting", "<r:extra><r:parent:nesting /></r:extra>"
assert_parse_output "extra * parent * child * nesting", "<r:extra:parent><r:child:nesting /></r:extra:parent>"
assert_raises(Radius::UndefinedTagError) { @parser.parse("<r:child />") }
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', '<r:parent><r:child:content /></r:parent>'
end

def test_parse_tag__binding_do_missing
define_tag 'test' do |tag|
tag.missing!
end
e = assert_raises(Radius::UndefinedTagError) { @parser.parse("<r:test />") }
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!', '<r:hello name="John" />'
end

def test_accessing_tag_attributes_through_tag_indexer
define_tag('test') { |tag| "Hello #{tag['name']}!" }
assert_parse_output 'Hello John!', '<r:test name="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!', '<r:hello>John</r:hello>'
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', "<r:outer><r:var /></r:outer>"
assert_parse_output 'outer:inner:outer', "<r:outer><r:var />:<r:inner><r:var /></r:inner>:<r:var /></r:outer>"
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>"
assert_parse_output 'outer', "<r:outer:var />"
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", %{<r:var /> <r:set value="true" /> <r:var /> <r:set value="false" /> <r:var />}
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: <r:each between=", ">"<r:item />"</r:each>}
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", %{<r:set value="false" #{multiplier} /> <r:var />}
end
end
end

def test_tag_option_for
define_tag 'fun', :for => 'just for kicks'
assert_parse_output 'just for kicks', '<r:fun />'
end

def test_tag_expose_option
define_tag 'user', :for => users.first, :expose => ['name', :age]
assert_parse_output 'John', '<r:user:name />'
assert_parse_output '25', '<r:user><r:age /></r:user>'
e = assert_raises(Radius::UndefinedTagError) { @parser.parse "<r:user:email />" }
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', '<r:user:name />'
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 "<r:user_without_attributes:name />" }
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("<r:reverse>") }
end

def test_parse_fail_on_wrong_end_tag
assert_raises(Radius::WrongEndTagError) { @parser.parse("<r:reverse><r:capitalize></r:reverse>") }
end

def test_parse_with_default_tag_prefix
@parser = Radius::Parser.new(@context)
define_tag("hello") { |tag| "Hello world!" }
assert_equal "<p>Hello world!</p>", @parser.parse('<p><radius:hello /></p>')
end

def test_parse_with_other_radius_like_tags
@parser = Radius::Parser.new(@context, :tag_prefix => "ralph")
define_tag('hello') { "hello" }
assert_equal "<r:ralph:hello />", @parser.parse("<r:ralph:hello />")
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 "<fb:test>hello world</fb:test>", @parser.parse("<fb:test>hello world</fb:test>")
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