radius 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (8) hide show
  1. data/DSL-SPEC +151 -0
  2. data/QUICKSTART +98 -0
  3. data/README +49 -0
  4. data/ROADMAP +18 -0
  5. data/Rakefile +41 -0
  6. data/lib/radius.rb +126 -0
  7. data/test/radius_test.rb +122 -0
  8. metadata +55 -0
@@ -0,0 +1,151 @@
1
+ =DSL Specification
2
+
3
+ I haven't implemented a domain specific languages for Contexts yet, but I am hoping to
4
+ do so in a future release. Below are some thoughts (in code) for how I would like it to
5
+ work. Note that with a robust DSL you will be able to define that certain tags are
6
+ only valid within certain containing tags.
7
+
8
+ class StoreContext < Radius::Context
9
+ def initialize(options)
10
+ @prefix = "r" # all tags must be prefixed with "r"
11
+ @user = options[:user]
12
+ @cart = options[:cart]
13
+ @session = options[:session]
14
+ end
15
+
16
+ # expose the @user object variable
17
+ container(:user, :exposes => :user) do
18
+ # Use protect() on an object that has been exposed to prevent access to
19
+ # an attribute in a template. Conversely you could use the expose() method
20
+ # to expose specific attributes to the template and protect all others.
21
+ protect :password
22
+
23
+ # add a single tag that returns the session_id
24
+ tag :session_id do |attributes|
25
+ @session.id
26
+ end
27
+ end
28
+
29
+ # expose the @cart object as the basket tag
30
+ container(:basket, :exposes => :cart) do
31
+ expand do |attributes|
32
+ #
33
+ # some initialization code with attributes before handling
34
+ # content block
35
+ #
36
+ yeild
37
+ end
38
+
39
+ container(:items, :exposes => :item) do
40
+ expand do |attributes|
41
+ @cart.items.each do |@item|
42
+ yield
43
+ end
44
+ end
45
+ end
46
+ end
47
+
48
+ container :cart do
49
+ expand do |attributes|
50
+ yield
51
+ end
52
+ end
53
+ end
54
+
55
+ class User
56
+ attr_accessor :name, :login, :password, :email
57
+ def initialize(name, login, password, email)
58
+ @name, @login, @password, @email = name, login, password, email
59
+ end
60
+ end
61
+
62
+ class Session
63
+ attr_accessor :id
64
+ def initialize(id)
65
+ @id = id
66
+ end
67
+ end
68
+
69
+ class Cart
70
+ attr_accessor :items
71
+
72
+ def initialize(*items)
73
+ @items = [items].flatten
74
+ end
75
+
76
+ def total
77
+ @items.map { |line_item| line_item.total }
78
+ end
79
+ end
80
+
81
+ class LineItem
82
+ attr_accessor :name, :description, :quantity, :item_price
83
+ def intialize(name, description, price, quantity)
84
+ @name, @description, @price, @quantity = name, description, price, quantity
85
+ end
86
+ def full_price
87
+ @price * @quantity
88
+ end
89
+ end
90
+
91
+ receipt = <<-RECEIPT
92
+ <p><r:user:name />, thank you for shopping with us! An order summary
93
+ is printed below for your convinience. Please print a copy for your records.</p>
94
+ <r:cart>
95
+ <table>
96
+ <thead>
97
+ <tr>
98
+ <td>Product</td>
99
+ <td>Price</td>
100
+ <td>Quantity</td>
101
+ <td>Totals</td>
102
+ </tr>
103
+ </thead>
104
+ <tbody>
105
+ <r:items>
106
+ <tr>
107
+ <td>
108
+ <strong><r:name /></strong><br >
109
+ <r:description />
110
+ </td>
111
+ <td><r:price /></td>
112
+ <td><r:quanity /></td>
113
+ <td><r:full_price /></td>
114
+ </tr>
115
+ </r:items>
116
+ </tbody>
117
+ <tr>
118
+ <td colspan="3">Total</td>
119
+ <td><r:total /></td>
120
+ </tr>
121
+ </table>
122
+ </r:cart>
123
+ RECEIPT
124
+
125
+ user = User.new('John', 'johnboy', 'm@x!mu5', 'johnboy@maximus.com')
126
+ cart = Cart.new(
127
+ LineItem.new('15in PowerBook', "Apple's premium notebook computer.", 1995.98, 1),
128
+ LineItem.new('Mac Notebook Case', "A beautiful black notebook case designed for Apple Powerbooks.", 54.05, 1)
129
+ )
130
+ session = Session.new('a4bd386e512bacd581')
131
+
132
+ context = StoreContext.new(
133
+ :user => user,
134
+ :cart => cart,
135
+ :session => session
136
+ )
137
+
138
+ template = Radius::Template.new(
139
+ :text => receipt,
140
+ :context => context
141
+ )
142
+
143
+ template.compile! # based on context parses text into abstract syntax tree
144
+ puts template.expand # outputs expanded template
145
+
146
+ # alternate usage
147
+ parser = Radius::Parser.new(context)
148
+ puts parser.parse(receipt) # compiles and outputs expanded template
149
+
150
+ # another alternate
151
+ puts Radius.parse(receipt, context)
@@ -0,0 +1,98 @@
1
+ =Quick Start
2
+
3
+ Before you can parse a template with Radius you need to create a Context object which defines
4
+ the tags that will be used in the template. This is pretty simple:
5
+
6
+ require 'radius'
7
+
8
+ class MyContext < Radius::Context
9
+ def hello(attr)
10
+ "Hello #{attr['name'] || 'World'}!"
11
+ end
12
+ end
13
+
14
+ Once you have defined a context you can create a Parser and parse to your heart's content:
15
+
16
+ parser = Radius::Parser.new(MyContext.new)
17
+ puts parser.parse('<p><radius:hello /></p>')
18
+ puts parser.parse('<p><radius:hello name="John" /></p>')
19
+
20
+ This will output:
21
+
22
+ <p>Hello World!</p>
23
+ <p>Hello John!</p>
24
+
25
+ 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
27
+ a name attribute so the code in the +hello+ method uses "World" instead. The second time the
28
+ tag is parsed the name attribute is set to "John" which is used to create the string "Hello
29
+ John!".
30
+
31
+ Radius also allows you to define "container" tags. That is, tags that contain content and
32
+ that may optionally manipulate it in some way. For example, if you have RedCloth installed
33
+ you could define another tag to parse and create Textile output:
34
+
35
+ require 'redcloth'
36
+
37
+ class MyContext < Radius::Context
38
+ def textile(attr)
39
+ contents = yield
40
+ RedCloth.new(contents).to_html
41
+ end
42
+ end
43
+
44
+ With the code above your parser can easily handle Textile:
45
+
46
+ parser.parse('<radius:textile>h1. Hello **World**!</radius:textile>')
47
+
48
+ This will output:
49
+
50
+ <h1>Hello <strong>World</strong>!</h1>
51
+
52
+ But wait!--it gets better. Because container tags can manipulate what they contain you can use
53
+ them to iterate over collections:
54
+
55
+ class ThreeStoogesContext < Radius::Context
56
+ def initialize
57
+ @prefix = 'ts'
58
+ end
59
+ def stooge(attr)
60
+ content = ''
61
+ ["Larry", "Moe", "Curly"].each do |name|
62
+ @name = name
63
+ content << yield
64
+ end
65
+ content
66
+ end
67
+ def name(attr)
68
+ @name
69
+ end
70
+ end
71
+
72
+ parser = Radius::Parser.new(ThreeStoogesContext.new)
73
+
74
+ template = <<-TEMPLATE
75
+ <ul>
76
+ <ts:stooge>
77
+ <li><ts:name /></li>
78
+ </ts:stooge>
79
+ </ul>
80
+ TEMPLATE
81
+
82
+ puts parser.parse(template)
83
+
84
+ This will output:
85
+
86
+ <ul>
87
+
88
+ <li>Larry</li>
89
+
90
+ <li>Moe</li>
91
+
92
+ <li>Curly</li>
93
+
94
+ </ul>
95
+
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.
data/README ADDED
@@ -0,0 +1,49 @@
1
+ = Radius -- Powerful Tag-Based Templates
2
+
3
+ Radius is a small, but powerful 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 HTML, but can be used to generate any form of plain text (XML, e-mail,
6
+ etc...).
7
+
8
+
9
+ == Download
10
+
11
+ The latest version of Radius can be found on RubyForge:
12
+
13
+ http://rubyforge.org/projects/radius/
14
+
15
+
16
+ == Installation
17
+
18
+ It is recommended that you install Radius using the RubyGems packaging system:
19
+
20
+ % gem install --remote radius
21
+
22
+ You can also install Radius by copying radius.rb into the Ruby load path.
23
+
24
+ == License
25
+
26
+ Radius is free software and may be redistributed under the same terms and Ruby itself. For
27
+ more details, see the readme file in the Ruby distribution.
28
+
29
+
30
+ == Quick Start
31
+
32
+ To get up and running fast with Radius read:
33
+
34
+ link:files/QUICKSTART.html
35
+
36
+
37
+ == A Call to Action
38
+
39
+ Radius is still very much in the development stages. Take a look at the roadmap to see where
40
+ we want to go:
41
+
42
+ link:files/ROADMAP.html
43
+
44
+ If you are a smart developer with a passion for excellence, now is the time to jump on board.
45
+ Contact me and we'll talk. :)
46
+
47
+ --
48
+
49
+ John Long :: http://wiseheartdesign.com
data/ROADMAP ADDED
@@ -0,0 +1,18 @@
1
+ = Roadmap
2
+
3
+ This is a prioritized roadmap for future releases:
4
+
5
+ 1. Clean up the current code base.
6
+
7
+ 2. Add support for multi-level contexts: tags should be able to be
8
+ defined to only be valid within other sets of tags.
9
+
10
+ 3. Create a simple DSL for defining contexts. I had done some thinking
11
+ about this in the past. This thread on Ruby-Talk defined part of it:
12
+
13
+ http://blade.nagaokaut.ac.jp/cgi-bin/scat.rb/ruby/ruby-talk/127640/
14
+
15
+ Update: See link:files/DSL-SPEC.html for a fuller explanation of how
16
+ the DSL should behave.
17
+
18
+ 4. Optimize for speed. Incorporate strscan?
@@ -0,0 +1,41 @@
1
+ require 'rubygems'
2
+ require 'rake/testtask'
3
+ require 'rake/rdoctask'
4
+ require 'rake/gempackagetask'
5
+
6
+ task :default => :test
7
+
8
+ Rake::TestTask.new do |t|
9
+ t.pattern = 'test/**/*_test.rb'
10
+ end
11
+
12
+ Rake::RDocTask.new do |rd|
13
+ rd.title = 'Radius -- Powerful Tag-Based Templates'
14
+ rd.main = "README"
15
+ rd.rdoc_files.include("README", "QUICKSTART", "ROADMAP", "DSL-SPEC", "lib/**/*.rb")
16
+ rd.rdoc_dir = 'doc'
17
+ end
18
+
19
+ spec = Gem::Specification.new do |s|
20
+ s.platform = Gem::Platform::RUBY
21
+ s.summary = "Powerful tag-based template system."
22
+ s.name = 'radius'
23
+ s.version = '0.0.1'
24
+ s.requirements << 'none'
25
+ s.require_path = 'lib'
26
+ s.autorequire = 'radius'
27
+ 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'
32
+ files = FileList['**/*']
33
+ files.exclude 'doc'
34
+ 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
+ end
37
+
38
+ Rake::GemPackageTask.new(spec) do |pkg|
39
+ pkg.need_zip = true
40
+ pkg.need_tar = true
41
+ end
@@ -0,0 +1,126 @@
1
+ module Radius
2
+ class ParseError < StandardError # :nodoc:
3
+ end
4
+
5
+ class MissingEndTagError < ParseError # :nodoc:
6
+ def initialize(tag_name)
7
+ super("end tag not found for start tag `#{tag_name}'")
8
+ end
9
+ end
10
+
11
+ #
12
+ # An abstract class for creating a Context. A context defines the tags that
13
+ # are available for use in a template.
14
+ #
15
+ class Context
16
+ # The prefix attribute controls the string of text that is helps the parser
17
+ # identify template tags. By default this attribute is set to "radius", but
18
+ # you may want to override this for your own contexts.
19
+ attr_accessor :prefix
20
+
21
+ # Creates a new Context object.
22
+ def initialize
23
+ @prefix = 'radius'
24
+ end
25
+ end
26
+
27
+ class Tag # :nodoc:
28
+ def initialize(&b)
29
+ @block = b
30
+ end
31
+ def on_parse(&b)
32
+ @block = b
33
+ end
34
+ def to_s
35
+ @block.call(self)
36
+ end
37
+ end
38
+
39
+ class ContainerTag < Tag # :nodoc:
40
+ attr_accessor :name, :attributes, :contents
41
+
42
+ def initialize(name="", attributes={}, contents=[], &b)
43
+ @name, @attributes, @contents = name, attributes, contents
44
+ super(&b)
45
+ end
46
+ end
47
+
48
+ #
49
+ # The Radius parser. Initialize a parser with the Context object that defines
50
+ # how tags should be expanded.
51
+ #
52
+ class Parser
53
+ # The Context object used to expand template tags.
54
+ attr_accessor :context
55
+
56
+ # Creates a new parser object initialized with a context.
57
+ def initialize(context = Context.new)
58
+ @context = context
59
+ end
60
+
61
+ # Parse string for tags, expand them, and return the result.
62
+ def parse(string)
63
+ @stack = [ContainerTag.new { |t| t.contents.to_s }]
64
+ pre_parse(string)
65
+ @stack.last.to_s
66
+ end
67
+
68
+ def pre_parse(text) # :nodoc:
69
+ re = %r{<#{@context.prefix}:(\w+?)(?:\s+?([^/>]*?)|)>|</#{@context.prefix}:(\w+?)\s*?>}
70
+ if md = re.match(text)
71
+ start_tag, attr, end_tag = $1, $2, $3
72
+ @stack.last.contents << Tag.new { parse_individual(md.pre_match) }
73
+ remaining = md.post_match
74
+ if start_tag
75
+ parse_start_tag(start_tag, attr, remaining)
76
+ else
77
+ parse_end_tag(end_tag, remaining)
78
+ end
79
+ else
80
+ if @stack.length == 1
81
+ @stack.last.contents << Tag.new { parse_individual(text) }
82
+ else
83
+ raise MissingEndTagError.new(@stack.last.name)
84
+ end
85
+ end
86
+ end
87
+
88
+ def parse_start_tag(start_tag, attr, remaining) # :nodoc:
89
+ @stack.push(ContainerTag.new(start_tag, parse_attributes(attr)))
90
+ pre_parse(remaining)
91
+ end
92
+
93
+ def parse_end_tag(end_tag, remaining) # :nodoc:
94
+ popped = @stack.pop
95
+ if popped.name == end_tag
96
+ popped.on_parse { |t| @context.send(popped.name, popped.attributes) { t.contents.to_s } }
97
+ tag = @stack.last
98
+ tag.contents << popped
99
+ pre_parse(remaining)
100
+ else
101
+ raise MissingEndTagError.new(popped.name)
102
+ end
103
+ end
104
+
105
+ def parse_individual(text) # :nodoc:
106
+ re = /<#{@context.prefix}:(\w+?)\s+?(.*?)\s*?\/>/
107
+ if md = re.match(text)
108
+ attr = parse_attributes($2)
109
+ replace = @context.send($1, attr)
110
+ md.pre_match + replace + parse_individual(md.post_match)
111
+ else
112
+ text || ''
113
+ end
114
+ end
115
+
116
+ def parse_attributes(text) # :nodoc:
117
+ attr = {}
118
+ re = /(\w+?)\s*=\s*('|")(.*?)\2/
119
+ while md = re.match(text)
120
+ attr[$1] = $3
121
+ text = md.post_match
122
+ end
123
+ attr
124
+ end
125
+ end
126
+ end
@@ -0,0 +1,122 @@
1
+ require 'test/unit'
2
+ require 'radius'
3
+
4
+ class ContextTest < Test::Unit::TestCase
5
+ def test_initialize
6
+ c = Radius::Context.new
7
+ assert_equal 'radius', c.prefix
8
+ end
9
+ end
10
+
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
44
+ end
45
+ end
46
+
47
+ def loop(attr)
48
+ t = attr["times"].to_i
49
+ result = ""
50
+ t.times { result += yield }
51
+ result
52
+ end
53
+
54
+ def each_item(attr)
55
+ result = []
56
+ @items.each { |@item| result << yield }
57
+ @item = nil
58
+ result.join(attr["between"] || "")
59
+ end
60
+
61
+ def item(attr)
62
+ @item
63
+ end
64
+ end
65
+
66
+ def setup
67
+ @t = Radius::Parser.new(TestContext.new )
68
+ end
69
+
70
+ def test_parse_individual
71
+ r = @t.parse_individual(%{<<test:echo text="hello world!" />>})
72
+ assert_equal("<hello world!>", r)
73
+
74
+ r = @t.parse_individual(%{<test:add param1="1" param2='2'/>})
75
+ assert_equal("3", r)
76
+
77
+ r = @t.parse_individual(%{a <test:echo text="3 + 1 =" /> <test:add param1="3" param2="1"/> b})
78
+ assert_equal("a 3 + 1 = 4 b", r)
79
+ end
80
+
81
+ def test_parse_attributes
82
+ r = @t.parse_attributes(%{ a="1" b='2'c="3"d="'" })
83
+ assert_equal({"a" => "1", "b" => "2", "c" => "3", "d" => "'"}, r)
84
+ end
85
+
86
+ def test_parse
87
+ r = @t.parse(%{<<test:echo text="hello world!" />>})
88
+ assert_equal("<hello world!>", r)
89
+
90
+ r = @t.parse("<test:reverse>test</test:reverse>")
91
+ assert_equal("test".reverse, r)
92
+
93
+ r = @t.parse("<test:reverse>test</test:reverse> <test:capitalize>test</test:capitalize>")
94
+ assert_equal("tset TEST", r)
95
+
96
+ r = @t.parse("<test:echo text='hello world!' /> cool: <test:reverse>a <test:capitalize>test</test:capitalize> b</test:reverse> !")
97
+ assert_equal("hello world! cool: b TSET a !", r)
98
+
99
+ r = @t.parse("<test:reverse><test:echo text='hello world!' /></test:reverse>")
100
+ assert_equal("!dlrow olleh", r)
101
+
102
+ r = @t.parse("<test:reverse><test:capitalize>test</test:capitalize> <test:echo text='hello world!' /></test:reverse>")
103
+ assert_equal("!dlrow olleh TSET", r)
104
+
105
+ r = @t.parse("<test:reverse>12<test:capitalize>at</test:capitalize>34</test:reverse>")
106
+ assert_equal("43TA21", r)
107
+ 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>})
110
+ assert_equal("12345", r)
111
+
112
+ r = @t.parse(%{Three Stooges: <test:each_item between=", ">"<test:item />"</test:each_item>})
113
+ assert_equal(%{Three Stooges: "Larry", "Moe", "Curly"}, r)
114
+ end
115
+
116
+ def test_parse__fail_on_no_end_tag
117
+ assert_raises(Radius::MissingEndTagError) { @t.parse("<test:reverse>") }
118
+ end
119
+ def test_parse__fail_on_no_end_tag_2
120
+ assert_raises(Radius::MissingEndTagError) { @t.parse("<test:reverse><test:capitalize></test:reverse>") }
121
+ end
122
+ end
metadata ADDED
@@ -0,0 +1,55 @@
1
+ --- !ruby/object:Gem::Specification
2
+ rubygems_version: 0.8.10
3
+ specification_version: 1
4
+ name: radius
5
+ version: !ruby/object:Gem::Version
6
+ version: 0.0.1
7
+ date: 2006-01-21
8
+ summary: Powerful tag-based template system.
9
+ require_paths:
10
+ - lib
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."
16
+ autorequire: radius
17
+ default_executable:
18
+ bindir: bin
19
+ has_rdoc: true
20
+ required_ruby_version: !ruby/object:Gem::Version::Requirement
21
+ requirements:
22
+ -
23
+ - ">"
24
+ - !ruby/object:Gem::Version
25
+ version: 0.0.0
26
+ version:
27
+ platform: ruby
28
+ authors: []
29
+ files:
30
+ - DSL-SPEC
31
+ - lib
32
+ - QUICKSTART
33
+ - Rakefile
34
+ - README
35
+ - ROADMAP
36
+ - test
37
+ - lib/radius.rb
38
+ - test/radius_test.rb
39
+ test_files: []
40
+ rdoc_options:
41
+ - "--title"
42
+ - "Radius -- Powerful Tag-Based Templates"
43
+ - "--main"
44
+ - README
45
+ - "--line-numbers"
46
+ - ROADMAP
47
+ - QUICKSTART
48
+ - DSL-SPEC
49
+ - README
50
+ extra_rdoc_files: []
51
+ executables: []
52
+ extensions: []
53
+ requirements:
54
+ - none
55
+ dependencies: []