add radius
This commit is contained in:
parent
8d3e11cabe
commit
6e100d3499
|
@ -0,0 +1,42 @@
|
|||
= Change Log
|
||||
|
||||
== edge
|
||||
* Support for Rubinius [jlong]
|
||||
* Support for Ruby 1.9 [aemadrid]
|
||||
* More tests [aemadrid]
|
||||
* Fixed issue #5 - problem with other namespace tags [jemmyw]
|
||||
* Switched to Jeweler for better gem management [jlong]
|
||||
* Allow operation in a threaded environment (parser per-thread, shared context)
|
||||
* Allow switching scanners that tokenize templates.
|
||||
* Include SquiggleScanner to parse tags that look like "{ hello /}"
|
||||
|
||||
== 0.6.1
|
||||
* Fixed a problem with non-tags that have no prefix or tag name (see test_parse_chirpy_bird)
|
||||
|
||||
== 0.6.0 (private release)
|
||||
* Split radius.rb into multiple files.
|
||||
* Ported the really hairy regexes from Radius::Parser to a single Ragel machine.
|
||||
* Added and refactored tests.
|
||||
* Refactored Rakefile and other administrativia.
|
||||
|
||||
== 0.5.1
|
||||
* Fixed a problem with parsing quotes where a single tag preceding a double tag would consume the start tag of the double tag if both contained attributes.
|
||||
|
||||
== 0.5.0
|
||||
* Created a DSL for tag definitions (introducing a DSL makes this version of Radiant incompatible with the last). The DSL has the following features:
|
||||
- full support for nested tags
|
||||
- global and local tag variables
|
||||
- Contexts can now be defined dynamically (instead of being subclassed)
|
||||
- see the QUICKSTART for more info
|
||||
* Many refactorings of the library and unit tests.
|
||||
* Changed the license to the MIT-LICENSE.
|
||||
* Updated documentation to reflect the changes.
|
||||
* Updated the version number to reflect the maturity of the code base.
|
||||
|
||||
== 0.0.2
|
||||
* Refactored Parser to use Context#render_tag instead of #send when rendering tags defined on a Context.
|
||||
* UndefinedTagError is now thrown when Parser tries to render a tag which doesn't exist on a Context.
|
||||
* Added Context#tag_missing which works like method_method missing on Object, but is tag specific.
|
||||
|
||||
== 0.0.1
|
||||
* First release.
|
|
@ -0,0 +1,19 @@
|
|||
Copyright (c) 2006-2010, John W. Long
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
|
@ -0,0 +1,322 @@
|
|||
= Radius Quick Start
|
||||
|
||||
|
||||
== Defining Tags
|
||||
|
||||
Before you can parse a template with Radius you need to create a Context object which defines
|
||||
the tags that will be used in the template. This is actually quite simple:
|
||||
|
||||
require 'radius'
|
||||
|
||||
context = Radius::Context.new
|
||||
context.define_tag "hello" do |tag|
|
||||
"Hello #{tag.attr['name'] || 'World'}!"
|
||||
end
|
||||
|
||||
Once you have defined a context you can easily create a Parser:
|
||||
|
||||
parser = Radius::Parser.new(context)
|
||||
puts parser.parse('<p><radius:hello /></p>')
|
||||
puts parser.parse('<p><radius:hello name="John" /></p>')
|
||||
|
||||
This code will output:
|
||||
|
||||
<p>Hello World!</p>
|
||||
<p>Hello John!</p>
|
||||
|
||||
Note how you can pass attributes from the template to the context using the attributes hash.
|
||||
Above, the first tag that was parsed didn't have a name attribute so the code in the +hello+
|
||||
tag definition uses "World" instead. The second time the tag is parsed the name attribute is
|
||||
set to "John" which is used to create the string "Hello John!". Tags that do not follow this
|
||||
rule will be treated as if they were undefined (like normal methods).
|
||||
|
||||
|
||||
== Container Tags
|
||||
|
||||
Radius also allows you to define "container" tags. That is, tags that contain content and
|
||||
that may optionally manipulate it in some way. For example, if you have RedCloth installed
|
||||
you could define another tag to parse and create Textile output:
|
||||
|
||||
require 'redcloth'
|
||||
|
||||
context.define_tag "textile" do |tag|
|
||||
contents = tag.expand
|
||||
RedCloth.new(contents).to_html
|
||||
end
|
||||
|
||||
(The code <tt>tag.expand</tt> above returns the contents of the template between the start and end
|
||||
tags.)
|
||||
|
||||
With the code above your parser can easily handle Textile:
|
||||
|
||||
parser.parse('<radius:textile>h1. Hello **World**!</radius:textile>')
|
||||
|
||||
This code will output:
|
||||
|
||||
<h1>Hello <b>World</b>!</h1>
|
||||
|
||||
|
||||
== Nested Tags
|
||||
|
||||
But wait!--it gets better. Because container tags can manipulate the content they contain
|
||||
you can use them to iterate over collections:
|
||||
|
||||
context = Radius::Context.new
|
||||
|
||||
context.define_tag "stooge" do |tag|
|
||||
content = ''
|
||||
["Larry", "Moe", "Curly"].each do |name|
|
||||
tag.locals.name = name
|
||||
content << tag.expand
|
||||
end
|
||||
content
|
||||
end
|
||||
|
||||
context.define_tag "stooge:name" do |tag|
|
||||
tag.locals.name
|
||||
end
|
||||
|
||||
parser = Radius::Parser.new(context)
|
||||
|
||||
template = <<-TEMPLATE
|
||||
<ul>
|
||||
<radius:stooge>
|
||||
<li><radius:name /></li>
|
||||
</radius:stooge>
|
||||
</ul>
|
||||
TEMPLATE
|
||||
|
||||
puts parser.parse(template)
|
||||
|
||||
This code will output:
|
||||
|
||||
<ul>
|
||||
|
||||
<li>Larry</li>
|
||||
|
||||
<li>Moe</li>
|
||||
|
||||
<li>Curly</li>
|
||||
|
||||
</ul>
|
||||
|
||||
Note how the definition for the +name+ tag is defined. Because "name" is prefixed
|
||||
with "stooge:" the +name+ tag cannot appear outside the +stooge+ tag. Had it been defined
|
||||
simply as "name" it would be valid anywhere, even outside the +stooge+ tag (which was
|
||||
not what we wanted). Using the colon operator you can define tags with any amount of
|
||||
nesting.
|
||||
|
||||
|
||||
== Exposing Objects to Templates
|
||||
|
||||
During normal operation, you will often want to expose certain objects to your templates.
|
||||
Writing the tags to do this all by hand would be cumbersome of Radius did not provide
|
||||
several mechanisms to make this easier. The first is a way of exposing objects as tags
|
||||
on the context object. To expose an object simply call the +define_tag+
|
||||
method with the +for+ option:
|
||||
|
||||
context.define_tag "count", :for => 1
|
||||
|
||||
This would expose the object <tt>1</tt> to the template as the +count+ tag. It's basically the
|
||||
equivalent of writing:
|
||||
|
||||
context.define_tag("count") { 1 }
|
||||
|
||||
So far this doesn't save you a whole lot of typing, but suppose you want to expose certain
|
||||
methods that are on that object? You could do this:
|
||||
|
||||
context.define_tag "user", :for => user, :expose => [ :name, :age, :email ]
|
||||
|
||||
This will add a total of four tags to the context. One for the <tt>user</tt> variable, and
|
||||
one for each of the three methods listed in the +expose+ clause. You could now get the user's
|
||||
name inside your template like this:
|
||||
|
||||
<radius:user><radius:name /></radius:user>
|
||||
|
||||
If "John" was the value stored in <tt>user.name</tt> the template would render as "John".
|
||||
|
||||
|
||||
== Tag Shorthand
|
||||
|
||||
In the example above we made reference to <tt>user.name</tt> in our template by using the
|
||||
following code:
|
||||
|
||||
<radius:user><radius:name /></radius:user>
|
||||
|
||||
There is a much easer way to refer to the <tt>user.name</tt> variable. Use the colon operator
|
||||
to "scope" the reference to <tt>name</tt>:
|
||||
|
||||
<radius:user:name />
|
||||
|
||||
Radius allows you to use this shortcut for all tags.
|
||||
|
||||
|
||||
== Changing the Tag Prefix
|
||||
|
||||
By default, all Radius tags must begin with "radius". You can change this by altering the
|
||||
tag_prefix attribute on a Parser. For example:
|
||||
|
||||
parser = Radius::Parser.new(context, :tag_prefix => 'r')
|
||||
|
||||
Now, when parsing templates with +parser+, Radius will require that every tag begin with "r"
|
||||
instead of "radius".
|
||||
|
||||
|
||||
== Custom Behavior for Undefined Tags
|
||||
|
||||
Context#tag_missing behaves much like Object#method_missing only it allows you to define
|
||||
specific behavior for when a tag is not defined on a Context. For example:
|
||||
|
||||
class LazyContext < Radius::Context
|
||||
def tag_missing(tag, attr, &block)
|
||||
"<strong>ERROR: Undefined tag `#{tag}' with attributes #{attr.inspect}</strong>"
|
||||
end
|
||||
end
|
||||
|
||||
parser = Radius::Parser.new(LazyContext.new, :tag_prefix => 'lazy')
|
||||
puts parser.parse('<lazy:weird value="true" />')
|
||||
|
||||
This will output:
|
||||
|
||||
<strong>ERROR: Undefined tag `weird' with attributes {"value"=>"true"}</strong>
|
||||
|
||||
Normally, when the Radius Parser encounters an undefined tag for a Context it raises an
|
||||
UndefinedTagError, but since we have defined #tag_missing on LazyContext the Parser now
|
||||
outputs a nicely formated error message when we parse a string that does not contain a
|
||||
valid tag.
|
||||
|
||||
|
||||
== Tag Bindings
|
||||
|
||||
Radius passes a TagBinding into the block of the Context#define_tag method. The tag
|
||||
binding is useful for a number of tasks. A tag binding has an #expand instance method
|
||||
which processes a tag's contents and returns the result. It also has a #attr method
|
||||
which returns a hash of the attributes that were passed into the tag. TagBinding also
|
||||
contains the TagBinding#single? and TagBinding#double? methods which return true or false
|
||||
based on wether the tag is a container tag or not. More about the methods which are
|
||||
available on tag bindings can be found on the Radius::TagBinding documentation page.
|
||||
|
||||
|
||||
== Tag Binding Locals, Globals, and Context Sensitive Tags
|
||||
|
||||
A TagBinding also contains two OpenStruct-like objects which are useful when developing
|
||||
tags. TagBinding#globals is useful for storing variables which you would like to be
|
||||
accessible to all tags:
|
||||
|
||||
context.define_tag "inc" do |tag|
|
||||
tag.globals.count ||= 0
|
||||
tag.globals.count += 1
|
||||
""
|
||||
end
|
||||
|
||||
context.define_tag "count" do |tag|
|
||||
tag.globals.count || 0
|
||||
end
|
||||
|
||||
TagBinding#locals mirrors the variables that are in TagBinding#globals, but allows child
|
||||
tags to redefine variables. This is valuable when defining context sensitive tags:
|
||||
|
||||
class Person
|
||||
attr_accessor :name, :friend
|
||||
def initialize(name)
|
||||
@name = name
|
||||
end
|
||||
end
|
||||
|
||||
jack = Person.new('Jack')
|
||||
jill = Person.new('Jill')
|
||||
jack.friend = jill
|
||||
jill.friend = jack
|
||||
|
||||
context = Radius::Context.new do |c|
|
||||
c.define_tag "jack" do |tag|
|
||||
tag.locals.person = jack
|
||||
tag.expand
|
||||
end
|
||||
c.define_tag "jill" do |tag|
|
||||
tag.locals.person = jill
|
||||
tag.expand
|
||||
end
|
||||
c.define_tag "name" do |tag|
|
||||
tag.locals.person.name rescue tag.missing!
|
||||
end
|
||||
c.define_tag "friend" do |tag|
|
||||
tag.locals.person = tag.locals.person.friend rescue tag.missing!
|
||||
tag.expand
|
||||
end
|
||||
end
|
||||
|
||||
parser = Radius::Parser.new(context, :tag_prefix => 'r')
|
||||
|
||||
parser.parse('<r:jack:name />') #=> "Jack"
|
||||
parser.parse('<r:jill:name />') #=> "Jill"
|
||||
parser.parse('<r:jill:friend:name />') #=> "Jack"
|
||||
parser.parse('<r:jack:friend:friend:name />') #=> "Jack"
|
||||
parser.parse('<r:jill><r:friend:name /> and <r:name /></r:jill>') #=> "Jack and Jill"
|
||||
parser.parse('<r:name />') # raises a Radius::UndefinedTagError exception
|
||||
|
||||
Notice how TagBinding#locals enables intelligent nesting. "<r:jill:name />" evaluates to
|
||||
"Jill", but "<r:jill:friend:name />" evaluates to "Jack". Locals lose scope as soon as
|
||||
the tag they were defined in closes. Globals on the other hand, never lose scope.
|
||||
|
||||
The final line in the example above demonstrates that calling "<r:name />" raises a
|
||||
TagMissing error. This is because of the way the name tag was defined:
|
||||
|
||||
tag.locals.person.name rescue tag.missing!
|
||||
|
||||
If person is not defined on locals it will return nil. Calling #name on nil would normally
|
||||
raise a NoMethodError exception, but because of the 'rescue' clause the TagBinding#missing!
|
||||
method is called which fires off Context#tag_missing. By default Context#tag_missing raises
|
||||
a UndefinedTagError exception. The 'rescue tag.missing!' idiom is extremly useful for adding
|
||||
simple error checking to context sensitive tags.
|
||||
|
||||
|
||||
== Tag Specificity
|
||||
|
||||
When Radius is presented with two tags that have the same name, but different nesting
|
||||
Radius uses an algorithm similar to the way winning rules are calculated in Cascading Style
|
||||
Sheets (CSS) to determine which definition should be used. Each time a tag is encountered
|
||||
in a template potential tags are assigned specificity values and the tag with the highest
|
||||
specificity wins.
|
||||
|
||||
For example, given the following tag definitions:
|
||||
|
||||
nesting
|
||||
extra:nesting
|
||||
parent:child:nesting
|
||||
|
||||
And template:
|
||||
|
||||
<r:parent:extra:child:nesting />
|
||||
|
||||
Radius will calculate specificity values like this:
|
||||
|
||||
nesting => 1.0.0.0
|
||||
extra:nesting => 1.0.1.0
|
||||
parent:child:nesting => 1.1.0.1
|
||||
|
||||
Meaning that parent:child:nesting will win. If a template contained:
|
||||
|
||||
<r:parent:child:extra:nesting />
|
||||
|
||||
The following specificity values would be assigned to each of the tag definitions:
|
||||
|
||||
nesting => 1.0.0.0
|
||||
extra:nesting => 1.1.0.0
|
||||
parent:child:nesting => 1.0.1.1
|
||||
|
||||
Meaning that extra:nesting would win because it is more "specific".
|
||||
|
||||
Values are assigned by assigning points to each of the tags from right to left.
|
||||
Given a tag found in a template with nesting four levels deep, the maximum
|
||||
specificity a tag could be assigned would be:
|
||||
|
||||
1.1.1.1
|
||||
|
||||
One point for each of the levels.
|
||||
|
||||
In practice, you don't need to understand this topic to be effective with Radius.
|
||||
For the most part you will find that Radius resolves tags precisely the way that
|
||||
you would expect. If you find this section confusing forget about it and refer
|
||||
back to it if you find that tags are resolving differently from the way that you
|
||||
expected.
|
|
@ -0,0 +1,100 @@
|
|||
= Radius -- Powerful Tag-Based Templates
|
||||
|
||||
Radius is a powerful tag-based template language for Ruby inspired by the
|
||||
template languages used in MovableType[http://www.movabletype.org] and
|
||||
TextPattern[http://www.textpattern.com]. It uses tags similar to XML, but can
|
||||
be used to generate any form of plain text (HTML, e-mail, etc...).
|
||||
|
||||
|
||||
== Usage
|
||||
|
||||
With Radius, it is extremely easy to create custom tags and parse them. Here's a small
|
||||
example:
|
||||
|
||||
require 'radius'
|
||||
|
||||
# Define tags on a context that will be available to a template:
|
||||
context = Radius::Context.new do |c|
|
||||
c.define_tag 'hello' do
|
||||
'Hello world'
|
||||
end
|
||||
c.define_tag 'repeat' do |tag|
|
||||
number = (tag.attr['times'] || '1').to_i
|
||||
result = ''
|
||||
number.times { result << tag.expand }
|
||||
result
|
||||
end
|
||||
end
|
||||
|
||||
# Create a parser to parse tags that begin with 'r:'
|
||||
parser = Radius::Parser.new(context, :tag_prefix => 'r')
|
||||
|
||||
# Parse tags and output the result
|
||||
puts parser.parse(%{A small example:\n<r:repeat times="3">* <r:hello />!\n</r:repeat>})
|
||||
|
||||
Output:
|
||||
|
||||
A small example:
|
||||
* Hello world!
|
||||
* Hello world!
|
||||
* Hello world!
|
||||
|
||||
|
||||
== Quick Start
|
||||
|
||||
Read the QUICKSTART file to get up and running with Radius.
|
||||
|
||||
|
||||
== Requirements
|
||||
|
||||
Radius does not have any external requirements for using the library in your
|
||||
own programs.
|
||||
|
||||
Ragel is required to create the ruby parser from the Ragel specification,
|
||||
and both Ragel and Graphviz are required to draw the state graph for the
|
||||
parser.
|
||||
|
||||
|
||||
== Installation
|
||||
|
||||
It is recommended that you install Radius using the RubyGems packaging system:
|
||||
|
||||
% gem install --remote radius
|
||||
|
||||
|
||||
== License
|
||||
|
||||
Radius is released under the MIT license and is copyright (c) 2006-2010
|
||||
John W. Long. A copy of the MIT license can be found in the LICENSE file.
|
||||
|
||||
|
||||
== Roadmap
|
||||
|
||||
This is a prioritized roadmap for future releases:
|
||||
|
||||
1. Clean up the current code base. [Done]
|
||||
|
||||
2. Add support for multi-level contexts: tags should be able to be
|
||||
defined to only be valid within other sets of tags. [Done]
|
||||
|
||||
3. Create a simple DSL for defining contexts. [Done]
|
||||
|
||||
4. Optimize for speed, modify scan.rl to emit C.
|
||||
|
||||
|
||||
== Development
|
||||
|
||||
The latest version of Radius can be found on RubyForge:
|
||||
|
||||
http://rubyforge.org/projects/radius
|
||||
|
||||
Experimental and development versions of Radius can be found on Github:
|
||||
|
||||
http://github.com/jlong/radius
|
||||
|
||||
If you are interested in helping with the development of Radius, feel free to
|
||||
fork the project on GitHub and send me a pull request.
|
||||
|
||||
|
||||
John Long ::
|
||||
http://wiseheartdesign.com
|
|
@ -0,0 +1,8 @@
|
|||
require 'rubygems'
|
||||
require 'rake'
|
||||
|
||||
require File.dirname(__FILE__) + '/lib/radius/version'
|
||||
|
||||
Dir['tasks/**/*.rake'].each { |t| load t }
|
||||
|
||||
task :default => :test
|
|
@ -0,0 +1 @@
|
|||
0.7.0.prerelease
|
|
@ -0,0 +1,11 @@
|
|||
require 'radius/version'
|
||||
require 'radius/error'
|
||||
require 'radius/tag_definitions'
|
||||
require 'radius/delegating_open_struct'
|
||||
require 'radius/tag_binding'
|
||||
require 'radius/context'
|
||||
require 'radius/parse_tag'
|
||||
require 'radius/ord_string'
|
||||
require 'radius/parser/scanner'
|
||||
require 'radius/parser'
|
||||
require 'radius/utility'
|
|
@ -0,0 +1,147 @@
|
|||
module Radius
|
||||
#
|
||||
# A context contains the tag definitions which are available for use in a template.
|
||||
# See the QUICKSTART for a detailed explaination its
|
||||
# usage.
|
||||
#
|
||||
class Context
|
||||
# A hash of tag definition blocks that define tags accessible on a Context.
|
||||
attr_accessor :definitions # :nodoc:
|
||||
attr_accessor :globals # :nodoc:
|
||||
|
||||
# Creates a new Context object.
|
||||
def initialize(&block)
|
||||
@definitions = {}
|
||||
@tag_binding_stack = []
|
||||
@globals = DelegatingOpenStruct.new
|
||||
with(&block) if block_given?
|
||||
end
|
||||
|
||||
# Yield an instance of self for tag definitions:
|
||||
#
|
||||
# context.with do |c|
|
||||
# c.define_tag 'test' do
|
||||
# 'test'
|
||||
# end
|
||||
# end
|
||||
#
|
||||
def with
|
||||
yield self
|
||||
self
|
||||
end
|
||||
|
||||
# Creates a tag definition on a context. Several options are available to you
|
||||
# when creating a tag:
|
||||
#
|
||||
# +for+:: Specifies an object that the tag is in reference to. This is
|
||||
# applicable when a block is not passed to the tag, or when the
|
||||
# +expose+ option is also used.
|
||||
#
|
||||
# +expose+:: Specifies that child tags should be set for each of the methods
|
||||
# contained in this option. May be either a single symbol/string or
|
||||
# an array of symbols/strings.
|
||||
#
|
||||
# +attributes+:: Specifies whether or not attributes should be exposed
|
||||
# automatically. Useful for ActiveRecord objects. Boolean. Defaults
|
||||
# to +true+.
|
||||
#
|
||||
def define_tag(name, options = {}, &block)
|
||||
type = Utility.impartial_hash_delete(options, :type).to_s
|
||||
klass = Utility.constantize('Radius::TagDefinitions::' + Utility.camelize(type) + 'TagFactory') rescue raise(ArgumentError.new("Undefined type `#{type}' in options hash"))
|
||||
klass.new(self).define_tag(name, options, &block)
|
||||
end
|
||||
|
||||
# Returns the value of a rendered tag. Used internally by Parser#parse.
|
||||
def render_tag(name, attributes = {}, &block)
|
||||
if name =~ /^(.+?):(.+)$/
|
||||
render_tag($1) { render_tag($2, attributes, &block) }
|
||||
else
|
||||
tag_definition_block = @definitions[qualified_tag_name(name.to_s)]
|
||||
if tag_definition_block
|
||||
stack(name, attributes, block) do |tag|
|
||||
tag_definition_block.call(tag).to_s
|
||||
end
|
||||
else
|
||||
tag_missing(name, attributes, &block)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# Like method_missing for objects, but fired when a tag is undefined.
|
||||
# Override in your own Context to change what happens when a tag is
|
||||
# undefined. By default this method raises an UndefinedTagError.
|
||||
def tag_missing(name, attributes, &block)
|
||||
raise UndefinedTagError.new(name)
|
||||
end
|
||||
|
||||
# Returns the state of the current render stack. Useful from inside
|
||||
# a tag definition. Normally just use TagBinding#nesting.
|
||||
def current_nesting
|
||||
@tag_binding_stack.collect { |tag| tag.name }.join(':')
|
||||
end
|
||||
|
||||
# make a usable copy of this context
|
||||
def dup # :nodoc:
|
||||
rv = self.class.new
|
||||
rv.globals = globals.dup
|
||||
rv.definitions = definitions.dup
|
||||
rv
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
# A convienence method for managing the various parts of the
|
||||
# tag binding stack.
|
||||
def stack(name, attributes, block)
|
||||
previous = @tag_binding_stack.last
|
||||
previous_locals = previous.nil? ? globals : previous.locals
|
||||
locals = DelegatingOpenStruct.new(previous_locals)
|
||||
binding = TagBinding.new(self, locals, name, attributes, block)
|
||||
@tag_binding_stack.push(binding)
|
||||
result = yield(binding)
|
||||
@tag_binding_stack.pop
|
||||
result
|
||||
end
|
||||
|
||||
# Returns a fully qualified tag name based on state of the
|
||||
# tag binding stack.
|
||||
def qualified_tag_name(name)
|
||||
nesting_parts = @tag_binding_stack.collect { |tag| tag.name }
|
||||
nesting_parts << name unless nesting_parts.last == name
|
||||
specific_name = nesting_parts.join(':') # specific_name always has the highest specificity
|
||||
unless @definitions.has_key? specific_name
|
||||
possible_matches = @definitions.keys.grep(/(^|:)#{name}$/)
|
||||
specificity = possible_matches.inject({}) { |hash, tag| hash[numeric_specificity(tag, nesting_parts)] = tag; hash }
|
||||
max = specificity.keys.max
|
||||
if max != 0
|
||||
specificity[max]
|
||||
else
|
||||
name
|
||||
end
|
||||
else
|
||||
specific_name
|
||||
end
|
||||
end
|
||||
|
||||
# Returns the specificity for +tag_name+ at nesting defined
|
||||
# by +nesting_parts+ as a number.
|
||||
def numeric_specificity(tag_name, nesting_parts)
|
||||
nesting_parts = nesting_parts.dup
|
||||
name_parts = tag_name.split(':')
|
||||
specificity = 0
|
||||
value = 1
|
||||
if nesting_parts.last == name_parts.last
|
||||
while nesting_parts.size > 0
|
||||
if nesting_parts.last == name_parts.last
|
||||
specificity += value
|
||||
name_parts.pop
|
||||
end
|
||||
nesting_parts.pop
|
||||
value *= 0.1
|
||||
end
|
||||
specificity = 0 if (name_parts.size > 0)
|
||||
end
|
||||
specificity
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,37 @@
|
|||
module Radius
|
||||
class DelegatingOpenStruct # :nodoc:
|
||||
attr_accessor :object
|
||||
|
||||
def initialize(object = nil)
|
||||
@object = object
|
||||
@hash = {}
|
||||
end
|
||||
|
||||
def dup
|
||||
rv = self.class.new
|
||||
rv.instance_variable_set(:@hash, @hash.dup)
|
||||
rv
|
||||
end
|
||||
|
||||
def method_missing(method, *args, &block)
|
||||
symbol = (method.to_s =~ /^(.*?)=$/) ? $1.intern : method
|
||||
if (0..1).include?(args.size)
|
||||
if args.size == 1
|
||||
@hash[symbol] = args.first
|
||||
else
|
||||
if @hash.has_key?(symbol)
|
||||
@hash[symbol]
|
||||
else
|
||||
unless object.nil?
|
||||
@object.send(method, *args, &block)
|
||||
else
|
||||
nil
|
||||
end
|
||||
end
|
||||
end
|
||||
else
|
||||
super
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,43 @@
|
|||
module Radius
|
||||
# Abstract base class for all parsing errors.
|
||||
class ParseError < StandardError
|
||||
end
|
||||
|
||||
# Occurs when the Parser expects an end tag for one tag and finds the end tag for another.
|
||||
class WrongEndTagError < ParseError
|
||||
def initialize(expected_tag, got_tag, stack)
|
||||
stack_message = " with stack #{stack.inspect}" if stack
|
||||
super("wrong end tag `#{got_tag}' found for start tag `#{expected_tag}'#{stack_message}")
|
||||
end
|
||||
end
|
||||
|
||||
# Occurs when Parser cannot find an end tag for a given tag in a template or when
|
||||
# tags are miss-matched in a template.
|
||||
class MissingEndTagError < ParseError
|
||||
# Create a new MissingEndTagError object for +tag_name+.
|
||||
def initialize(tag_name, stack)
|
||||
stack_message = " with stack #{stack.inspect}" if stack
|
||||
super("end tag not found for start tag `#{tag_name}'#{stack_message}")
|
||||
end
|
||||
end
|
||||
|
||||
# Occurs when Context#render_tag cannot find the specified tag on a Context.
|
||||
class UndefinedTagError < ParseError
|
||||
# Create a new UndefinedTagError object for +tag_name+.
|
||||
def initialize(tag_name)
|
||||
super("undefined tag `#{tag_name}'")
|
||||
end
|
||||
end
|
||||
|
||||
class TastelessTagError < ParseError #:nodoc:
|
||||
def initialize(tag, stack)
|
||||
super("internal error with tasteless tag #{tag.inspect} and stack #{stack.inspect}")
|
||||
end
|
||||
end
|
||||
|
||||
class UndefinedFlavorError < ParseError #:nodoc:
|
||||
def initialize(tag, stack)
|
||||
super("internal error with unknown flavored tag #{tag.inspect} and stack #{stack.inspect}")
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,13 @@
|
|||
module Radius
|
||||
class OrdString < String
|
||||
if RUBY_VERSION[0,3] == '1.9'
|
||||
def [](*args)
|
||||
if args.size == 1 && args.first.is_a?(Integer)
|
||||
slice(args.first).ord
|
||||
else
|
||||
super
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,24 @@
|
|||
module Radius
|
||||
class ParseTag # :nodoc:
|
||||
def initialize(&b)
|
||||
@block = b
|
||||
end
|
||||
|
||||
def on_parse(&b)
|
||||
@block = b
|
||||
end
|
||||
|
||||
def to_s
|
||||
@block.call(self)
|
||||
end
|
||||
end
|
||||
|
||||
class ParseContainerTag < ParseTag # :nodoc:
|
||||
attr_accessor :name, :attributes, :contents
|
||||
|
||||
def initialize(name = "", attributes = {}, contents = [], &b)
|
||||
@name, @attributes, @contents = name, attributes, contents
|
||||
super(&b)
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,79 @@
|
|||
module Radius
|
||||
#
|
||||
# The Radius parser. Initialize a parser with a Context object that
|
||||
# defines how tags should be expanded. See the QUICKSTART[link:files/QUICKSTART.html]
|
||||
# for a detailed explaination of its usage.
|
||||
#
|
||||
class Parser
|
||||
# The Context object used to expand template tags.
|
||||
attr_accessor :context
|
||||
|
||||
# The string that prefixes all tags that are expanded by a parser
|
||||
# (the part in the tag name before the first colon).
|
||||
attr_accessor :tag_prefix
|
||||
|
||||
# The class that performs tokenization of the input string
|
||||
attr_accessor :scanner
|
||||
|
||||
# Creates a new parser object initialized with a Context.
|
||||
def initialize(context = Context.new, options = {})
|
||||
if context.kind_of?(Hash) and options.empty?
|
||||
options, context = context, (context[:context] || context['context'])
|
||||
end
|
||||
options = Utility.symbolize_keys(options)
|
||||
self.context = context ? context.dup : Context.new
|
||||
self.tag_prefix = options[:tag_prefix] || 'radius'
|
||||
self.scanner = options[:scanner] || default_scanner
|
||||
end
|
||||
|
||||
# Parses string for tags, expands them, and returns the result.
|
||||
def parse(string)
|
||||
@stack = [ ParseContainerTag.new { |t| Utility.array_to_s(t.contents) } ]
|
||||
tokenize(string)
|
||||
stack_up
|
||||
@stack.last.to_s
|
||||
end
|
||||
|
||||
protected
|
||||
# Convert the string into a list of text blocks and scanners (tokens)
|
||||
def tokenize(string)
|
||||
@tokens = scanner.operate(tag_prefix, string)
|
||||
end
|
||||
|
||||
def stack_up
|
||||
@tokens.each do |t|
|
||||
if t.is_a? String
|
||||
@stack.last.contents << t
|
||||
next
|
||||
end
|
||||
case t[:flavor]
|
||||
when :open
|
||||
@stack.push(ParseContainerTag.new(t[:name], t[:attrs]))
|
||||
when :self
|
||||
@stack.last.contents << ParseTag.new {@context.render_tag(t[:name], t[:attrs])}
|
||||
when :close
|
||||
popped = @stack.pop
|
||||
raise WrongEndTagError.new(popped.name, t[:name], @stack) if popped.name != t[:name]
|
||||
popped.on_parse { |b| @context.render_tag(popped.name, popped.attributes) { Utility.array_to_s(b.contents) } }
|
||||
@stack.last.contents << popped
|
||||
when :tasteless
|
||||
raise TastelessTagError.new(t, @stack)
|
||||
else
|
||||
raise UndefinedFlavorError.new(t, @stack)
|
||||
end
|
||||
end
|
||||
raise MissingEndTagError.new(@stack.last.name, @stack) if @stack.length != 1
|
||||
end
|
||||
|
||||
def default_scanner
|
||||
if RUBY_PLATFORM == 'java'
|
||||
require 'java'
|
||||
require 'radius/parser/java_scanner.jar'
|
||||
::Radius.send(:include_package, 'radius.parser')
|
||||
Radius::JavaScanner.new(JRuby.runtime)
|
||||
else
|
||||
Radius::Scanner.new
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
@ -0,0 +1,634 @@
|
|||
|
||||
// line 1 "JavaScanner.rl"
|
||||
|
||||
// line 84 "JavaScanner.rl"
|
||||
|
||||
|
||||
package radius.parser;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.LinkedList;
|
||||
import org.jruby.Ruby; // runtime
|
||||
import org.jruby.RubyObject;
|
||||
import org.jruby.runtime.builtin.IRubyObject;
|
||||
import org.jruby.RubyArray;
|
||||
import org.jruby.RubyString;
|
||||
import org.jruby.RubyHash;
|
||||
import org.jruby.RubySymbol;
|
||||
|
||||
public class JavaScanner {
|
||||
|
||||
Ruby runtime = null;
|
||||
RubyArray rv = null;
|
||||
|
||||
void pass_through(String str) {
|
||||
RubyObject last = ((RubyObject)rv.last());
|
||||
if ( rv.size() > 0 && last != null && (last instanceof RubyString) ){
|
||||
// XXX concat changes for ruby 1.9
|
||||
((RubyString) last).concat(RubyString.newString(runtime, str));
|
||||
} else {
|
||||
rv.append(RubyString.newString(runtime, str));
|
||||
}
|
||||
}
|
||||
|
||||
void tag(String prefix, String name, RubyHash attr, RubySymbol flavor) {
|
||||
RubyHash tag = RubyHash.newHash(runtime);
|
||||
tag.op_aset(
|
||||
runtime.getCurrentContext(),
|
||||
RubySymbol.newSymbol(runtime, "prefix"),
|
||||
RubyString.newString(runtime, prefix)
|
||||
);
|
||||
tag.op_aset(
|
||||
runtime.getCurrentContext(),
|
||||
RubySymbol.newSymbol(runtime, "name"),
|
||||
RubyString.newString(runtime, name)
|
||||
);
|
||||
tag.op_aset(
|
||||
runtime.getCurrentContext(),
|
||||
RubySymbol.newSymbol(runtime, "attrs"),
|
||||
attr
|
||||
);
|
||||
tag.op_aset(
|
||||
runtime.getCurrentContext(),
|
||||
RubySymbol.newSymbol(runtime, "flavor"),
|
||||
flavor
|
||||
);
|
||||
rv.append(tag);
|
||||
}
|
||||
|
||||
public JavaScanner(Ruby runtime) {
|
||||
this.runtime = runtime;
|
||||
}
|
||||
|
||||
|
||||
// line 65 "JavaScanner.java"
|
||||
private static byte[] init__parser_actions_0()
|
||||
{
|
||||
return new byte [] {
|
||||
0, 1, 0, 1, 3, 1, 4, 1, 5, 1, 6, 1,
|
||||
7, 1, 8, 1, 9, 1, 10, 1, 14, 1, 15, 1,
|
||||
19, 1, 21, 1, 22, 1, 23, 2, 1, 2, 2, 5,
|
||||
6, 2, 6, 7, 2, 9, 5, 2, 9, 10, 2, 10,
|
||||
9, 2, 11, 20, 2, 12, 20, 2, 13, 20, 2, 16,
|
||||
17, 2, 16, 18, 3, 5, 6, 7, 3, 9, 5, 6,
|
||||
3, 16, 6, 17, 4, 9, 5, 6, 7, 4, 16, 5,
|
||||
6, 17, 5, 16, 9, 5, 6, 17
|
||||
};
|
||||
}
|
||||
|
||||
private static final byte _parser_actions[] = init__parser_actions_0();
|
||||
|
||||
|
||||
private static short[] init__parser_key_offsets_0()
|
||||
{
|
||||
return new short [] {
|
||||
0, 0, 11, 21, 34, 47, 61, 65, 70, 72, 74, 87,
|
||||
100, 101, 103, 118, 133, 149, 155, 161, 176, 179, 182, 185,
|
||||
200, 202, 204, 219, 235, 241, 247, 250, 253, 269, 285, 302,
|
||||
309, 315, 331, 335, 351, 366, 369, 371, 381, 392, 402, 416,
|
||||
420, 420, 421, 430, 430, 430, 432, 434, 437, 440, 442, 444
|
||||
};
|
||||
}
|
||||
|
||||
private static final short _parser_key_offsets[] = init__parser_key_offsets_0();
|
||||
|
||||
|
||||
private static char[] init__parser_trans_keys_0()
|
||||
{
|
||||
return new char [] {
|
||||
58, 63, 95, 45, 46, 48, 57, 65, 90, 97, 122, 63,
|
||||
95, 45, 46, 48, 58, 65, 90, 97, 122, 32, 47, 62,
|
||||
63, 95, 9, 13, 45, 58, 65, 90, 97, 122, 32, 47,
|
||||
62, 63, 95, 9, 13, 45, 58, 65, 90, 97, 122, 32,
|
||||
61, 63, 95, 9, 13, 45, 46, 48, 58, 65, 90, 97,
|
||||
122, 32, 61, 9, 13, 32, 34, 39, 9, 13, 34, 92,
|
||||
34, 92, 32, 47, 62, 63, 95, 9, 13, 45, 58, 65,
|
||||
90, 97, 122, 32, 47, 62, 63, 95, 9, 13, 45, 58,
|
||||
65, 90, 97, 122, 62, 34, 92, 32, 34, 47, 62, 63,
|
||||
92, 95, 9, 13, 45, 58, 65, 90, 97, 122, 32, 34,
|
||||
47, 62, 63, 92, 95, 9, 13, 45, 58, 65, 90, 97,
|
||||
122, 32, 34, 61, 63, 92, 95, 9, 13, 45, 46, 48,
|
||||
58, 65, 90, 97, 122, 32, 34, 61, 92, 9, 13, 32,
|
||||
34, 39, 92, 9, 13, 32, 34, 47, 62, 63, 92, 95,
|
||||
9, 13, 45, 58, 65, 90, 97, 122, 34, 62, 92, 34,
|
||||
39, 92, 34, 39, 92, 32, 39, 47, 62, 63, 92, 95,
|
||||
9, 13, 45, 58, 65, 90, 97, 122, 39, 92, 39, 92,
|
||||
32, 39, 47, 62, 63, 92, 95, 9, 13, 45, 58, 65,
|
||||
90, 97, 122, 32, 39, 61, 63, 92, 95, 9, 13, 45,
|
||||
46, 48, 58, 65, 90, 97, 122, 32, 39, 61, 92, 9,
|
||||
13, 32, 34, 39, 92, 9, 13, 34, 39, 92, 34, 39,
|
||||
92, 32, 34, 39, 47, 62, 63, 92, 95, 9, 13, 45,
|
||||
58, 65, 90, 97, 122, 32, 34, 39, 47, 62, 63, 92,
|
||||
95, 9, 13, 45, 58, 65, 90, 97, 122, 32, 34, 39,
|
||||
61, 63, 92, 95, 9, 13, 45, 46, 48, 58, 65, 90,
|
||||
97, 122, 32, 34, 39, 61, 92, 9, 13, 32, 34, 39,
|
||||
92, 9, 13, 32, 34, 39, 47, 62, 63, 92, 95, 9,
|
||||
13, 45, 58, 65, 90, 97, 122, 34, 39, 62, 92, 32,
|
||||
34, 39, 47, 62, 63, 92, 95, 9, 13, 45, 58, 65,
|
||||
90, 97, 122, 32, 39, 47, 62, 63, 92, 95, 9, 13,
|
||||
45, 58, 65, 90, 97, 122, 39, 62, 92, 39, 92, 63,
|
||||
95, 45, 46, 48, 57, 65, 90, 97, 122, 58, 63, 95,
|
||||
45, 46, 48, 57, 65, 90, 97, 122, 63, 95, 45, 46,
|
||||
48, 58, 65, 90, 97, 122, 32, 62, 63, 95, 9, 13,
|
||||
45, 46, 48, 58, 65, 90, 97, 122, 32, 62, 9, 13,
|
||||
60, 47, 63, 95, 45, 57, 65, 90, 97, 122, 34, 92,
|
||||
34, 92, 34, 39, 92, 34, 39, 92, 39, 92, 39, 92,
|
||||
0
|
||||
};
|
||||
}
|
||||
|
||||
private static final char _parser_trans_keys[] = init__parser_trans_keys_0();
|
||||
|
||||
|
||||
private static byte[] init__parser_single_lengths_0()
|
||||
{
|
||||
return new byte [] {
|
||||
0, 3, 2, 5, 5, 4, 2, 3, 2, 2, 5, 5,
|
||||
1, 2, 7, 7, 6, 4, 4, 7, 3, 3, 3, 7,
|
||||
2, 2, 7, 6, 4, 4, 3, 3, 8, 8, 7, 5,
|
||||
4, 8, 4, 8, 7, 3, 2, 2, 3, 2, 4, 2,
|
||||
0, 1, 3, 0, 0, 2, 2, 3, 3, 2, 2, 0
|
||||
};
|
||||
}
|
||||
|
||||
private static final byte _parser_single_lengths[] = init__parser_single_lengths_0();
|
||||
|
||||
|
||||
private static byte[] init__parser_range_lengths_0()
|
||||
{
|
||||
return new byte [] {
|
||||
0, 4, 4, 4, 4, 5, 1, 1, 0, 0, 4, 4,
|
||||
0, 0, 4, 4, 5, 1, 1, 4, 0, 0, 0, 4,
|
||||
0, 0, 4, 5, 1, 1, 0, 0, 4, 4, 5, 1,
|
||||
1, 4, 0, 4, 4, 0, 0, 4, 4, 4, 5, 1,
|
||||
0, 0, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0
|
||||
};
|
||||
}
|
||||
|
||||
private static final byte _parser_range_lengths[] = init__parser_range_lengths_0();
|
||||
|
||||
|
||||
private static short[] init__parser_index_offsets_0()
|
||||
{
|
||||
return new short [] {
|
||||
0, 0, 8, 15, 25, 35, 45, 49, 54, 57, 60, 70,
|
||||
80, 82, 85, 97, 109, 121, 127, 133, 145, 149, 153, 157,
|
||||
169, 172, 175, 187, 199, 205, 211, 215, 219, 232, 245, 258,
|
||||
265, 271, 284, 289, 302, 314, 318, 321, 328, 336, 343, 353,
|
||||
357, 358, 360, 367, 368, 369, 372, 375, 379, 383, 386, 389
|
||||
};
|
||||
}
|
||||
|
||||
private static final short _parser_index_offsets[] = init__parser_index_offsets_0();
|
||||
|
||||
|
||||
private static byte[] init__parser_indicies_0()
|
||||
{
|
||||
return new byte [] {
|
||||
2, 1, 1, 1, 1, 1, 1, 0, 3, 3, 3, 3,
|
||||
3, 3, 0, 4, 6, 7, 5, 5, 4, 5, 5, 5,
|
||||
0, 8, 10, 11, 9, 9, 8, 9, 9, 9, 0, 13,
|
||||
15, 14, 14, 13, 14, 14, 14, 14, 12, 16, 17, 16,
|
||||
12, 17, 18, 19, 17, 12, 21, 22, 20, 24, 25, 23,
|
||||
26, 28, 29, 27, 27, 26, 27, 27, 27, 12, 30, 32,
|
||||
33, 31, 31, 30, 31, 31, 31, 12, 34, 12, 35, 25,
|
||||
23, 36, 24, 38, 39, 37, 25, 37, 36, 37, 37, 37,
|
||||
23, 40, 24, 42, 43, 41, 25, 41, 40, 41, 41, 41,
|
||||
23, 44, 24, 46, 45, 25, 45, 44, 45, 45, 45, 45,
|
||||
23, 47, 24, 48, 25, 47, 23, 48, 49, 50, 25, 48,
|
||||
23, 51, 21, 53, 54, 52, 22, 52, 51, 52, 52, 52,
|
||||
20, 24, 55, 25, 23, 57, 58, 59, 56, 61, 35, 62,
|
||||
60, 64, 24, 66, 67, 65, 68, 65, 64, 65, 65, 65,
|
||||
63, 24, 68, 63, 61, 68, 63, 69, 24, 71, 72, 70,
|
||||
68, 70, 69, 70, 70, 70, 63, 73, 24, 75, 74, 68,
|
||||
74, 73, 74, 74, 74, 74, 63, 76, 24, 77, 68, 76,
|
||||
63, 77, 78, 79, 68, 77, 63, 80, 58, 59, 56, 81,
|
||||
81, 62, 60, 82, 61, 35, 84, 85, 83, 62, 83, 82,
|
||||
83, 83, 83, 60, 86, 61, 35, 88, 89, 87, 62, 87,
|
||||
86, 87, 87, 87, 60, 90, 61, 35, 92, 91, 62, 91,
|
||||
90, 91, 91, 91, 91, 60, 93, 61, 35, 94, 62, 93,
|
||||
60, 94, 95, 96, 62, 94, 60, 97, 80, 58, 99, 100,
|
||||
98, 59, 98, 97, 98, 98, 98, 56, 61, 35, 101, 62,
|
||||
60, 97, 57, 58, 99, 100, 98, 59, 98, 97, 98, 98,
|
||||
98, 56, 103, 21, 105, 106, 104, 107, 104, 103, 104, 104,
|
||||
104, 102, 24, 108, 68, 63, 21, 107, 102, 109, 109, 109,
|
||||
109, 109, 109, 0, 111, 110, 110, 110, 110, 110, 110, 0,
|
||||
112, 112, 112, 112, 112, 112, 0, 113, 115, 114, 114, 113,
|
||||
114, 114, 114, 114, 0, 116, 117, 116, 0, 118, 120, 119,
|
||||
123, 122, 122, 122, 122, 122, 121, 124, 125, 24, 25, 23,
|
||||
24, 25, 23, 61, 35, 62, 60, 61, 35, 62, 60, 24,
|
||||
68, 63, 24, 68, 63, 126, 0
|
||||
};
|
||||
}
|
||||
|
||||
private static final byte _parser_indicies[] = init__parser_indicies_0();
|
||||
|
||||
|
||||
private static byte[] init__parser_trans_targs_0()
|
||||
{
|
||||
return new byte [] {
|
||||
49, 1, 2, 3, 4, 3, 12, 52, 4, 5, 12, 52,
|
||||
49, 6, 5, 7, 6, 7, 8, 42, 9, 10, 13, 9,
|
||||
10, 13, 11, 5, 12, 52, 11, 5, 12, 52, 51, 14,
|
||||
15, 16, 20, 54, 15, 16, 20, 54, 17, 16, 18, 17,
|
||||
18, 19, 21, 15, 16, 20, 54, 53, 22, 23, 14, 31,
|
||||
22, 23, 31, 24, 26, 27, 41, 58, 25, 26, 27, 41,
|
||||
58, 28, 27, 29, 28, 29, 30, 40, 23, 32, 33, 34,
|
||||
38, 56, 33, 34, 38, 56, 35, 34, 36, 35, 36, 37,
|
||||
39, 33, 34, 38, 56, 55, 24, 26, 27, 41, 58, 25,
|
||||
57, 44, 44, 45, 46, 47, 46, 59, 47, 59, 0, 49,
|
||||
50, 49, 1, 43, 49, 49, 49
|
||||
};
|
||||
}
|
||||
|
||||
private static final byte _parser_trans_targs[] = init__parser_trans_targs_0();
|
||||
|
||||
|
||||
private static byte[] init__parser_trans_actions_0()
|
||||
{
|
||||
return new byte [] {
|
||||
27, 0, 31, 3, 5, 0, 5, 5, 0, 11, 0, 0,
|
||||
29, 13, 0, 13, 0, 0, 0, 0, 15, 43, 15, 0,
|
||||
17, 0, 7, 64, 34, 34, 0, 37, 9, 9, 0, 17,
|
||||
7, 64, 34, 81, 0, 37, 9, 72, 13, 0, 13, 0,
|
||||
0, 17, 0, 40, 76, 68, 86, 58, 15, 46, 43, 15,
|
||||
0, 17, 0, 0, 7, 64, 34, 81, 0, 0, 37, 9,
|
||||
72, 13, 0, 13, 0, 0, 0, 17, 43, 17, 7, 64,
|
||||
34, 81, 0, 37, 9, 72, 13, 0, 13, 0, 0, 17,
|
||||
17, 40, 76, 68, 86, 58, 15, 40, 76, 68, 86, 15,
|
||||
58, 1, 0, 31, 3, 5, 0, 5, 0, 0, 0, 23,
|
||||
61, 25, 1, 0, 52, 49, 55
|
||||
};
|
||||
}
|
||||
|
||||
private static final byte _parser_trans_actions[] = init__parser_trans_actions_0();
|
||||
|
||||
|
||||
private static byte[] init__parser_to_state_actions_0()
|
||||
{
|
||||
return new byte [] {
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
19, 19, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
|
||||
};
|
||||
}
|
||||
|
||||
private static final byte _parser_to_state_actions[] = init__parser_to_state_actions_0();
|
||||
|
||||
|
||||
private static byte[] init__parser_from_state_actions_0()
|
||||
{
|
||||
return new byte [] {
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, 21, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
|
||||
};
|
||||
}
|
||||
|
||||
private static final byte _parser_from_state_actions[] = init__parser_from_state_actions_0();
|
||||
|
||||
|
||||
private static short[] init__parser_eof_trans_0()
|
||||
{
|
||||
return new short [] {
|
||||
0, 1, 1, 1, 1, 13, 13, 13, 13, 13, 13, 13,
|
||||
13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13,
|
||||
13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13,
|
||||
13, 13, 13, 13, 13, 13, 13, 1, 1, 1, 1, 1,
|
||||
0, 0, 122, 125, 126, 125, 126, 125, 126, 125, 126, 127
|
||||
};
|
||||
}
|
||||
|
||||
private static final short _parser_eof_trans[] = init__parser_eof_trans_0();
|
||||
|
||||
|
||||
static final int parser_start = 49;
|
||||
static final int parser_first_final = 49;
|
||||
static final int parser_error = 0;
|
||||
|
||||
static final int parser_en_Closeout = 48;
|
||||
static final int parser_en_main = 49;
|
||||
|
||||
|
||||
// line 143 "JavaScanner.rl"
|
||||
|
||||
public RubyArray operate(String tag_prefix, String input) {
|
||||
char[] data = input.toCharArray();
|
||||
String disposable_string;
|
||||
|
||||
String name = "";
|
||||
String prefix = "";
|
||||
RubySymbol flavor = RubySymbol.newSymbol(runtime, "tasteless".intern());
|
||||
RubyHash attributes = RubyHash.newHash(runtime);
|
||||
|
||||
int tagstart = 0;
|
||||
int mark_pfx = 0;
|
||||
int mark_stg = 0;
|
||||
int mark_attr = 0;
|
||||
int mark_nat = 0;
|
||||
int mark_vat = 0;
|
||||
|
||||
String nat = "";
|
||||
String vat = "";
|
||||
|
||||
int cs;
|
||||
int p = 0;
|
||||
int pe = data.length;
|
||||
int eof = pe;
|
||||
int act;
|
||||
int ts;
|
||||
int te;
|
||||
|
||||
rv = RubyArray.newArray(runtime);
|
||||
char[] remainder = data;
|
||||
|
||||
|
||||
// line 351 "JavaScanner.java"
|
||||
{
|
||||
cs = parser_start;
|
||||
ts = -1;
|
||||
te = -1;
|
||||
act = 0;
|
||||
}
|
||||
|
||||
// line 175 "JavaScanner.rl"
|
||||
|
||||
// line 361 "JavaScanner.java"
|
||||
{
|
||||
int _klen;
|
||||
int _trans = 0;
|
||||
int _acts;
|
||||
int _nacts;
|
||||
int _keys;
|
||||
int _goto_targ = 0;
|
||||
|
||||
_goto: while (true) {
|
||||
switch ( _goto_targ ) {
|
||||
case 0:
|
||||
if ( p == pe ) {
|
||||
_goto_targ = 4;
|
||||
continue _goto;
|
||||
}
|
||||
if ( cs == 0 ) {
|
||||
_goto_targ = 5;
|
||||
continue _goto;
|
||||
}
|
||||
case 1:
|
||||
_acts = _parser_from_state_actions[cs];
|
||||
_nacts = (int) _parser_actions[_acts++];
|
||||
while ( _nacts-- > 0 ) {
|
||||
switch ( _parser_actions[_acts++] ) {
|
||||
case 15:
|
||||
// line 1 "NONE"
|
||||
{ts = p;}
|
||||
break;
|
||||
// line 390 "JavaScanner.java"
|
||||
}
|
||||
}
|
||||
|
||||
_match: do {
|
||||
_keys = _parser_key_offsets[cs];
|
||||
_trans = _parser_index_offsets[cs];
|
||||
_klen = _parser_single_lengths[cs];
|
||||
if ( _klen > 0 ) {
|
||||
int _lower = _keys;
|
||||
int _mid;
|
||||
int _upper = _keys + _klen - 1;
|
||||
while (true) {
|
||||
if ( _upper < _lower )
|
||||
break;
|
||||
|
||||
_mid = _lower + ((_upper-_lower) >> 1);
|
||||
if ( data[p] < _parser_trans_keys[_mid] )
|
||||
_upper = _mid - 1;
|
||||
else if ( data[p] > _parser_trans_keys[_mid] )
|
||||
_lower = _mid + 1;
|
||||
else {
|
||||
_trans += (_mid - _keys);
|
||||
break _match;
|
||||
}
|
||||
}
|
||||
_keys += _klen;
|
||||
_trans += _klen;
|
||||
}
|
||||
|
||||
_klen = _parser_range_lengths[cs];
|
||||
if ( _klen > 0 ) {
|
||||
int _lower = _keys;
|
||||
int _mid;
|
||||
int _upper = _keys + (_klen<<1) - 2;
|
||||
while (true) {
|
||||
if ( _upper < _lower )
|
||||
break;
|
||||
|
||||
_mid = _lower + (((_upper-_lower) >> 1) & ~1);
|
||||
if ( data[p] < _parser_trans_keys[_mid] )
|
||||
_upper = _mid - 2;
|
||||
else if ( data[p] > _parser_trans_keys[_mid+1] )
|
||||
_lower = _mid + 2;
|
||||
else {
|
||||
_trans += ((_mid - _keys)>>1);
|
||||
break _match;
|
||||
}
|
||||
}
|
||||
_trans += _klen;
|
||||
}
|
||||
} while (false);
|
||||
|
||||
_trans = _parser_indicies[_trans];
|
||||
case 3:
|
||||
cs = _parser_trans_targs[_trans];
|
||||
|
||||
if ( _parser_trans_actions[_trans] != 0 ) {
|
||||
_acts = _parser_trans_actions[_trans];
|
||||
_nacts = (int) _parser_actions[_acts++];
|
||||
while ( _nacts-- > 0 )
|
||||
{
|
||||
switch ( _parser_actions[_acts++] )
|
||||
{
|
||||
case 0:
|
||||
// line 4 "JavaScanner.rl"
|
||||
{ mark_pfx = p; }
|
||||
break;
|
||||
case 1:
|
||||
// line 5 "JavaScanner.rl"
|
||||
{
|
||||
prefix = input.substring(mark_pfx, p);
|
||||
}
|
||||
break;
|
||||
case 2:
|
||||
// line 8 "JavaScanner.rl"
|
||||
{
|
||||
if ( !prefix.equals(tag_prefix) ) {
|
||||
// have to manually add ':' / Sep
|
||||
// pass the text through & reset state
|
||||
pass_through(input.substring(tagstart, p) + ":");
|
||||
prefix = "";
|
||||
{cs = 49; _goto_targ = 2; if (true) continue _goto;}
|
||||
}
|
||||
}
|
||||
break;
|
||||
case 3:
|
||||
// line 18 "JavaScanner.rl"
|
||||
{ mark_stg = p; }
|
||||
break;
|
||||
case 4:
|
||||
// line 19 "JavaScanner.rl"
|
||||
{ name = input.substring(mark_stg, p); }
|
||||
break;
|
||||
case 5:
|
||||
// line 20 "JavaScanner.rl"
|
||||
{ mark_attr = p; }
|
||||
break;
|
||||
case 6:
|
||||
// line 21 "JavaScanner.rl"
|
||||
{
|
||||
attributes.op_aset(
|
||||
runtime.getCurrentContext(),
|
||||
RubyString.newString(runtime, nat),
|
||||
RubyString.newString(runtime, vat)
|
||||
);
|
||||
}
|
||||
break;
|
||||
case 7:
|
||||
// line 29 "JavaScanner.rl"
|
||||
{ mark_nat = p; }
|
||||
break;
|
||||
case 8:
|
||||
// line 30 "JavaScanner.rl"
|
||||
{ nat = input.substring(mark_nat, p); }
|
||||
break;
|
||||
case 9:
|
||||
// line 31 "JavaScanner.rl"
|
||||
{ mark_vat = p; }
|
||||
break;
|
||||
case 10:
|
||||
// line 32 "JavaScanner.rl"
|
||||
{ vat = input.substring(mark_vat, p); }
|
||||
break;
|
||||
case 11:
|
||||
// line 34 "JavaScanner.rl"
|
||||
{ flavor = RubySymbol.newSymbol(runtime, "open".intern()); }
|
||||
break;
|
||||
case 12:
|
||||
// line 35 "JavaScanner.rl"
|
||||
{ flavor = RubySymbol.newSymbol(runtime, "self".intern()); }
|
||||
break;
|
||||
case 13:
|
||||
// line 36 "JavaScanner.rl"
|
||||
{ flavor = RubySymbol.newSymbol(runtime, "close".intern()); }
|
||||
break;
|
||||
case 16:
|
||||
// line 1 "NONE"
|
||||
{te = p+1;}
|
||||
break;
|
||||
case 17:
|
||||
// line 72 "JavaScanner.rl"
|
||||
{act = 1;}
|
||||
break;
|
||||
case 18:
|
||||
// line 79 "JavaScanner.rl"
|
||||
{act = 2;}
|
||||
break;
|
||||
case 19:
|
||||
// line 79 "JavaScanner.rl"
|
||||
{te = p+1;{
|
||||
pass_through(input.substring(p, p + 1));
|
||||
tagstart = p + 1;
|
||||
}}
|
||||
break;
|
||||
case 20:
|
||||
// line 72 "JavaScanner.rl"
|
||||
{te = p;p--;{
|
||||
tag(prefix, name, attributes, flavor);
|
||||
prefix = "";
|
||||
name = "";
|
||||
attributes = RubyHash.newHash(runtime);
|
||||
flavor = RubySymbol.newSymbol(runtime, "tasteless".intern());
|
||||
}}
|
||||
break;
|
||||
case 21:
|
||||
// line 79 "JavaScanner.rl"
|
||||
{te = p;p--;{
|
||||
pass_through(input.substring(p, p + 1));
|
||||
tagstart = p + 1;
|
||||
}}
|
||||
break;
|
||||
case 22:
|
||||
// line 79 "JavaScanner.rl"
|
||||
{{p = ((te))-1;}{
|
||||
pass_through(input.substring(p, p + 1));
|
||||
tagstart = p + 1;
|
||||
}}
|
||||
break;
|
||||
case 23:
|
||||
// line 1 "NONE"
|
||||
{ switch( act ) {
|
||||
case 1:
|
||||
{{p = ((te))-1;}
|
||||
tag(prefix, name, attributes, flavor);
|
||||
prefix = "";
|
||||
name = "";
|
||||
attributes = RubyHash.newHash(runtime);
|
||||
flavor = RubySymbol.newSymbol(runtime, "tasteless".intern());
|
||||
}
|
||||
break;
|
||||
case 2:
|
||||
{{p = ((te))-1;}
|
||||
pass_through(input.substring(p, p + 1));
|
||||
tagstart = p + 1;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
break;
|
||||
// line 590 "JavaScanner.java"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
case 2:
|
||||
_acts = _parser_to_state_actions[cs];
|
||||
_nacts = (int) _parser_actions[_acts++];
|
||||
while ( _nacts-- > 0 ) {
|
||||
switch ( _parser_actions[_acts++] ) {
|
||||
case 14:
|
||||
// line 1 "NONE"
|
||||
{ts = -1;}
|
||||
break;
|
||||
// line 604 "JavaScanner.java"
|
||||
}
|
||||
}
|
||||
|
||||
if ( cs == 0 ) {
|
||||
_goto_targ = 5;
|
||||
continue _goto;
|
||||
}
|
||||
if ( ++p != pe ) {
|
||||
_goto_targ = 1;
|
||||
continue _goto;
|
||||
}
|
||||
case 4:
|
||||
if ( p == eof )
|
||||
{
|
||||
if ( _parser_eof_trans[cs] > 0 ) {
|
||||
_trans = _parser_eof_trans[cs] - 1;
|
||||
_goto_targ = 3;
|
||||
continue _goto;
|
||||
}
|
||||
}
|
||||
|
||||
case 5:
|
||||
}
|
||||
break; }
|
||||
}
|
||||
|
||||
// line 176 "JavaScanner.rl"
|
||||
|
||||
return rv;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,179 @@
|
|||
%%{
|
||||
machine parser;
|
||||
|
||||
action _prefix { mark_pfx = p; }
|
||||
action prefix {
|
||||
prefix = input.substring(mark_pfx, p);
|
||||
}
|
||||
action _check_prefix {
|
||||
if ( !prefix.equals(tag_prefix) ) {
|
||||
// have to manually add ':' / Sep
|
||||
// pass the text through & reset state
|
||||
pass_through(input.substring(tagstart, p) + ":");
|
||||
prefix = "";
|
||||
fgoto main;
|
||||
}
|
||||
}
|
||||
|
||||
action _starttag { mark_stg = p; }
|
||||
action starttag { name = input.substring(mark_stg, p); }
|
||||
action _attr { mark_attr = p; }
|
||||
action attr {
|
||||
attributes.op_aset(
|
||||
runtime.getCurrentContext(),
|
||||
RubyString.newString(runtime, nat),
|
||||
RubyString.newString(runtime, vat)
|
||||
);
|
||||
}
|
||||
|
||||
action _nameattr { mark_nat = p; }
|
||||
action nameattr { nat = input.substring(mark_nat, p); }
|
||||
action _valattr { mark_vat = p; }
|
||||
action valattr { vat = input.substring(mark_vat, p); }
|
||||
|
||||
action opentag { flavor = RubySymbol.newSymbol(runtime, "open".intern()); }
|
||||
action selftag { flavor = RubySymbol.newSymbol(runtime, "self".intern()); }
|
||||
action closetag { flavor = RubySymbol.newSymbol(runtime, "close".intern()); }
|
||||
|
||||
Closeout := empty;
|
||||
|
||||
# words
|
||||
PrefixChar = [\-A-Za-z0-9._?] ;
|
||||
NameChar = [\-A-Za-z0-9._:?] ;
|
||||
TagName = NameChar+ >_starttag %starttag;
|
||||
Prefix = PrefixChar+ >_prefix %prefix;
|
||||
Open = "<";
|
||||
Sep = ":" >_check_prefix;
|
||||
End = "/";
|
||||
Close = ">";
|
||||
|
||||
Name = Prefix Sep TagName;
|
||||
|
||||
NameAttr = NameChar+ >_nameattr %nameattr;
|
||||
Q1Char = ( "\\\'" | [^'] ) ;
|
||||
Q1Attr = Q1Char* >_valattr %valattr;
|
||||
Q2Char = ( "\\\"" | [^"] ) ;
|
||||
Q2Attr = Q2Char* >_valattr %valattr;
|
||||
|
||||
Attr = NameAttr space* "=" space* ('"' Q2Attr '"' | "'" Q1Attr "'") space* >_attr %attr;
|
||||
Attrs = (space+ Attr* | empty);
|
||||
|
||||
CloseTrailer = End Close %selftag;
|
||||
OpenTrailer = Close %opentag;
|
||||
|
||||
Trailer = (OpenTrailer | CloseTrailer);
|
||||
|
||||
OpenOrSelfTag = Name Attrs? Trailer;
|
||||
CloseTag = End Name space* Close %closetag;
|
||||
|
||||
SomeTag = Open (OpenOrSelfTag | CloseTag);
|
||||
|
||||
main := |*
|
||||
SomeTag => {
|
||||
tag(prefix, name, attributes, flavor);
|
||||
prefix = "";
|
||||
name = "";
|
||||
attributes = RubyHash.newHash(runtime);
|
||||
flavor = RubySymbol.newSymbol(runtime, "tasteless".intern());
|
||||
};
|
||||
any => {
|
||||
pass_through(input.substring(p, p + 1));
|
||||
tagstart = p + 1;
|
||||
};
|
||||
*|;
|
||||
}%%
|
||||
|
||||
package radius.parser;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.LinkedList;
|
||||
import org.jruby.Ruby; // runtime
|
||||
import org.jruby.RubyObject;
|
||||
import org.jruby.runtime.builtin.IRubyObject;
|
||||
import org.jruby.RubyArray;
|
||||
import org.jruby.RubyString;
|
||||
import org.jruby.RubyHash;
|
||||
import org.jruby.RubySymbol;
|
||||
|
||||
public class JavaScanner {
|
||||
|
||||
Ruby runtime = null;
|
||||
RubyArray rv = null;
|
||||
|
||||
void pass_through(String str) {
|
||||
RubyObject last = ((RubyObject)rv.last());
|
||||
if ( rv.size() > 0 && last != null && (last instanceof RubyString) ){
|
||||
// XXX concat changes for ruby 1.9
|
||||
((RubyString) last).concat(RubyString.newString(runtime, str));
|
||||
} else {
|
||||
rv.append(RubyString.newString(runtime, str));
|
||||
}
|
||||
}
|
||||
|
||||
void tag(String prefix, String name, RubyHash attr, RubySymbol flavor) {
|
||||
RubyHash tag = RubyHash.newHash(runtime);
|
||||
tag.op_aset(
|
||||
runtime.getCurrentContext(),
|
||||
RubySymbol.newSymbol(runtime, "prefix"),
|
||||
RubyString.newString(runtime, prefix)
|
||||
);
|
||||
tag.op_aset(
|
||||
runtime.getCurrentContext(),
|
||||
RubySymbol.newSymbol(runtime, "name"),
|
||||
RubyString.newString(runtime, name)
|
||||
);
|
||||
tag.op_aset(
|
||||
runtime.getCurrentContext(),
|
||||
RubySymbol.newSymbol(runtime, "attrs"),
|
||||
attr
|
||||
);
|
||||
tag.op_aset(
|
||||
runtime.getCurrentContext(),
|
||||
RubySymbol.newSymbol(runtime, "flavor"),
|
||||
flavor
|
||||
);
|
||||
rv.append(tag);
|
||||
}
|
||||
|
||||
public JavaScanner(Ruby runtime) {
|
||||
this.runtime = runtime;
|
||||
}
|
||||
|
||||
%% write data;
|
||||
|
||||
public RubyArray operate(String tag_prefix, String input) {
|
||||
char[] data = input.toCharArray();
|
||||
String disposable_string;
|
||||
|
||||
String name = "";
|
||||
String prefix = "";
|
||||
RubySymbol flavor = RubySymbol.newSymbol(runtime, "tasteless".intern());
|
||||
RubyHash attributes = RubyHash.newHash(runtime);
|
||||
|
||||
int tagstart = 0;
|
||||
int mark_pfx = 0;
|
||||
int mark_stg = 0;
|
||||
int mark_attr = 0;
|
||||
int mark_nat = 0;
|
||||
int mark_vat = 0;
|
||||
|
||||
String nat = "";
|
||||
String vat = "";
|
||||
|
||||
int cs;
|
||||
int p = 0;
|
||||
int pe = data.length;
|
||||
int eof = pe;
|
||||
int act;
|
||||
int ts;
|
||||
int te;
|
||||
|
||||
rv = RubyArray.newArray(runtime);
|
||||
char[] remainder = data;
|
||||
|
||||
%% write init;
|
||||
%% write exec;
|
||||
|
||||
return rv;
|
||||
}
|
||||
}
|
Binary file not shown.
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,125 @@
|
|||
%%{
|
||||
machine parser;
|
||||
|
||||
|
||||
action _prefix { mark_pfx = p }
|
||||
action prefix {
|
||||
if data[mark_pfx..p-1] != @prefix
|
||||
closing = data[mark_pfx-1,1] == '/'
|
||||
@nodes.last << data[mark_pfx-(closing ? 2 : 1)..p]
|
||||
fbreak;
|
||||
end
|
||||
}
|
||||
action _starttag { mark_stg = p }
|
||||
action starttag { @starttag = data[mark_stg..p-1] }
|
||||
action _attr { mark_attr = p }
|
||||
action attr {
|
||||
@attrs[@nat] = @vat
|
||||
}
|
||||
|
||||
action prematch {
|
||||
@prematch_end = p
|
||||
@prematch = data[0..p] if p > 0
|
||||
}
|
||||
|
||||
action _nameattr { mark_nat = p }
|
||||
action nameattr { @nat = data[mark_nat..p-1] }
|
||||
action _valattr { mark_vat = p }
|
||||
action valattr { @vat = data[mark_vat..p-1] }
|
||||
|
||||
action opentag { @flavor = :open }
|
||||
action selftag { @flavor = :self }
|
||||
action closetag { @flavor = :close }
|
||||
|
||||
action stopparse {
|
||||
@cursor = p;
|
||||
fbreak;
|
||||
}
|
||||
|
||||
|
||||
Closeout := empty;
|
||||
|
||||
# words
|
||||
PrefixChar = [\-A-Za-z0-9._?] ;
|
||||
NameChar = [\-A-Za-z0-9._:?] ;
|
||||
TagName = NameChar+ >_starttag %starttag;
|
||||
Prefix = PrefixChar+ >_prefix %prefix;
|
||||
|
||||
Name = Prefix ":" TagName;
|
||||
|
||||
NameAttr = NameChar+ >_nameattr %nameattr;
|
||||
Q1Char = ( "\\\'" | [^'] ) ;
|
||||
Q1Attr = Q1Char* >_valattr %valattr;
|
||||
Q2Char = ( "\\\"" | [^"] ) ;
|
||||
Q2Attr = Q2Char* >_valattr %valattr;
|
||||
|
||||
Attr = NameAttr space* "=" space* ('"' Q2Attr '"' | "'" Q1Attr "'") space* >_attr %attr;
|
||||
Attrs = (space+ Attr* | empty);
|
||||
|
||||
CloseTrailer = "/>" %selftag;
|
||||
OpenTrailer = ">" %opentag;
|
||||
|
||||
Trailer = (OpenTrailer | CloseTrailer);
|
||||
|
||||
OpenOrSelfTag = Name Attrs? Trailer;
|
||||
CloseTag = "/" Name space* ">" %closetag;
|
||||
|
||||
SomeTag = '<' (OpenOrSelfTag | CloseTag);
|
||||
|
||||
main := |*
|
||||
SomeTag => {
|
||||
tag = {:prefix=>@prefix, :name=>@starttag, :flavor => @flavor, :attrs => @attrs}
|
||||
@prefix = nil
|
||||
@name = nil
|
||||
@flavor = :tasteless
|
||||
@attrs = {}
|
||||
@nodes << tag << ''
|
||||
fbreak;
|
||||
};
|
||||
any => {
|
||||
@nodes.last << data[p]
|
||||
@tagstart = p
|
||||
};
|
||||
*|;
|
||||
}%%
|
||||
|
||||
module Radius
|
||||
class Scanner
|
||||
def operate(prefix, data)
|
||||
data = Radius::OrdString.new data
|
||||
buf = ""
|
||||
csel = ""
|
||||
@prematch = ''
|
||||
@starttag = nil
|
||||
@attrs = {}
|
||||
@flavor = :tasteless
|
||||
@cursor = 0
|
||||
@tagstart = 0
|
||||
@nodes = ['']
|
||||
remainder = data.dup
|
||||
|
||||
until remainder.length == 0
|
||||
p = perform_parse(prefix, remainder)
|
||||
remainder = remainder[p..-1]
|
||||
end
|
||||
|
||||
return @nodes
|
||||
end
|
||||
|
||||
private
|
||||
def perform_parse(prefix, data)
|
||||
stack = []
|
||||
p = 0
|
||||
ts = 0
|
||||
te = 0
|
||||
act = 0
|
||||
eof = data.length
|
||||
|
||||
@prefix = prefix
|
||||
%% write data;
|
||||
%% write init;
|
||||
%% write exec;
|
||||
return p
|
||||
end
|
||||
end
|
||||
end
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,126 @@
|
|||
%%{
|
||||
machine parser;
|
||||
|
||||
|
||||
# action _prefix { mark_pfx = p }
|
||||
# action prefix {
|
||||
# if data[mark_pfx..p-1] != @prefix
|
||||
# @nodes.last << data[mark_pfx-1..p]
|
||||
# fbreak;
|
||||
# end
|
||||
# }
|
||||
action _starttag { mark_stg = p }
|
||||
action starttag { @starttag = data[mark_stg..p-1] }
|
||||
action _attr { mark_attr = p }
|
||||
action attr {
|
||||
@attrs[@nat] = @vat
|
||||
}
|
||||
|
||||
action prematch {
|
||||
@prematch_end = p
|
||||
@prematch = data[0..p] if p > 0
|
||||
}
|
||||
|
||||
action _nameattr { mark_nat = p }
|
||||
action nameattr { @nat = data[mark_nat..p-1] }
|
||||
action _valattr { mark_vat = p }
|
||||
action valattr { @vat = data[mark_vat..p-1] }
|
||||
|
||||
action opentag { @flavor = :open }
|
||||
action selftag { @flavor = :self }
|
||||
action closetag { @flavor = :close }
|
||||
|
||||
action stopparse {
|
||||
@cursor = p;
|
||||
fbreak;
|
||||
}
|
||||
|
||||
|
||||
Closeout := empty;
|
||||
|
||||
# words
|
||||
# PrefixChar = [\-A-Za-z0-9._?] ;
|
||||
NameChar = [\-A-Za-z0-9._:?] ;
|
||||
TagName = NameChar+ >_starttag %starttag;
|
||||
# Prefix = PrefixChar+ >_prefix %prefix;
|
||||
|
||||
# Name = Prefix ":" TagName;
|
||||
|
||||
NameAttr = NameChar+ >_nameattr %nameattr;
|
||||
Q1Char = ( "\\\'" | [^'] ) ;
|
||||
Q1Attr = Q1Char* >_valattr %valattr;
|
||||
Q2Char = ( "\\\"" | [^"] ) ;
|
||||
Q2Attr = Q2Char* >_valattr %valattr;
|
||||
|
||||
Attr = NameAttr space* "=" space* ('"' Q2Attr '"' | "'" Q1Attr "'") space* >_attr %attr;
|
||||
Attrs = (space+ Attr* | empty);
|
||||
|
||||
CloseTrailer = "/}" %selftag;
|
||||
OpenTrailer = "}" %opentag;
|
||||
|
||||
Trailer = (OpenTrailer | CloseTrailer);
|
||||
|
||||
# OpenOrSelfTag = Name Attrs? Trailer;
|
||||
OpenOrSelfTag = TagName Attrs? Trailer;
|
||||
# CloseTag = "/" Name space* "}" %closetag;
|
||||
CloseTag = "/" TagName space* "}" %closetag;
|
||||
|
||||
SomeTag = '{' space* (OpenOrSelfTag | CloseTag);
|
||||
|
||||
main := |*
|
||||
SomeTag => {
|
||||
tag = {:prefix=>@prefix, :name=>@starttag, :flavor => @flavor, :attrs => @attrs}
|
||||
@prefix = nil
|
||||
@name = nil
|
||||
@flavor = :tasteless
|
||||
@attrs = {}
|
||||
@nodes << tag << ''
|
||||
fbreak;
|
||||
};
|
||||
any => {
|
||||
@nodes.last << data[p]
|
||||
@tagstart = p
|
||||
};
|
||||
*|;
|
||||
}%%
|
||||
|
||||
module Radius
|
||||
class SquiggleScanner
|
||||
def operate(prefix, data)
|
||||
data = Radius::OrdString.new data
|
||||
buf = ""
|
||||
csel = ""
|
||||
@prematch = ''
|
||||
@starttag = nil
|
||||
@attrs = {}
|
||||
@flavor = :tasteless
|
||||
@cursor = 0
|
||||
@tagstart = 0
|
||||
@nodes = ['']
|
||||
remainder = data.dup
|
||||
|
||||
until remainder.length == 0
|
||||
p = perform_parse(prefix, remainder)
|
||||
remainder = remainder[p..-1]
|
||||
end
|
||||
|
||||
return @nodes
|
||||
end
|
||||
|
||||
private
|
||||
def perform_parse(prefix, data)
|
||||
stack = []
|
||||
p = 0
|
||||
ts = 0
|
||||
te = 0
|
||||
act = 0
|
||||
eof = data.length
|
||||
|
||||
@prefix = prefix
|
||||
%% write data;
|
||||
%% write init;
|
||||
%% write exec;
|
||||
return p
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,71 @@
|
|||
module Radius
|
||||
#
|
||||
# A tag binding is passed into each tag definition and contains helper methods for working
|
||||
# with tags. Use it to gain access to the attributes that were passed to the tag, to
|
||||
# render the tag contents, and to do other tasks.
|
||||
#
|
||||
class TagBinding
|
||||
# The Context that the TagBinding is associated with. Used internally. Try not to use
|
||||
# this object directly.
|
||||
attr_reader :context
|
||||
|
||||
# The locals object for the current tag.
|
||||
attr_reader :locals
|
||||
|
||||
# The name of the tag (as used in a template string).
|
||||
attr_reader :name
|
||||
|
||||
# The attributes of the tag. Also aliased as TagBinding#attr.
|
||||
attr_reader :attributes
|
||||
alias :attr :attributes
|
||||
|
||||
# The render block. When called expands the contents of the tag. Use TagBinding#expand
|
||||
# instead.
|
||||
attr_reader :block
|
||||
|
||||
# Creates a new TagBinding object.
|
||||
def initialize(context, locals, name, attributes, block)
|
||||
@context, @locals, @name, @attributes, @block = context, locals, name, attributes, block
|
||||
end
|
||||
|
||||
# Evaluates the current tag and returns the rendered contents.
|
||||
def expand
|
||||
double? ? block.call : ''
|
||||
end
|
||||
|
||||
# Returns true if the current tag is a single tag.
|
||||
def single?
|
||||
block.nil?
|
||||
end
|
||||
|
||||
# Returns true if the current tag is a container tag.
|
||||
def double?
|
||||
not single?
|
||||
end
|
||||
|
||||
# The globals object from which all locals objects ultimately inherit their values.
|
||||
def globals
|
||||
@context.globals
|
||||
end
|
||||
|
||||
# Returns a list of the way tags are nested around the current tag as a string.
|
||||
def nesting
|
||||
@context.current_nesting
|
||||
end
|
||||
|
||||
# Fires off Context#tag_missing for the current tag.
|
||||
def missing!
|
||||
@context.tag_missing(name, attributes, &block)
|
||||
end
|
||||
|
||||
# Renders the tag using the current context .
|
||||
def render(tag, attributes = {}, &block)
|
||||
@context.render_tag(tag, attributes, &block)
|
||||
end
|
||||
|
||||
# Shortcut for accessing tag.attr[key]
|
||||
def [](key)
|
||||
attr[key]
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,78 @@
|
|||
module Radius
|
||||
module TagDefinitions # :nodoc:
|
||||
class TagFactory # :nodoc:
|
||||
def initialize(context)
|
||||
@context = context
|
||||
end
|
||||
|
||||
def define_tag(name, options, &block)
|
||||
options = prepare_options(name, options)
|
||||
validate_params(name, options, &block)
|
||||
construct_tag_set(name, options, &block)
|
||||
expose_methods_as_tags(name, options)
|
||||
end
|
||||
|
||||
protected
|
||||
|
||||
# Adds the tag definition to the context. Override in subclasses to add additional tags
|
||||
# (child tags) when the tag is created.
|
||||
def construct_tag_set(name, options, &block)
|
||||
if block
|
||||
@context.definitions[name.to_s] = block
|
||||
else
|
||||
lp = last_part(name)
|
||||
@context.define_tag(name) do |tag|
|
||||
if tag.single?
|
||||
options[:for]
|
||||
else
|
||||
tag.locals.send("#{ lp }=", options[:for]) unless options[:for].nil?
|
||||
tag.expand
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# Normalizes options pased to tag definition. Override in decendants to preform
|
||||
# additional normalization.
|
||||
def prepare_options(name, options)
|
||||
options = Utility.symbolize_keys(options)
|
||||
options[:expose] = expand_array_option(options[:expose])
|
||||
object = options[:for]
|
||||
options[:attributes] = object.respond_to?(:attributes) unless options.has_key? :attributes
|
||||
options[:expose] += object.attributes.keys if options[:attributes]
|
||||
options
|
||||
end
|
||||
|
||||
# Validates parameters passed to tag definition. Override in decendants to add custom
|
||||
# validations.
|
||||
def validate_params(name, options, &block)
|
||||
unless options.has_key? :for
|
||||
raise ArgumentError.new("tag definition must contain a :for option or a block") unless block
|
||||
raise ArgumentError.new("tag definition must contain a :for option when used with the :expose option") unless options[:expose].empty?
|
||||
end
|
||||
end
|
||||
|
||||
# Exposes the methods of an object as child tags.
|
||||
def expose_methods_as_tags(name, options)
|
||||
options[:expose].each do |method|
|
||||
tag_name = "#{name}:#{method}"
|
||||
lp = last_part(name)
|
||||
@context.define_tag(tag_name) do |tag|
|
||||
object = tag.locals.send(lp)
|
||||
object.send(method)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
protected
|
||||
|
||||
def expand_array_option(value)
|
||||
[*value].compact.map { |m| m.to_s.intern }
|
||||
end
|
||||
|
||||
def last_part(name)
|
||||
name.split(':').last
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,40 @@
|
|||
module Radius
|
||||
module Utility # :nodoc:
|
||||
def self.symbolize_keys(hash)
|
||||
new_hash = {}
|
||||
hash.keys.each do |k|
|
||||
new_hash[k.to_s.intern] = hash[k]
|
||||
end
|
||||
new_hash
|
||||
end
|
||||
|
||||
def self.impartial_hash_delete(hash, key)
|
||||
string = key.to_s
|
||||
symbol = string.intern
|
||||
value1 = hash.delete(symbol)
|
||||
value2 = hash.delete(string)
|
||||
value1 || value2
|
||||
end
|
||||
|
||||
def self.constantize(camelized_string)
|
||||
raise "invalid constant name `#{camelized_string}'" unless camelized_string.split('::').all? { |part| part =~ /^[A-Za-z]+$/ }
|
||||
Object.module_eval(camelized_string)
|
||||
end
|
||||
|
||||
def self.camelize(underscored_string)
|
||||
string = ''
|
||||
underscored_string.split('_').each { |part| string << part.capitalize }
|
||||
string
|
||||
end
|
||||
|
||||
if RUBY_VERSION[0,3] == '1.8'
|
||||
def self.array_to_s(c)
|
||||
c.to_s
|
||||
end
|
||||
else
|
||||
def self.array_to_s(c)
|
||||
c.map{|x| x.is_a?(Array) ? array_to_s(x) : x.to_s }.join
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,8 @@
|
|||
module Radius #:nodoc:
|
||||
def self.version
|
||||
@version ||= begin
|
||||
filename = File.join(File.dirname(__FILE__), '..', '..', 'VERSION')
|
||||
IO.read(filename).strip
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,91 @@
|
|||
# Generated by jeweler
|
||||
# DO NOT EDIT THIS FILE DIRECTLY
|
||||
# Instead, edit Jeweler::Tasks in Rakefile, and run the gemspec command
|
||||
# -*- encoding: utf-8 -*-
|
||||
|
||||
Gem::Specification.new do |s|
|
||||
s.name = %q{radius}
|
||||
s.version = "0.7.0.prerelease"
|
||||
|
||||
s.required_rubygems_version = Gem::Requirement.new("> 1.3.1") if s.respond_to? :required_rubygems_version=
|
||||
s.authors = ["John W. Long (me@johnwlong.com)", "David Chelimsky (dchelimsky@gmail.com)", "Bryce Kerley (bkerley@brycekerley.net)"]
|
||||
s.date = %q{2010-05-24}
|
||||
s.description = %q{Radius is a powerful tag-based template language for Ruby inspired by the template languages used in MovableType and TextPattern. It uses tags similar to XML, but can be used to generate any form of plain text (HTML, e-mail, etc...).}
|
||||
s.email = %q{me@johnwlong.com}
|
||||
s.extra_rdoc_files = [
|
||||
"CHANGELOG",
|
||||
"LICENSE",
|
||||
"QUICKSTART.rdoc",
|
||||
"README.rdoc"
|
||||
]
|
||||
s.files = [
|
||||
"CHANGELOG",
|
||||
"LICENSE",
|
||||
"QUICKSTART.rdoc",
|
||||
"README.rdoc",
|
||||
"Rakefile",
|
||||
"VERSION",
|
||||
"lib/radius.rb",
|
||||
"lib/radius/context.rb",
|
||||
"lib/radius/delegating_open_struct.rb",
|
||||
"lib/radius/error.rb",
|
||||
"lib/radius/parse_tag.rb",
|
||||
"lib/radius/parser.rb",
|
||||
"lib/radius/parser/JavaScanner$Flavor.class",
|
||||
"lib/radius/parser/JavaScanner$Tag.class",
|
||||
"lib/radius/parser/JavaScanner.class",
|
||||
"lib/radius/parser/JavaScanner.java",
|
||||
"lib/radius/parser/JavaScanner.rl",
|
||||
"lib/radius/parser/java_scanner.jar",
|
||||
"lib/radius/parser/scanner.rb",
|
||||
"lib/radius/parser/scanner.rl",
|
||||
"lib/radius/parser/squiggle_scanner.rb",
|
||||
"lib/radius/parser/squiggle_scanner.rl",
|
||||
"lib/radius/tag_binding.rb",
|
||||
"lib/radius/tag_definitions.rb",
|
||||
"lib/radius/utility.rb",
|
||||
"lib/radius/version.rb",
|
||||
"tasks/jeweler.rake",
|
||||
"tasks/rdoc.rake",
|
||||
"tasks/rubinius.rake",
|
||||
"tasks/scan.rake",
|
||||
"tasks/test.rake",
|
||||
"test/benchmarks.rb",
|
||||
"test/context_test.rb",
|
||||
"test/multithreaded_test.rb",
|
||||
"test/parser_test.rb",
|
||||
"test/quickstart_test.rb",
|
||||
"test/squiggle_test.rb",
|
||||
"test/test_helper.rb",
|
||||
"test/utility_test.rb"
|
||||
]
|
||||
s.homepage = %q{http://github.com/jlong/radius}
|
||||
s.rdoc_options = ["--charset=UTF-8"]
|
||||
s.require_paths = ["lib"]
|
||||
s.rubygems_version = %q{1.3.6}
|
||||
s.summary = %q{A tag-based templating language for Ruby.}
|
||||
s.test_files = [
|
||||
"test/context_test.rb",
|
||||
"test/parser_test.rb",
|
||||
"test/quickstart_test.rb",
|
||||
"test/test_helper.rb",
|
||||
"test/multithreaded_test.rb",
|
||||
"test/squiggle_test.rb",
|
||||
"test/utility_test.rb",
|
||||
"test/benchmarks.rb"
|
||||
]
|
||||
|
||||
if s.respond_to? :specification_version then
|
||||
current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
|
||||
s.specification_version = 3
|
||||
|
||||
if Gem::Version.new(Gem::RubyGemsVersion) >= Gem::Version.new('1.2.0') then
|
||||
s.add_development_dependency(%q<RedCloth>, [">= 0"])
|
||||
else
|
||||
s.add_dependency(%q<RedCloth>, [">= 0"])
|
||||
end
|
||||
else
|
||||
s.add_dependency(%q<RedCloth>, [">= 0"])
|
||||
end
|
||||
end
|
||||
|
|
@ -0,0 +1,22 @@
|
|||
begin
|
||||
require 'jeweler'
|
||||
Jeweler::Tasks.new do |gem|
|
||||
gem.name = "radius"
|
||||
gem.summary = "A tag-based templating language for Ruby."
|
||||
gem.description = "Radius is a powerful tag-based template language for Ruby inspired by the template languages used in MovableType and TextPattern. It uses tags similar to XML, but can be used to generate any form of plain text (HTML, e-mail, etc...)."
|
||||
gem.email = "me@johnwlong.com"
|
||||
gem.homepage = "http://github.com/jlong/radius"
|
||||
gem.authors = [
|
||||
"John W. Long (me@johnwlong.com)",
|
||||
"David Chelimsky (dchelimsky@gmail.com)",
|
||||
"Bryce Kerley (bkerley@brycekerley.net)"
|
||||
]
|
||||
gem.files = FileList["[A-Z]*", "{bin,lib,tasks,test}/**/*"].exclude("tmp")
|
||||
gem.extra_rdoc_files = ['README.rdoc', 'QUICKSTART.rdoc', 'LICENSE', 'CHANGELOG']
|
||||
gem.add_development_dependency('RedCloth')
|
||||
# gem is a Gem::Specification... see http://www.rubygems.org/read/chapter/20 for additional settings
|
||||
end
|
||||
Jeweler::GemcutterTasks.new
|
||||
rescue LoadError
|
||||
puts "Jeweler (or a dependency) not available. Install it with: gem install jeweler"
|
||||
end
|
|
@ -0,0 +1,13 @@
|
|||
require 'rake/rdoctask'
|
||||
Rake::RDocTask.new do |rdoc|
|
||||
version = File.exist?('VERSION') ? File.read('VERSION') : ""
|
||||
|
||||
rdoc.rdoc_dir = 'rdoc'
|
||||
rdoc.title = "Radius #{version}"
|
||||
rdoc.main = "README.rdoc"
|
||||
rdoc.rdoc_files.include('*.rdoc')
|
||||
rdoc.rdoc_files.include('LICENSE')
|
||||
rdoc.rdoc_files.include('CHANGELOG')
|
||||
rdoc.rdoc_files.include('QUICKSTART.rdoc')
|
||||
rdoc.rdoc_files.include('lib/**/*.rb')
|
||||
end
|
|
@ -0,0 +1,4 @@
|
|||
desc "remove Rubinius rbc files"
|
||||
task "rubinius:clean" do
|
||||
(Dir['**/*.rbc']).each { |f| rm f }
|
||||
end
|
|
@ -0,0 +1,79 @@
|
|||
namespace :scan do
|
||||
desc 'Generate the parsers'
|
||||
task 'build' => [
|
||||
'lib/radius/parser/scanner.rb',
|
||||
'lib/radius/parser/squiggle_scanner.rb',
|
||||
'lib/radius/parser/java_scanner.jar'
|
||||
]
|
||||
|
||||
desc 'Generate a PDF state graph from the parsers'
|
||||
task 'graph' => ['doc/scanner.pdf', 'doc/squiggle_scanner.pdf']
|
||||
|
||||
desc 'turn the scanner.rl file into a ruby file'
|
||||
file 'lib/radius/parser/scanner.rb' => 'lib/radius/parser/scanner.rl' do |t|
|
||||
cd 'lib/radius/parser' do
|
||||
sh "ragel -R -F1 scanner.rl"
|
||||
end
|
||||
end
|
||||
|
||||
desc 'turn the squiggle_scanner.rl file into a ruby file'
|
||||
file 'lib/radius/parser/squiggle_scanner.rb' =>
|
||||
['lib/radius/parser/squiggle_scanner.rl'] \
|
||||
do |t|
|
||||
cd 'lib/radius/parser' do
|
||||
sh "ragel -R -F1 squiggle_scanner.rl"
|
||||
end
|
||||
end
|
||||
|
||||
desc 'package JavaScanner into a jar file'
|
||||
file 'lib/radius/parser/java_scanner.jar' => 'lib/radius/parser/JavaScanner.class' do
|
||||
cd 'lib' do
|
||||
sh "jar -cf radius/parser/java_scanner.jar radius/parser/*.class"
|
||||
end
|
||||
end
|
||||
|
||||
desc 'turn the JavaScanner.java file into a java class file'
|
||||
file 'lib/radius/parser/JavaScanner.class' => 'lib/radius/parser/JavaScanner.java' do |t|
|
||||
cd 'lib' do
|
||||
jruby_path = ENV['JRUBY_HOME'] || '/usr/local/jruby/current'
|
||||
sh "javac -cp #{jruby_path}/lib/jruby.jar radius/parser/JavaScanner.java"
|
||||
end
|
||||
end
|
||||
|
||||
desc 'turn the JavaScanner.rl file into a java source file'
|
||||
file 'lib/radius/parser/JavaScanner.java' => 'lib/radius/parser/JavaScanner.rl' do |t|
|
||||
cd 'lib/radius/parser' do
|
||||
sh "ragel -J -F1 JavaScanner.rl"
|
||||
end
|
||||
end
|
||||
|
||||
desc 'pdf of the ragel scanner'
|
||||
file 'doc/scanner.pdf' => 'lib/radius/parser/scanner.dot' do |t|
|
||||
cd 'lib/radius/parser' do
|
||||
sh "dot -Tpdf -o ../../../doc/scanner.pdf scanner.dot"
|
||||
end
|
||||
end
|
||||
|
||||
desc 'pdf of the ragel squiggle scanner'
|
||||
file 'doc/squiggle_scanner.pdf' =>
|
||||
['lib/radius/parser/squiggle_scanner.dot'] \
|
||||
do |t|
|
||||
cd 'lib/radius/parser' do
|
||||
sh "dot -Tpdf -o ../../../doc/squiggle_scanner.pdf squiggle_scanner.dot"
|
||||
end
|
||||
end
|
||||
|
||||
file 'lib/radius/parser/scanner.dot' => 'lib/radius/parser/scanner.rl' do |t|
|
||||
cd 'lib/radius/parser' do
|
||||
sh "ragel -Vp scanner.rl > scanner.dot"
|
||||
end
|
||||
end
|
||||
|
||||
file 'lib/radius/parser/squiggle_scanner.dot' =>
|
||||
['lib/radius/parser/squiggle_scanner.rl'] \
|
||||
do |t|
|
||||
cd 'lib/radius/parser' do
|
||||
sh "ragel -Vp squiggle_scanner.rl > squiggle_scanner.dot"
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,7 @@
|
|||
require 'rake/testtask'
|
||||
|
||||
Rake::TestTask.new do |t|
|
||||
t.libs << "lib" << "test"
|
||||
t.test_files = FileList['test/*_test.rb']
|
||||
t.verbose = true
|
||||
end
|
|
@ -0,0 +1,35 @@
|
|||
$: << File.join(File.dirname(__FILE__), '..', 'lib')
|
||||
require 'radius'
|
||||
|
||||
if RUBY_PLATFORM == 'java'
|
||||
require 'java'
|
||||
require 'radius/parser/jscanner'
|
||||
end
|
||||
|
||||
require 'benchmark'
|
||||
|
||||
document = <<EOF
|
||||
Before it all
|
||||
<r:foo>
|
||||
Middle Top
|
||||
<r:bar />
|
||||
Middle Bottom
|
||||
</r:foo>
|
||||
After it all
|
||||
EOF
|
||||
|
||||
amount = 1000
|
||||
|
||||
Benchmark.bmbm do |bm|
|
||||
bm.report('vanilla') do
|
||||
scanner = Radius::Scanner.new(:scanner => Radius::Scanner)
|
||||
amount.times { scanner.operate('r', document) }
|
||||
end
|
||||
|
||||
if RUBY_PLATFORM == 'java'
|
||||
bm.report('JavaScanner') do
|
||||
scanner = Radius::JavaScanner.new(JRuby.runtime)
|
||||
amount.times { scanner.operate('r', document) }
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,61 @@
|
|||
require File.expand_path(File.dirname(__FILE__) + '/test_helper')
|
||||
|
||||
class RadiusContextTest < Test::Unit::TestCase
|
||||
include RadiusTestHelper
|
||||
|
||||
def setup
|
||||
@context = new_context
|
||||
end
|
||||
|
||||
def test_initialize
|
||||
@context = Radius::Context.new
|
||||
end
|
||||
|
||||
def test_initialize_with_block
|
||||
@context = Radius::Context.new do |c|
|
||||
assert_kind_of Radius::Context, c
|
||||
c.define_tag('test') { 'just a test' }
|
||||
end
|
||||
assert_not_equal Hash.new, @context.definitions
|
||||
end
|
||||
|
||||
def test_with
|
||||
got = @context.with do |c|
|
||||
assert_equal @context, c
|
||||
end
|
||||
assert_equal @context, got
|
||||
end
|
||||
|
||||
def test_render_tag
|
||||
define_global_tag "hello" do |tag|
|
||||
"Hello #{tag.attr['name'] || 'World'}!"
|
||||
end
|
||||
assert_render_tag_output 'Hello World!', 'hello'
|
||||
assert_render_tag_output 'Hello John!', 'hello', 'name' => 'John'
|
||||
end
|
||||
|
||||
def test_render_tag__undefined_tag
|
||||
e = assert_raises(Radius::UndefinedTagError) { @context.render_tag('undefined_tag') }
|
||||
assert_equal "undefined tag `undefined_tag'", e.message
|
||||
end
|
||||
|
||||
def test_tag_missing
|
||||
class << @context
|
||||
def tag_missing(tag, attr, &block)
|
||||
"undefined tag `#{tag}' with attributes #{attr.inspect}"
|
||||
end
|
||||
end
|
||||
|
||||
text = ''
|
||||
expected = %{undefined tag `undefined_tag' with attributes {"cool"=>"beans"}}
|
||||
assert_nothing_raised { text = @context.render_tag('undefined_tag', 'cool' => 'beans') }
|
||||
assert_equal expected, text
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def assert_render_tag_output(output, *render_tag_params)
|
||||
assert_equal output, @context.render_tag(*render_tag_params)
|
||||
end
|
||||
|
||||
end
|
|
@ -0,0 +1,62 @@
|
|||
require 'test/unit'
|
||||
require 'radius'
|
||||
|
||||
class MultithreadTest < Test::Unit::TestCase
|
||||
|
||||
def setup
|
||||
Thread.abort_on_exception
|
||||
@context = Radius::Context.new do |c|
|
||||
c.define_tag('thread') do |tag|
|
||||
"#{tag.locals.thread_id} / #{tag.globals.object_id}"
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
if RUBY_PLATFORM == 'java'
|
||||
require 'java'
|
||||
# call once before the thread to keep from using hidden require in a thread
|
||||
Radius::Parser.new
|
||||
def test_runs_multithreaded
|
||||
lock = java.lang.String.new("lock")
|
||||
threads = []
|
||||
1000.times do |t|
|
||||
thread = Thread.new do
|
||||
parser = Radius::Parser.new(@context, :tag_prefix => 'r')
|
||||
parser.context.globals.thread_id = Thread.current.object_id
|
||||
expected = "#{Thread.current.object_id} / "+
|
||||
"#{parser.context.globals.object_id}"
|
||||
actual = parser.parse('<r:thread />')
|
||||
assert_equal expected, actual
|
||||
end
|
||||
lock.synchronized do
|
||||
threads << thread
|
||||
end
|
||||
end
|
||||
lock.synchronized do
|
||||
threads.each{|t| t.join }
|
||||
end
|
||||
end
|
||||
else
|
||||
def test_runs_multithreaded
|
||||
threads = []
|
||||
mute = Mutex.new
|
||||
1000.times do |t|
|
||||
thread = Thread.new do
|
||||
parser = Radius::Parser.new(@context, :tag_prefix => 'r')
|
||||
parser.context.globals.thread_id = Thread.current.object_id
|
||||
expected = "#{Thread.current.object_id} / "+
|
||||
"#{parser.context.globals.object_id}"
|
||||
actual = parser.parse('<r:thread />')
|
||||
assert_equal expected, actual
|
||||
end
|
||||
mute.synchronize do
|
||||
threads << thread
|
||||
end
|
||||
end
|
||||
mute.synchronize do
|
||||
threads.each{|t| t.join }
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
end
|
|
@ -0,0 +1,18 @@
|
|||
require 'test/unit'
|
||||
require 'radius'
|
||||
|
||||
class RadiusOrdStringTest < Test::Unit::TestCase
|
||||
|
||||
def test_string_slice_integer
|
||||
str = Radius::OrdString.new "abc"
|
||||
assert_equal str[0], 97
|
||||
assert_equal str[1], 98
|
||||
assert_equal str[2], 99
|
||||
end
|
||||
|
||||
def test_string_slice_range
|
||||
str = Radius::OrdString.new "abc"
|
||||
assert_equal str[0..-1], "abc"
|
||||
end
|
||||
|
||||
end
|
|
@ -0,0 +1,307 @@
|
|||
require File.expand_path(File.dirname(__FILE__) + '/test_helper')
|
||||
|
||||
class RadiusParserTest < Test::Unit::TestCase
|
||||
include RadiusTestHelper
|
||||
|
||||
def setup
|
||||
@context = new_context
|
||||
@parser = Radius::Parser.new(@context, :tag_prefix => 'r')
|
||||
end
|
||||
|
||||
def test_initialize
|
||||
@parser = Radius::Parser.new
|
||||
assert_kind_of Radius::Context, @parser.context
|
||||
end
|
||||
|
||||
def test_initialize_with_params
|
||||
@parser = Radius::Parser.new(TestContext.new)
|
||||
assert_kind_of TestContext, @parser.context
|
||||
|
||||
@parser = Radius::Parser.new(:context => TestContext.new)
|
||||
assert_kind_of TestContext, @parser.context
|
||||
|
||||
@parser = Radius::Parser.new('context' => TestContext.new)
|
||||
assert_kind_of TestContext, @parser.context
|
||||
|
||||
@parser = Radius::Parser.new(:tag_prefix => 'r')
|
||||
assert_kind_of Radius::Context, @parser.context
|
||||
assert_equal 'r', @parser.tag_prefix
|
||||
|
||||
@parser = Radius::Parser.new(TestContext.new, :tag_prefix => 'r')
|
||||
assert_kind_of TestContext, @parser.context
|
||||
assert_equal 'r', @parser.tag_prefix
|
||||
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'/>>}
|
||||
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>}
|
||||
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>}
|
||||
end
|
||||
|
||||
def test_parse_quotes
|
||||
assert_parse_output "test []", %{<r:echo value="test" /> <r:wrap attr="test"></r:wrap>}
|
||||
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}>"
|
||||
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>}
|
||||
end
|
||||
|
||||
def test_parse_result_is_always_a_string
|
||||
define_tag("twelve") { 12 }
|
||||
assert_parse_output "12", "<r: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>"
|
||||
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
|
|
@ -0,0 +1,151 @@
|
|||
require File.expand_path(File.dirname(__FILE__) + '/test_helper')
|
||||
|
||||
class QuickstartTest < Test::Unit::TestCase
|
||||
|
||||
def test_hello_world
|
||||
context = Radius::Context.new
|
||||
context.define_tag "hello" do |tag|
|
||||
"Hello #{tag.attr['name'] || 'World'}!"
|
||||
end
|
||||
parser = Radius::Parser.new(context)
|
||||
assert_equal "<p>Hello World!</p>", parser.parse('<p><radius:hello /></p>')
|
||||
assert_equal "<p>Hello John!</p>", parser.parse('<p><radius:hello name="John" /></p>')
|
||||
end
|
||||
|
||||
def test_example_2
|
||||
require 'redcloth'
|
||||
context = Radius::Context.new
|
||||
context.define_tag "textile" do |tag|
|
||||
contents = tag.expand
|
||||
RedCloth.new(contents).to_html
|
||||
end
|
||||
parser = Radius::Parser.new(context)
|
||||
assert_equal "<p>Hello <b>World</b>!</p>", parser.parse('<radius:textile>Hello **World**!</radius:textile>')
|
||||
end
|
||||
|
||||
def test_nested_example
|
||||
context = Radius::Context.new
|
||||
|
||||
context.define_tag "stooge" do |tag|
|
||||
content = ''
|
||||
["Larry", "Moe", "Curly"].each do |name|
|
||||
tag.locals.name = name
|
||||
content << tag.expand
|
||||
end
|
||||
content
|
||||
end
|
||||
|
||||
context.define_tag "stooge:name" do |tag|
|
||||
tag.locals.name
|
||||
end
|
||||
|
||||
parser = Radius::Parser.new(context)
|
||||
|
||||
template = <<-TEMPLATE
|
||||
<ul>
|
||||
<radius:stooge>
|
||||
<li><radius:name /></li>
|
||||
</radius:stooge>
|
||||
</ul>
|
||||
TEMPLATE
|
||||
|
||||
output = <<-OUTPUT
|
||||
<ul>
|
||||
|
||||
<li>Larry</li>
|
||||
|
||||
<li>Moe</li>
|
||||
|
||||
<li>Curly</li>
|
||||
|
||||
</ul>
|
||||
OUTPUT
|
||||
|
||||
assert_equal output, parser.parse(template)
|
||||
end
|
||||
|
||||
class User
|
||||
attr_accessor :name, :age, :email
|
||||
end
|
||||
def test_exposing_objects_example
|
||||
parser = Radius::Parser.new
|
||||
|
||||
parser.context.define_tag "count", :for => 1
|
||||
assert_equal "1", parser.parse("<radius:count />")
|
||||
|
||||
user = User.new
|
||||
user.name, user.age, user.email = "John", 29, "john@example.com"
|
||||
parser.context.define_tag "user", :for => user, :expose => [ :name, :age, :email ]
|
||||
assert_equal "John", parser.parse("<radius:user><radius:name /></radius:user>")
|
||||
|
||||
assert_equal "John", parser.parse("<radius:user:name />")
|
||||
end
|
||||
|
||||
class LazyContext < Radius::Context
|
||||
def tag_missing(tag, attr, &block)
|
||||
"<strong>ERROR: Undefined tag `#{tag}' with attributes #{attr.inspect}</strong>"
|
||||
end
|
||||
end
|
||||
def test_tag_missing_example
|
||||
parser = Radius::Parser.new(LazyContext.new, :tag_prefix => 'lazy')
|
||||
output = %{<strong>ERROR: Undefined tag `weird' with attributes {"value"=>"true"}</strong>}
|
||||
assert_equal output, parser.parse('<lazy:weird value="true" />')
|
||||
end
|
||||
|
||||
def test_tag_globals_example
|
||||
parser = Radius::Parser.new
|
||||
|
||||
parser.context.define_tag "inc" do |tag|
|
||||
tag.globals.count ||= 0
|
||||
tag.globals.count += 1
|
||||
""
|
||||
end
|
||||
|
||||
parser.context.define_tag "count" do |tag|
|
||||
tag.globals.count || 0
|
||||
end
|
||||
|
||||
assert_equal "0 1", parser.parse("<radius:count /> <radius:inc /><radius:count />")
|
||||
end
|
||||
|
||||
class Person
|
||||
attr_accessor :name, :friend
|
||||
def initialize(name)
|
||||
@name = name
|
||||
end
|
||||
end
|
||||
def test_tag_locals_and_globals_example
|
||||
jack = Person.new('Jack')
|
||||
jill = Person.new('Jill')
|
||||
jack.friend = jill
|
||||
jill.friend = jack
|
||||
|
||||
context = Radius::Context.new do |c|
|
||||
c.define_tag "jack" do |tag|
|
||||
tag.locals.person = jack
|
||||
tag.expand
|
||||
end
|
||||
c.define_tag "jill" do |tag|
|
||||
tag.locals.person = jill
|
||||
tag.expand
|
||||
end
|
||||
c.define_tag "name" do |tag|
|
||||
tag.locals.person.name rescue tag.missing!
|
||||
end
|
||||
c.define_tag "friend" do |tag|
|
||||
tag.locals.person = tag.locals.person.friend rescue tag.missing!
|
||||
tag.expand
|
||||
end
|
||||
end
|
||||
|
||||
parser = Radius::Parser.new(context, :tag_prefix => 'r')
|
||||
|
||||
assert_equal "Jack", parser.parse('<r:jack:name />') #=> "Jack"
|
||||
assert_equal "Jill", parser.parse('<r:jill:name />') #=> "Jill"
|
||||
assert_equal "Jack", parser.parse('<r:jill:friend:name />') #=> "Jack"
|
||||
assert_equal "Jack", parser.parse('<r:jack:friend:friend:name />') #=> "Jack"
|
||||
assert_equal "Jack and Jill", parser.parse('<r:jill><r:friend:name /> and <r:name /></r:jill>') #=> "Jack and Jill"
|
||||
assert_raises(Radius::UndefinedTagError) { parser.parse('<r:name />') } # raises a Radius::UndefinedTagError exception
|
||||
end
|
||||
|
||||
end
|
|
@ -0,0 +1,281 @@
|
|||
require File.expand_path(File.dirname(__FILE__) + '/test_helper')
|
||||
require 'radius/parser/squiggle_scanner'
|
||||
|
||||
class RadiusSquiggleTest < Test::Unit::TestCase
|
||||
include RadiusTestHelper
|
||||
|
||||
def setup
|
||||
@context = new_context
|
||||
@parser = Radius::Parser.new(@context, :scanner => Radius::SquiggleScanner.new)
|
||||
end
|
||||
|
||||
def test_sane_scanner_default
|
||||
assert !Radius::Parser.new.scanner.is_a?(Radius::SquiggleScanner)
|
||||
end
|
||||
|
||||
def test_initialize_with_params
|
||||
@parser = Radius::Parser.new(:scanner => Radius::SquiggleScanner.new)
|
||||
assert_kind_of Radius::SquiggleScanner, @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}", %[{{ add param1="1" param2='2'/}}]
|
||||
end
|
||||
|
||||
def test_parse_attributes
|
||||
attributes = %[{"a"=>"1", "b"=>"2", "c"=>"3", "d"=>"'"}]
|
||||
assert_parse_output attributes, %[{attr a="1" b='2'c="3"d="'" /}]
|
||||
assert_parse_output attributes, %[{attr a="1" b='2'c="3"d="'"}{/attr}]
|
||||
end
|
||||
|
||||
def test_parse_attributes_with_slashes_or_angle_brackets
|
||||
slash = %[{"slash"=>"/"}]
|
||||
angle = %[{"angle"=>">"}]
|
||||
assert_parse_output slash, %[{attr slash="/"}{/attr}]
|
||||
assert_parse_output slash, %[{attr slash="/"}{attr /}{/attr}]
|
||||
assert_parse_output angle, %[{attr angle=">"}{/attr}]
|
||||
end
|
||||
|
||||
def test_parse_quotes
|
||||
assert_parse_output "test []", %[{echo value="test" /} {wrap attr="test"}{/wrap}]
|
||||
end
|
||||
|
||||
def test_things_that_should_be_left_alone
|
||||
[
|
||||
%[ test="2"="4" ],
|
||||
%[="2" ]
|
||||
].each do |middle|
|
||||
assert_parsed_is_unchanged "{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="{reverse}tibizx{/reverse}">tags in yo tags</div>]
|
||||
end
|
||||
|
||||
def test_parse_result_is_always_a_string
|
||||
define_tag("twelve") { 12 }
|
||||
assert_parse_output "12", "{ twelve /}"
|
||||
end
|
||||
|
||||
def test_parse_double_tags
|
||||
assert_parse_output "test".reverse, "{reverse}test{/reverse}"
|
||||
assert_parse_output "tset TEST", "{reverse}test{/reverse} {capitalize}test{/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", "{nesting /}"
|
||||
assert_parse_output "parent:nesting", "{parent:nesting /}"
|
||||
assert_parse_output "extra > nesting", "{extra:nesting /}"
|
||||
assert_parse_output "parent * child * nesting", "{parent:child:nesting /}"
|
||||
assert_parse_output "parent > extra > nesting", "{parent:extra:nesting /}"
|
||||
assert_parse_output "parent > child > extra > nesting", "{parent:child:extra:nesting /}"
|
||||
assert_parse_output "parent * extra * child * nesting", "{parent:extra:child:nesting /}"
|
||||
assert_parse_output "parent > extra > child > extra > nesting", "{parent:extra:child:extra:nesting /}"
|
||||
assert_parse_output "parent > extra > child > extra > nesting", "{parent}{extra}{child}{extra}{nesting /}{/extra}{/child}{/extra}{/parent}"
|
||||
assert_parse_output "extra * parent * child * nesting", "{extra:parent:child:nesting /}"
|
||||
assert_parse_output "extra > parent > nesting", "{extra}{parent:nesting /}{/extra}"
|
||||
assert_parse_output "extra * parent * child * nesting", "{extra:parent}{child:nesting /}{/extra:parent}"
|
||||
assert_raises(Radius::UndefinedTagError) { @parser.parse("{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', '{parent}{child:content /}{/parent}'
|
||||
end
|
||||
|
||||
def test_parse_tag__binding_do_missing
|
||||
define_tag 'test' do |tag|
|
||||
tag.missing!
|
||||
end
|
||||
e = assert_raises(Radius::UndefinedTagError) { @parser.parse("{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!', '{hello name="John" /}'
|
||||
end
|
||||
|
||||
def test_accessing_tag_attributes_through_tag_indexer
|
||||
define_tag('test') { |tag| "Hello #{tag['name']}!" }
|
||||
assert_parse_output 'Hello John!', '{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!', '{hello}John{/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', "{outer}{var /}{/outer}"
|
||||
assert_parse_output 'outer:inner:outer', "{outer}{var /}:{inner}{var /}{/inner}:{var /}{/outer}"
|
||||
assert_parse_output 'outer:inner:outer:inner:outer', "{outer}{var /}:{inner}{var /}:{outer}{var /}{/outer}:{var /}{/inner}:{var /}{/outer}"
|
||||
assert_parse_output 'outer', "{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", %[{var /} {set value="true" /} {var /} {set value="false" /} {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: {each between=", "}"{item /}"{/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", %[{set value="false" #{multiplier} /} {var /}]
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def test_tag_option_for
|
||||
define_tag 'fun', :for => 'just for kicks'
|
||||
assert_parse_output 'just for kicks', '{fun /}'
|
||||
end
|
||||
|
||||
def test_tag_expose_option
|
||||
define_tag 'user', :for => users.first, :expose => ['name', :age]
|
||||
assert_parse_output 'John', '{user:name /}'
|
||||
assert_parse_output '25', '{user}{age /}{/user}'
|
||||
e = assert_raises(Radius::UndefinedTagError) { @parser.parse "{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', '{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 "{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("{reverse}") }
|
||||
end
|
||||
|
||||
def test_parse_fail_on_wrong_end_tag
|
||||
assert_raises(Radius::WrongEndTagError) { @parser.parse("{reverse}{capitalize}{/reverse}") }
|
||||
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
|
|
@ -0,0 +1,36 @@
|
|||
require 'rubygems'
|
||||
require 'timeout'
|
||||
|
||||
unless defined? RADIUS_LIB
|
||||
|
||||
RADIUS_LIB = File.join(File.dirname(__FILE__), '..', 'lib')
|
||||
$LOAD_PATH << RADIUS_LIB
|
||||
|
||||
require 'radius'
|
||||
require 'test/unit'
|
||||
|
||||
module RadiusTestHelper
|
||||
class TestContext < Radius::Context; end
|
||||
|
||||
def new_context
|
||||
Radius::Context.new do |c|
|
||||
c.define_tag("reverse" ) { |tag| tag.expand.reverse }
|
||||
c.define_tag("capitalize") { |tag| tag.expand.upcase }
|
||||
c.define_tag("echo" ) { |tag| tag.attr['value'] }
|
||||
c.define_tag("wrap" ) { |tag| "[#{tag.expand}]" }
|
||||
c.define_tag("attr") do |tag|
|
||||
kv = tag.attr.keys.sort.collect{|k| "#{k.inspect}=>#{tag[k].inspect}"}
|
||||
"{#{kv.join(', ')}}"
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def define_tag(name, options = {}, &block)
|
||||
@parser.context.define_tag name, options, &block
|
||||
end
|
||||
|
||||
def define_global_tag(name, options = {}, &block)
|
||||
@context.define_tag name, options, &block
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,30 @@
|
|||
require 'test/unit'
|
||||
require 'radius'
|
||||
|
||||
class RadiusUtilityTest < Test::Unit::TestCase
|
||||
|
||||
def test_symbolize_keys
|
||||
h = Radius::Utility.symbolize_keys({ 'a' => 1, :b => 2 })
|
||||
assert_equal h[:a], 1
|
||||
assert_equal h[:b], 2
|
||||
end
|
||||
|
||||
def test_impartial_hash_delete
|
||||
h = { 'a' => 1, :b => 2 }
|
||||
assert_equal Radius::Utility.impartial_hash_delete(h, :a), 1
|
||||
assert_equal Radius::Utility.impartial_hash_delete(h, 'b'), 2
|
||||
assert_equal h.empty?, true
|
||||
end
|
||||
|
||||
def test_constantize
|
||||
assert_equal Radius::Utility.constantize('String'), String
|
||||
end
|
||||
|
||||
def test_camelize
|
||||
assert_equal Radius::Utility.camelize('ab_cd_ef'), 'AbCdEf'
|
||||
end
|
||||
|
||||
def test_array_to_s
|
||||
assert_equal Radius::Utility.array_to_s(['a', 1, [:c]]), 'a1c'
|
||||
end
|
||||
end
|
Loading…
Reference in New Issue