radius 0.5.0 → 0.5.1

Sign up to get free protection for your applications and to get access to all the features.
data/CHANGELOG CHANGED
@@ -1,5 +1,9 @@
1
1
  = Change Log
2
2
 
3
+ === 0.5.1
4
+ * Fixed a problem with parsing quotes where a single tag preceding a double tag would consume the start
5
+ tag of the double tag if both contained attributes.
6
+
3
7
  === 0.5.0
4
8
  * Created a DSL for tag definitions (introducing a DSL makes this version of Radiant incompatible with
5
9
  the last). The DSL has the following features:
data/Rakefile CHANGED
@@ -4,7 +4,7 @@ require 'rake/rdoctask'
4
4
  require 'rake/gempackagetask'
5
5
 
6
6
  PKG_NAME = 'radius'
7
- PKG_VERSION = '0.5.0'
7
+ PKG_VERSION = '0.5.1'
8
8
  PKG_FILE_NAME = "#{PKG_NAME}-#{PKG_VERSION}"
9
9
  RUBY_FORGE_PROJECT = PKG_NAME
10
10
  RUBY_FORGE_USER = 'jlong'
@@ -83,6 +83,4 @@ task :release => [:gem, :package] do
83
83
  files.each do |file|
84
84
  system("rubyforge add_release #{RUBY_FORGE_GROUPID} #{RUBY_FORGE_PACKAGEID} \"#{RELEASE_NAME}\" #{file}")
85
85
  end
86
-
87
- puts ">>>> done <<<<"
88
86
  end
@@ -406,10 +406,10 @@ module Radius
406
406
 
407
407
  protected
408
408
 
409
- def pre_parse(text)
410
- re = %r{<#{@tag_prefix}:([\w:]+?)(\s+(?:\w+\s*=\s*(["']).*?\3\s*)*|)>|</#{@tag_prefix}:([\w:]+?)\s*>}
409
+ def pre_parse(text) # :nodoc:
410
+ re = %r{<#{@tag_prefix}:([\w:]+?)(\s+(?:\w+\s*=\s*(?:"[^"]*?"|'[^']*?')\s*)*|)>|</#{@tag_prefix}:([\w:]+?)\s*>}
411
411
  if md = re.match(text)
412
- start_tag, attr, end_tag = $1, $2, $4
412
+ start_tag, attr, end_tag = $1, $2, $3
413
413
  @stack.last.contents << ParseTag.new { parse_individual(md.pre_match) }
414
414
  remaining = md.post_match
415
415
  if start_tag
@@ -444,7 +444,7 @@ module Radius
444
444
  end
445
445
 
446
446
  def parse_individual(text) # :nodoc:
447
- re = %r{<#{@tag_prefix}:([\w:]+?)(\s+(?:\w+\s*=\s*(["']).*?\3\s*)*|)/>}
447
+ re = %r{<#{@tag_prefix}:([\w:]+?)(\s+(?:\w+\s*=\s*(?:"[^"]*?"|'[^']*?')\s*)*|)/>}
448
448
  if md = re.match(text)
449
449
  attr = parse_attributes($2)
450
450
  replace = @context.render_tag($1, attr)
Binary file
Binary file
Binary file
@@ -0,0 +1,25 @@
1
+ = Change Log
2
+
3
+ === 0.5.1
4
+ * Fixed a problem with parsing quotes where a single tag preceding a double tag would consume the start
5
+ tag of the double tag if both contained attributes.
6
+
7
+ === 0.5.0
8
+ * Created a DSL for tag definitions (introducing a DSL makes this version of Radiant incompatible with
9
+ the last). The DSL has the following features:
10
+ - full support for nested tags
11
+ - global and local tag variables
12
+ - Contexts can now be defined dynamically (instead of being subclassed)
13
+ - see the QUICKSTART for more info
14
+ * Many refactorings of the library and unit tests.
15
+ * Changed the license to the MIT-LICENSE.
16
+ * Updated documentation to reflect the changes.
17
+ * Updated the version number to reflect the maturity of the code base.
18
+
19
+ === 0.0.2
20
+ * Refactored Parser to use Context#render_tag instead of #send when rendering tags defined on a Context.
21
+ * UndefinedTagError is now thrown when Parser tries to render a tag which doesn't exist on a Context.
22
+ * Added Context#tag_missing which works like method_method missing on Object, but is tag specific.
23
+
24
+ === 0.0.1
25
+ * First release.
@@ -0,0 +1,323 @@
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 = 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 <strong>World</strong>!</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 = 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
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
+ end
210
+
211
+ context.define_tag "count" do |tag|
212
+ tag.globals.count || 0
213
+ end
214
+
215
+ TagBinding#locals mirrors the variables that are in TagBinding#globals, but allows child
216
+ tags to redefine variables. This is valuable when defining context sensitive tags:
217
+
218
+ require 'radius'
219
+
220
+ class Person
221
+ attr_accessor :name, :friend
222
+ def initialize(name)
223
+ @name = name
224
+ end
225
+ end
226
+
227
+ jack = Person.new('Jack')
228
+ jill = Person.new('Jill')
229
+ jack.friend = jill
230
+ jill.friend = jack
231
+
232
+ context = Radius::Context.new do |c|
233
+ c.define_tag "jack" do |tag|
234
+ tag.locals.person = jack
235
+ tag.expand
236
+ end
237
+ c.define_tag "jill" do |tag|
238
+ tag.locals.person = jill
239
+ tag.expand
240
+ end
241
+ c.define_tag "name" do |tag|
242
+ tag.locals.person.name rescue tag.missing!
243
+ end
244
+ c.define_tag "friend" do |tag|
245
+ tag.locals.person = tag.locals.person.friend rescue tag.missing!
246
+ tag.expand
247
+ end
248
+ end
249
+
250
+ parser = Radius::Parser.new(context, :tag_prefix => 'r')
251
+
252
+ parser.parse('<r:jack:name />') #=> "Jack"
253
+ parser.parse('<r:jill:name />') #=> "Jill"
254
+ parser.parse('<r:jill:friend:name />') #=> "Jack"
255
+ parser.parse('<r:jill:friend:friend:name />') #=> "Jack"
256
+ parser.parse('<r:jill><r:friend:name /> and <r:name /></r:jill>') #=> "Jack and Jill"
257
+ parser.parse('<r:name />') # raises an UndefinedTagError exception
258
+
259
+ Notice how TagBinding#locals enables intelligent nesting. "<r:jill:name />" evaluates to
260
+ "Jill", but "<r:jill:friend:name />" evaluates to "Jack". Locals loose scope as soon as
261
+ the tag they were defined in closes. Globals on the other hand, never loose scope.
262
+
263
+ The final line in the example above demonstrates that calling "<r:name />" raises a
264
+ TagMissing error. This is because of the way the name tag was defined:
265
+
266
+ tag.locals.person.name rescue tag.missing!
267
+
268
+ If person is not defined on locals it will return nil. Calling #name on nil would normally
269
+ raise a NoMethodError exception, but because of the 'rescue' clause the TagBinding#missing!
270
+ method is called which fires off Context#tag_missing. By default Context#tag_missing raises
271
+ a UndefinedTagError exception. The 'rescue tag.missing!' idiom is extremly useful for adding
272
+ simple error checking to context sensitive tags.
273
+
274
+
275
+ == Tag Specificity
276
+
277
+ When Radius is presented with two tags that have the same name, but different nesting
278
+ Radius uses an algorithm similar to the way winning rules are calculated in Cascading Style
279
+ Sheets (CSS) to determine which definition should be used. Each time a tag is encountered
280
+ in a template potential tags are assigned specificity values and the tag with the highest
281
+ specificity wins.
282
+
283
+ For example, given the following tag definitions:
284
+
285
+ nesting
286
+ extra:nesting
287
+ parent:child:nesting
288
+
289
+ And template:
290
+
291
+ <r:parent:extra:child:nesting />
292
+
293
+ Radius will calculate specificity values like this:
294
+
295
+ nesting => 1.0.0.0
296
+ extra:nesting => 1.0.1.0
297
+ parent:child:nesting => 1.1.0.1
298
+
299
+ Meaning that parent:child:nesting will win. If a template contained:
300
+
301
+ <r:parent:child:extra:nesting />
302
+
303
+ The following specificity values would be assigned to each of the tag definitions:
304
+
305
+ nesting => 1.0.0.0
306
+ extra:nesting => 1.1.0.0
307
+ parent:child:nesting => 1.0.1.1
308
+
309
+ Meaning that extra:nesting would win because it is more "specific".
310
+
311
+ Values are assigned by assigning points to each of the tags from right to left.
312
+ Given a tag found in a template with nesting four levels deep, the maximum
313
+ specificity a tag could be assigned would be:
314
+
315
+ 1.1.1.1
316
+
317
+ One point for each of the levels.
318
+
319
+ A deep understanding of tag specificity is not necessary to be effective with
320
+ Radius. For the most part you will find that Radius resolves tags precisely the
321
+ way that you would expect. If you find this section confusing forget about it and
322
+ refer back to it if you find that tags are resolving differently from the way that
323
+ you expected.
@@ -0,0 +1,97 @@
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
+ == Example
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[link:files/QUICKSTART.html] to get up and running fast with Radius.
45
+
46
+
47
+ == Download
48
+
49
+ The latest version of Radius can be found on RubyForge:
50
+
51
+ http://rubyforge.org/projects/radius/
52
+
53
+
54
+ == Installation
55
+
56
+ It is recommended that you install Radius using the RubyGems packaging system:
57
+
58
+ % gem install --remote radius
59
+
60
+ You can also install Radius by copying lib/radius.rb into the Ruby load path.
61
+
62
+
63
+ == License
64
+
65
+ Radius is free software and may be redistributed under the terms of the MIT-LICENSE:
66
+
67
+ Copyright (c) 2006, John W. Long
68
+
69
+ Permission is hereby granted, free of charge, to any person obtaining a copy of this
70
+ software and associated documentation files (the "Software"), to deal in the Software
71
+ without restriction, including without limitation the rights to use, copy, modify, merge,
72
+ publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons
73
+ to whom the Software is furnished to do so, subject to the following conditions:
74
+
75
+ The above copyright notice and this permission notice shall be included in all copies or
76
+ substantial portions of the Software.
77
+
78
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
79
+ INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
80
+ PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE
81
+ FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
82
+ OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
83
+ DEALINGS IN THE SOFTWARE.
84
+
85
+
86
+ == The Future
87
+
88
+ Radius is nearing completion, but is still very much in the development stages. Take a look
89
+ at the ROADMAP[link:files/ROADMAP.html] to see where we want to go.
90
+
91
+ If you are interested in helping with the development of Radiant, contact me and we'll talk.
92
+
93
+ Enjoy!
94
+
95
+ --
96
+ John Long ::
97
+ http://wiseheartdesign.com