Class: RagTag

Inherits:
Object
  • Object
show all
Defined in:
lib/ragtag.rb,
lib/ragtag/version.rb

Overview

RAGTAG - A Tag Attribute Language for Ruby

RubyTals is a Ruby variation on Zope Page Templates and it’s TAL specification. It differs from TAL in that it is specifically geared for use by Ruby.

Usage

s = %q{
  <html>
  <body>
    <h1 r:content="x">[X]</h1>
    <div r:each="animal" r:do="a">
      <b r:content="a">[ANIMAL]</b>
    </div>
    <div r:if="animal.size > 1">
      There are <b r:content="animal.size">[ANIMAL SIZE]</b> animals.
    </div>
  </body>
  </html>
}

x = 'Our Little Zoo'
animal = ['Zebra', 'Monkey', 'Tiger' ]

puts Ragtag.compile(s, binding)

Note

Presently RagTag clauses can run arbitraty Ruby code. Although upping the safety level before executing a compiled template should be sufficiently protective in most cases, perhaps it would be better to limit valid expressions to single object references, ie. ‘this.that’, and then use a substitution of ‘.’ for ‘/’. Not only would this be highly protective, it would also be more compatible with the original TAL spec; albeit this isn’t exacty how TALs interprets the ‘/’ divider.

On the other hand perhaps it is too much restraint. For instance it would require the if-clause in the above exmaple to be something like:

<div r:if="animal/plenty">

and have a definition in the evaluating code:

def animal.plenty
  size > 1
end

It’s a classic Saftey vs. Usability trade-off. Something to consider for the future.

Constant Summary collapse

VERSION =

TODO: This is only here b/c of bug in Ruby 1.8.x.

['version']

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(xml) ⇒ RagTag

Returns a new instance of RagTag.



77
78
79
80
81
82
83
84
# File 'lib/ragtag.rb', line 77

def initialize(xml)
  case xml
  when String
    @xml = Nokogiri::XML(xml)
  else
    @xml = xml
  end
end

Instance Attribute Details

#scopeObject (readonly)

Returns the value of attribute scope.



74
75
76
# File 'lib/ragtag.rb', line 74

def scope
  @scope
end

#xmlObject (readonly)

Returns the value of attribute xml.



71
72
73
# File 'lib/ragtag.rb', line 71

def xml
  @xml
end

Class Method Details

.compile(xml, scope = nil) ⇒ Object



60
61
62
# File 'lib/ragtag.rb', line 60

def self.compile(xml, scope=nil)
  new(xml).compile(scope)
end

.const_missing(name) ⇒ Object

Access to project metadata as constants.



12
13
14
15
# File 'lib/ragtag/version.rb', line 12

def self.const_missing(name)
  key = name.to_s.downcase
  [key] || super(name)
end

.metadataObject

Access to project metadata.



4
5
6
7
8
9
# File 'lib/ragtag/version.rb', line 4

def self.
  @metadata ||= (
    require 'yaml'
    YAML.load(File.new(File.dirname(__FILE__) + '/../ragtag.yml'))
  )
end

Instance Method Details

#compile(scope = nil) ⇒ Object



87
88
89
90
91
# File 'lib/ragtag.rb', line 87

def compile(scope=nil)
  scope = scope || $TOPLEVEL_BINDING
  parse(@xml.root, scope)
  xml
end

#parse(node, scope) ⇒ Object



103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
# File 'lib/ragtag.rb', line 103

def parse(node, scope)
  case node
  when Nokogiri::XML::Text
    # nothing
  when Nokogiri::XML::NodeSet
    parse_nodeset(node, scope)
  when Nokogiri::XML::Element
    if value = node['define']
      eval(value, scope)
    end

    if node['if']
      parse_if(node, scope)
    #elsif node['condition']
    #  parse_condition(node, scope)
    end

    if node['content']
      parse_content(node, scope)
    elsif node['replace']
      parse_replace(node, scope)
    end

    if node['attr'] || node['attributes']
      parse_attributes(node, scope)
    end

    if node['each']
      parse_each(node, scope)
      return
    elsif node['repeat']
      parse_repeat(node, scope)
      return
    end

    node.children.each do |child|
      parse(child, scope)
    end

    if node['omit'] && node['omit'] != 'false'
      parse_omit(node, scope)
    end
  else
    raise node.inspect
  end
  return node
end

#parse_attributes(node, scope) ⇒ Object



174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
# File 'lib/ragtag.rb', line 174

def parse_attributes(node, scope)
  if attrs = node['attr']
    assoc = attrs.split(',').map{ |e| e.strip.split(':') }
    assoc.each do |(k,v)|
      node[k] = eval(v, scope).to_s
    end
    node.remove_attribute('attr')
  end
  if attrs = node['attributes']
    assoc = attrs.split(',').map{ |e| e.strip.split(':') }
    assoc.each do |(k,v)|
      node[k] = eval(v, scope).to_s
    end
    node.remove_attribute('attributes')
  end
  node
end

#parse_content(node, scope) ⇒ Object



160
161
162
163
164
# File 'lib/ragtag.rb', line 160

def parse_content(node, scope)
  value = node['content']
  node.content = eval(value, scope)
  node.remove_attribute('content')
end

#parse_each(node, scope) ⇒ Object



219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
# File 'lib/ragtag.rb', line 219

def parse_each(node, scope)
  value = node['each']
  args  = node['do'] || 'x'
  copy  = node.dup
  node.children.remove
  bindings = eval("#{value}.map{ |#{args}| binding }", scope)
  bindings.each do |each_scope|
    sect = parse(copy.dup.children, each_scope)
    sect.each do |x|
      node << x
    end
  end
  node.remove_attribute('each')
  node.remove_attribute('do')
  value
end

#parse_if(node, scope) ⇒ Object



193
194
195
196
197
198
199
200
201
202
# File 'lib/ragtag.rb', line 193

def parse_if(node, scope)
  value = node['if']
  if eval(value, scope)
    node.remove_attribute('if')
    parse(node.children, scope)
  else
    node.unlink
  end
  node
end

#parse_nodeset(nodeset, scope) ⇒ Object



152
153
154
155
156
157
# File 'lib/ragtag.rb', line 152

def parse_nodeset(nodeset, scope)
  nodeset.each do |node|
    parse(node, scope)
  end
  nodeset
end

#parse_omit(node, scope) ⇒ Object



254
255
256
257
258
259
260
261
# File 'lib/ragtag.rb', line 254

def parse_omit(node, scope)
  #parse(node.children, scope).each do |x|
  node.children.each do |x|
    x.unlink
    node.add_previous_sibling(x) 
  end
  node.unlink
end

#parse_repeat(node, scope) ⇒ Object



237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
# File 'lib/ragtag.rb', line 237

def parse_repeat(node, scope)
  value = node['repeat']
  args  = node['do'] || 'x'
  copy  = node.dup
  copy.remove_attribute('repeat')
  copy.remove_attribute('do')
  bindings = eval("#{value}.map{ |#{args}| binding }", scope)
  bindings.each do |each_scope|
    sect = parse(copy.dup, each_scope)
    node.add_previous_sibling(sect)
    # parse_omit(sect, scope) if sect['omit'] && sect['omit'] != 'false'
  end
  node.unlink
  value
end

#parse_replace(node, scope) ⇒ Object



167
168
169
170
171
# File 'lib/ragtag.rb', line 167

def parse_replace(node, scope)
  value = node['replace']
  node.before(eval(value, scope).to_s)
  node.unlink
end