├── .github
└── workflows
│ └── ci.yml
├── .gitignore
├── CHANGELOG
├── CONTRIBUTORS
├── Gemfile
├── LICENSE
├── QUICKSTART.rdoc
├── README.rdoc
├── Rakefile
├── lib
├── radius.rb
└── radius
│ ├── context.rb
│ ├── delegating_open_struct.rb
│ ├── error.rb
│ ├── ord_string.rb
│ ├── parse_tag.rb
│ ├── parser.rb
│ ├── parser
│ ├── JavaScanner$Flavor.class
│ ├── JavaScanner$Tag.class
│ ├── JavaScanner.class
│ ├── JavaScanner.java
│ ├── JavaScanner.rl
│ ├── java_scanner.jar
│ ├── scanner.rb
│ └── squiggle_scanner.rb
│ ├── tag_binding.rb
│ ├── tag_definitions.rb
│ ├── utility.rb
│ └── version.rb
├── radius.gemspec
├── tasks
├── rdoc.rake
├── rubinius.rake
└── test.rake
└── test
├── benchmarks.rb
├── context_test.rb
├── multithreaded_test.rb
├── ord_string_test.rb
├── parser_test.rb
├── quickstart_test.rb
├── squiggle_test.rb
├── test_helper.rb
└── utility_test.rb
/.github/workflows/ci.yml:
--------------------------------------------------------------------------------
1 | name: CI
2 |
3 | on:
4 | push:
5 | branches: [ master ]
6 | pull_request:
7 | types: [opened, synchronize, reopened]
8 | branches: [ master ]
9 |
10 | jobs:
11 | test:
12 | runs-on: ubuntu-latest
13 | strategy:
14 | matrix:
15 | include:
16 | - ruby-version: '2.7.8'
17 | bundler-version: '2.4.22'
18 | - ruby-version: '3.0.7'
19 | bundler-version: '2.5.3'
20 | - ruby-version: '3.1.6'
21 | bundler-version: '2.6.2'
22 | - ruby-version: '3.3.6'
23 | bundler-version: '2.6.2'
24 | - ruby-version: '3.4.2'
25 | bundler-version: '2.6.2'
26 | - ruby-version: 'jruby'
27 | bundler-version: '2.6.1'
28 |
29 | steps:
30 | - uses: actions/checkout@v4
31 | - name: Set up Ruby
32 | uses: ruby/setup-ruby@v1
33 | with:
34 | ruby-version: ${{ matrix.ruby-version }}
35 | bundler: ${{ matrix.bundler-version }}
36 | bundler-cache: true
37 | - name: Run tests
38 | env:
39 | COVERALLS: ${{ matrix.ruby-version != 'jruby' }}
40 | JRUBY_OPTS: ${{ matrix.ruby-version == 'jruby' && '--debug' || '' }}
41 | run: bundle exec rake
42 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | Gemfile.lock
2 | doc
3 | *.dot
4 | pkg
5 | *.rbc
6 | *.gem
7 | coverage/*
8 |
--------------------------------------------------------------------------------
/CHANGELOG:
--------------------------------------------------------------------------------
1 | = Change Log
2 |
3 | == Unreleased
4 |
5 | * Add OrdString coercion method to avoid extra allocations [saturnflyer]
6 |
7 | == 0.8.0
8 | * Ensure that the DelegatingOpenStruct is a true copy of the original [saturnflyer]
9 | * Avoid errors with frozen strings in Ruby 3.4+ [jeremyevans]
10 | * Drop support for EOL versions of Ruby
11 |
12 | == 0.7.5
13 | * Added support for tags and attributes containing dashes [yurivm, ronald]
14 |
15 | == 0.7.4
16 | * added support for Ruby 2+
17 |
18 | == 0.7.1
19 | * Running tests with bundler requires 'rake' to be specified [jgay]
20 |
21 | == 0.7.0
22 | * Improve performance by switching back to regular expression parsing [jfahrenkrug]
23 | * Support for Rubinius [jlong]
24 | * Support for Ruby 1.9 [aemadrid]
25 | * More tests [aemadrid]
26 | * Fixed issue #5 - problem with other namespace tags [jemmyw]
27 | * Switched to Jeweler for better gem management [jlong]
28 | * Allow operation in a threaded environment (parser per-thread, shared context)
29 | * Allow switching scanners that tokenize templates.
30 | * Include SquiggleScanner to parse tags that look like "{ hello /}"
31 |
32 | == 0.6.1
33 | * Fixed a problem with non-tags that have no prefix or tag name (see test_parse_chirpy_bird)
34 |
35 | == 0.6.0 (private release)
36 | * Split radius.rb into multiple files.
37 | * Ported the really hairy regexes from Radius::Parser to a single Ragel machine.
38 | * Added and refactored tests.
39 | * Refactored Rakefile and other administrativia.
40 |
41 | == 0.5.1
42 | * 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.
43 |
44 | == 0.5.0
45 | * Created a DSL for tag definitions (introducing a DSL makes this version of Radiant incompatible with the last). The DSL has the following features:
46 | - full support for nested tags
47 | - global and local tag variables
48 | - Contexts can now be defined dynamically (instead of being subclassed)
49 | - see the QUICKSTART for more info
50 | * Many refactorings of the library and unit tests.
51 | * Changed the license to the MIT-LICENSE.
52 | * Updated documentation to reflect the changes.
53 | * Updated the version number to reflect the maturity of the code base.
54 |
55 | == 0.0.2
56 | * Refactored Parser to use Context#render_tag instead of #send when rendering tags defined on a Context.
57 | * UndefinedTagError is now thrown when Parser tries to render a tag which doesn't exist on a Context.
58 | * Added Context#tag_missing which works like method_method missing on Object, but is tag specific.
59 |
60 | == 0.0.1
61 | * First release.
62 |
--------------------------------------------------------------------------------
/CONTRIBUTORS:
--------------------------------------------------------------------------------
1 | CONTRIBUTORS
2 | ============
3 |
4 | The following people have committed code to Radius. Thank you!
5 |
6 | John W. Long
7 | Todd Willey
8 | Bryce Kerley
9 | Jim Gay
10 | Johannes Fahrenkrug
11 | Adrian Madrid
12 | Benny Degezelle
13 | Moritz Heidkamp
14 | Jeremy
15 | Jason Garber
16 | David Chelimsky
17 | Yuri Veremeyenko
18 | Mr Ronald
19 |
--------------------------------------------------------------------------------
/Gemfile:
--------------------------------------------------------------------------------
1 | source "https://rubygems.org"
2 |
3 | gemspec
4 |
5 | group :test do
6 | gem 'rdoc'
7 | gem 'rake'
8 | gem 'kramdown'
9 | gem "simplecov"
10 | gem 'coveralls_reborn', :require => false
11 | gem 'minitest'
12 | end
13 |
14 |
15 | platforms :rbx do
16 | gem 'racc' # if using gems like ruby_parser or parser
17 | # gem 'rubysl', '~> 2.0'
18 | # gem 'psych'
19 | gem 'rubinius-developer_tools'
20 | end
21 |
22 | platforms :jruby do
23 | gem 'jar-dependencies', '~> 0.4.1'
24 | gem 'ruby-maven', '~> 3.3.11'
25 | end
26 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Copyright (c) 2006-2010, John W. Long
2 |
3 | Permission is hereby granted, free of charge, to any person obtaining a copy
4 | of this software and associated documentation files (the "Software"), to deal
5 | in the Software without restriction, including without limitation the rights
6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7 | copies of the Software, and to permit persons to whom the Software is
8 | furnished to do so, subject to the following conditions:
9 |
10 | The above copyright notice and this permission notice shall be included in all
11 | copies or substantial portions of the Software.
12 |
13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
19 | SOFTWARE.
--------------------------------------------------------------------------------
/QUICKSTART.rdoc:
--------------------------------------------------------------------------------
1 | = Radius Quick Start
2 |
3 |
4 | == Defining Tags
5 |
6 | Before you can parse a template with Radius you need to create a Context object which defines
7 | the tags that will be used in the template. This is actually quite simple:
8 |
9 | require 'radius'
10 |
11 | context = Radius::Context.new
12 | context.define_tag "hello" do |tag|
13 | "Hello #{tag.attr['name'] || 'World'}!"
14 | end
15 |
16 | Once you have defined a context you can easily create a Parser:
17 |
18 | parser = Radius::Parser.new(context)
19 | puts parser.parse('
')
20 | puts parser.parse('
')
21 |
22 | This code will output:
23 |
24 | Hello World!
25 | Hello John!
26 |
27 | Note how you can pass attributes from the template to the context using the attributes hash.
28 | Above, the first tag that was parsed didn't have a name attribute so the code in the +hello+
29 | tag definition uses "World" instead. The second time the tag is parsed the name attribute is
30 | set to "John" which is used to create the string "Hello John!". Tags that do not follow this
31 | rule will be treated as if they were undefined (like normal methods).
32 |
33 |
34 | == Container Tags
35 |
36 | Radius also allows you to define "container" tags. That is, tags that contain content and
37 | that may optionally manipulate it in some way. For example, if you have RedCloth installed
38 | you could define another tag to parse and create Textile output:
39 |
40 | require 'redcloth'
41 |
42 | context.define_tag "textile" do |tag|
43 | contents = tag.expand
44 | RedCloth.new(contents).to_html
45 | end
46 |
47 | (The code tag.expand above returns the contents of the template between the start and end
48 | tags.)
49 |
50 | With the code above your parser can easily handle Textile:
51 |
52 | parser.parse('h1. Hello **World**!')
53 |
54 | This code will output:
55 |
56 | Hello World!
57 |
58 |
59 | == Nested Tags
60 |
61 | But wait!--it gets better. Because container tags can manipulate the content they contain
62 | you can use them to iterate over collections:
63 |
64 | context = Radius::Context.new
65 |
66 | context.define_tag "stooge" do |tag|
67 | content = ''
68 | ["Larry", "Moe", "Curly"].each do |name|
69 | tag.locals.name = name
70 | content << tag.expand
71 | end
72 | content
73 | end
74 |
75 | context.define_tag "stooge:name" do |tag|
76 | tag.locals.name
77 | end
78 |
79 | parser = Radius::Parser.new(context)
80 |
81 | template = <<-TEMPLATE
82 |
87 | TEMPLATE
88 |
89 | puts parser.parse(template)
90 |
91 | This code will output:
92 |
93 |
94 |
95 | - Larry
96 |
97 | - Moe
98 |
99 | - Curly
100 |
101 |
102 |
103 | Note how the definition for the +name+ tag is defined. Because "name" is prefixed
104 | with "stooge:" the +name+ tag cannot appear outside the +stooge+ tag. Had it been defined
105 | simply as "name" it would be valid anywhere, even outside the +stooge+ tag (which was
106 | not what we wanted). Using the colon operator you can define tags with any amount of
107 | nesting.
108 |
109 |
110 | == Exposing Objects to Templates
111 |
112 | During normal operation, you will often want to expose certain objects to your templates.
113 | Writing the tags to do this all by hand would be cumbersome of Radius did not provide
114 | several mechanisms to make this easier. The first is a way of exposing objects as tags
115 | on the context object. To expose an object simply call the +define_tag+
116 | method with the +for+ option:
117 |
118 | context.define_tag "count", :for => 1
119 |
120 | This would expose the object 1 to the template as the +count+ tag. It's basically the
121 | equivalent of writing:
122 |
123 | context.define_tag("count") { 1 }
124 |
125 | So far this doesn't save you a whole lot of typing, but suppose you want to expose certain
126 | methods that are on that object? You could do this:
127 |
128 | context.define_tag "user", :for => user, :expose => [ :name, :age, :email ]
129 |
130 | This will add a total of four tags to the context. One for the user variable, and
131 | one for each of the three methods listed in the +expose+ clause. You could now get the user's
132 | name inside your template like this:
133 |
134 |
135 |
136 | If "John" was the value stored in user.name the template would render as "John".
137 |
138 |
139 | == Tag Shorthand
140 |
141 | In the example above we made reference to user.name in our template by using the
142 | following code:
143 |
144 |
145 |
146 | There is a much easer way to refer to the user.name variable. Use the colon operator
147 | to "scope" the reference to name:
148 |
149 |
150 |
151 | Radius allows you to use this shortcut for all tags.
152 |
153 |
154 | == Changing the Tag Prefix
155 |
156 | By default, all Radius tags must begin with "radius". You can change this by altering the
157 | tag_prefix attribute on a Parser. For example:
158 |
159 | parser = Radius::Parser.new(context, :tag_prefix => 'r')
160 |
161 | Now, when parsing templates with +parser+, Radius will require that every tag begin with "r"
162 | instead of "radius".
163 |
164 |
165 | == Custom Behavior for Undefined Tags
166 |
167 | Context#tag_missing behaves much like Object#method_missing only it allows you to define
168 | specific behavior for when a tag is not defined on a Context. For example:
169 |
170 | class LazyContext < Radius::Context
171 | def tag_missing(tag, attr, &block)
172 | "ERROR: Undefined tag `#{tag}' with attributes #{attr.inspect}"
173 | end
174 | end
175 |
176 | parser = Radius::Parser.new(LazyContext.new, :tag_prefix => 'lazy')
177 | puts parser.parse('')
178 |
179 | This will output:
180 |
181 | ERROR: Undefined tag `weird' with attributes {"value"=>"true"}
182 |
183 | Normally, when the Radius Parser encounters an undefined tag for a Context it raises an
184 | UndefinedTagError, but since we have defined #tag_missing on LazyContext the Parser now
185 | outputs a nicely formated error message when we parse a string that does not contain a
186 | valid tag.
187 |
188 |
189 | == Tag Bindings
190 |
191 | Radius passes a TagBinding into the block of the Context#define_tag method. The tag
192 | binding is useful for a number of tasks. A tag binding has an #expand instance method
193 | which processes a tag's contents and returns the result. It also has a #attr method
194 | which returns a hash of the attributes that were passed into the tag. TagBinding also
195 | contains the TagBinding#single? and TagBinding#double? methods which return true or false
196 | based on wether the tag is a container tag or not. More about the methods which are
197 | available on tag bindings can be found on the Radius::TagBinding documentation page.
198 |
199 |
200 | == Tag Binding Locals, Globals, and Context Sensitive Tags
201 |
202 | A TagBinding also contains two OpenStruct-like objects which are useful when developing
203 | tags. TagBinding#globals is useful for storing variables which you would like to be
204 | accessible to all tags:
205 |
206 | context.define_tag "inc" do |tag|
207 | tag.globals.count ||= 0
208 | tag.globals.count += 1
209 | ""
210 | end
211 |
212 | context.define_tag "count" do |tag|
213 | tag.globals.count || 0
214 | end
215 |
216 | TagBinding#locals mirrors the variables that are in TagBinding#globals, but allows child
217 | tags to redefine variables. This is valuable when defining context sensitive tags:
218 |
219 | class Person
220 | attr_accessor :name, :friend
221 | def initialize(name)
222 | @name = name
223 | end
224 | end
225 |
226 | jack = Person.new('Jack')
227 | jill = Person.new('Jill')
228 | jack.friend = jill
229 | jill.friend = jack
230 |
231 | context = Radius::Context.new do |c|
232 | c.define_tag "jack" do |tag|
233 | tag.locals.person = jack
234 | tag.expand
235 | end
236 | c.define_tag "jill" do |tag|
237 | tag.locals.person = jill
238 | tag.expand
239 | end
240 | c.define_tag "name" do |tag|
241 | tag.locals.person.name rescue tag.missing!
242 | end
243 | c.define_tag "friend" do |tag|
244 | tag.locals.person = tag.locals.person.friend rescue tag.missing!
245 | tag.expand
246 | end
247 | end
248 |
249 | parser = Radius::Parser.new(context, :tag_prefix => 'r')
250 |
251 | parser.parse('') #=> "Jack"
252 | parser.parse('') #=> "Jill"
253 | parser.parse('') #=> "Jack"
254 | parser.parse('') #=> "Jack"
255 | parser.parse(' and ') #=> "Jack and Jill"
256 | parser.parse('') # raises a Radius::UndefinedTagError exception
257 |
258 | Notice how TagBinding#locals enables intelligent nesting. "" evaluates to
259 | "Jill", but "" evaluates to "Jack". Locals lose scope as soon as
260 | the tag they were defined in closes. Globals on the other hand, never lose scope.
261 |
262 | The final line in the example above demonstrates that calling "" raises a
263 | TagMissing error. This is because of the way the name tag was defined:
264 |
265 | tag.locals.person.name rescue tag.missing!
266 |
267 | If person is not defined on locals it will return nil. Calling #name on nil would normally
268 | raise a NoMethodError exception, but because of the 'rescue' clause the TagBinding#missing!
269 | method is called which fires off Context#tag_missing. By default Context#tag_missing raises
270 | a UndefinedTagError exception. The 'rescue tag.missing!' idiom is extremly useful for adding
271 | simple error checking to context sensitive tags.
272 |
273 |
274 | == Tag Specificity
275 |
276 | When Radius is presented with two tags that have the same name, but different nesting
277 | Radius uses an algorithm similar to the way winning rules are calculated in Cascading Style
278 | Sheets (CSS) to determine which definition should be used. Each time a tag is encountered
279 | in a template potential tags are assigned specificity values and the tag with the highest
280 | specificity wins.
281 |
282 | For example, given the following tag definitions:
283 |
284 | nesting
285 | extra:nesting
286 | parent:child:nesting
287 |
288 | And template:
289 |
290 |
291 |
292 | Radius will calculate specificity values like this:
293 |
294 | nesting => 1.0.0.0
295 | extra:nesting => 1.0.1.0
296 | parent:child:nesting => 1.1.0.1
297 |
298 | Meaning that parent:child:nesting will win. If a template contained:
299 |
300 |
301 |
302 | The following specificity values would be assigned to each of the tag definitions:
303 |
304 | nesting => 1.0.0.0
305 | extra:nesting => 1.1.0.0
306 | parent:child:nesting => 1.0.1.1
307 |
308 | Meaning that extra:nesting would win because it is more "specific".
309 |
310 | Values are assigned by assigning points to each of the tags from right to left.
311 | Given a tag found in a template with nesting four levels deep, the maximum
312 | specificity a tag could be assigned would be:
313 |
314 | 1.1.1.1
315 |
316 | One point for each of the levels.
317 |
318 | In practice, you don't need to understand this topic to be effective with Radius.
319 | For the most part you will find that Radius resolves tags precisely the way that
320 | you would expect. If you find this section confusing forget about it and refer
321 | back to it if you find that tags are resolving differently from the way that you
322 | expected.
323 |
--------------------------------------------------------------------------------
/README.rdoc:
--------------------------------------------------------------------------------
1 | = Radius -- Powerful Tag-Based Templates
2 |
3 | {rdoc-image:https://github.com/jlong/radius/workflows/CI/badge.svg}[https://github.com/jlong/radius/actions] {rdoc-image:https://codeclimate.com/github/jlong/radius.png}[https://codeclimate.com/github/jlong/radius] {rdoc-image:https://coveralls.io/repos/jlong/radius/badge.png}[https://coveralls.io/r/jlong/radius]
4 |
5 |
6 | Radius is a powerful tag-based template language for Ruby inspired by the
7 | template languages used in MovableType[http://www.movabletype.org] and
8 | TextPattern[http://www.textpattern.com]. It uses tags similar to XML, but can
9 | be used to generate any form of plain text (HTML, e-mail, etc...).
10 |
11 | == Usage
12 |
13 | With Radius, it is extremely easy to create custom tags and parse them. Here's a small
14 | example:
15 |
16 | require 'radius'
17 |
18 | # Define tags on a context that will be available to a template:
19 | context = Radius::Context.new do |c|
20 | c.define_tag 'hello' do
21 | 'Hello world'
22 | end
23 | c.define_tag 'repeat' do |tag|
24 | number = (tag.attr['times'] || '1').to_i
25 | result = ''
26 | number.times { result << tag.expand }
27 | result
28 | end
29 | end
30 |
31 | # Create a parser to parse tags that begin with 'r:'
32 | parser = Radius::Parser.new(context, :tag_prefix => 'r')
33 |
34 | # Parse tags and output the result
35 | puts parser.parse(%{A small example:\n* !\n})
36 |
37 | Output:
38 |
39 | A small example:
40 | * Hello world!
41 | * Hello world!
42 | * Hello world!
43 |
44 |
45 | == Quick Start
46 |
47 | Read the QUICKSTART file to get up and running with Radius.
48 |
49 |
50 | == Requirements
51 |
52 | Radius does not have any external requirements for using the library in your
53 | own programs.
54 |
55 | Ragel is required to create the ruby parser from the Ragel specification,
56 | and both Ragel and Graphviz are required to draw the state graph for the
57 | parser.
58 |
59 |
60 | == Installation
61 |
62 | It is recommended that you install Radius using the RubyGems packaging system:
63 |
64 | % gem install --remote radius
65 |
66 |
67 | == License
68 |
69 | Radius is released under the MIT license and is copyright (c) 2006-2025
70 | John W. Long. A copy of the MIT license can be found in the LICENSE file.
71 |
72 |
73 | == Roadmap
74 |
75 | This is a prioritized roadmap for future releases:
76 |
77 | 1. Clean up the current code base. [Done]
78 |
79 | 2. Add support for multi-level contexts: tags should be able to be
80 | defined to only be valid within other sets of tags. [Done]
81 |
82 | 3. Create a simple DSL for defining contexts. [Done]
83 |
84 | 4. Optimize for speed, modify scan.rl to emit C.
85 |
86 |
87 | == Development
88 |
89 | The latest version of Radius can be found on RubyForge:
90 |
91 | http://github.com/jlong/radius
92 |
93 | If you are interested in helping with the development of Radius, feel free to
94 | fork the project on GitHub and send me a pull request.
95 |
96 |
97 | John Long ::
98 | http://wiseheartdesign.com
99 |
--------------------------------------------------------------------------------
/Rakefile:
--------------------------------------------------------------------------------
1 | require 'bundler/gem_tasks'
2 | require 'rake'
3 |
4 | require File.dirname(__FILE__) + '/lib/radius/version'
5 |
6 | Dir['tasks/**/*.rake'].each { |t| load t }
7 |
8 | task :default => :test
9 |
10 | namespace :build do
11 | desc "Build both Ruby and Java platform gems"
12 | task :all do
13 | version = ::Radius.version
14 | system "gem build radius.gemspec --platform ruby -o radius-#{version}.gem"
15 | system "gem build radius.gemspec --platform java -o radius-#{version}-java.gem"
16 | end
17 | end
18 |
--------------------------------------------------------------------------------
/lib/radius.rb:
--------------------------------------------------------------------------------
1 | require_relative "radius/version"
2 | require_relative "radius/error"
3 | require_relative "radius/tag_definitions"
4 | require_relative "radius/delegating_open_struct"
5 | require_relative "radius/tag_binding"
6 | require_relative "radius/context"
7 | require_relative "radius/parse_tag"
8 | require_relative "radius/ord_string"
9 | require_relative "radius/parser/scanner"
10 | require_relative "radius/parser"
11 | require_relative "radius/utility"
12 |
--------------------------------------------------------------------------------
/lib/radius/context.rb:
--------------------------------------------------------------------------------
1 | module Radius
2 | #
3 | # A context contains the tag definitions which are available for use in a template.
4 | # See the QUICKSTART for a detailed explaination its
5 | # usage.
6 | #
7 | class Context
8 | # A hash of tag definition blocks that define tags accessible on a Context.
9 | attr_accessor :definitions # :nodoc:
10 | attr_accessor :globals # :nodoc:
11 |
12 | # Creates a new Context object.
13 | def initialize(*args, &block)
14 | @definitions = {}
15 | @tag_binding_stack = []
16 | @globals = DelegatingOpenStruct.new
17 | with(&block) if block_given?
18 | end
19 |
20 | # Yield an instance of self for tag definitions:
21 | #
22 | # context.with do |c|
23 | # c.define_tag 'test' do
24 | # 'test'
25 | # end
26 | # end
27 | #
28 | def with
29 | yield self
30 | self
31 | end
32 |
33 | # Creates a tag definition on a context. Several options are available to you
34 | # when creating a tag:
35 | #
36 | # +for+:: Specifies an object that the tag is in reference to. This is
37 | # applicable when a block is not passed to the tag, or when the
38 | # +expose+ option is also used.
39 | #
40 | # +expose+:: Specifies that child tags should be set for each of the methods
41 | # contained in this option. May be either a single symbol/string or
42 | # an array of symbols/strings.
43 | #
44 | # +attributes+:: Specifies whether or not attributes should be exposed
45 | # automatically. Useful for ActiveRecord objects. Boolean. Defaults
46 | # to +true+.
47 | #
48 | def define_tag(name, options = {}, &block)
49 | type = Utility.impartial_hash_delete(options, :type).to_s
50 | klass = Utility.constantize('Radius::TagDefinitions::' + Utility.camelize(type) + 'TagFactory') rescue raise(ArgumentError.new("Undefined type `#{type}' in options hash"))
51 | klass.new(self).define_tag(name, options, &block)
52 | end
53 |
54 | # Returns the value of a rendered tag. Used internally by Parser#parse.
55 | def render_tag(name, attributes = {}, &block)
56 | if name =~ /^(.+?):(.+)$/
57 | render_tag($1) { render_tag($2, attributes, &block) }
58 | else
59 | tag_definition_block = @definitions[qualified_tag_name(name.to_s)]
60 | if tag_definition_block
61 | # puts name: name, attributes: attributes
62 | stack(name, attributes, block) do |tag|
63 | tag_definition_block.call(tag).to_s
64 | end
65 | else
66 | tag_missing(name, attributes, &block)
67 | end
68 | end
69 | end
70 |
71 | # Like method_missing for objects, but fired when a tag is undefined.
72 | # Override in your own Context to change what happens when a tag is
73 | # undefined. By default this method raises an UndefinedTagError.
74 | def tag_missing(name, attributes, &block)
75 | raise UndefinedTagError.new(name)
76 | end
77 |
78 | # Returns the state of the current render stack. Useful from inside
79 | # a tag definition. Normally just use TagBinding#nesting.
80 | def current_nesting
81 | @tag_binding_stack.collect { |tag| tag.name }.join(':')
82 | end
83 |
84 | # make a usable copy of this context
85 | def dup # :nodoc:
86 | rv = self.class.new
87 | rv.globals = globals.dup
88 | rv.definitions = definitions.dup
89 | rv
90 | end
91 |
92 | private
93 |
94 | # A convienence method for managing the various parts of the
95 | # tag binding stack.
96 | def stack(name, attributes, block)
97 | previous = @tag_binding_stack.last
98 | previous_locals = previous.nil? ? globals : previous.locals
99 | locals = previous_locals.dup
100 | binding = TagBinding.new(self, locals, name, attributes, block)
101 | @tag_binding_stack.push(binding)
102 | result = yield(binding)
103 | @tag_binding_stack.pop
104 | result
105 | end
106 |
107 | # Returns a fully qualified tag name based on state of the
108 | # tag binding stack.
109 | def qualified_tag_name(name)
110 | nesting_parts = @tag_binding_stack.collect { |tag| tag.name }
111 | nesting_parts << name unless nesting_parts.last == name
112 | specific_name = nesting_parts.join(':') # specific_name always has the highest specificity
113 | unless @definitions.has_key? specific_name
114 | possible_matches = @definitions.keys.grep(/(^|:)#{name}$/)
115 | specificity = possible_matches.inject({}) { |hash, tag| hash[numeric_specificity(tag, nesting_parts)] = tag; hash }
116 | max = specificity.keys.max
117 | if max != 0
118 | specificity[max]
119 | else
120 | name
121 | end
122 | else
123 | specific_name
124 | end
125 | end
126 |
127 | # Returns the specificity for +tag_name+ at nesting defined
128 | # by +nesting_parts+ as a number.
129 | def numeric_specificity(tag_name, nesting_parts)
130 | nesting_parts = nesting_parts.dup
131 | name_parts = tag_name.split(':')
132 | specificity = 0
133 | value = 1
134 | if nesting_parts.last == name_parts.last
135 | while nesting_parts.size > 0
136 | if nesting_parts.last == name_parts.last
137 | specificity += value
138 | name_parts.pop
139 | end
140 | nesting_parts.pop
141 | value *= 0.1
142 | end
143 | specificity = 0 if (name_parts.size > 0)
144 | end
145 | specificity
146 | end
147 | end
148 | end
149 |
--------------------------------------------------------------------------------
/lib/radius/delegating_open_struct.rb:
--------------------------------------------------------------------------------
1 | module Radius
2 | class DelegatingOpenStruct # :nodoc:
3 | attr_accessor :object
4 |
5 | def initialize(object = nil)
6 | @object = object
7 | @hash = {}
8 | end
9 |
10 | def dup
11 | self.class.new.tap do |copy|
12 | copy.instance_variable_set(:@hash, @hash.dup)
13 | copy.object = @object
14 | end
15 | end
16 |
17 | def method_missing(method, *args, &block)
18 | return super if args.size > 1
19 |
20 | symbol = method.to_s.chomp('=').to_sym
21 |
22 | if method.to_s.end_with?('=')
23 | @hash[symbol] = args.first
24 | else
25 | @hash.fetch(symbol) { @object&.public_send(method, *args, &block) }
26 | end
27 | end
28 |
29 | def respond_to_missing?(method, include_private = false)
30 | (args.size <= 1) || super
31 | end
32 | end
33 | end
34 |
--------------------------------------------------------------------------------
/lib/radius/error.rb:
--------------------------------------------------------------------------------
1 | module Radius
2 | # Abstract base class for all parsing errors.
3 | class ParseError < StandardError
4 | end
5 |
6 | # Occurs when the Parser expects an end tag for one tag and finds the end tag for another.
7 | class WrongEndTagError < ParseError
8 | def initialize(expected_tag, got_tag, stack)
9 | stack_message = " with stack #{stack.inspect}" if stack
10 | super("wrong end tag `#{got_tag}' found for start tag `#{expected_tag}'#{stack_message}")
11 | end
12 | end
13 |
14 | # Occurs when Parser cannot find an end tag for a given tag in a template or when
15 | # tags are miss-matched in a template.
16 | class MissingEndTagError < ParseError
17 | # Create a new MissingEndTagError object for +tag_name+.
18 | def initialize(tag_name, stack)
19 | stack_message = " with stack #{stack.inspect}" if stack
20 | super("end tag not found for start tag `#{tag_name}'#{stack_message}")
21 | end
22 | end
23 |
24 | # Occurs when Context#render_tag cannot find the specified tag on a Context.
25 | class UndefinedTagError < ParseError
26 | # Create a new UndefinedTagError object for +tag_name+.
27 | def initialize(tag_name)
28 | super("undefined tag `#{tag_name}'")
29 | end
30 | end
31 |
32 | class TastelessTagError < ParseError #:nodoc:
33 | def initialize(tag, stack)
34 | super("internal error with tasteless tag #{tag.inspect} and stack #{stack.inspect}")
35 | end
36 | end
37 |
38 | class UndefinedFlavorError < ParseError #:nodoc:
39 | def initialize(tag, stack)
40 | super("internal error with unknown flavored tag #{tag.inspect} and stack #{stack.inspect}")
41 | end
42 | end
43 | end
--------------------------------------------------------------------------------
/lib/radius/ord_string.rb:
--------------------------------------------------------------------------------
1 | module Radius
2 | module_function def OrdString(string_or_ord_string)
3 | if string_or_ord_string.is_a?(OrdString)
4 | string_or_ord_string
5 | else
6 | OrdString.new(string_or_ord_string)
7 | end
8 | end
9 |
10 | class OrdString < String
11 | def [](*args)
12 | if args.size == 1 && args.first.is_a?(Integer)
13 | slice(args.first).ord
14 | else
15 | super
16 | end
17 | end
18 | end
19 | end
20 |
--------------------------------------------------------------------------------
/lib/radius/parse_tag.rb:
--------------------------------------------------------------------------------
1 | module Radius
2 | class ParseTag # :nodoc:
3 | def initialize(&b)
4 | @block = b
5 | end
6 |
7 | def on_parse(&b)
8 | @block = b
9 | end
10 |
11 | def to_s
12 | @block.call(self) if @block
13 | end
14 | end
15 |
16 | class ParseContainerTag < ParseTag # :nodoc:
17 | attr_accessor :name, :attributes, :contents
18 |
19 | def initialize(name = "", attributes = {}, contents = [], &b)
20 | @name, @attributes, @contents = name, attributes, contents
21 | super(&b)
22 | end
23 | end
24 | end
--------------------------------------------------------------------------------
/lib/radius/parser.rb:
--------------------------------------------------------------------------------
1 | module Radius
2 | #
3 | # The Radius parser. Initialize a parser with a Context object that
4 | # defines how tags should be expanded. See the QUICKSTART[link:files/QUICKSTART.html]
5 | # for a detailed explaination of its usage.
6 | #
7 | class Parser
8 | # The Context object used to expand template tags.
9 | attr_accessor :context
10 |
11 | # The string that prefixes all tags that are expanded by a parser
12 | # (the part in the tag name before the first colon).
13 | attr_accessor :tag_prefix
14 |
15 | # The class that performs tokenization of the input string
16 | attr_accessor :scanner
17 |
18 | # Creates a new parser object initialized with a Context.
19 | def initialize(context = Context.new, options = {})
20 | context, options = normalize_initialization_params(context, options)
21 | @context = context ? context.dup : Context.new
22 | @tag_prefix = options[:tag_prefix] || 'radius'
23 | @scanner = options[:scanner] || default_scanner
24 | @stack = nil # Pre-initialize stack
25 | end
26 |
27 | # Parses string for tags, expands them, and returns the result.
28 | def parse(string)
29 | @stack = [create_root_container]
30 | process_tokens(scanner.operate(tag_prefix, string))
31 | @stack.last.to_s
32 | end
33 |
34 | private
35 |
36 | def normalize_initialization_params(context, options)
37 | return [context['context'] || context[:context], context] if context.is_a?(Hash) && options.empty?
38 | [context, Utility.symbolize_keys(options)]
39 | end
40 |
41 | def create_root_container
42 | ParseContainerTag.new { |t| Utility.array_to_s(t.contents) }
43 | end
44 |
45 | def process_tokens(tokens)
46 | tokens.each { |token| process_token(token) }
47 | validate_final_stack
48 | end
49 |
50 | def process_token(token)
51 | return @stack.last.contents << token if token.is_a?(String)
52 |
53 | case token[:flavor]
54 | when :open then handle_open_tag(token)
55 | when :self then handle_self_tag(token)
56 | when :close then handle_close_tag(token)
57 | when :tasteless then raise TastelessTagError.new(token, @stack)
58 | else raise UndefinedFlavorError.new(token, @stack)
59 | end
60 | end
61 |
62 | def handle_open_tag(token)
63 | @stack.push(ParseContainerTag.new(token[:name], token[:attrs]))
64 | end
65 |
66 | def handle_self_tag(token)
67 | @stack.last.contents << ParseTag.new { @context.render_tag(token[:name], token[:attrs]) }
68 | end
69 |
70 | def handle_close_tag(token)
71 | popped = @stack.pop
72 | validate_tag_match(popped, token[:name])
73 | wrap_and_push_tag(popped)
74 | end
75 |
76 | def validate_tag_match(popped, name)
77 | raise WrongEndTagError.new(popped.name, name, @stack) if popped.name != name
78 | end
79 |
80 | def wrap_and_push_tag(tag)
81 | tag.on_parse { |b| @context.render_tag(tag.name, tag.attributes) { Utility.array_to_s(b.contents) } }
82 | @stack.last.contents << tag
83 | end
84 |
85 | def validate_final_stack
86 | raise MissingEndTagError.new(@stack.last.name, @stack) if @stack.length != 1
87 | end
88 |
89 | def default_scanner
90 | if RUBY_PLATFORM == 'java'
91 | load_java_scanner
92 | else
93 | Radius::Scanner.new
94 | end
95 | end
96 |
97 | def load_java_scanner
98 | if Gem::Version.new(JRUBY_VERSION) >= Gem::Version.new('9.3')
99 | require 'jruby'
100 | else
101 | require 'java'
102 | end
103 | require 'radius/parser/java_scanner.jar'
104 | ::Radius.send(:include_package, 'radius.parser')
105 | Radius::JavaScanner.new(JRuby.runtime)
106 | end
107 | end
108 | end
109 |
--------------------------------------------------------------------------------
/lib/radius/parser/JavaScanner$Flavor.class:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jlong/radius/7f7347b156d01ffd499dddd412b94a8eb2882c62/lib/radius/parser/JavaScanner$Flavor.class
--------------------------------------------------------------------------------
/lib/radius/parser/JavaScanner$Tag.class:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jlong/radius/7f7347b156d01ffd499dddd412b94a8eb2882c62/lib/radius/parser/JavaScanner$Tag.class
--------------------------------------------------------------------------------
/lib/radius/parser/JavaScanner.class:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jlong/radius/7f7347b156d01ffd499dddd412b94a8eb2882c62/lib/radius/parser/JavaScanner.class
--------------------------------------------------------------------------------
/lib/radius/parser/JavaScanner.java:
--------------------------------------------------------------------------------
1 |
2 | // line 1 "JavaScanner.rl"
3 |
4 | // line 101 "JavaScanner.rl"
5 |
6 |
7 | package radius.parser;
8 |
9 | import java.util.HashMap;
10 | import java.util.LinkedList;
11 | import org.jruby.Ruby; // runtime
12 | import org.jruby.RubyObject;
13 | import org.jruby.runtime.builtin.IRubyObject;
14 | import org.jruby.RubyArray;
15 | import org.jruby.RubyString;
16 | import org.jruby.RubyHash;
17 | import org.jruby.RubySymbol;
18 |
19 | public class JavaScanner {
20 |
21 | Ruby runtime = null;
22 | RubyArray rv = null;
23 |
24 | void pass_through(String str) {
25 | RubyObject last = ((RubyObject)rv.last());
26 | if ( rv.size() > 0 && last != null && (last instanceof RubyString) ){
27 | // XXX concat changes for ruby 1.9
28 | ((RubyString) last).concat(RubyString.newString(runtime, str));
29 | } else {
30 | rv.append(RubyString.newString(runtime, str));
31 | }
32 | }
33 |
34 | void tag(String prefix, String name, RubyHash attr, RubySymbol flavor) {
35 | // Validate both prefix and name
36 | if ((prefix == null || prefix.trim().isEmpty()) &&
37 | (name == null || name.trim().isEmpty())) {
38 | pass_through("<");
39 | return;
40 | }
41 |
42 | if (name == null || name.trim().isEmpty()) {
43 | pass_through("<" + prefix + ":");
44 | return;
45 | }
46 |
47 | RubyHash tag = RubyHash.newHash(runtime);
48 | tag.op_aset(
49 | runtime.getCurrentContext(),
50 | RubySymbol.newSymbol(runtime, "prefix"),
51 | RubyString.newString(runtime, prefix != null ? prefix : "")
52 | );
53 | tag.op_aset(
54 | runtime.getCurrentContext(),
55 | RubySymbol.newSymbol(runtime, "name"),
56 | RubyString.newString(runtime, name)
57 | );
58 | tag.op_aset(
59 | runtime.getCurrentContext(),
60 | RubySymbol.newSymbol(runtime, "attrs"),
61 | attr
62 | );
63 | tag.op_aset(
64 | runtime.getCurrentContext(),
65 | RubySymbol.newSymbol(runtime, "flavor"),
66 | flavor
67 | );
68 | rv.append(tag);
69 | }
70 |
71 | public JavaScanner(Ruby runtime) {
72 | this.runtime = runtime;
73 | }
74 |
75 |
76 | // line 77 "JavaScanner.java"
77 | private static byte[] init__parser_actions_0()
78 | {
79 | return new byte [] {
80 | 0, 1, 0, 1, 3, 1, 4, 1, 5, 1, 6, 1,
81 | 7, 1, 8, 1, 9, 1, 10, 1, 14, 1, 15, 1,
82 | 19, 1, 21, 1, 22, 1, 23, 2, 1, 2, 2, 5,
83 | 6, 2, 6, 7, 2, 9, 5, 2, 9, 10, 2, 10,
84 | 9, 2, 11, 20, 2, 12, 20, 2, 13, 20, 2, 16,
85 | 17, 2, 16, 18, 3, 5, 6, 7, 3, 9, 5, 6,
86 | 3, 16, 6, 17, 4, 9, 5, 6, 7, 4, 16, 5,
87 | 6, 17, 5, 16, 9, 5, 6, 17
88 | };
89 | }
90 |
91 | private static final byte _parser_actions[] = init__parser_actions_0();
92 |
93 |
94 | private static short[] init__parser_key_offsets_0()
95 | {
96 | return new short [] {
97 | 0, 0, 11, 21, 35, 48, 62, 66, 71, 73, 75, 88,
98 | 101, 102, 104, 119, 134, 150, 156, 162, 177, 180, 183, 186,
99 | 201, 203, 205, 220, 236, 242, 248, 251, 254, 270, 286, 303,
100 | 310, 316, 332, 336, 352, 367, 370, 372, 382, 392, 403, 413,
101 | 428, 432, 442, 442, 443, 452, 452, 452, 454, 456, 459, 462,
102 | 464, 466
103 | };
104 | }
105 |
106 | private static final short _parser_key_offsets[] = init__parser_key_offsets_0();
107 |
108 |
109 | private static char[] init__parser_trans_keys_0()
110 | {
111 | return new char [] {
112 | 58, 63, 95, 45, 46, 48, 57, 65, 90, 97, 122, 63,
113 | 95, 45, 46, 48, 57, 65, 90, 97, 122, 32, 47, 58,
114 | 62, 63, 95, 9, 13, 45, 57, 65, 90, 97, 122, 32,
115 | 47, 62, 63, 95, 9, 13, 45, 58, 65, 90, 97, 122,
116 | 32, 61, 63, 95, 9, 13, 45, 46, 48, 58, 65, 90,
117 | 97, 122, 32, 61, 9, 13, 32, 34, 39, 9, 13, 34,
118 | 92, 34, 92, 32, 47, 62, 63, 95, 9, 13, 45, 58,
119 | 65, 90, 97, 122, 32, 47, 62, 63, 95, 9, 13, 45,
120 | 58, 65, 90, 97, 122, 62, 34, 92, 32, 34, 47, 62,
121 | 63, 92, 95, 9, 13, 45, 58, 65, 90, 97, 122, 32,
122 | 34, 47, 62, 63, 92, 95, 9, 13, 45, 58, 65, 90,
123 | 97, 122, 32, 34, 61, 63, 92, 95, 9, 13, 45, 46,
124 | 48, 58, 65, 90, 97, 122, 32, 34, 61, 92, 9, 13,
125 | 32, 34, 39, 92, 9, 13, 32, 34, 47, 62, 63, 92,
126 | 95, 9, 13, 45, 58, 65, 90, 97, 122, 34, 62, 92,
127 | 34, 39, 92, 34, 39, 92, 32, 39, 47, 62, 63, 92,
128 | 95, 9, 13, 45, 58, 65, 90, 97, 122, 39, 92, 39,
129 | 92, 32, 39, 47, 62, 63, 92, 95, 9, 13, 45, 58,
130 | 65, 90, 97, 122, 32, 39, 61, 63, 92, 95, 9, 13,
131 | 45, 46, 48, 58, 65, 90, 97, 122, 32, 39, 61, 92,
132 | 9, 13, 32, 34, 39, 92, 9, 13, 34, 39, 92, 34,
133 | 39, 92, 32, 34, 39, 47, 62, 63, 92, 95, 9, 13,
134 | 45, 58, 65, 90, 97, 122, 32, 34, 39, 47, 62, 63,
135 | 92, 95, 9, 13, 45, 58, 65, 90, 97, 122, 32, 34,
136 | 39, 61, 63, 92, 95, 9, 13, 45, 46, 48, 58, 65,
137 | 90, 97, 122, 32, 34, 39, 61, 92, 9, 13, 32, 34,
138 | 39, 92, 9, 13, 32, 34, 39, 47, 62, 63, 92, 95,
139 | 9, 13, 45, 58, 65, 90, 97, 122, 34, 39, 62, 92,
140 | 32, 34, 39, 47, 62, 63, 92, 95, 9, 13, 45, 58,
141 | 65, 90, 97, 122, 32, 39, 47, 62, 63, 92, 95, 9,
142 | 13, 45, 58, 65, 90, 97, 122, 39, 62, 92, 39, 92,
143 | 63, 95, 45, 46, 48, 57, 65, 90, 97, 122, 63, 95,
144 | 45, 46, 48, 57, 65, 90, 97, 122, 58, 63, 95, 45,
145 | 46, 48, 57, 65, 90, 97, 122, 63, 95, 45, 46, 48,
146 | 57, 65, 90, 97, 122, 32, 58, 62, 63, 95, 9, 13,
147 | 45, 46, 48, 57, 65, 90, 97, 122, 32, 62, 9, 13,
148 | 63, 95, 45, 46, 48, 57, 65, 90, 97, 122, 60, 47,
149 | 63, 95, 45, 57, 65, 90, 97, 122, 34, 92, 34, 92,
150 | 34, 39, 92, 34, 39, 92, 39, 92, 39, 92, 0
151 | };
152 | }
153 |
154 | private static final char _parser_trans_keys[] = init__parser_trans_keys_0();
155 |
156 |
157 | private static byte[] init__parser_single_lengths_0()
158 | {
159 | return new byte [] {
160 | 0, 3, 2, 6, 5, 4, 2, 3, 2, 2, 5, 5,
161 | 1, 2, 7, 7, 6, 4, 4, 7, 3, 3, 3, 7,
162 | 2, 2, 7, 6, 4, 4, 3, 3, 8, 8, 7, 5,
163 | 4, 8, 4, 8, 7, 3, 2, 2, 2, 3, 2, 5,
164 | 2, 2, 0, 1, 3, 0, 0, 2, 2, 3, 3, 2,
165 | 2, 0
166 | };
167 | }
168 |
169 | private static final byte _parser_single_lengths[] = init__parser_single_lengths_0();
170 |
171 |
172 | private static byte[] init__parser_range_lengths_0()
173 | {
174 | return new byte [] {
175 | 0, 4, 4, 4, 4, 5, 1, 1, 0, 0, 4, 4,
176 | 0, 0, 4, 4, 5, 1, 1, 4, 0, 0, 0, 4,
177 | 0, 0, 4, 5, 1, 1, 0, 0, 4, 4, 5, 1,
178 | 1, 4, 0, 4, 4, 0, 0, 4, 4, 4, 4, 5,
179 | 1, 4, 0, 0, 3, 0, 0, 0, 0, 0, 0, 0,
180 | 0, 0
181 | };
182 | }
183 |
184 | private static final byte _parser_range_lengths[] = init__parser_range_lengths_0();
185 |
186 |
187 | private static short[] init__parser_index_offsets_0()
188 | {
189 | return new short [] {
190 | 0, 0, 8, 15, 26, 36, 46, 50, 55, 58, 61, 71,
191 | 81, 83, 86, 98, 110, 122, 128, 134, 146, 150, 154, 158,
192 | 170, 173, 176, 188, 200, 206, 212, 216, 220, 233, 246, 259,
193 | 266, 272, 285, 290, 303, 315, 319, 322, 329, 336, 344, 351,
194 | 362, 366, 373, 374, 376, 383, 384, 385, 388, 391, 395, 399,
195 | 402, 405
196 | };
197 | }
198 |
199 | private static final short _parser_index_offsets[] = init__parser_index_offsets_0();
200 |
201 |
202 | private static byte[] init__parser_trans_targs_0()
203 | {
204 | return new byte [] {
205 | 2, 1, 1, 1, 1, 1, 1, 51, 3, 3, 3, 3,
206 | 3, 3, 51, 4, 12, 43, 54, 3, 3, 4, 3, 3,
207 | 3, 51, 4, 12, 54, 5, 5, 4, 5, 5, 5, 51,
208 | 6, 7, 5, 5, 6, 5, 5, 5, 5, 51, 6, 7,
209 | 6, 51, 7, 8, 42, 7, 51, 10, 13, 9, 10, 13,
210 | 9, 11, 12, 54, 5, 5, 11, 5, 5, 5, 51, 11,
211 | 12, 54, 5, 5, 11, 5, 5, 5, 51, 53, 51, 14,
212 | 13, 9, 15, 10, 20, 56, 16, 13, 16, 15, 16, 16,
213 | 16, 9, 15, 10, 20, 56, 16, 13, 16, 15, 16, 16,
214 | 16, 9, 17, 10, 18, 16, 13, 16, 17, 16, 16, 16,
215 | 16, 9, 17, 10, 18, 13, 17, 9, 18, 19, 21, 13,
216 | 18, 9, 15, 10, 20, 56, 16, 13, 16, 15, 16, 16,
217 | 16, 9, 10, 55, 13, 9, 23, 14, 31, 22, 23, 14,
218 | 31, 22, 26, 10, 41, 60, 27, 25, 27, 26, 27, 27,
219 | 27, 24, 10, 25, 24, 23, 25, 24, 26, 10, 41, 60,
220 | 27, 25, 27, 26, 27, 27, 27, 24, 28, 10, 29, 27,
221 | 25, 27, 28, 27, 27, 27, 27, 24, 28, 10, 29, 25,
222 | 28, 24, 29, 30, 40, 25, 29, 24, 23, 14, 31, 22,
223 | 32, 32, 31, 22, 33, 23, 14, 38, 58, 34, 31, 34,
224 | 33, 34, 34, 34, 22, 33, 23, 14, 38, 58, 34, 31,
225 | 34, 33, 34, 34, 34, 22, 35, 23, 14, 36, 34, 31,
226 | 34, 35, 34, 34, 34, 34, 22, 35, 23, 14, 36, 31,
227 | 35, 22, 36, 37, 39, 31, 36, 22, 33, 23, 14, 38,
228 | 58, 34, 31, 34, 33, 34, 34, 34, 22, 23, 14, 57,
229 | 31, 22, 33, 23, 14, 38, 58, 34, 31, 34, 33, 34,
230 | 34, 34, 22, 26, 10, 41, 60, 27, 25, 27, 26, 27,
231 | 27, 27, 24, 10, 59, 25, 24, 10, 25, 24, 3, 3,
232 | 3, 3, 3, 3, 51, 45, 45, 45, 45, 45, 45, 51,
233 | 46, 45, 45, 45, 45, 45, 45, 51, 47, 47, 47, 47,
234 | 47, 47, 51, 48, 49, 61, 47, 47, 48, 47, 47, 47,
235 | 47, 51, 48, 61, 48, 51, 47, 47, 47, 47, 47, 47,
236 | 51, 0, 52, 51, 44, 1, 1, 1, 1, 1, 51, 51,
237 | 51, 10, 13, 9, 10, 13, 9, 23, 14, 31, 22, 23,
238 | 14, 31, 22, 10, 25, 24, 10, 25, 24, 51, 51, 51,
239 | 51, 51, 51, 51, 51, 51, 51, 51, 51, 51, 51, 51,
240 | 51, 51, 51, 51, 51, 51, 51, 51, 51, 51, 51, 51,
241 | 51, 51, 51, 51, 51, 51, 51, 51, 51, 51, 51, 51,
242 | 51, 51, 51, 51, 51, 51, 51, 51, 51, 51, 51, 51,
243 | 51, 51, 51, 51, 51, 51, 51, 51, 51, 0
244 | };
245 | }
246 |
247 | private static final byte _parser_trans_targs[] = init__parser_trans_targs_0();
248 |
249 |
250 | private static byte[] init__parser_trans_actions_0()
251 | {
252 | return new byte [] {
253 | 31, 0, 0, 0, 0, 0, 0, 27, 3, 3, 3, 3,
254 | 3, 3, 27, 5, 5, 0, 5, 0, 0, 5, 0, 0,
255 | 0, 27, 0, 0, 0, 11, 11, 0, 11, 11, 11, 27,
256 | 13, 13, 0, 0, 13, 0, 0, 0, 0, 29, 0, 0,
257 | 0, 29, 0, 0, 0, 0, 29, 43, 15, 15, 17, 0,
258 | 0, 7, 34, 34, 64, 64, 7, 64, 64, 64, 29, 0,
259 | 9, 9, 37, 37, 0, 37, 37, 37, 29, 0, 29, 17,
260 | 0, 0, 7, 17, 34, 81, 64, 0, 64, 7, 64, 64,
261 | 64, 0, 0, 17, 9, 72, 37, 0, 37, 0, 37, 37,
262 | 37, 0, 13, 17, 13, 0, 0, 0, 13, 0, 0, 0,
263 | 0, 0, 0, 17, 0, 0, 0, 0, 0, 17, 0, 0,
264 | 0, 0, 40, 43, 68, 86, 76, 15, 76, 40, 76, 76,
265 | 76, 15, 17, 58, 0, 0, 46, 43, 15, 15, 17, 17,
266 | 0, 0, 7, 17, 34, 81, 64, 0, 64, 7, 64, 64,
267 | 64, 0, 17, 0, 0, 17, 0, 0, 0, 17, 9, 72,
268 | 37, 0, 37, 0, 37, 37, 37, 0, 13, 17, 13, 0,
269 | 0, 0, 13, 0, 0, 0, 0, 0, 0, 17, 0, 0,
270 | 0, 0, 0, 0, 17, 0, 0, 0, 43, 43, 15, 15,
271 | 17, 17, 0, 0, 7, 17, 17, 34, 81, 64, 0, 64,
272 | 7, 64, 64, 64, 0, 0, 17, 17, 9, 72, 37, 0,
273 | 37, 0, 37, 37, 37, 0, 13, 17, 17, 13, 0, 0,
274 | 0, 13, 0, 0, 0, 0, 0, 0, 17, 17, 0, 0,
275 | 0, 0, 0, 17, 17, 0, 0, 0, 40, 43, 43, 68,
276 | 86, 76, 15, 76, 40, 76, 76, 76, 15, 17, 17, 58,
277 | 0, 0, 40, 46, 43, 68, 86, 76, 15, 76, 40, 76,
278 | 76, 76, 15, 40, 43, 68, 86, 76, 15, 76, 40, 76,
279 | 76, 76, 15, 17, 58, 0, 0, 43, 15, 15, 0, 0,
280 | 0, 0, 0, 0, 27, 1, 1, 1, 1, 1, 1, 27,
281 | 31, 0, 0, 0, 0, 0, 0, 27, 3, 3, 3, 3,
282 | 3, 3, 27, 5, 0, 5, 0, 0, 5, 0, 0, 0,
283 | 0, 27, 0, 0, 0, 27, 0, 0, 0, 0, 0, 0,
284 | 27, 0, 61, 23, 0, 1, 1, 1, 1, 1, 25, 52,
285 | 49, 17, 0, 0, 17, 0, 0, 17, 17, 0, 0, 17,
286 | 17, 0, 0, 17, 0, 0, 17, 0, 0, 55, 27, 27,
287 | 27, 27, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29,
288 | 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29,
289 | 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29,
290 | 29, 29, 29, 29, 27, 27, 27, 27, 27, 27, 27, 25,
291 | 52, 49, 52, 49, 52, 49, 52, 49, 55, 0
292 | };
293 | }
294 |
295 | private static final byte _parser_trans_actions[] = init__parser_trans_actions_0();
296 |
297 |
298 | private static byte[] init__parser_to_state_actions_0()
299 | {
300 | return new byte [] {
301 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
302 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
303 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
304 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
305 | 0, 0, 19, 19, 0, 0, 0, 0, 0, 0, 0, 0,
306 | 0, 0
307 | };
308 | }
309 |
310 | private static final byte _parser_to_state_actions[] = init__parser_to_state_actions_0();
311 |
312 |
313 | private static byte[] init__parser_from_state_actions_0()
314 | {
315 | return new byte [] {
316 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
317 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
318 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
319 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
320 | 0, 0, 0, 21, 0, 0, 0, 0, 0, 0, 0, 0,
321 | 0, 0
322 | };
323 | }
324 |
325 | private static final byte _parser_from_state_actions[] = init__parser_from_state_actions_0();
326 |
327 |
328 | private static short[] init__parser_eof_trans_0()
329 | {
330 | return new short [] {
331 | 0, 455, 455, 455, 455, 448, 448, 448, 448, 448, 448, 448,
332 | 448, 448, 448, 448, 448, 448, 448, 448, 448, 448, 448, 448,
333 | 448, 448, 448, 448, 448, 448, 448, 448, 448, 448, 448, 448,
334 | 448, 448, 448, 448, 448, 448, 448, 455, 455, 455, 455, 455,
335 | 455, 455, 0, 0, 456, 463, 464, 463, 464, 463, 464, 463,
336 | 464, 465
337 | };
338 | }
339 |
340 | private static final short _parser_eof_trans[] = init__parser_eof_trans_0();
341 |
342 |
343 | static final int parser_start = 51;
344 | static final int parser_first_final = 51;
345 | static final int parser_error = 0;
346 |
347 | static final int parser_en_Closeout = 50;
348 | static final int parser_en_main = 51;
349 |
350 |
351 | // line 172 "JavaScanner.rl"
352 |
353 | public RubyArray operate(String tag_prefix, String input) {
354 | char[] data = input.toCharArray();
355 | String disposable_string;
356 |
357 | String name = "";
358 | String prefix = "";
359 | RubySymbol flavor = RubySymbol.newSymbol(runtime, "tasteless".intern());
360 | RubyHash attributes = RubyHash.newHash(runtime);
361 |
362 | int tagstart = 0;
363 | int mark_pfx = 0;
364 | int mark_stg = 0;
365 | int mark_attr = 0;
366 | int mark_nat = 0;
367 | int mark_vat = 0;
368 |
369 | String nat = "";
370 | String vat = "";
371 |
372 | int cs;
373 | int p = 0;
374 | int pe = data.length;
375 | int eof = pe;
376 | int act;
377 | int ts;
378 | int te;
379 |
380 | rv = RubyArray.newArray(runtime);
381 | char[] remainder = data;
382 |
383 |
384 | // line 385 "JavaScanner.java"
385 | {
386 | cs = parser_start;
387 | ts = -1;
388 | te = -1;
389 | act = 0;
390 | }
391 |
392 | // line 204 "JavaScanner.rl"
393 |
394 | // line 395 "JavaScanner.java"
395 | {
396 | int _klen;
397 | int _trans = 0;
398 | int _acts;
399 | int _nacts;
400 | int _keys;
401 | int _goto_targ = 0;
402 |
403 | _goto: while (true) {
404 | switch ( _goto_targ ) {
405 | case 0:
406 | if ( p == pe ) {
407 | _goto_targ = 4;
408 | continue _goto;
409 | }
410 | if ( cs == 0 ) {
411 | _goto_targ = 5;
412 | continue _goto;
413 | }
414 | case 1:
415 | _acts = _parser_from_state_actions[cs];
416 | _nacts = (int) _parser_actions[_acts++];
417 | while ( _nacts-- > 0 ) {
418 | switch ( _parser_actions[_acts++] ) {
419 | case 15:
420 | // line 1 "NONE"
421 | {ts = p;}
422 | break;
423 | // line 424 "JavaScanner.java"
424 | }
425 | }
426 |
427 | _match: do {
428 | _keys = _parser_key_offsets[cs];
429 | _trans = _parser_index_offsets[cs];
430 | _klen = _parser_single_lengths[cs];
431 | if ( _klen > 0 ) {
432 | int _lower = _keys;
433 | int _mid;
434 | int _upper = _keys + _klen - 1;
435 | while (true) {
436 | if ( _upper < _lower )
437 | break;
438 |
439 | _mid = _lower + ((_upper-_lower) >> 1);
440 | if ( data[p] < _parser_trans_keys[_mid] )
441 | _upper = _mid - 1;
442 | else if ( data[p] > _parser_trans_keys[_mid] )
443 | _lower = _mid + 1;
444 | else {
445 | _trans += (_mid - _keys);
446 | break _match;
447 | }
448 | }
449 | _keys += _klen;
450 | _trans += _klen;
451 | }
452 |
453 | _klen = _parser_range_lengths[cs];
454 | if ( _klen > 0 ) {
455 | int _lower = _keys;
456 | int _mid;
457 | int _upper = _keys + (_klen<<1) - 2;
458 | while (true) {
459 | if ( _upper < _lower )
460 | break;
461 |
462 | _mid = _lower + (((_upper-_lower) >> 1) & ~1);
463 | if ( data[p] < _parser_trans_keys[_mid] )
464 | _upper = _mid - 2;
465 | else if ( data[p] > _parser_trans_keys[_mid+1] )
466 | _lower = _mid + 2;
467 | else {
468 | _trans += ((_mid - _keys)>>1);
469 | break _match;
470 | }
471 | }
472 | _trans += _klen;
473 | }
474 | } while (false);
475 |
476 | case 3:
477 | cs = _parser_trans_targs[_trans];
478 |
479 | if ( _parser_trans_actions[_trans] != 0 ) {
480 | _acts = _parser_trans_actions[_trans];
481 | _nacts = (int) _parser_actions[_acts++];
482 | while ( _nacts-- > 0 )
483 | {
484 | switch ( _parser_actions[_acts++] )
485 | {
486 | case 0:
487 | // line 4 "JavaScanner.rl"
488 | { mark_pfx = p; }
489 | break;
490 | case 1:
491 | // line 5 "JavaScanner.rl"
492 | {
493 | prefix = String.valueOf(input.substring(mark_pfx, p));
494 | }
495 | break;
496 | case 2:
497 | // line 8 "JavaScanner.rl"
498 | {
499 | if ( !prefix.equals(tag_prefix) ) {
500 | // Pass through the entire tag markup as text
501 | pass_through(input.substring(tagstart, p + 1));
502 | // Reset all state
503 | prefix = "";
504 | name = "";
505 | attributes = RubyHash.newHash(runtime);
506 | flavor = RubySymbol.newSymbol(runtime, "tasteless".intern());
507 | tagstart = p + 1;
508 | {cs = 51; _goto_targ = 2; if (true) continue _goto;}
509 | }
510 | }
511 | break;
512 | case 3:
513 | // line 22 "JavaScanner.rl"
514 | { mark_stg = p; }
515 | break;
516 | case 4:
517 | // line 23 "JavaScanner.rl"
518 | {
519 | name = String.valueOf(input.substring(mark_stg, p));
520 | if (name == null || name.trim().isEmpty()) {
521 | // Pass through the entire tag markup as text
522 | pass_through(input.substring(tagstart, p + 1));
523 | // Reset all state
524 | prefix = "";
525 | name = "";
526 | attributes = RubyHash.newHash(runtime);
527 | flavor = RubySymbol.newSymbol(runtime, "tasteless".intern());
528 | tagstart = p + 1;
529 | {cs = 51; _goto_targ = 2; if (true) continue _goto;}
530 | }
531 | }
532 | break;
533 | case 5:
534 | // line 37 "JavaScanner.rl"
535 | { mark_attr = p; }
536 | break;
537 | case 6:
538 | // line 38 "JavaScanner.rl"
539 | {
540 | attributes.op_aset(
541 | runtime.getCurrentContext(),
542 | RubyString.newString(runtime, nat),
543 | RubyString.newString(runtime, vat)
544 | );
545 | }
546 | break;
547 | case 7:
548 | // line 46 "JavaScanner.rl"
549 | { mark_nat = p; }
550 | break;
551 | case 8:
552 | // line 47 "JavaScanner.rl"
553 | { nat = input.substring(mark_nat, p); }
554 | break;
555 | case 9:
556 | // line 48 "JavaScanner.rl"
557 | { mark_vat = p; }
558 | break;
559 | case 10:
560 | // line 49 "JavaScanner.rl"
561 | { vat = input.substring(mark_vat, p); }
562 | break;
563 | case 11:
564 | // line 51 "JavaScanner.rl"
565 | { flavor = RubySymbol.newSymbol(runtime, "open".intern()); }
566 | break;
567 | case 12:
568 | // line 52 "JavaScanner.rl"
569 | { flavor = RubySymbol.newSymbol(runtime, "self".intern()); }
570 | break;
571 | case 13:
572 | // line 53 "JavaScanner.rl"
573 | { flavor = RubySymbol.newSymbol(runtime, "close".intern()); }
574 | break;
575 | case 16:
576 | // line 1 "NONE"
577 | {te = p+1;}
578 | break;
579 | case 17:
580 | // line 89 "JavaScanner.rl"
581 | {act = 1;}
582 | break;
583 | case 18:
584 | // line 96 "JavaScanner.rl"
585 | {act = 2;}
586 | break;
587 | case 19:
588 | // line 96 "JavaScanner.rl"
589 | {te = p+1;{
590 | pass_through(input.substring(p, p + 1));
591 | tagstart = p + 1;
592 | }}
593 | break;
594 | case 20:
595 | // line 89 "JavaScanner.rl"
596 | {te = p;p--;{
597 | tag(prefix, name, attributes, flavor);
598 | prefix = "";
599 | name = "";
600 | attributes = RubyHash.newHash(runtime);
601 | flavor = RubySymbol.newSymbol(runtime, "tasteless".intern());
602 | }}
603 | break;
604 | case 21:
605 | // line 96 "JavaScanner.rl"
606 | {te = p;p--;{
607 | pass_through(input.substring(p, p + 1));
608 | tagstart = p + 1;
609 | }}
610 | break;
611 | case 22:
612 | // line 96 "JavaScanner.rl"
613 | {{p = ((te))-1;}{
614 | pass_through(input.substring(p, p + 1));
615 | tagstart = p + 1;
616 | }}
617 | break;
618 | case 23:
619 | // line 1 "NONE"
620 | { switch( act ) {
621 | case 1:
622 | {{p = ((te))-1;}
623 | tag(prefix, name, attributes, flavor);
624 | prefix = "";
625 | name = "";
626 | attributes = RubyHash.newHash(runtime);
627 | flavor = RubySymbol.newSymbol(runtime, "tasteless".intern());
628 | }
629 | break;
630 | case 2:
631 | {{p = ((te))-1;}
632 | pass_through(input.substring(p, p + 1));
633 | tagstart = p + 1;
634 | }
635 | break;
636 | }
637 | }
638 | break;
639 | // line 640 "JavaScanner.java"
640 | }
641 | }
642 | }
643 |
644 | case 2:
645 | _acts = _parser_to_state_actions[cs];
646 | _nacts = (int) _parser_actions[_acts++];
647 | while ( _nacts-- > 0 ) {
648 | switch ( _parser_actions[_acts++] ) {
649 | case 14:
650 | // line 1 "NONE"
651 | {ts = -1;}
652 | break;
653 | // line 654 "JavaScanner.java"
654 | }
655 | }
656 |
657 | if ( cs == 0 ) {
658 | _goto_targ = 5;
659 | continue _goto;
660 | }
661 | if ( ++p != pe ) {
662 | _goto_targ = 1;
663 | continue _goto;
664 | }
665 | case 4:
666 | if ( p == eof )
667 | {
668 | if ( _parser_eof_trans[cs] > 0 ) {
669 | _trans = _parser_eof_trans[cs] - 1;
670 | _goto_targ = 3;
671 | continue _goto;
672 | }
673 | }
674 |
675 | case 5:
676 | }
677 | break; }
678 | }
679 |
680 | // line 205 "JavaScanner.rl"
681 |
682 | return rv;
683 | }
684 | }
685 |
--------------------------------------------------------------------------------
/lib/radius/parser/JavaScanner.rl:
--------------------------------------------------------------------------------
1 | %%{
2 | machine parser;
3 |
4 | action _prefix { mark_pfx = p; }
5 | action prefix {
6 | prefix = String.valueOf(input.substring(mark_pfx, p));
7 | }
8 | action _check_prefix {
9 | if ( !prefix.equals(tag_prefix) ) {
10 | // Pass through the entire tag markup as text
11 | pass_through(input.substring(tagstart, p + 1));
12 | // Reset all state
13 | prefix = "";
14 | name = "";
15 | attributes = RubyHash.newHash(runtime);
16 | flavor = RubySymbol.newSymbol(runtime, "tasteless".intern());
17 | tagstart = p + 1;
18 | fgoto main;
19 | }
20 | }
21 |
22 | action _starttag { mark_stg = p; }
23 | action starttag {
24 | name = String.valueOf(input.substring(mark_stg, p));
25 | if (name == null || name.trim().isEmpty()) {
26 | // Pass through the entire tag markup as text
27 | pass_through(input.substring(tagstart, p + 1));
28 | // Reset all state
29 | prefix = "";
30 | name = "";
31 | attributes = RubyHash.newHash(runtime);
32 | flavor = RubySymbol.newSymbol(runtime, "tasteless".intern());
33 | tagstart = p + 1;
34 | fgoto main;
35 | }
36 | }
37 | action _attr { mark_attr = p; }
38 | action attr {
39 | attributes.op_aset(
40 | runtime.getCurrentContext(),
41 | RubyString.newString(runtime, nat),
42 | RubyString.newString(runtime, vat)
43 | );
44 | }
45 |
46 | action _nameattr { mark_nat = p; }
47 | action nameattr { nat = input.substring(mark_nat, p); }
48 | action _valattr { mark_vat = p; }
49 | action valattr { vat = input.substring(mark_vat, p); }
50 |
51 | action opentag { flavor = RubySymbol.newSymbol(runtime, "open".intern()); }
52 | action selftag { flavor = RubySymbol.newSymbol(runtime, "self".intern()); }
53 | action closetag { flavor = RubySymbol.newSymbol(runtime, "close".intern()); }
54 |
55 | Closeout := empty;
56 |
57 | # words
58 | PrefixChar = [\-A-Za-z0-9._?] ;
59 | NameChar = [\-A-Za-z0-9._:?] ;
60 | TagName = ([\-A-Za-z0-9._?]+ (':' [\-A-Za-z0-9._?]+)*) >_starttag %starttag;
61 | Prefix = PrefixChar+ >_prefix %prefix;
62 | Open = "<";
63 | Sep = ":" >_check_prefix;
64 | End = "/";
65 | Close = ">";
66 |
67 | Name = Prefix Sep TagName;
68 |
69 | NameAttr = NameChar+ >_nameattr %nameattr;
70 | Q1Char = ( "\\\'" | [^'] ) ;
71 | Q1Attr = Q1Char* >_valattr %valattr;
72 | Q2Char = ( "\\\"" | [^"] ) ;
73 | Q2Attr = Q2Char* >_valattr %valattr;
74 |
75 | Attr = NameAttr space* "=" space* ('"' Q2Attr '"' | "'" Q1Attr "'") space* >_attr %attr;
76 | Attrs = (space+ Attr* | empty);
77 |
78 | CloseTrailer = End Close %selftag;
79 | OpenTrailer = Close %opentag;
80 |
81 | Trailer = (OpenTrailer | CloseTrailer);
82 |
83 | OpenOrSelfTag = Name Attrs? Trailer;
84 | CloseTag = End Name space* Close %closetag;
85 |
86 | SomeTag = Open (OpenOrSelfTag | CloseTag);
87 |
88 | main := |*
89 | SomeTag => {
90 | tag(prefix, name, attributes, flavor);
91 | prefix = "";
92 | name = "";
93 | attributes = RubyHash.newHash(runtime);
94 | flavor = RubySymbol.newSymbol(runtime, "tasteless".intern());
95 | };
96 | any => {
97 | pass_through(input.substring(p, p + 1));
98 | tagstart = p + 1;
99 | };
100 | *|;
101 | }%%
102 |
103 | package radius.parser;
104 |
105 | import java.util.HashMap;
106 | import java.util.LinkedList;
107 | import org.jruby.Ruby; // runtime
108 | import org.jruby.RubyObject;
109 | import org.jruby.runtime.builtin.IRubyObject;
110 | import org.jruby.RubyArray;
111 | import org.jruby.RubyString;
112 | import org.jruby.RubyHash;
113 | import org.jruby.RubySymbol;
114 |
115 | public class JavaScanner {
116 |
117 | Ruby runtime = null;
118 | RubyArray rv = null;
119 |
120 | void pass_through(String str) {
121 | RubyObject last = ((RubyObject)rv.last());
122 | if ( rv.size() > 0 && last != null && (last instanceof RubyString) ){
123 | // XXX concat changes for ruby 1.9
124 | ((RubyString) last).concat(RubyString.newString(runtime, str));
125 | } else {
126 | rv.append(RubyString.newString(runtime, str));
127 | }
128 | }
129 |
130 | void tag(String prefix, String name, RubyHash attr, RubySymbol flavor) {
131 | // Validate both prefix and name
132 | if ((prefix == null || prefix.trim().isEmpty()) &&
133 | (name == null || name.trim().isEmpty())) {
134 | pass_through("<");
135 | return;
136 | }
137 |
138 | if (name == null || name.trim().isEmpty()) {
139 | pass_through("<" + prefix + ":");
140 | return;
141 | }
142 |
143 | RubyHash tag = RubyHash.newHash(runtime);
144 | tag.op_aset(
145 | runtime.getCurrentContext(),
146 | RubySymbol.newSymbol(runtime, "prefix"),
147 | RubyString.newString(runtime, prefix != null ? prefix : "")
148 | );
149 | tag.op_aset(
150 | runtime.getCurrentContext(),
151 | RubySymbol.newSymbol(runtime, "name"),
152 | RubyString.newString(runtime, name)
153 | );
154 | tag.op_aset(
155 | runtime.getCurrentContext(),
156 | RubySymbol.newSymbol(runtime, "attrs"),
157 | attr
158 | );
159 | tag.op_aset(
160 | runtime.getCurrentContext(),
161 | RubySymbol.newSymbol(runtime, "flavor"),
162 | flavor
163 | );
164 | rv.append(tag);
165 | }
166 |
167 | public JavaScanner(Ruby runtime) {
168 | this.runtime = runtime;
169 | }
170 |
171 | %% write data;
172 |
173 | public RubyArray operate(String tag_prefix, String input) {
174 | char[] data = input.toCharArray();
175 | String disposable_string;
176 |
177 | String name = "";
178 | String prefix = "";
179 | RubySymbol flavor = RubySymbol.newSymbol(runtime, "tasteless".intern());
180 | RubyHash attributes = RubyHash.newHash(runtime);
181 |
182 | int tagstart = 0;
183 | int mark_pfx = 0;
184 | int mark_stg = 0;
185 | int mark_attr = 0;
186 | int mark_nat = 0;
187 | int mark_vat = 0;
188 |
189 | String nat = "";
190 | String vat = "";
191 |
192 | int cs;
193 | int p = 0;
194 | int pe = data.length;
195 | int eof = pe;
196 | int act;
197 | int ts;
198 | int te;
199 |
200 | rv = RubyArray.newArray(runtime);
201 | char[] remainder = data;
202 |
203 | %% write init;
204 | %% write exec;
205 |
206 | return rv;
207 | }
208 | }
209 |
--------------------------------------------------------------------------------
/lib/radius/parser/java_scanner.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jlong/radius/7f7347b156d01ffd499dddd412b94a8eb2882c62/lib/radius/parser/java_scanner.jar
--------------------------------------------------------------------------------
/lib/radius/parser/scanner.rb:
--------------------------------------------------------------------------------
1 | module Radius
2 | class Scanner
3 |
4 | # The regular expression used to find (1) opening and self-enclosed tag names, (2) self-enclosing trailing slash,
5 | # (3) attributes and (4) closing tag
6 | def scanner_regex(prefix = nil)
7 | %r{<#{prefix}:([-\w:]+?)(\s+(?:[-\w]+\s*=\s*(?:"[^"]*?"|'[^']*?')\s*)*|)(\/?)>|<\/#{prefix}:([-\w:]+?)\s*>}
8 | end
9 |
10 | # Parses a given string and returns an array of nodes.
11 | # The nodes consist of strings and hashes that describe a Radius tag that was found.
12 | def operate(prefix, data)
13 | data = Radius::OrdString(data)
14 | @nodes = ['']
15 |
16 | re = scanner_regex(prefix)
17 | if md = re.match(data)
18 | remainder = ''
19 | while md
20 | start_tag, attributes, self_enclosed, end_tag = $1, $2, $3, $4
21 |
22 | flavor = self_enclosed == '/' ? :self : (start_tag ? :open : :close)
23 |
24 | # save the part before the current match as a string node
25 | @nodes << md.pre_match
26 |
27 | # save the tag that was found as a tag hash node
28 | @nodes << {:prefix=>prefix, :name=>(start_tag || end_tag), :flavor => flavor, :attrs => parse_attributes(attributes)}
29 |
30 | # remember the part after the current match
31 | remainder = md.post_match
32 |
33 | # see if we find another tag in the remaining string
34 | md = re.match(md.post_match)
35 | end
36 |
37 | # add the last remaining string after the last tag that was found as a string node
38 | @nodes << remainder
39 | else
40 | @nodes << data
41 | end
42 |
43 | return @nodes
44 | end
45 |
46 | private
47 |
48 | def parse_attributes(text) # :nodoc:
49 | attr = {}
50 | re = /([-\w]+?)\s*=\s*('|")(.*?)\2/
51 | while md = re.match(text)
52 | attr[$1] = $3
53 | text = md.post_match
54 | end
55 | attr
56 | end
57 | end
58 | end
59 |
--------------------------------------------------------------------------------
/lib/radius/parser/squiggle_scanner.rb:
--------------------------------------------------------------------------------
1 | module Radius
2 | class SquiggleScanner < Scanner
3 |
4 | # The regular expression used to find (1) opening and self-enclosed tag names, (2) self-enclosing trailing slash,
5 | # (3) attributes and (4) closing tag
6 | def scanner_regex(prefix = nil)
7 | %r{\{\s*([\w:]+?)(\s+(?:\w+\s*=\s*(?:"[^"]*?"|'[^']*?')\s*)*|)(\/?)\}|\{\/([\w:]+?)\s*\}}
8 | end
9 |
10 | end
11 | end
--------------------------------------------------------------------------------
/lib/radius/tag_binding.rb:
--------------------------------------------------------------------------------
1 | module Radius
2 | #
3 | # A tag binding is passed into each tag definition and contains helper methods for working
4 | # with tags. Use it to gain access to the attributes that were passed to the tag, to
5 | # render the tag contents, and to do other tasks.
6 | #
7 | class TagBinding
8 | # The Context that the TagBinding is associated with. Used internally. Try not to use
9 | # this object directly.
10 | attr_reader :context
11 |
12 | # The locals object for the current tag.
13 | attr_reader :locals
14 |
15 | # The name of the tag (as used in a template string).
16 | attr_reader :name
17 |
18 | # The attributes of the tag. Also aliased as TagBinding#attr.
19 | attr_reader :attributes
20 | alias :attr :attributes
21 |
22 | # The render block. When called expands the contents of the tag. Use TagBinding#expand
23 | # instead.
24 | attr_reader :block
25 |
26 | # Creates a new TagBinding object.
27 | def initialize(context, locals, name, attributes, block)
28 | @context, @locals, @name, @attributes, @block = context, locals, name, attributes, block
29 | end
30 |
31 | # Evaluates the current tag and returns the rendered contents.
32 | def expand
33 | double? ? block.call : ''
34 | end
35 |
36 | # Returns true if the current tag is a single tag.
37 | def single?
38 | block.nil?
39 | end
40 |
41 | # Returns true if the current tag is a container tag.
42 | def double?
43 | not single?
44 | end
45 |
46 | # The globals object from which all locals objects ultimately inherit their values.
47 | def globals
48 | @context.globals
49 | end
50 |
51 | # Returns a list of the way tags are nested around the current tag as a string.
52 | def nesting
53 | @context.current_nesting
54 | end
55 |
56 | # Fires off Context#tag_missing for the current tag.
57 | def missing!
58 | @context.tag_missing(name, attributes, &block)
59 | end
60 |
61 | # Renders the tag using the current context .
62 | def render(tag, attributes = {}, &block)
63 | @context.render_tag(tag, attributes, &block)
64 | end
65 |
66 | # Shortcut for accessing tag.attr[key]
67 | def [](key)
68 | attr[key]
69 | end
70 | end
71 | end
--------------------------------------------------------------------------------
/lib/radius/tag_definitions.rb:
--------------------------------------------------------------------------------
1 | module Radius
2 | module TagDefinitions # :nodoc:
3 | class TagFactory # :nodoc:
4 | def initialize(context)
5 | @context = context
6 | end
7 |
8 | def define_tag(name, options, &block)
9 | options = prepare_options(name, options)
10 | validate_params(name, options, &block)
11 | construct_tag_set(name, options, &block)
12 | expose_methods_as_tags(name, options)
13 | end
14 |
15 | protected
16 |
17 | # Adds the tag definition to the context. Override in subclasses to add additional tags
18 | # (child tags) when the tag is created.
19 | def construct_tag_set(name, options, &block)
20 | if block
21 | @context.definitions[name.to_s] = block
22 | else
23 | lp = last_part(name)
24 | @context.define_tag(name) do |tag|
25 | if tag.single?
26 | options[:for]
27 | else
28 | tag.locals.send("#{ lp }=", options[:for]) unless options[:for].nil?
29 | tag.expand
30 | end
31 | end
32 | end
33 | end
34 |
35 | # Normalizes options pased to tag definition. Override in decendants to preform
36 | # additional normalization.
37 | def prepare_options(name, options)
38 | options = Utility.symbolize_keys(options)
39 | options[:expose] = expand_array_option(options[:expose])
40 | object = options[:for]
41 | options[:attributes] = object.respond_to?(:attributes) unless options.has_key? :attributes
42 | options[:expose] += object.attributes.keys if options[:attributes]
43 | options
44 | end
45 |
46 | # Validates parameters passed to tag definition. Override in decendants to add custom
47 | # validations.
48 | def validate_params(name, options, &block)
49 | unless options.has_key? :for
50 | raise ArgumentError.new("tag definition must contain a :for option or a block") unless block
51 | raise ArgumentError.new("tag definition must contain a :for option when used with the :expose option") unless options[:expose].empty?
52 | end
53 | end
54 |
55 | # Exposes the methods of an object as child tags.
56 | def expose_methods_as_tags(name, options)
57 | options[:expose].each do |method|
58 | tag_name = "#{name}:#{method}"
59 | lp = last_part(name)
60 | @context.define_tag(tag_name) do |tag|
61 | object = tag.locals.send(lp)
62 | object.send(method)
63 | end
64 | end
65 | end
66 |
67 | protected
68 |
69 | def expand_array_option(value)
70 | [*value].compact.map { |m| m.to_s.intern }
71 | end
72 |
73 | def last_part(name)
74 | name.split(':').last
75 | end
76 | end
77 | end
78 | end
79 |
--------------------------------------------------------------------------------
/lib/radius/utility.rb:
--------------------------------------------------------------------------------
1 | module Radius
2 | module Utility # :nodoc:
3 | def self.symbolize_keys(hash)
4 | new_hash = {}
5 | hash.keys.each do |k|
6 | new_hash[k.to_s.intern] = hash[k]
7 | end
8 | new_hash
9 | end
10 |
11 | def self.impartial_hash_delete(hash, key)
12 | string = key.to_s
13 | symbol = string.intern
14 | value1 = hash.delete(symbol)
15 | value2 = hash.delete(string)
16 | value1 || value2
17 | end
18 |
19 | def self.constantize(camelized_string)
20 | raise "invalid constant name `#{camelized_string}'" unless camelized_string.split('::').all? { |part| part =~ /^[A-Za-z]+$/ }
21 | Object.module_eval(camelized_string)
22 | end
23 |
24 | def self.camelize(underscored_string)
25 | underscored_string.split('_').map(&:capitalize).join
26 | end
27 |
28 | def self.array_to_s(c)
29 | +c.map do |x|
30 | x.is_a?(Array) ? array_to_s(x) : x.to_s
31 | end.join
32 | end
33 | end
34 | end
--------------------------------------------------------------------------------
/lib/radius/version.rb:
--------------------------------------------------------------------------------
1 | module Radius #:nodoc:
2 | VERSION = "0.8.1"
3 | def self.version
4 | VERSION
5 | end
6 | end
7 |
--------------------------------------------------------------------------------
/radius.gemspec:
--------------------------------------------------------------------------------
1 | # -*- encoding: utf-8 -*-
2 |
3 | require File.join(File.dirname(__FILE__), 'lib', 'radius', 'version')
4 | Gem::Specification.new do |s|
5 | s.name = "radius"
6 | s.version = ::Radius.version
7 | s.licenses = ["MIT"]
8 |
9 | s.required_ruby_version = ">= 2.6.0"
10 |
11 | s.authors = ["John W. Long", "David Chelimsky", "Bryce Kerley"]
12 | 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...).}
13 | s.email = ["me@johnwlong.com", "dchelimsky@gmail.com", "bkerley@brycekerley.net"]
14 | s.extra_rdoc_files = [
15 | "CHANGELOG",
16 | "CONTRIBUTORS",
17 | "LICENSE",
18 | "QUICKSTART.rdoc",
19 | "README.rdoc"
20 | ]
21 |
22 | ignores = File.read('.gitignore').split("\n").inject([]) {|a,p| a + Dir[p] }
23 | s.files = Dir['**/*','.gitignore'] - ignores
24 |
25 | s.homepage = %q{http://github.com/jlong/radius}
26 | s.summary = %q{A tag-based templating language for Ruby.}
27 |
28 | s.metadata = {
29 | "homepage_uri" => "https://github.com/jlong/radius",
30 | "changelog_uri" => "https://github.com/jlong/radius/blob/master/CHANGELOG",
31 | "bug_tracker_uri" => "https://github.com/jlong/radius/issues"
32 | }
33 | end
34 |
35 |
--------------------------------------------------------------------------------
/tasks/rdoc.rake:
--------------------------------------------------------------------------------
1 | require 'rdoc/task'
2 | Rake::RDocTask.new do |rdoc|
3 | version = File.exist?('VERSION') ? File.read('VERSION') : ""
4 |
5 | rdoc.rdoc_dir = 'rdoc'
6 | rdoc.title = "Radius #{version}"
7 | rdoc.main = "README.rdoc"
8 | rdoc.rdoc_files.include('*.rdoc')
9 | rdoc.rdoc_files.include('LICENSE')
10 | rdoc.rdoc_files.include('CHANGELOG')
11 | rdoc.rdoc_files.include('QUICKSTART.rdoc')
12 | rdoc.rdoc_files.include('lib/**/*.rb')
13 | end
--------------------------------------------------------------------------------
/tasks/rubinius.rake:
--------------------------------------------------------------------------------
1 | desc "remove Rubinius rbc files"
2 | task "rubinius:clean" do
3 | (Dir['**/*.rbc']).each { |f| rm f }
4 | end
--------------------------------------------------------------------------------
/tasks/test.rake:
--------------------------------------------------------------------------------
1 | require 'rake/testtask'
2 |
3 | Rake::TestTask.new do |t|
4 | t.libs << "lib" << "test"
5 | t.test_files = FileList['test/*_test.rb']
6 | t.verbose = true
7 | end
--------------------------------------------------------------------------------
/test/benchmarks.rb:
--------------------------------------------------------------------------------
1 | $: << File.join(File.dirname(__FILE__), '..', 'lib')
2 | require 'radius'
3 |
4 | if RUBY_PLATFORM == 'java'
5 | require 'java'
6 | require 'radius/parser/jscanner'
7 | end
8 |
9 | require 'benchmark'
10 |
11 | document = <
14 | Middle Top
15 |
16 | Middle Bottom
17 |
18 | After it all
19 | EOF
20 |
21 | amount = 1000
22 |
23 | Benchmark.bmbm do |bm|
24 | bm.report('vanilla') do
25 | scanner = RUBY_VERSION =~ /1\.9/ ? Radius::Scanner.new(:scanner => Radius::Scanner) : Radius::Scanner.new
26 | amount.times { scanner.operate('r', document) }
27 | end
28 |
29 | bm.report('vanilla (huge)') do
30 | scanner = RUBY_VERSION =~ /1\.9/ ? Radius::Scanner.new(:scanner => Radius::Scanner) : Radius::Scanner.new
31 | scanner.operate('r', 'a' * 460000)
32 | end
33 |
34 | if RUBY_PLATFORM == 'java'
35 | bm.report('JavaScanner') do
36 | scanner = Radius::JavaScanner.new(JRuby.runtime)
37 | amount.times { scanner.operate('r', document) }
38 | end
39 |
40 | bm.report('JavaScanner (huge)') do
41 | scanner = Radius::JavaScanner.new(JRuby.runtime)
42 | scanner.operate('r', 'a' * 460000)
43 | end
44 | end
45 | end
46 |
--------------------------------------------------------------------------------
/test/context_test.rb:
--------------------------------------------------------------------------------
1 | require 'test_helper'
2 |
3 | class RadiusContextTest < Minitest::Test
4 | include RadiusTestHelper
5 |
6 | class SuperContext < Radius::Context
7 | attr_accessor :name
8 | def initialize(name)
9 | self.name = name
10 | super
11 | end
12 | end
13 |
14 | def setup
15 | @context = new_context
16 | end
17 |
18 | def test_initialize
19 | @context = Radius::Context.new
20 | end
21 |
22 | def test_initialize_with_block
23 | @context = Radius::Context.new do |c|
24 | assert_kind_of Radius::Context, c
25 | c.define_tag('test') { 'just a test' }
26 | end
27 | refute_equal Hash.new, @context.definitions
28 | end
29 |
30 | def test_initialize_with_arguments
31 | @context = SuperContext.new('arg') do |c|
32 | assert_kind_of Radius::Context, c
33 | c.define_tag('test') { 'just a test' }
34 | end
35 | assert_equal 'arg', @context.name
36 | end
37 |
38 | def test_dup_preserves_delegated_values
39 | @context = Radius::Context.new
40 | @context.globals.object = Object.new.tap { |o| def o.special_method; "special"; end }
41 | duped = @context.dup
42 |
43 | assert_equal "special", duped.globals.special_method, "Duped context should preserve delegated object methods"
44 | end
45 |
46 | def test_with
47 | got = @context.with do |c|
48 | assert_equal @context, c
49 | end
50 | assert_equal @context, got
51 | end
52 |
53 | def test_render_tag
54 | define_global_tag "hello" do |tag|
55 | "Hello #{tag.attr['name'] || 'World'}!"
56 | end
57 | assert_render_tag_output 'Hello World!', 'hello'
58 | assert_render_tag_output 'Hello John!', 'hello', 'name' => 'John'
59 | end
60 |
61 | def test_render_tag__undefined_tag
62 | e = assert_raises(Radius::UndefinedTagError) { @context.render_tag('undefined_tag') }
63 | assert_equal "undefined tag `undefined_tag'", e.message
64 | end
65 |
66 | def test_tag_missing
67 | class << @context
68 | def tag_missing(tag, attr, &block)
69 | "undefined tag `#{tag}' with attributes #{attr.inspect.sub(" => ", "=>")}"
70 | end
71 | end
72 |
73 | text = ''
74 | expected = %{undefined tag `undefined_tag' with attributes {"cool"=>"beans"}}
75 | text = @context.render_tag('undefined_tag', 'cool' => 'beans')
76 | assert_equal expected, text
77 | end
78 |
79 | private
80 |
81 | def assert_render_tag_output(output, *render_tag_params)
82 | assert_equal output, @context.render_tag(*render_tag_params)
83 | end
84 |
85 | end
86 |
--------------------------------------------------------------------------------
/test/multithreaded_test.rb:
--------------------------------------------------------------------------------
1 | require 'thread'
2 | require 'test_helper'
3 | require 'radius'
4 | require 'etc'
5 |
6 | class MultithreadTest < Minitest::Test
7 |
8 | def setup
9 | Thread.abort_on_exception = true
10 | @context = Radius::Context.new do |c|
11 | c.define_tag('thread') do |tag|
12 | "#{tag.locals.thread_id} / #{tag.globals.object_id}"
13 | end
14 | end
15 | end
16 |
17 | def test_runs_multithreaded
18 | thread_count = [Etc.nprocessors, 16].min
19 | iterations_per_thread = 500
20 | failures = Queue.new
21 | results = Array.new(thread_count) { [] }
22 | threads = []
23 |
24 | thread_count.times do |i|
25 | threads << Thread.new do
26 | local_results = []
27 | iterations_per_thread.times do
28 | begin
29 | thread_context = @context.dup
30 | parser = Radius::Parser.new(thread_context, :tag_prefix => 'r')
31 | parser.context.globals.thread_id = Thread.current.object_id
32 | expected = "#{Thread.current.object_id} / #{parser.context.globals.object_id}"
33 | result = parser.parse('')
34 |
35 | local_results << {
36 | result: result,
37 | thread_id: Thread.current.object_id,
38 | iteration: local_results.size
39 | }
40 |
41 | failures << "Expected: #{expected}, Got: #{result}" unless result == expected
42 | rescue => e
43 | failures << "Thread #{Thread.current.object_id} failed: #{e.message}\n#{e.backtrace.join("\n")}"
44 | end
45 | end
46 | results[i] = local_results
47 | end
48 | end
49 |
50 | threads.each(&:join)
51 |
52 | # Only try to show failures if there are any
53 | failure_message = if failures.empty?
54 | nil
55 | else
56 | failed_items = []
57 | 5.times { failed_items << failures.pop unless failures.empty? }
58 | "Thread failures detected:\n#{failures.size} times:\n#{failed_items.join("\n")}"
59 | end
60 |
61 | assert(failures.empty?, failure_message)
62 | total_results = results.flatten.uniq.size
63 | expected_unique_results = thread_count * iterations_per_thread
64 |
65 | if total_results != expected_unique_results
66 | duplicates = results.flatten.group_by { |r| r[:result] }
67 | .select { |_, v| v.size > 1 }
68 |
69 | puts "\nDuplicates found:"
70 | duplicates.each do |result, occurrences|
71 | puts "\nResult: #{result[:result]}"
72 | occurrences.each { |o| puts " Thread: #{o[:thread_id]}, Iteration: #{o[:iteration]}" }
73 | end
74 | end
75 |
76 | assert_equal expected_unique_results, total_results,
77 | "Expected #{expected_unique_results} unique results (#{thread_count} threads × #{iterations_per_thread} iterations)"
78 | end
79 |
80 | end
81 |
--------------------------------------------------------------------------------
/test/ord_string_test.rb:
--------------------------------------------------------------------------------
1 | require 'test_helper'
2 | require 'radius'
3 |
4 | class RadiusOrdStringTest < Minitest::Test
5 |
6 | def test_string_slice_integer
7 | str = Radius::OrdString.new "abc"
8 | assert_equal str[0], 97
9 | assert_equal str[1], 98
10 | assert_equal str[2], 99
11 | end
12 |
13 | def test_string_slice_range
14 | str = Radius::OrdString.new "abc"
15 | assert_equal str[0..-1], "abc"
16 | end
17 |
18 | end
19 |
--------------------------------------------------------------------------------
/test/parser_test.rb:
--------------------------------------------------------------------------------
1 | require File.expand_path(File.dirname(__FILE__) + '/test_helper')
2 |
3 | class RadiusParserTest < Minitest::Test
4 | include RadiusTestHelper
5 |
6 | def setup
7 | @context = new_context
8 | @parser = Radius::Parser.new(@context, :tag_prefix => 'r')
9 | end
10 |
11 | def test_initialize
12 | @parser = Radius::Parser.new
13 | assert_kind_of Radius::Context, @parser.context
14 | end
15 |
16 | def test_initialize_with_params
17 | @parser = Radius::Parser.new(TestContext.new)
18 | assert_kind_of TestContext, @parser.context
19 |
20 | @parser = Radius::Parser.new(:context => TestContext.new)
21 | assert_kind_of TestContext, @parser.context
22 |
23 | @parser = Radius::Parser.new('context' => TestContext.new)
24 | assert_kind_of TestContext, @parser.context
25 |
26 | @parser = Radius::Parser.new(:tag_prefix => 'r')
27 | assert_kind_of Radius::Context, @parser.context
28 | assert_equal 'r', @parser.tag_prefix
29 |
30 | @parser = Radius::Parser.new(TestContext.new, :tag_prefix => 'r')
31 | assert_kind_of TestContext, @parser.context
32 | assert_equal 'r', @parser.tag_prefix
33 | end
34 |
35 | def test_parse_tag_with_dashes
36 | define_tag 'some-tag' do |tag|
37 | 'ok'
38 | end
39 |
40 | assert_parse_output 'ok', 'nope'
41 | end
42 |
43 | def test_parse_tag_with_dashed_attributes
44 | define_tag 'tag-with-dashed-attributes' do |tag|
45 | "dashed: #{tag.attr['data-dashed']} regular: #{tag.attr['regular']}"
46 | end
47 | assert_parse_output 'dashed: dashed-value regular: value', ''
48 | end
49 |
50 |
51 | def test_parse_individual_tags_and_parameters
52 | define_tag "add" do |tag|
53 | tag.attr["param1"].to_i + tag.attr["param2"].to_i
54 | end
55 | assert_parse_output "<3>", %{<>}
56 | end
57 |
58 | def test_parse_attributes
59 | attributes = %{{"a"=>"1", "b"=>"2", "c"=>"3", "d"=>"'"}}
60 | assert_parse_output attributes, %{}
61 | assert_parse_output attributes, %{}
62 | end
63 |
64 | def test_parse_attributes_with_slashes_or_angle_brackets
65 | slash = %{{"slash"=>"/"}}
66 | angle = %{{"angle"=>">"}}
67 | assert_parse_output slash, %{}
68 | assert_parse_output slash, %{}
69 | assert_parse_output angle, %{}
70 | end
71 |
72 | def test_parse_quotes
73 | assert_parse_output "test []", %{ }
74 | end
75 |
76 | def test_things_that_should_be_left_alone
77 | [
78 | %{ test="2"="4" },
79 | %{="2" }
80 | ].each do |middle|
81 | assert_parsed_is_unchanged ""
82 | assert_parsed_is_unchanged ""
83 | end
84 | end
85 |
86 | def test_tags_inside_html_tags
87 | assert_parse_output %{tags in yo tags
},
88 | %{tags in yo tags
}
89 | end
90 |
91 | def test_parse_result_is_always_a_string
92 | define_tag("twelve") { 12 }
93 | assert_parse_output "12", ""
94 | end
95 |
96 | def test_parse_double_tags
97 | assert_parse_output "test".reverse, "test"
98 | assert_parse_output "tset TEST", "test test"
99 | end
100 |
101 | def test_parse_tag_nesting
102 | define_tag("parent", :for => '')
103 | define_tag("parent:child", :for => '')
104 | define_tag("extra", :for => '')
105 | define_tag("nesting") { |tag| tag.nesting }
106 | define_tag("extra:nesting") { |tag| tag.nesting.gsub(':', ' > ') }
107 | define_tag("parent:child:nesting") { |tag| tag.nesting.gsub(':', ' * ') }
108 | assert_parse_output "nesting", ""
109 | assert_parse_output "parent:nesting", ""
110 | assert_parse_output "extra > nesting", ""
111 | assert_parse_output "parent * child * nesting", ""
112 | assert_parse_output "parent > extra > nesting", ""
113 | assert_parse_output "parent > child > extra > nesting", ""
114 | assert_parse_output "parent * extra * child * nesting", ""
115 | assert_parse_output "parent > extra > child > extra > nesting", ""
116 | assert_parse_output "parent > extra > child > extra > nesting", ""
117 | assert_parse_output "extra * parent * child * nesting", ""
118 | assert_parse_output "extra > parent > nesting", ""
119 | assert_parse_output "extra * parent * child * nesting", ""
120 | assert_raises(Radius::UndefinedTagError) { @parser.parse("") }
121 | end
122 | def test_parse_tag_nesting_2
123 | define_tag("parent", :for => '')
124 | define_tag("parent:child", :for => '')
125 | define_tag("content") { |tag| tag.nesting }
126 | assert_parse_output 'parent:child:content', ''
127 | end
128 |
129 | def test_parse_tag__binding_do_missing
130 | define_tag 'test' do |tag|
131 | tag.missing!
132 | end
133 | e = assert_raises(Radius::UndefinedTagError) { @parser.parse("") }
134 | assert_equal "undefined tag `test'", e.message
135 | end
136 |
137 | def test_parse_chirpy_bird
138 | # :> chirp chirp
139 | assert_parse_output "<:", "<:"
140 | end
141 |
142 | def test_parse_tag__binding_render_tag
143 | define_tag('test') { |tag| "Hello #{tag.attr['name']}!" }
144 | define_tag('hello') { |tag| tag.render('test', tag.attr) }
145 | assert_parse_output 'Hello John!', ''
146 | end
147 |
148 | def test_accessing_tag_attributes_through_tag_indexer
149 | define_tag('test') { |tag| "Hello #{tag['name']}!" }
150 | assert_parse_output 'Hello John!', ''
151 | end
152 |
153 | def test_parse_tag__binding_render_tag_with_block
154 | define_tag('test') { |tag| "Hello #{tag.expand}!" }
155 | define_tag('hello') { |tag| tag.render('test') { tag.expand } }
156 | assert_parse_output 'Hello John!', 'John'
157 | end
158 |
159 | def test_tag_locals
160 | define_tag "outer" do |tag|
161 | tag.locals.var = 'outer'
162 | tag.expand
163 | end
164 | define_tag "outer:inner" do |tag|
165 | tag.locals.var = 'inner'
166 | tag.expand
167 | end
168 | define_tag "outer:var" do |tag|
169 | tag.locals.var
170 | end
171 | assert_parse_output 'outer', ""
172 | assert_parse_output 'outer:inner:outer', "::"
173 | assert_parse_output 'outer:inner:outer:inner:outer', "::::"
174 | assert_parse_output 'outer', ""
175 | end
176 |
177 | def test_tag_globals
178 | define_tag "set" do |tag|
179 | tag.globals.var = tag.attr['value']
180 | ''
181 | end
182 | define_tag "var" do |tag|
183 | tag.globals.var
184 | end
185 | assert_parse_output " true false", %{ }
186 | end
187 |
188 | def test_parse_loops
189 | @item = nil
190 | define_tag "each" do |tag|
191 | result = []
192 | ["Larry", "Moe", "Curly"].each do |item|
193 | tag.locals.item = item
194 | result << tag.expand
195 | end
196 | result.join(tag.attr["between"] || "")
197 | end
198 | define_tag "each:item" do |tag|
199 | tag.locals.item
200 | end
201 | assert_parse_output %{Three Stooges: "Larry", "Moe", "Curly"}, %{Three Stooges: ""}
202 | end
203 |
204 | def test_parse_speed
205 | define_tag "set" do |tag|
206 | tag.globals.var = tag.attr['value']
207 | ''
208 | end
209 | define_tag "var" do |tag|
210 | tag.globals.var
211 | end
212 | parts = %w{decima nobis augue at facer processus commodo legentis odio lectorum dolore nulla esse lius qui nonummy ullamcorper erat ii notare}
213 | multiplier = parts.map{|p| "#{p}=\"#{rand}\""}.join(' ')
214 | assert ->{
215 | Timeout.timeout(10) do
216 | assert_parse_output " false", %{ }
217 | end
218 | }.call
219 | end
220 |
221 | def test_tag_option_for
222 | define_tag 'fun', :for => 'just for kicks'
223 | assert_parse_output 'just for kicks', ''
224 | end
225 |
226 | def test_tag_expose_option
227 | define_tag 'user', :for => users.first, :expose => ['name', :age]
228 | assert_parse_output 'John', ''
229 | assert_parse_output '25', ''
230 | e = assert_raises(Radius::UndefinedTagError) { @parser.parse "" }
231 | assert_equal "undefined tag `email'", e.message
232 | end
233 |
234 | def test_tag_expose_attributes_option_on_by_default
235 | define_tag 'user', :for => user_with_attributes
236 | assert_parse_output 'John', ''
237 | end
238 | def test_tag_expose_attributes_set_to_false
239 | define_tag 'user_without_attributes', :for => user_with_attributes, :attributes => false
240 | assert_raises(Radius::UndefinedTagError) { @parser.parse "" }
241 | end
242 |
243 | def test_tag_options_must_contain_a_for_option_if_methods_are_exposed
244 | e = assert_raises(ArgumentError) { define_tag('fun', :expose => :today) { 'test' } }
245 | assert_equal "tag definition must contain a :for option when used with the :expose option", e.message
246 | end
247 |
248 | def test_parse_fail_on_missing_end_tag
249 | assert_raises(Radius::MissingEndTagError) { @parser.parse("") }
250 | end
251 |
252 | def test_parse_fail_on_wrong_end_tag
253 | assert_raises(Radius::WrongEndTagError) { @parser.parse("") }
254 | end
255 |
256 | def test_parse_with_default_tag_prefix
257 | @parser = Radius::Parser.new(@context)
258 | define_tag("hello") { |tag| "Hello world!" }
259 | assert_equal "Hello world!
", @parser.parse('
')
260 | end
261 |
262 | def test_parse_with_other_radius_like_tags
263 | @parser = Radius::Parser.new(@context, :tag_prefix => "ralph")
264 | define_tag('hello') { "hello" }
265 | assert_equal "", @parser.parse("")
266 | end
267 |
268 | def test_copyin_global_values
269 | @context.globals.foo = 'bar'
270 | assert_equal 'bar', Radius::Parser.new(@context).context.globals.foo
271 | end
272 |
273 | def test_does_not_pollute_copied_globals
274 | @context.globals.foo = 'bar'
275 | parser = Radius::Parser.new(@context)
276 | parser.context.globals.foo = '[baz]'
277 | assert_equal 'bar', @context.globals.foo
278 | end
279 |
280 | def test_parse_with_other_namespaces
281 | @parser = Radius::Parser.new(@context, :tag_prefix => 'r')
282 | assert_equal "hello world", @parser.parse("hello world")
283 | end
284 |
285 | protected
286 |
287 | def assert_parse_output(output, input, message = nil)
288 | r = @parser.parse(input)
289 | assert_equal(output, r, message)
290 | end
291 |
292 | def assert_parsed_is_unchanged(something)
293 | assert_parse_output something, something
294 | end
295 |
296 | class User
297 | attr_accessor :name, :age, :email, :friend
298 | def initialize(name, age, email)
299 | @name, @age, @email = name, age, email
300 | end
301 | def <=>(other)
302 | name <=> other.name
303 | end
304 | end
305 |
306 | class UserWithAttributes < User
307 | def attributes
308 | { :name => name, :age => age, :email => email }
309 | end
310 | end
311 |
312 | def users
313 | [
314 | User.new('John', 25, 'test@johnwlong.com'),
315 | User.new('James', 27, 'test@jameslong.com')
316 | ]
317 | end
318 |
319 | def user_with_attributes
320 | UserWithAttributes.new('John', 25, 'test@johnwlong.com')
321 | end
322 |
323 | end
324 |
--------------------------------------------------------------------------------
/test/quickstart_test.rb:
--------------------------------------------------------------------------------
1 | require File.expand_path(File.dirname(__FILE__) + '/test_helper')
2 |
3 | class QuickstartTest < Minitest::Test
4 |
5 | def test_hello_world
6 | context = Radius::Context.new
7 | context.define_tag "hello" do |tag|
8 | "Hello #{tag.attr['name'] || 'World'}!"
9 | end
10 | parser = Radius::Parser.new(context)
11 | assert_equal "Hello World!
", parser.parse('
')
12 | assert_equal "Hello John!
", parser.parse('
')
13 | end
14 |
15 | def test_example_2
16 | require 'kramdown'
17 | context = Radius::Context.new
18 | context.define_tag "markdown" do |tag|
19 | contents = tag.expand
20 | Kramdown::Document.new(contents).to_html
21 | end
22 | parser = Radius::Parser.new(context)
23 | assert_equal "Hello World!
\n", parser.parse('Hello **World**!')
24 | end
25 |
26 | def test_nested_example
27 | context = Radius::Context.new
28 |
29 | context.define_tag "stooge" do |tag|
30 | content = ''
31 | ["Larry", "Moe", "Curly"].each do |name|
32 | tag.locals.name = name
33 | content << tag.expand
34 | end
35 | content
36 | end
37 |
38 | context.define_tag "stooge:name" do |tag|
39 | tag.locals.name
40 | end
41 |
42 | parser = Radius::Parser.new(context)
43 |
44 | template = <<-TEMPLATE
45 |
50 | TEMPLATE
51 |
52 | output = <<-OUTPUT
53 |
54 |
55 | - Larry
56 |
57 | - Moe
58 |
59 | - Curly
60 |
61 |
62 | OUTPUT
63 |
64 | assert_equal output, parser.parse(template)
65 | end
66 |
67 | class User
68 | attr_accessor :name, :age, :email
69 | end
70 | def test_exposing_objects_example
71 | parser = Radius::Parser.new
72 |
73 | parser.context.define_tag "count", :for => 1
74 | assert_equal "1", parser.parse("")
75 |
76 | user = User.new
77 | user.name, user.age, user.email = "John", 29, "john@example.com"
78 | parser.context.define_tag "user", :for => user, :expose => [ :name, :age, :email ]
79 | assert_equal "John", parser.parse("")
80 |
81 | assert_equal "John", parser.parse("")
82 | end
83 |
84 | class LazyContext < Radius::Context
85 | def tag_missing(tag, attr, &block)
86 | "ERROR: Undefined tag `#{tag}' with attributes #{attr.inspect.sub(" => ", "=>")}"
87 | end
88 | end
89 | def test_tag_missing_example
90 | parser = Radius::Parser.new(LazyContext.new, :tag_prefix => 'lazy')
91 | output = %{ERROR: Undefined tag `weird' with attributes {"value"=>"true"}}
92 | assert_equal output, parser.parse('')
93 | end
94 |
95 | def test_tag_globals_example
96 | parser = Radius::Parser.new
97 |
98 | parser.context.define_tag "inc" do |tag|
99 | tag.globals.count ||= 0
100 | tag.globals.count += 1
101 | ""
102 | end
103 |
104 | parser.context.define_tag "count" do |tag|
105 | tag.globals.count || 0
106 | end
107 |
108 | assert_equal "0 1", parser.parse(" ")
109 | end
110 |
111 | class Person
112 | attr_accessor :name, :friend
113 | def initialize(name)
114 | @name = name
115 | end
116 | end
117 | def test_tag_locals_and_globals_example
118 | jack = Person.new('Jack')
119 | jill = Person.new('Jill')
120 | jack.friend = jill
121 | jill.friend = jack
122 |
123 | context = Radius::Context.new do |c|
124 | c.define_tag "jack" do |tag|
125 | tag.locals.person = jack
126 | tag.expand
127 | end
128 | c.define_tag "jill" do |tag|
129 | tag.locals.person = jill
130 | tag.expand
131 | end
132 | c.define_tag "name" do |tag|
133 | tag.locals.person.name rescue tag.missing!
134 | end
135 | c.define_tag "friend" do |tag|
136 | tag.locals.person = tag.locals.person.friend rescue tag.missing!
137 | tag.expand
138 | end
139 | end
140 |
141 | parser = Radius::Parser.new(context, :tag_prefix => 'r')
142 |
143 | assert_equal "Jack", parser.parse('') #=> "Jack"
144 | assert_equal "Jill", parser.parse('') #=> "Jill"
145 | assert_equal "Jack", parser.parse('') #=> "Jack"
146 | assert_equal "Jack", parser.parse('') #=> "Jack"
147 | assert_equal "Jack and Jill", parser.parse(' and ') #=> "Jack and Jill"
148 | assert_raises(Radius::UndefinedTagError) { parser.parse('') } # raises a Radius::UndefinedTagError exception
149 | end
150 |
151 | end
152 |
--------------------------------------------------------------------------------
/test/squiggle_test.rb:
--------------------------------------------------------------------------------
1 | require File.expand_path(File.dirname(__FILE__) + '/test_helper')
2 | require 'radius/parser/squiggle_scanner'
3 |
4 | class RadiusSquiggleTest < Minitest::Test
5 | include RadiusTestHelper
6 |
7 | def setup
8 | @context = new_context
9 | @parser = Radius::Parser.new(@context, :scanner => Radius::SquiggleScanner.new)
10 | end
11 |
12 | def test_sane_scanner_default
13 | assert !Radius::Parser.new.scanner.is_a?(Radius::SquiggleScanner)
14 | end
15 |
16 | def test_initialize_with_params
17 | @parser = Radius::Parser.new(:scanner => Radius::SquiggleScanner.new)
18 | assert_kind_of Radius::SquiggleScanner, @parser.scanner
19 | end
20 |
21 | def test_parse_individual_tags_and_parameters
22 | define_tag "add" do |tag|
23 | tag.attr["param1"].to_i + tag.attr["param2"].to_i
24 | end
25 | assert_parse_output "{3}", %[{{ add param1="1" param2='2'/}}]
26 | end
27 |
28 | def test_parse_attributes
29 | attributes = %[{"a"=>"1", "b"=>"2", "c"=>"3", "d"=>"'"}]
30 | assert_parse_output attributes, %[{attr a="1" b='2'c="3"d="'" /}]
31 | assert_parse_output attributes, %[{attr a="1" b='2'c="3"d="'"}{/attr}]
32 | end
33 |
34 | def test_parse_attributes_with_slashes_or_angle_brackets
35 | slash = %[{"slash"=>"/"}]
36 | angle = %[{"angle"=>">"}]
37 | assert_parse_output slash, %[{attr slash="/"}{/attr}]
38 | assert_parse_output slash, %[{attr slash="/"}{attr /}{/attr}]
39 | assert_parse_output angle, %[{attr angle=">"}{/attr}]
40 | end
41 |
42 | def test_parse_quotes
43 | assert_parse_output "test []", %[{echo value="test" /} {wrap attr="test"}{/wrap}]
44 | end
45 |
46 | def test_things_that_should_be_left_alone
47 | [
48 | %[ test="2"="4" ],
49 | %[="2" ]
50 | ].each do |middle|
51 | assert_parsed_is_unchanged "{attr#{middle}/}"
52 | assert_parsed_is_unchanged "{attr#{middle}}"
53 | end
54 | end
55 |
56 | def test_tags_inside_html_tags
57 | assert_parse_output %[tags in yo tags
],
58 | %[tags in yo tags
]
59 | end
60 |
61 | def test_parse_result_is_always_a_string
62 | define_tag("twelve") { 12 }
63 | assert_parse_output "12", "{ twelve /}"
64 | end
65 |
66 | def test_parse_double_tags
67 | assert_parse_output "test".reverse, "{reverse}test{/reverse}"
68 | assert_parse_output "tset TEST", "{reverse}test{/reverse} {capitalize}test{/capitalize}"
69 | end
70 |
71 | def test_parse_tag_nesting
72 | define_tag("parent", :for => '')
73 | define_tag("parent:child", :for => '')
74 | define_tag("extra", :for => '')
75 | define_tag("nesting") { |tag| tag.nesting }
76 | define_tag("extra:nesting") { |tag| tag.nesting.gsub(':', ' > ') }
77 | define_tag("parent:child:nesting") { |tag| tag.nesting.gsub(':', ' * ') }
78 | assert_parse_output "nesting", "{nesting /}"
79 | assert_parse_output "parent:nesting", "{parent:nesting /}"
80 | assert_parse_output "extra > nesting", "{extra:nesting /}"
81 | assert_parse_output "parent * child * nesting", "{parent:child:nesting /}"
82 | assert_parse_output "parent > extra > nesting", "{parent:extra:nesting /}"
83 | assert_parse_output "parent > child > extra > nesting", "{parent:child:extra:nesting /}"
84 | assert_parse_output "parent * extra * child * nesting", "{parent:extra:child:nesting /}"
85 | assert_parse_output "parent > extra > child > extra > nesting", "{parent:extra:child:extra:nesting /}"
86 | assert_parse_output "parent > extra > child > extra > nesting", "{parent}{extra}{child}{extra}{nesting /}{/extra}{/child}{/extra}{/parent}"
87 | assert_parse_output "extra * parent * child * nesting", "{extra:parent:child:nesting /}"
88 | assert_parse_output "extra > parent > nesting", "{extra}{parent:nesting /}{/extra}"
89 | assert_parse_output "extra * parent * child * nesting", "{extra:parent}{child:nesting /}{/extra:parent}"
90 | assert_raises(Radius::UndefinedTagError) { @parser.parse("{child /}") }
91 | end
92 | def test_parse_tag_nesting_2
93 | define_tag("parent", :for => '')
94 | define_tag("parent:child", :for => '')
95 | define_tag("content") { |tag| tag.nesting }
96 | assert_parse_output 'parent:child:content', '{parent}{child:content /}{/parent}'
97 | end
98 |
99 | def test_parse_tag__binding_do_missing
100 | define_tag 'test' do |tag|
101 | tag.missing!
102 | end
103 | e = assert_raises(Radius::UndefinedTagError) { @parser.parse("{test /}") }
104 | assert_equal "undefined tag `test'", e.message
105 | end
106 |
107 | def test_parse_chirpy_bird
108 | # :> chirp chirp
109 | assert_parse_output "<:", "<:"
110 | end
111 |
112 | def test_parse_tag__binding_render_tag
113 | define_tag('test') { |tag| "Hello #{tag.attr['name']}!" }
114 | define_tag('hello') { |tag| tag.render('test', tag.attr) }
115 | assert_parse_output 'Hello John!', '{hello name="John" /}'
116 | end
117 |
118 | def test_accessing_tag_attributes_through_tag_indexer
119 | define_tag('test') { |tag| "Hello #{tag['name']}!" }
120 | assert_parse_output 'Hello John!', '{test name="John" /}'
121 | end
122 |
123 | def test_parse_tag__binding_render_tag_with_block
124 | define_tag('test') { |tag| "Hello #{tag.expand}!" }
125 | define_tag('hello') { |tag| tag.render('test') { tag.expand } }
126 | assert_parse_output 'Hello John!', '{hello}John{/hello}'
127 | end
128 |
129 | def test_tag_locals
130 | define_tag "outer" do |tag|
131 | tag.locals.var = 'outer'
132 | tag.expand
133 | end
134 | define_tag "outer:inner" do |tag|
135 | tag.locals.var = 'inner'
136 | tag.expand
137 | end
138 | define_tag "outer:var" do |tag|
139 | tag.locals.var
140 | end
141 | assert_parse_output 'outer', "{outer}{var /}{/outer}"
142 | assert_parse_output 'outer:inner:outer', "{outer}{var /}:{inner}{var /}{/inner}:{var /}{/outer}"
143 | assert_parse_output 'outer:inner:outer:inner:outer', "{outer}{var /}:{inner}{var /}:{outer}{var /}{/outer}:{var /}{/inner}:{var /}{/outer}"
144 | assert_parse_output 'outer', "{outer:var /}"
145 | end
146 |
147 | def test_tag_globals
148 | define_tag "set" do |tag|
149 | tag.globals.var = tag.attr['value']
150 | ''
151 | end
152 | define_tag "var" do |tag|
153 | tag.globals.var
154 | end
155 | assert_parse_output " true false", %[{var /} {set value="true" /} {var /} {set value="false" /} {var /}]
156 | end
157 |
158 | def test_parse_loops
159 | @item = nil
160 | define_tag "each" do |tag|
161 | result = []
162 | ["Larry", "Moe", "Curly"].each do |item|
163 | tag.locals.item = item
164 | result << tag.expand
165 | end
166 | result.join(tag.attr["between"] || "")
167 | end
168 | define_tag "each:item" do |tag|
169 | tag.locals.item
170 | end
171 | assert_parse_output %[Three Stooges: "Larry", "Moe", "Curly"], %[Three Stooges: {each between=", "}"{item /}"{/each}]
172 | end
173 |
174 | def test_parse_speed
175 | define_tag "set" do |tag|
176 | tag.globals.var = tag.attr['value']
177 | ''
178 | end
179 | define_tag "var" do |tag|
180 | tag.globals.var
181 | end
182 | parts = %w{decima nobis augue at facer processus commodo legentis odio lectorum dolore nulla esse lius qui nonummy ullamcorper erat ii notare}
183 | multiplier = parts.map{|p| "#{p}=\"#{rand}\""}.join(' ')
184 | assert ->{
185 | Timeout.timeout(10) do
186 | assert_parse_output " false", %[{set value="false" #{multiplier} /} {var /}]
187 | end
188 | }.call
189 | end
190 |
191 | def test_tag_option_for
192 | define_tag 'fun', :for => 'just for kicks'
193 | assert_parse_output 'just for kicks', '{fun /}'
194 | end
195 |
196 | def test_tag_expose_option
197 | define_tag 'user', :for => users.first, :expose => ['name', :age]
198 | assert_parse_output 'John', '{user:name /}'
199 | assert_parse_output '25', '{user}{age /}{/user}'
200 | e = assert_raises(Radius::UndefinedTagError) { @parser.parse "{user:email /}" }
201 | assert_equal "undefined tag `email'", e.message
202 | end
203 |
204 | def test_tag_expose_attributes_option_on_by_default
205 | define_tag 'user', :for => user_with_attributes
206 | assert_parse_output 'John', '{user:name /}'
207 | end
208 | def test_tag_expose_attributes_set_to_false
209 | define_tag 'user_without_attributes', :for => user_with_attributes, :attributes => false
210 | assert_raises(Radius::UndefinedTagError) { @parser.parse "{user_without_attributes:name /}" }
211 | end
212 |
213 | def test_tag_options_must_contain_a_for_option_if_methods_are_exposed
214 | e = assert_raises(ArgumentError) { define_tag('fun', :expose => :today) { 'test' } }
215 | assert_equal "tag definition must contain a :for option when used with the :expose option", e.message
216 | end
217 |
218 | def test_parse_fail_on_missing_end_tag
219 | assert_raises(Radius::MissingEndTagError) { @parser.parse("{reverse}") }
220 | end
221 |
222 | def test_parse_fail_on_wrong_end_tag
223 | assert_raises(Radius::WrongEndTagError) { @parser.parse("{reverse}{capitalize}{/reverse}") }
224 | end
225 |
226 | def test_copyin_global_values
227 | @context.globals.foo = 'bar'
228 | assert_equal 'bar', Radius::Parser.new(@context).context.globals.foo
229 | end
230 |
231 | def test_does_not_pollute_copied_globals
232 | @context.globals.foo = 'bar'
233 | parser = Radius::Parser.new(@context)
234 | parser.context.globals.foo = '[baz]'
235 | assert_equal 'bar', @context.globals.foo
236 | end
237 |
238 | def test_parse_with_other_namespaces
239 | @parser = Radius::Parser.new(@context, :tag_prefix => 'r')
240 | assert_equal "{fb:test}hello world{/fb:test}", @parser.parse("{fb:test}hello world{/fb:test}")
241 | end
242 |
243 | protected
244 |
245 | def assert_parse_output(output, input, message = nil)
246 | r = @parser.parse(input)
247 | assert_equal(output, r, message)
248 | end
249 |
250 | def assert_parsed_is_unchanged(something)
251 | assert_parse_output something, something
252 | end
253 |
254 | class User
255 | attr_accessor :name, :age, :email, :friend
256 | def initialize(name, age, email)
257 | @name, @age, @email = name, age, email
258 | end
259 | def <=>(other)
260 | name <=> other.name
261 | end
262 | end
263 |
264 | class UserWithAttributes < User
265 | def attributes
266 | { :name => name, :age => age, :email => email }
267 | end
268 | end
269 |
270 | def users
271 | [
272 | User.new('John', 25, 'test@johnwlong.com'),
273 | User.new('James', 27, 'test@jameslong.com')
274 | ]
275 | end
276 |
277 | def user_with_attributes
278 | UserWithAttributes.new('John', 25, 'test@johnwlong.com')
279 | end
280 |
281 | end
282 |
--------------------------------------------------------------------------------
/test/test_helper.rb:
--------------------------------------------------------------------------------
1 | require 'timeout'
2 | require 'simplecov'
3 | SimpleCov.start do
4 | add_filter 'test'
5 | end
6 |
7 | require 'coveralls'
8 | if ENV['COVERALLS']
9 | Coveralls.wear!
10 | end
11 |
12 | $LOAD_PATH << File.join(File.dirname(__FILE__), '..', 'lib')
13 |
14 | require 'radius'
15 |
16 | module RadiusTestHelper
17 | class TestContext < Radius::Context; end
18 |
19 | def new_context
20 | Radius::Context.new do |c|
21 | c.define_tag("reverse" ) { |tag| tag.expand.reverse }
22 | c.define_tag("capitalize") { |tag| tag.expand.upcase }
23 | c.define_tag("echo" ) { |tag| tag.attr['value'] }
24 | c.define_tag("wrap" ) { |tag| "[#{tag.expand}]" }
25 | c.define_tag("attr") do |tag|
26 | kv = tag.attr.keys.sort.collect{|k| "#{k.inspect}=>#{tag[k].inspect}"}
27 | "{#{kv.join(', ')}}"
28 | end
29 | end
30 | end
31 |
32 | def define_tag(name, options = {}, &block)
33 | @parser.context.define_tag name, options, &block
34 | end
35 |
36 | def define_global_tag(name, options = {}, &block)
37 | @context.define_tag name, options, &block
38 | end
39 | end
40 | require 'minitest/autorun'
41 |
--------------------------------------------------------------------------------
/test/utility_test.rb:
--------------------------------------------------------------------------------
1 | require 'test_helper'
2 | require 'radius'
3 |
4 | class RadiusUtilityTest < Minitest::Test
5 |
6 | def test_symbolize_keys
7 | h = Radius::Utility.symbolize_keys({ 'a' => 1, :b => 2 })
8 | assert_equal h[:a], 1
9 | assert_equal h[:b], 2
10 | end
11 |
12 | def test_impartial_hash_delete
13 | h = { 'a' => 1, :b => 2 }
14 | assert_equal Radius::Utility.impartial_hash_delete(h, :a), 1
15 | assert_equal Radius::Utility.impartial_hash_delete(h, 'b'), 2
16 | assert_equal h.empty?, true
17 | end
18 |
19 | def test_constantize
20 | assert_equal Radius::Utility.constantize('String'), String
21 | end
22 |
23 | def test_camelize
24 | assert_equal Radius::Utility.camelize('ab_cd_ef'), 'AbCdEf'
25 | end
26 |
27 | def test_array_to_s
28 | assert_equal Radius::Utility.array_to_s(['a', 1, [:c], 'brain'.freeze]), 'a1cbrain'
29 | end
30 | end
31 |
--------------------------------------------------------------------------------