radius-ts 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,37 @@
1
+ = Change Log
2
+ === 1.0.0-ts
3
+ * Create a fork that is threadsafe (per-parser).
4
+ * Bump revision to 1.0.0 since the API is stable
5
+
6
+ === 0.6.1
7
+ * Fixed a problem with non-tags that have no prefix or tagname (see test_parse_chirpy_bird)
8
+
9
+ === 0.6.0 (private release)
10
+ * Split radius.rb into multiple files.
11
+ * Port the really hairy regexes from Radius::Parser to a single Ragel machine.
12
+ * Added and refactored tests.
13
+ * Refactored Rakefile and other administrativia.
14
+
15
+ === 0.5.1
16
+ * Fixed a problem with parsing quotes where a single tag preceding a double tag would consume the start
17
+ tag of the double tag if both contained attributes.
18
+
19
+ === 0.5.0
20
+ * Created a DSL for tag definitions (introducing a DSL makes this version of Radiant incompatible with
21
+ the last). The DSL has the following features:
22
+ - full support for nested tags
23
+ - global and local tag variables
24
+ - Contexts can now be defined dynamically (instead of being subclassed)
25
+ - see the QUICKSTART for more info
26
+ * Many refactorings of the library and unit tests.
27
+ * Changed the license to the MIT-LICENSE.
28
+ * Updated documentation to reflect the changes.
29
+ * Updated the version number to reflect the maturity of the code base.
30
+
31
+ === 0.0.2
32
+ * Refactored Parser to use Context#render_tag instead of #send when rendering tags defined on a Context.
33
+ * UndefinedTagError is now thrown when Parser tries to render a tag which doesn't exist on a Context.
34
+ * Added Context#tag_missing which works like method_method missing on Object, but is tag specific.
35
+
36
+ === 0.0.1
37
+ * First release.
@@ -0,0 +1,21 @@
1
+ CHANGELOG
2
+ Manifest.txt
3
+ QUICKSTART.rdoc
4
+ README.rdoc
5
+ Rakefile
6
+ lib/radius.rb
7
+ lib/radius/context.rb
8
+ lib/radius/delegating_open_struct.rb
9
+ lib/radius/error.rb
10
+ lib/radius/parse_tag.rb
11
+ lib/radius/parser.rb
12
+ lib/radius/parser/scan.rb
13
+ lib/radius/parser/scan.rl
14
+ lib/radius/tag_binding.rb
15
+ lib/radius/tag_definitions.rb
16
+ lib/radius/utility.rb
17
+ lib/radius/version.rb
18
+ tasks/scan.rake
19
+ test/context_test.rb
20
+ test/parser_test.rb
21
+ test/test_helper.rb
@@ -0,0 +1,322 @@
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('<p><radius:hello /></p>')
20
+ puts parser.parse('<p><radius:hello name="John" /></p>')
21
+
22
+ This code will output:
23
+
24
+ <p>Hello World!</p>
25
+ <p>Hello John!</p>
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 <tt>tag.expand</tt> 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('<radius:textile>h1. Hello **World**!</radius:textile>')
53
+
54
+ This code will output:
55
+
56
+ <h1>Hello <b>World</b>!</h1>
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
+ <ul>
83
+ <radius:stooge>
84
+ <li><radius:name /></li>
85
+ </radius:stooge>
86
+ </ul>
87
+ TEMPLATE
88
+
89
+ puts parser.parse(template)
90
+
91
+ This code will output:
92
+
93
+ <ul>
94
+
95
+ <li>Larry</li>
96
+
97
+ <li>Moe</li>
98
+
99
+ <li>Curly</li>
100
+
101
+ </ul>
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 <tt>1</tt> 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 <tt>user</tt> 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
+ <radius:user><radius:name /></radius:user>
135
+
136
+ If "John" was the value stored in <tt>user.name</tt> the template would render as "John".
137
+
138
+
139
+ == Tag Shorthand
140
+
141
+ In the example above we made reference to <tt>user.name</tt> in our template by using the
142
+ following code:
143
+
144
+ <radius:user><radius:name /></radius:user>
145
+
146
+ There is a much easer way to refer to the <tt>user.name</tt> variable. Use the colon operator
147
+ to "scope" the reference to <tt>name</tt>:
148
+
149
+ <radius:user:name />
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
+ "<strong>ERROR: Undefined tag `#{tag}' with attributes #{attr.inspect}</strong>"
173
+ end
174
+ end
175
+
176
+ parser = Radius::Parser.new(LazyContext.new, :tag_prefix => 'lazy')
177
+ puts parser.parse('<lazy:weird value="true" />')
178
+
179
+ This will output:
180
+
181
+ <strong>ERROR: Undefined tag `weird' with attributes {"value"=>"true"}</strong>
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('<r:jack:name />') #=> "Jack"
252
+ parser.parse('<r:jill:name />') #=> "Jill"
253
+ parser.parse('<r:jill:friend:name />') #=> "Jack"
254
+ parser.parse('<r:jack:friend:friend:name />') #=> "Jack"
255
+ parser.parse('<r:jill><r:friend:name /> and <r:name /></r:jill>') #=> "Jack and Jill"
256
+ parser.parse('<r:name />') # raises a Radius::UndefinedTagError exception
257
+
258
+ Notice how TagBinding#locals enables intelligent nesting. "<r:jill:name />" evaluates to
259
+ "Jill", but "<r:jill:friend:name />" 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 "<r:name />" 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
+ <r:parent:extra:child:nesting />
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
+ <r:parent:child:extra:nesting />
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.
@@ -0,0 +1,118 @@
1
+ = Radius -- Powerful Tag-Based Templates
2
+
3
+ Radius is a powerful tag-based template language for Ruby inspired by the template languages
4
+ used in MovableType[http://www.movabletype.org] and TextPattern[http://www.textpattern.com].
5
+ It uses tags similar to XML, but can be used to generate any form of plain text (HTML, e-mail,
6
+ etc...).
7
+
8
+ == Usage
9
+
10
+ With Radius, it is extremely easy to create custom tags and parse them. Here's a small
11
+ example:
12
+
13
+ require 'radius'
14
+
15
+ # Define tags on a context that will be available to a template:
16
+ context = Radius::Context.new do |c|
17
+ c.define_tag 'hello' do
18
+ 'Hello world'
19
+ end
20
+ c.define_tag 'repeat' do |tag|
21
+ number = (tag.attr['times'] || '1').to_i
22
+ result = ''
23
+ number.times { result << tag.expand }
24
+ result
25
+ end
26
+ end
27
+
28
+ # Create a parser to parse tags that begin with 'r:'
29
+ parser = Radius::Parser.new(context, :tag_prefix => 'r')
30
+
31
+ # Parse tags and output the result
32
+ puts parser.parse(%{A small example:\n<r:repeat times="3">* <r:hello />!\n</r:repeat>})
33
+
34
+ Output:
35
+
36
+ A small example:
37
+ * Hello world!
38
+ * Hello world!
39
+ * Hello world!
40
+
41
+
42
+ == Quick Start
43
+
44
+ Read the QUICKSTART file to get up and running with Radius.
45
+
46
+
47
+ = Requirements
48
+
49
+ Radius does not have any external requirements for using the library in your
50
+ own programs.
51
+
52
+ Ragel is required to create the ruby parser from the Ragel specification,
53
+ and both Ragel and Graphviz are required to draw the state graph for the
54
+ parser.
55
+
56
+
57
+ == Installation
58
+
59
+ It is recommended that you install Radius using the RubyGems packaging system:
60
+
61
+ % gem install --remote radius
62
+
63
+ You can also install Radius by copying lib/radius.rb into the Ruby load path.
64
+
65
+
66
+ == License
67
+
68
+ Radius is free software and may be redistributed under the terms of the MIT-LICENSE:
69
+
70
+ Copyright (c) 2006-2009, John W. Long
71
+
72
+ Permission is hereby granted, free of charge, to any person obtaining a copy of this
73
+ software and associated documentation files (the "Software"), to deal in the Software
74
+ without restriction, including without limitation the rights to use, copy, modify, merge,
75
+ publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons
76
+ to whom the Software is furnished to do so, subject to the following conditions:
77
+
78
+ The above copyright notice and this permission notice shall be included in all copies or
79
+ substantial portions of the Software.
80
+
81
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
82
+ INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
83
+ PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE
84
+ FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
85
+ OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
86
+ DEALINGS IN THE SOFTWARE.
87
+
88
+
89
+ == Roadmap
90
+
91
+ This is a prioritized roadmap for future releases:
92
+
93
+ 1. Clean up the current code base. [Done]
94
+
95
+ 2. Add support for multi-level contexts: tags should be able to be
96
+ defined to only be valid within other sets of tags. [Done]
97
+
98
+ 3. Create a simple DSL for defining contexts. [Done]
99
+
100
+ 4. Optimize for speed, modify scan.rl to emit C.
101
+
102
+
103
+ == Development
104
+
105
+ The latest version of Radius can be found on RubyForge:
106
+
107
+ http://rubyforge.org/projects/radius
108
+
109
+ Experimental and development versions of Radius can be found on Github:
110
+
111
+ http://github.com/jlong/radius
112
+
113
+ If you are interested in helping with the development of Radius, feel free to
114
+ fork the project on GitHub and send me a pull request.
115
+
116
+
117
+ John Long ::
118
+ http://wiseheartdesign.com