radius 0.0.1 → 0.0.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (8) hide show
  1. data/CHANGELOG +9 -0
  2. data/QUICKSTART +55 -17
  3. data/README +5 -3
  4. data/ROADMAP +1 -1
  5. data/Rakefile +15 -10
  6. data/lib/radius.rb +37 -6
  7. data/test/radius_test.rb +102 -55
  8. metadata +13 -10
@@ -0,0 +1,9 @@
1
+ = Change Log
2
+
3
+ === 0.0.2
4
+ * Refactored Parser to use Context#render_tag instead of #send when rendering tags defined on a Context.
5
+ * UndefinedTagError is now thrown when Parser tries to render a tag which doesn't exist on a Context.
6
+ * Added Context#tag_missing which works like method_method missing on Object, but is tag specific.
7
+
8
+ === 0.0.1
9
+ * First release.
data/QUICKSTART CHANGED
@@ -1,4 +1,6 @@
1
- =Quick Start
1
+ = Radius Quick Start
2
+
3
+ == Defining Tags
2
4
 
3
5
  Before you can parse a template with Radius you need to create a Context object which defines
4
6
  the tags that will be used in the template. This is pretty simple:
@@ -17,16 +19,19 @@ Once you have defined a context you can create a Parser and parse to your heart'
17
19
  puts parser.parse('<p><radius:hello /></p>')
18
20
  puts parser.parse('<p><radius:hello name="John" /></p>')
19
21
 
20
- This will output:
22
+ This code will output:
21
23
 
22
24
  <p>Hello World!</p>
23
25
  <p>Hello John!</p>
24
26
 
25
27
  Note how you can pass attributes from the template to the context using the attributes hash
26
- (which is passed in as the first parameter.). Above the first tag that was parsed didn't have
28
+ (which is passed in as the first parameter). Above, the first tag that was parsed didn't have
27
29
  a name attribute so the code in the +hello+ method uses "World" instead. The second time the
28
30
  tag is parsed the name attribute is set to "John" which is used to create the string "Hello
29
- John!".
31
+ John!". <b>All tag definitions must accept only one parameter--the attributes hash.</b> Tags
32
+ that do not follow this rule will be treated as if they were undefined (like normal methods).
33
+
34
+ == Container Tags
30
35
 
31
36
  Radius also allows you to define "container" tags. That is, tags that contain content and
32
37
  that may optionally manipulate it in some way. For example, if you have RedCloth installed
@@ -45,17 +50,14 @@ With the code above your parser can easily handle Textile:
45
50
 
46
51
  parser.parse('<radius:textile>h1. Hello **World**!</radius:textile>')
47
52
 
48
- This will output:
53
+ This code will output:
49
54
 
50
55
  <h1>Hello <strong>World</strong>!</h1>
51
56
 
52
- But wait!--it gets better. Because container tags can manipulate what they contain you can use
53
- them to iterate over collections:
57
+ But wait!--it gets better. Because container tags can manipulate the content they contain
58
+ you can use them to iterate over collections:
54
59
 
55
60
  class ThreeStoogesContext < Radius::Context
56
- def initialize
57
- @prefix = 'ts'
58
- end
59
61
  def stooge(attr)
60
62
  content = ''
61
63
  ["Larry", "Moe", "Curly"].each do |name|
@@ -73,15 +75,15 @@ them to iterate over collections:
73
75
 
74
76
  template = <<-TEMPLATE
75
77
  <ul>
76
- <ts:stooge>
77
- <li><ts:name /></li>
78
- </ts:stooge>
78
+ <radius:stooge>
79
+ <li><radius:name /></li>
80
+ </radius:stooge>
79
81
  </ul>
80
82
  TEMPLATE
81
83
 
82
84
  puts parser.parse(template)
83
85
 
84
- This will output:
86
+ This code will output:
85
87
 
86
88
  <ul>
87
89
 
@@ -93,6 +95,42 @@ This will output:
93
95
 
94
96
  </ul>
95
97
 
96
- The above code also illustrates how you can set the prefix instance variable to control the
97
- string that prefixes Radius tags. By setting the prefix to "ts" our tags must begin with "ts"
98
- instead of "radius" like they did in the other examples.
98
+
99
+ == Altering the Tag Prefix
100
+
101
+ By default, all Radius tags must begin with "radius". You can change this by altering the
102
+ prefix attribute on a Context. For example:
103
+
104
+ class MyContext < Radius::Context
105
+ def initialize
106
+ @prefix = 'r'
107
+ end
108
+ end
109
+
110
+ Now, when parsing templates with MyContext, Radius will require that tags begin with "r".
111
+
112
+
113
+ == Using Context#tag_missing to Define Behavior for Missing Tags
114
+
115
+ Context#tag_missing behaves much like Object#method_missing only it allows you to define
116
+ specific behavior for when a tag is not defined on a Context. For example:
117
+
118
+ class LazyContext < Radius::Context
119
+ def initialize
120
+ @prefix = 'lazy'
121
+ end
122
+ def tag_missing(tag, attr, &block)
123
+ "<strong>ERROR: Undefined tag `#{tag}' with attributes #{attr.inspect}</strong>"
124
+ end
125
+ end
126
+
127
+ parser = Radius::Parser.new(LazyContext.new)
128
+ puts parser.parse('<lazy:weird value="true" />')
129
+
130
+ This code will output:
131
+
132
+ <strong>ERROR: Undefined tag `weird' with attributes {"value"=>"true"}</strong>
133
+
134
+ Normally, when the Radius Parser encounters an undefined tag for a Context it raises an
135
+ UndefinedTagError, but since we have defined #tag_missing on LazyContext the Parser now
136
+ outputs our custom message.
data/README CHANGED
@@ -19,7 +19,7 @@ It is recommended that you install Radius using the RubyGems packaging system:
19
19
 
20
20
  % gem install --remote radius
21
21
 
22
- You can also install Radius by copying radius.rb into the Ruby load path.
22
+ You can also install Radius by copying lib/radius.rb into the Ruby load path.
23
23
 
24
24
  == License
25
25
 
@@ -44,6 +44,8 @@ link:files/ROADMAP.html
44
44
  If you are a smart developer with a passion for excellence, now is the time to jump on board.
45
45
  Contact me and we'll talk. :)
46
46
 
47
- --
47
+ Enjoy!
48
48
 
49
- John Long :: http://wiseheartdesign.com
49
+ --
50
+ John Long ::
51
+ http://wiseheartdesign.com
data/ROADMAP CHANGED
@@ -13,6 +13,6 @@ This is a prioritized roadmap for future releases:
13
13
  http://blade.nagaokaut.ac.jp/cgi-bin/scat.rb/ruby/ruby-talk/127640/
14
14
 
15
15
  Update: See link:files/DSL-SPEC.html for a fuller explanation of how
16
- the DSL should behave.
16
+ the DSL might behave.
17
17
 
18
18
  4. Optimize for speed. Incorporate strscan?
data/Rakefile CHANGED
@@ -9,30 +9,35 @@ Rake::TestTask.new do |t|
9
9
  t.pattern = 'test/**/*_test.rb'
10
10
  end
11
11
 
12
+ RDOC_TITLE = "Radius -- Powerful Tag-Based Templates"
13
+ RDOC_EXTRAS = ["README", "QUICKSTART", "ROADMAP", "DSL-SPEC", "CHANGELOG"]
14
+
12
15
  Rake::RDocTask.new do |rd|
13
16
  rd.title = 'Radius -- Powerful Tag-Based Templates'
14
- rd.main = "README"
15
- rd.rdoc_files.include("README", "QUICKSTART", "ROADMAP", "DSL-SPEC", "lib/**/*.rb")
17
+ rd.main = "Radius"
18
+ rd.rdoc_files.include("lib/**/*.rb")
19
+ rd.rdoc_files.include(RDOC_EXTRAS)
16
20
  rd.rdoc_dir = 'doc'
17
21
  end
18
22
 
19
23
  spec = Gem::Specification.new do |s|
20
- s.platform = Gem::Platform::RUBY
21
- s.summary = "Powerful tag-based template system."
22
24
  s.name = 'radius'
23
- s.version = '0.0.1'
25
+ s.rubyforge_project = 'radius'
26
+ s.version = '0.0.2'
27
+ s.summary = 'Powerful tag-based template system.'
28
+ s.description = "Radius is a small, but powerful tag-based template language for Ruby\nsimilar to the ones used in MovableType and TextPattern. It has tags\nsimilar to HTML or XML, but can be used to generate any form of plain\ntext (not just HTML)."
29
+ s.homepage = 'http://radius.rubyforge.org'
30
+ s.platform = Gem::Platform::RUBY
24
31
  s.requirements << 'none'
25
32
  s.require_path = 'lib'
26
33
  s.autorequire = 'radius'
27
34
  s.has_rdoc = true
28
- s.rdoc_options << '--title' << 'Radius -- Powerful Tag-Based Templates' <<
29
- '--main' << 'README' <<
30
- '--line-numbers' << 'ROADMAP' <<
31
- 'QUICKSTART' << 'DSL-SPEC' << 'README'
35
+ s.rdoc_options << '--title' << RDOC_TITLE << '--line-numbers' << '--main' << 'README'
36
+ s.extra_rdoc_files = RDOC_EXTRAS
32
37
  files = FileList['**/*']
33
38
  files.exclude 'doc'
39
+ files.exclude '**/._*'
34
40
  s.files = files.to_a
35
- s.description = "Radius is a small, but powerful tag-based template language inspired\nby the template languages used in MovableType and TextPattern."
36
41
  end
37
42
 
38
43
  Rake::GemPackageTask.new(spec) do |pkg|
@@ -1,13 +1,25 @@
1
1
  module Radius
2
- class ParseError < StandardError # :nodoc:
2
+ # Abstract base class for all parsing errors.
3
+ class ParseError < StandardError
3
4
  end
4
5
 
5
- class MissingEndTagError < ParseError # :nodoc:
6
+ # Occurs when Parser cannot find an end tag for a given tag in a template or when
7
+ # tags are miss-matched in a template.
8
+ class MissingEndTagError < ParseError
9
+ # Create a new MissingEndTagError object for +tag_name+.
6
10
  def initialize(tag_name)
7
11
  super("end tag not found for start tag `#{tag_name}'")
8
12
  end
9
13
  end
10
14
 
15
+ # Occurs when Context#render_tag cannot find the specified tag on a Context.
16
+ class UndefinedTagError < ParseError
17
+ # Create a new MissingEndTagError object for +tag_name+.
18
+ def initialize(tag_name)
19
+ super("undefined tag `#{tag_name}'")
20
+ end
21
+ end
22
+
11
23
  #
12
24
  # An abstract class for creating a Context. A context defines the tags that
13
25
  # are available for use in a template.
@@ -15,22 +27,41 @@ module Radius
15
27
  class Context
16
28
  # The prefix attribute controls the string of text that is helps the parser
17
29
  # identify template tags. By default this attribute is set to "radius", but
18
- # you may want to override this for your own contexts.
30
+ # you may want to override this when creating your own contexts.
19
31
  attr_accessor :prefix
20
32
 
21
33
  # Creates a new Context object.
22
34
  def initialize
23
35
  @prefix = 'radius'
24
36
  end
37
+
38
+ # Returns the value of a rendered tag. Used internally by Parser#parse.
39
+ def render_tag(tag, attributes = {}, &block)
40
+ symbol = tag.to_s.intern
41
+ if respond_to?(symbol) and method(symbol).arity == 1
42
+ send(symbol, attributes, &block)
43
+ else
44
+ tag_missing(tag, attributes, &block)
45
+ end
46
+ end
47
+
48
+ # Like method_missing for objects, but fired when a tag is undefined.
49
+ # Override in your own Context to change what happens when a tag is
50
+ # undefined. By default this method raises an UndefinedTagError.
51
+ def tag_missing(tag, attributes, &block)
52
+ raise UndefinedTagError.new(tag)
53
+ end
25
54
  end
26
55
 
27
56
  class Tag # :nodoc:
28
57
  def initialize(&b)
29
58
  @block = b
30
59
  end
60
+
31
61
  def on_parse(&b)
32
62
  @block = b
33
63
  end
64
+
34
65
  def to_s
35
66
  @block.call(self)
36
67
  end
@@ -53,7 +84,7 @@ module Radius
53
84
  # The Context object used to expand template tags.
54
85
  attr_accessor :context
55
86
 
56
- # Creates a new parser object initialized with a context.
87
+ # Creates a new parser object initialized with a Context.
57
88
  def initialize(context = Context.new)
58
89
  @context = context
59
90
  end
@@ -93,7 +124,7 @@ module Radius
93
124
  def parse_end_tag(end_tag, remaining) # :nodoc:
94
125
  popped = @stack.pop
95
126
  if popped.name == end_tag
96
- popped.on_parse { |t| @context.send(popped.name, popped.attributes) { t.contents.to_s } }
127
+ popped.on_parse { |t| @context.render_tag(popped.name, popped.attributes) { t.contents.to_s } }
97
128
  tag = @stack.last
98
129
  tag.contents << popped
99
130
  pre_parse(remaining)
@@ -106,7 +137,7 @@ module Radius
106
137
  re = /<#{@context.prefix}:(\w+?)\s+?(.*?)\s*?\/>/
107
138
  if md = re.match(text)
108
139
  attr = parse_attributes($2)
109
- replace = @context.send($1, attr)
140
+ replace = @context.render_tag($1, attr)
110
141
  md.pre_match + replace + parse_individual(md.post_match)
111
142
  else
112
143
  text || ''
@@ -1,68 +1,115 @@
1
1
  require 'test/unit'
2
2
  require 'radius'
3
3
 
4
- class ContextTest < Test::Unit::TestCase
5
- def test_initialize
6
- c = Radius::Context.new
7
- assert_equal 'radius', c.prefix
4
+ class TestContext < Radius::Context
5
+ def initialize
6
+ @prefix = "test"
7
+ @items = ["Larry", "Moe", "Curly"]
8
+ end
9
+
10
+ def echo(attr)
11
+ attr["text"]
12
+ end
13
+
14
+ def add(attr)
15
+ (attr["param1"].to_i + attr["param2"].to_i).to_s
16
+ end
17
+
18
+ def reverse(attr)
19
+ yield.reverse
20
+ end
21
+
22
+ def capitalize(attr)
23
+ yield.upcase
24
+ end
25
+
26
+ def count(attr)
27
+ case
28
+ when attr["set"]
29
+ @count = attr["set"].to_i
30
+ ""
31
+ when attr["inc"] == "true"
32
+ @count = (@count || 0) + 1
33
+ ""
34
+ else
35
+ @count.to_s
36
+ end
37
+ end
38
+
39
+ def repeat(attr)
40
+ string = ''
41
+ (attr['count'] || '1').to_i.times { string << yield }
42
+ string
43
+ end
44
+
45
+ def each_item(attr)
46
+ result = []
47
+ @items.each { |@item| result << yield }
48
+ @item = nil
49
+ result.join(attr["between"] || "")
50
+ end
51
+
52
+ def item(attr)
53
+ @item
54
+ end
55
+
56
+ def hello(attr)
57
+ "Hello #{attr['name'] || 'World'}!"
8
58
  end
9
59
  end
10
60
 
11
- class RadiusTest < Test::Unit::TestCase
12
- class TestContext < Radius::Context
13
- def initialize
14
- @prefix = "test"
15
- @items = ["Larry", "Moe", "Curly"]
16
- end
17
-
18
- def echo(attr)
19
- attr["text"]
20
- end
21
-
22
- def add(attr)
23
- (attr["param1"].to_i + attr["param2"].to_i).to_s
24
- end
25
-
26
- def reverse(attr)
27
- yield.reverse
28
- end
29
-
30
- def capitalize(attr)
31
- yield.upcase
32
- end
33
-
34
- def count(attr)
35
- case
36
- when attr["set"]
37
- @count = attr["set"].to_i
38
- ""
39
- when attr["inc"] == "true"
40
- @count = (@count || 0) + 1
41
- ""
42
- else
43
- @count.to_s
61
+ class RadiusContextTest < Test::Unit::TestCase
62
+ def setup
63
+ @context = TestContext.new
64
+ end
65
+
66
+ def test_initialize
67
+ @context = Radius::Context.new
68
+ assert_equal 'radius', @context.prefix
69
+ end
70
+
71
+ def test_render_tag__individual
72
+ text = @context.render_tag('hello')
73
+ assert_equal('Hello World!', text)
74
+
75
+ text = @context.render_tag('hello', 'name' => 'John')
76
+ assert_equal('Hello John!', text)
77
+ end
78
+
79
+ def test_render_tag__container
80
+ text = @context.render_tag('repeat', 'count' => '5') { 'o' }
81
+ assert_equal('ooooo', text)
82
+ end
83
+
84
+ def test_render_tag__undefined_tag
85
+ e = assert_raises(Radius::UndefinedTagError) { @context.render_tag('undefined_tag') }
86
+ assert_equal "undefined tag `undefined_tag'", e.message
87
+ end
88
+
89
+ def test_render_tag__undefined_tag_with_wrong_signature
90
+ class << @context
91
+ def helper_method
44
92
  end
45
93
  end
46
-
47
- def loop(attr)
48
- t = attr["times"].to_i
49
- result = ""
50
- t.times { result += yield }
51
- result
94
+ assert_raises(Radius::UndefinedTagError) { @context.render_tag('helper_method') }
95
+ end
96
+
97
+ def test_tag_missing
98
+ class << @context
99
+ def tag_missing(tag, attr, &block)
100
+ "undefined tag `#{tag}' with attributes #{attr.inspect}"
101
+ end
52
102
  end
53
103
 
54
- def each_item(attr)
55
- result = []
56
- @items.each { |@item| result << yield }
57
- @item = nil
58
- result.join(attr["between"] || "")
59
- end
104
+ text = ''
105
+ assert_nothing_raised { text = @context.render_tag('undefined_tag', 'cool' => 'beans') }
60
106
 
61
- def item(attr)
62
- @item
63
- end
107
+ expected = %{undefined tag `undefined_tag' with attributes {"cool"=>"beans"}}
108
+ assert_equal expected, text
64
109
  end
65
-
110
+ end
111
+
112
+ class RadiusParserTest < Test::Unit::TestCase
66
113
  def setup
67
114
  @t = Radius::Parser.new(TestContext.new )
68
115
  end
@@ -105,8 +152,8 @@ class RadiusTest < Test::Unit::TestCase
105
152
  r = @t.parse("<test:reverse>12<test:capitalize>at</test:capitalize>34</test:reverse>")
106
153
  assert_equal("43TA21", r)
107
154
  end
108
- def test_parse__loop
109
- r = @t.parse(%{<test:count set="0" /><test:loop times="5"><test:count inc="true" /><test:count /></test:loop>})
155
+ def test_parse__looping
156
+ r = @t.parse(%{<test:count set="0" /><test:repeat count="5"><test:count inc="true" /><test:count /></test:repeat>})
110
157
  assert_equal("12345", r)
111
158
 
112
159
  r = @t.parse(%{Three Stooges: <test:each_item between=", ">"<test:item />"</test:each_item>})
metadata CHANGED
@@ -3,16 +3,17 @@ rubygems_version: 0.8.10
3
3
  specification_version: 1
4
4
  name: radius
5
5
  version: !ruby/object:Gem::Version
6
- version: 0.0.1
7
- date: 2006-01-21
6
+ version: 0.0.2
7
+ date: 2006-02-14
8
8
  summary: Powerful tag-based template system.
9
9
  require_paths:
10
10
  - lib
11
11
  email:
12
- homepage:
13
- rubyforge_project:
14
- description: "Radius is a small, but powerful tag-based template language inspired by the
15
- template languages used in MovableType and TextPattern."
12
+ homepage: http://radius.rubyforge.org
13
+ rubyforge_project: radius
14
+ description: "Radius is a small, but powerful tag-based template language for Ruby similar to
15
+ the ones used in MovableType and TextPattern. It has tags similar to HTML or
16
+ XML, but can be used to generate any form of plain text (not just HTML)."
16
17
  autorequire: radius
17
18
  default_executable:
18
19
  bindir: bin
@@ -27,6 +28,7 @@ required_ruby_version: !ruby/object:Gem::Version::Requirement
27
28
  platform: ruby
28
29
  authors: []
29
30
  files:
31
+ - CHANGELOG
30
32
  - DSL-SPEC
31
33
  - lib
32
34
  - QUICKSTART
@@ -40,14 +42,15 @@ test_files: []
40
42
  rdoc_options:
41
43
  - "--title"
42
44
  - "Radius -- Powerful Tag-Based Templates"
45
+ - "--line-numbers"
43
46
  - "--main"
44
47
  - README
45
- - "--line-numbers"
46
- - ROADMAP
48
+ extra_rdoc_files:
49
+ - README
47
50
  - QUICKSTART
51
+ - ROADMAP
48
52
  - DSL-SPEC
49
- - README
50
- extra_rdoc_files: []
53
+ - CHANGELOG
51
54
  executables: []
52
55
  extensions: []
53
56
  requirements: