hobo 0.5.3 → 0.6
Sign up to get free protection for your applications and to get access to all the features.
- data/bin/hobo +18 -4
- data/hobo_files/plugin/CHANGES.txt +511 -0
- data/hobo_files/plugin/README +8 -3
- data/hobo_files/plugin/Rakefile +81 -0
- data/hobo_files/plugin/generators/hobo/hobo_generator.rb +4 -4
- data/hobo_files/plugin/generators/hobo/templates/guest.rb +1 -1
- data/hobo_files/plugin/generators/hobo_front_controller/hobo_front_controller_generator.rb +1 -1
- data/hobo_files/plugin/generators/hobo_front_controller/templates/index.dryml +16 -22
- data/hobo_files/plugin/generators/hobo_front_controller/templates/login.dryml +4 -6
- data/hobo_files/plugin/generators/hobo_front_controller/templates/search.dryml +6 -5
- data/hobo_files/plugin/generators/hobo_front_controller/templates/signup.dryml +4 -6
- data/hobo_files/plugin/generators/hobo_migration/hobo_migration_generator.rb +237 -0
- data/hobo_files/plugin/generators/hobo_migration/templates/migration.rb +9 -0
- data/hobo_files/plugin/generators/hobo_model/USAGE +2 -3
- data/hobo_files/plugin/generators/hobo_model/hobo_model_generator.rb +1 -14
- data/hobo_files/plugin/generators/hobo_model/templates/fixtures.yml +1 -6
- data/hobo_files/plugin/generators/hobo_model/templates/model.rb +10 -4
- data/hobo_files/plugin/generators/hobo_rapid/hobo_rapid_generator.rb +7 -6
- data/hobo_files/plugin/generators/hobo_rapid/templates/hobo_base.css +68 -0
- data/hobo_files/plugin/generators/hobo_rapid/templates/hobo_rapid.css +93 -0
- data/hobo_files/plugin/generators/hobo_rapid/templates/hobo_rapid.js +11 -6
- data/hobo_files/plugin/generators/hobo_rapid/templates/themes/default/public/images/plus.png +0 -0
- data/hobo_files/plugin/generators/hobo_rapid/templates/themes/default/public/stylesheets/application.css +24 -14
- data/hobo_files/plugin/generators/hobo_rapid/templates/themes/default/views/application.dryml +28 -44
- data/hobo_files/plugin/generators/hobo_user_model/USAGE +2 -12
- data/hobo_files/plugin/generators/hobo_user_model/hobo_user_model_generator.rb +1 -14
- data/hobo_files/plugin/generators/hobo_user_model/templates/fixtures.yml +0 -6
- data/hobo_files/plugin/generators/hobo_user_model/templates/model.rb +8 -1
- data/hobo_files/plugin/init.rb +6 -2
- data/hobo_files/plugin/lib/active_record/has_many_association.rb +23 -12
- data/hobo_files/plugin/lib/extensions.rb +134 -40
- data/hobo_files/plugin/lib/extensions/test_case.rb +0 -1
- data/hobo_files/plugin/lib/hobo.rb +77 -46
- data/hobo_files/plugin/lib/hobo/authenticated_user.rb +24 -2
- data/hobo_files/plugin/lib/hobo/authentication_support.rb +2 -1
- data/hobo_files/plugin/lib/hobo/controller.rb +35 -12
- data/hobo_files/plugin/lib/hobo/define_tags.rb +4 -4
- data/hobo_files/plugin/lib/hobo/dryml.rb +33 -51
- data/hobo_files/plugin/lib/hobo/dryml/dryml_builder.rb +47 -34
- data/hobo_files/plugin/lib/hobo/dryml/scoped_variables.rb +37 -0
- data/hobo_files/plugin/lib/hobo/dryml/taglib.rb +27 -5
- data/hobo_files/plugin/lib/hobo/dryml/template.rb +545 -302
- data/hobo_files/plugin/lib/hobo/dryml/template_environment.rb +305 -135
- data/hobo_files/plugin/lib/hobo/email_address.rb +5 -0
- data/hobo_files/plugin/lib/hobo/field_spec.rb +66 -0
- data/hobo_files/plugin/lib/hobo/hobo_helper.rb +325 -0
- data/hobo_files/plugin/lib/hobo/html_string.rb +2 -0
- data/hobo_files/plugin/lib/hobo/lazy_hash.rb +13 -1
- data/hobo_files/plugin/lib/hobo/markdown_string.rb +3 -1
- data/hobo_files/plugin/lib/hobo/model.rb +185 -66
- data/hobo_files/plugin/lib/hobo/model_controller.rb +56 -49
- data/hobo_files/plugin/lib/hobo/password_string.rb +2 -0
- data/hobo_files/plugin/lib/hobo/plugins.rb +75 -0
- data/hobo_files/plugin/lib/hobo/rapid_helper.rb +98 -0
- data/hobo_files/plugin/lib/hobo/static_tags +0 -3
- data/hobo_files/plugin/lib/hobo/textile_string.rb +11 -1
- data/hobo_files/plugin/lib/hobo/undefined.rb +1 -1
- data/hobo_files/plugin/lib/rexml.rb +166 -75
- data/hobo_files/plugin/spec/fixtures/users.yml +9 -0
- data/hobo_files/plugin/spec/spec.opts +6 -0
- data/hobo_files/plugin/spec/spec_helper.rb +28 -0
- data/hobo_files/plugin/spec/unit/hobo/dryml/template_spec.rb +650 -0
- data/hobo_files/plugin/tags/core.dryml +58 -4
- data/hobo_files/plugin/tags/rapid.dryml +289 -135
- data/hobo_files/plugin/tags/rapid_document_tags.dryml +49 -0
- data/hobo_files/plugin/tags/rapid_editing.dryml +92 -69
- data/hobo_files/plugin/tags/rapid_forms.dryml +242 -0
- data/hobo_files/plugin/tags/rapid_navigation.dryml +65 -65
- data/hobo_files/plugin/tags/rapid_pages.dryml +197 -124
- data/hobo_files/plugin/tags/rapid_support.dryml +23 -0
- metadata +29 -22
- data/hobo_files/plugin/generators/hobo_model/templates/migration.rb +0 -13
- data/hobo_files/plugin/generators/hobo_rapid/templates/themes/default/default_mapping.rb +0 -11
- data/hobo_files/plugin/generators/hobo_user_model/templates/migration.rb +0 -15
- data/hobo_files/plugin/lib/hobo/HtmlString +0 -3
- data/hobo_files/plugin/lib/hobo/controller_helpers.rb +0 -135
- data/hobo_files/plugin/lib/hobo/core.rb +0 -475
- data/hobo_files/plugin/lib/hobo/rapid.rb +0 -447
- data/hobo_files/plugin/test/hobo_dryml_template_test.rb +0 -7
- data/hobo_files/plugin/test/hobo_test.rb +0 -7
@@ -0,0 +1,37 @@
|
|
1
|
+
module Hobo::Dryml
|
2
|
+
|
3
|
+
class ScopedVariables
|
4
|
+
|
5
|
+
def initialize
|
6
|
+
@scopes = [{}]
|
7
|
+
end
|
8
|
+
|
9
|
+
def [](key)
|
10
|
+
@scopes.reverse_each do |s|
|
11
|
+
return s[key] if s.has_key?(key)
|
12
|
+
end
|
13
|
+
nil
|
14
|
+
end
|
15
|
+
|
16
|
+
def []=(key, val)
|
17
|
+
@scopes.last[key] = val
|
18
|
+
end
|
19
|
+
|
20
|
+
def new_scope
|
21
|
+
@scopes << {}
|
22
|
+
res = yield
|
23
|
+
@scopes.pop
|
24
|
+
res
|
25
|
+
end
|
26
|
+
|
27
|
+
def method_missing(name, *args)
|
28
|
+
if name.to_s =~ /=$/
|
29
|
+
self[name.to_s[0..-2].to_sym] = args.first
|
30
|
+
else
|
31
|
+
self[name]
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
end
|
36
|
+
|
37
|
+
end
|
@@ -36,20 +36,42 @@ module Hobo::Dryml
|
|
36
36
|
end
|
37
37
|
|
38
38
|
def load
|
39
|
-
@module = Module.new
|
39
|
+
@module = Module.new do
|
40
|
+
@tag_attrs = {}
|
41
|
+
def self._register_tag_attrs(tag_name, attrs)
|
42
|
+
@tag_attrs[tag_name] = attrs
|
43
|
+
end
|
44
|
+
class << self
|
45
|
+
attr_reader :tag_attrs
|
46
|
+
end
|
47
|
+
end
|
40
48
|
@file.rewind
|
41
49
|
template = Template.new(@file.read, @module, @file.path)
|
42
|
-
template.compile([],
|
50
|
+
template.compile([], [])
|
43
51
|
@last_load_time = @file.mtime
|
44
52
|
end
|
45
53
|
|
46
54
|
def import_into(class_or_module, as)
|
47
55
|
if as
|
48
|
-
|
49
|
-
|
50
|
-
|
56
|
+
# Define a method on class_or_module named whatever 'as'
|
57
|
+
# is. The first time the method is called it creates and
|
58
|
+
# returns an object that provides the taglib's tags as
|
59
|
+
# methods. On subsequent calls the object is cached in an
|
60
|
+
# instance variable "@_#{as}_taglib"
|
61
|
+
|
62
|
+
taglib_module = @module
|
63
|
+
ivar = "@_#{as}_taglib"
|
64
|
+
class_or_module.send(:define_method, as) do
|
65
|
+
instance_variable_get(ivar) or begin
|
66
|
+
as_class = Class.new(TemplateEnvironment) { include taglib_module }
|
67
|
+
as_object = as_class.new
|
68
|
+
as_object.copy_instance_variables_from(self)
|
69
|
+
instance_variable_set(ivar, as_object)
|
70
|
+
end
|
71
|
+
end
|
51
72
|
else
|
52
73
|
class_or_module.send(:include, @module)
|
74
|
+
class_or_module.tag_attrs.update(@module.tag_attrs) if @module.respond_to?(:tag_attrs)
|
53
75
|
end
|
54
76
|
end
|
55
77
|
|
@@ -5,6 +5,11 @@ module Hobo::Dryml
|
|
5
5
|
class Template
|
6
6
|
|
7
7
|
DRYML_NAME = "[a-zA-Z_][a-zA-Z0-9_]*"
|
8
|
+
DRYML_NAME_RX = /^#{DRYML_NAME}$/
|
9
|
+
|
10
|
+
CODE_ATTRIBUTE_CHAR = "&"
|
11
|
+
|
12
|
+
SPECIAL_ATTRIBUTES = %w(param merge_attrs for_type if unless repeat part restore)
|
8
13
|
|
9
14
|
@build_cache = {}
|
10
15
|
|
@@ -20,7 +25,6 @@ module Hobo::Dryml
|
|
20
25
|
@src = src
|
21
26
|
|
22
27
|
@environment = environment # a class or a module
|
23
|
-
@environment.send(:include, Hobo::PredicateDispatch)
|
24
28
|
|
25
29
|
@template_path = template_path.sub(/^#{Regexp.escape(RAILS_ROOT)}/, "")
|
26
30
|
|
@@ -32,12 +36,12 @@ module Hobo::Dryml
|
|
32
36
|
|
33
37
|
attr_reader :tags, :template_path
|
34
38
|
|
35
|
-
def compile(local_names=[], auto_taglibs=
|
39
|
+
def compile(local_names=[], auto_taglibs=[])
|
36
40
|
now = Time.now
|
37
41
|
|
38
42
|
unless @template_path == EMPTY_PAGE
|
39
43
|
filename = RAILS_ROOT + (@template_path.starts_with?("/") ? @template_path : "/" + @template_path)
|
40
|
-
mtime = File.stat(filename).mtime
|
44
|
+
mtime = File.stat(filename).mtime rescue nil
|
41
45
|
end
|
42
46
|
|
43
47
|
if mtime.nil? || !@builder.ready?(mtime)
|
@@ -61,12 +65,10 @@ module Hobo::Dryml
|
|
61
65
|
logger.info(" DRYML: Compiled#{from_cache} #{template_path} in %.2fs" % (Time.now - now))
|
62
66
|
end
|
63
67
|
|
68
|
+
|
64
69
|
def create_render_page_method
|
65
70
|
erb_src = process_src
|
66
|
-
|
67
|
-
src = ERB.new(erb_src).src[("_erbout = '';").length..-1]
|
68
|
-
|
69
|
-
@builder.add_build_instruction(:type => :render_page, :src => src, :line_num => 1)
|
71
|
+
@builder.add_build_instruction(:render_page, :src => erb_src, :line_num => 1)
|
70
72
|
end
|
71
73
|
|
72
74
|
|
@@ -87,29 +89,26 @@ module Hobo::Dryml
|
|
87
89
|
end
|
88
90
|
|
89
91
|
@xmlsrc = "<dryml_page>" + src + "</dryml_page>"
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
92
|
+
begin
|
93
|
+
@doc = REXML::Document.new(RexSource.new(@xmlsrc), :dryml_mode => true)
|
94
|
+
rescue REXML::ParseException => e
|
95
|
+
raise DrymlSyntaxError, "File: #{@template_path}\n#{e}"
|
96
|
+
end
|
97
|
+
@doc.default_attribute_value = "&true"
|
94
98
|
|
95
|
-
|
99
|
+
restore_erb_scriptlets(children_to_erb(@doc.root))
|
96
100
|
end
|
97
101
|
|
98
102
|
|
99
103
|
def restore_erb_scriptlets(src)
|
100
|
-
src.gsub(/\[!\[HOBO-ERB(\d+)\s*\]!\]/m) { "<%#{@scriptlets[
|
101
|
-
end
|
102
|
-
|
103
|
-
|
104
|
-
def erb_process(src)
|
105
|
-
ERB.new(restore_erb_scriptlets(src)).src
|
104
|
+
src.gsub(/\[!\[HOBO-ERB(\d+)\s*\]!\]/m) {|s| "<%#{@scriptlets[$1.to_i]}%>" }
|
106
105
|
end
|
107
106
|
|
108
|
-
|
107
|
+
|
109
108
|
def children_to_erb(nodes)
|
110
109
|
nodes.map{|x| node_to_erb(x)}.join
|
111
110
|
end
|
112
|
-
|
111
|
+
|
113
112
|
|
114
113
|
def node_to_erb(node)
|
115
114
|
case node
|
@@ -131,50 +130,62 @@ module Hobo::Dryml
|
|
131
130
|
|
132
131
|
|
133
132
|
def element_to_erb(el)
|
134
|
-
dryml_exception("
|
133
|
+
dryml_exception("parameter tags (<#{el.name}>) are no more, wake up and smell the coffee", el) if
|
135
134
|
el.name.starts_with?(":")
|
136
135
|
|
137
136
|
@last_element = el
|
138
|
-
case el.
|
137
|
+
case el.dryml_name
|
139
138
|
|
140
|
-
when "
|
141
|
-
|
142
|
-
# return nothing - the
|
139
|
+
when "include"
|
140
|
+
include_element(el)
|
141
|
+
# return nothing - the include has no presence in the erb source
|
143
142
|
tag_newlines(el)
|
144
143
|
|
145
144
|
when "set_theme"
|
146
145
|
require_attribute(el, "name", /^#{DRYML_NAME}$/)
|
147
|
-
@builder.add_build_instruction(:
|
146
|
+
@builder.add_build_instruction(:set_theme, :name => el.attributes['name'])
|
148
147
|
|
149
148
|
# return nothing - set_theme has no presence in the erb source
|
150
149
|
tag_newlines(el)
|
151
150
|
|
152
151
|
when "def"
|
153
152
|
def_element(el)
|
154
|
-
|
153
|
+
|
155
154
|
when "tagbody"
|
156
155
|
tagbody_element(el)
|
157
|
-
|
156
|
+
|
157
|
+
when "set"
|
158
|
+
set_element(el)
|
159
|
+
|
160
|
+
when "set_scoped"
|
161
|
+
set_scoped_element(el)
|
162
|
+
|
163
|
+
when "default_tagbody"
|
164
|
+
default_tagbody_element(el)
|
165
|
+
|
158
166
|
else
|
159
|
-
if el.
|
160
|
-
|
161
|
-
|
167
|
+
if el.dryml_name.not_in?(Hobo.static_tags) || el.attributes['param'] || el.attributes['restore']
|
168
|
+
if el.dryml_name =~ /^[A-Z]/
|
169
|
+
template_call(el)
|
170
|
+
else
|
171
|
+
tag_call(el)
|
172
|
+
end
|
162
173
|
else
|
163
|
-
|
174
|
+
static_element_to_erb(el)
|
164
175
|
end
|
165
176
|
end
|
166
177
|
end
|
167
178
|
|
168
179
|
|
169
|
-
def
|
180
|
+
def include_element(el)
|
170
181
|
require_toplevel(el)
|
171
182
|
require_attribute(el, "as", /^#{DRYML_NAME}$/, true)
|
172
183
|
if el.attributes["src"]
|
173
|
-
@builder.add_build_instruction(:
|
184
|
+
@builder.add_build_instruction(:include,
|
174
185
|
:name => el.attributes["src"],
|
175
186
|
:as => el.attributes["as"])
|
176
187
|
elsif el.attributes["module"]
|
177
|
-
@builder.add_build_instruction(:
|
188
|
+
@builder.add_build_instruction(:module,
|
178
189
|
:name => el.attributes["module"],
|
179
190
|
:as => el.attributes["as"])
|
180
191
|
end
|
@@ -186,353 +197,578 @@ module Hobo::Dryml
|
|
186
197
|
end
|
187
198
|
|
188
199
|
|
200
|
+
def set_element(el)
|
201
|
+
assigns = el.attributes.map do |name, value|
|
202
|
+
dryml_exception(el, "invalid name in set") unless name =~ DRYML_NAME_RX
|
203
|
+
"#{name} = #{attribute_to_ruby(value)}; "
|
204
|
+
end.join
|
205
|
+
"<% #{assigns}#{tag_newlines(el)} %>"
|
206
|
+
end
|
207
|
+
|
208
|
+
|
209
|
+
def set_scoped_element(el)
|
210
|
+
assigns = el.attributes.map do |name, value|
|
211
|
+
dryml_exception(el, "invalid name in set_scoped") unless name =~ DRYML_NAME_RX
|
212
|
+
"scope[:#{name}] = #{attribute_to_ruby(value)}; "
|
213
|
+
end.join
|
214
|
+
"<% scope.new_scope { #{assigns}#{tag_newlines(el)} %>#{children_to_erb(el)}<% } %>"
|
215
|
+
end
|
216
|
+
|
217
|
+
|
218
|
+
def declared_attributes(def_element)
|
219
|
+
attrspec = def_element.attributes["attrs"]
|
220
|
+
attr_names = attrspec ? attrspec.split(/\s*,\s*/).every(:to_sym) : []
|
221
|
+
invalids = attr_names & ([:with, :field, :this] + SPECIAL_ATTRIBUTES.every(:to_sym))
|
222
|
+
dryml_exception("invalid attrs in def: #{invalids * ', '}", def_element) unless invalids.empty?
|
223
|
+
attr_names
|
224
|
+
end
|
225
|
+
|
226
|
+
|
189
227
|
def def_element(el)
|
190
228
|
require_toplevel(el)
|
191
|
-
require_attribute(el, "tag",
|
229
|
+
require_attribute(el, "tag", DRYML_NAME_RX)
|
192
230
|
require_attribute(el, "attrs", /^\s*#{DRYML_NAME}(\s*,\s*#{DRYML_NAME})*\s*$/, true)
|
193
|
-
require_attribute(el, "alias_of",
|
194
|
-
require_attribute(el, "
|
195
|
-
|
196
|
-
|
197
|
-
name =
|
231
|
+
require_attribute(el, "alias_of", DRYML_NAME_RX, true)
|
232
|
+
require_attribute(el, "alias_current_as", DRYML_NAME_RX, true)
|
233
|
+
|
234
|
+
unsafe_name = el.attributes["tag"]
|
235
|
+
name = Hobo::Dryml.unreserve(unsafe_name)
|
236
|
+
if (for_type = el.attributes['for'])
|
237
|
+
type_name = if for_type =~ /^[a-z]/
|
238
|
+
# It's a symbolic type name - look up the Ruby type name
|
239
|
+
Hobo.field_types[for_type].name
|
240
|
+
else
|
241
|
+
for_type
|
242
|
+
end.underscore.gsub('/', '__')
|
243
|
+
suffix = "__for_#{type_name}"
|
244
|
+
name += suffix
|
245
|
+
unsafe_name += suffix
|
246
|
+
end
|
247
|
+
|
248
|
+
# While processing this def, @def_name contains
|
249
|
+
# the names of all nested defs join with '_'. It's used to
|
250
|
+
# disambiguate local variables as a workaround for the broken
|
251
|
+
# scope semantics of Ruby 1.8.
|
252
|
+
old_def_name = @def_name
|
253
|
+
@def_name = @def_name ? "#{@def_name}_#{unsafe_name}" : unsafe_name
|
198
254
|
|
199
255
|
alias_of = el.attributes['alias_of']
|
200
|
-
alias_current = el.attributes['
|
256
|
+
alias_current = el.attributes['alias_current_as']
|
201
257
|
|
202
|
-
dryml_exception("def cannot have both alias_of and
|
258
|
+
dryml_exception("def cannot have both alias_of and alias_current_as", el) if alias_of && alias_current
|
203
259
|
dryml_exception("def with alias_of must be empty", el) if alias_of and el.size > 0
|
204
|
-
|
260
|
+
|
261
|
+
# If we're redefining, we need a statement in the method body
|
262
|
+
# that does the alias_method on the fly.
|
263
|
+
re_alias = ""
|
264
|
+
|
205
265
|
if alias_of || alias_current
|
206
266
|
old_name = alias_current ? name : alias_of
|
207
267
|
new_name = alias_current ? alias_current : name
|
208
268
|
|
209
|
-
@builder.add_build_instruction(:
|
269
|
+
@builder.add_build_instruction(:alias_method, :new => new_name.to_sym, :old => old_name.to_sym)
|
210
270
|
end
|
211
|
-
|
212
|
-
if alias_of
|
213
|
-
"<% #{tag_newlines(el)} %>"
|
214
|
-
else
|
215
|
-
attrspec = el.attributes["attrs"]
|
216
|
-
attr_names = attrspec ? attrspec.split(/\s*,\s*/) : []
|
217
|
-
|
218
|
-
invalids = attr_names & %w{obj attr this}
|
219
|
-
dryml_exception("invalid attrs in def: #{invalids * ', '}", el) unless invalids.empty?
|
220
|
-
|
221
|
-
create_tag_method(el, name.to_sym, attr_names.omap{to_sym})
|
222
|
-
end
|
223
|
-
end
|
224
|
-
|
225
|
-
|
226
|
-
def create_tag_method(el, name, attrs)
|
227
|
-
name = Hobo::Dryml.unreserve(name)
|
228
271
|
|
229
|
-
|
230
|
-
|
231
|
-
|
232
|
-
|
233
|
-
|
234
|
-
|
235
|
-
|
272
|
+
res = if alias_of
|
273
|
+
"#{re_alias}<% #{tag_newlines(el)} %>"
|
274
|
+
else
|
275
|
+
src = if template_name?(name)
|
276
|
+
template_method(name, re_alias, el)
|
277
|
+
else
|
278
|
+
tag_method(name, re_alias, el)
|
279
|
+
end
|
280
|
+
src << "<% _register_tag_attrs(:#{name}, #{declared_attributes(el).inspect}) %>"
|
281
|
+
|
282
|
+
logger.debug(restore_erb_scriptlets(src)) if el.attributes["debug_source"]
|
283
|
+
|
284
|
+
@builder.add_build_instruction(:def,
|
285
|
+
:src => restore_erb_scriptlets(src),
|
286
|
+
:line_num => element_line_num(el))
|
287
|
+
# keep line numbers matching up
|
288
|
+
"<% #{"\n" * src.count("\n")} %>"
|
289
|
+
end
|
290
|
+
@def_name = old_def_name
|
291
|
+
res
|
292
|
+
end
|
293
|
+
|
294
|
+
|
295
|
+
def template_call?(el)
|
296
|
+
template_name?(el.name)
|
297
|
+
end
|
298
|
+
|
299
|
+
|
300
|
+
def template_name?(name)
|
301
|
+
name =~ /^[A-Z]/
|
302
|
+
end
|
303
|
+
|
304
|
+
|
305
|
+
def param_names_in_template(el)
|
306
|
+
REXML::XPath.match(el, ".//*[@param]").map do |e|
|
307
|
+
name = get_param_name(e)
|
308
|
+
dryml_exception("invalid param name: #{name.inspect}", e) unless
|
309
|
+
is_code_attribute?(name) || name =~ DRYML_NAME_RX || name =~ /#\{/
|
310
|
+
name.to_sym unless is_code_attribute?(name)
|
311
|
+
end.compact
|
312
|
+
end
|
313
|
+
|
314
|
+
|
315
|
+
def template_method(name, re_alias, el)
|
316
|
+
param_names = param_names_in_template(el)
|
236
317
|
|
237
|
-
|
238
|
-
|
318
|
+
"<% def #{name}(all_attributes={}, all_parameters={}, &__block__); " +
|
319
|
+
"parameters = all_parameters - #{param_names.inspect}; " +
|
320
|
+
tag_method_body(el) +
|
321
|
+
"; end %>"
|
322
|
+
end
|
323
|
+
|
324
|
+
|
325
|
+
def tag_method(name, re_alias, el)
|
326
|
+
"<% def #{name}(all_attributes={}, &__block__); " +
|
327
|
+
"parameters = nil; " +
|
328
|
+
tag_method_body(el) +
|
329
|
+
"; end %>"
|
330
|
+
end
|
331
|
+
|
332
|
+
|
333
|
+
def tag_method_body(el, attributes_var="all_attributes", block_var="__block__")
|
334
|
+
attrs = declared_attributes(el)
|
239
335
|
|
240
|
-
|
241
|
-
|
242
|
-
|
243
|
-
|
244
|
-
"defp :#{name} do |__options__, __block__|"
|
245
|
-
else
|
246
|
-
"def #{name}(__options__={}, &__block__)"
|
247
|
-
end
|
336
|
+
# A statement to assign values to local variables named after the tag's attrs
|
337
|
+
# The trailing comma on `attributes` is supposed to be there!
|
338
|
+
setup_locals = attrs.map{|a| "#{Hobo::Dryml.unreserve(a)}, "}.join + "attributes, = " +
|
339
|
+
"_tag_locals(#{attributes_var}, #{attrs.inspect})"
|
248
340
|
|
249
|
-
|
250
|
-
# reproduce any line breaks in the start-tag so that line numbers are preserved
|
251
|
-
tag_newlines(el) + "%>" +
|
252
|
-
children_to_erb(el) +
|
253
|
-
"<% @output; end; end %>" )
|
341
|
+
start = "_tag_context(#{attributes_var}, #{block_var}) do |tagbody| #{setup_locals}"
|
254
342
|
|
255
|
-
|
256
|
-
|
257
|
-
|
258
|
-
|
259
|
-
|
260
|
-
"<% #{"\n" * method_src.count("\n")} %>"
|
343
|
+
"#{start} " +
|
344
|
+
# reproduce any line breaks in the start-tag so that line numbers are preserved
|
345
|
+
tag_newlines(el) + "%>" +
|
346
|
+
children_to_erb(el) +
|
347
|
+
"<% _erbout; end"
|
261
348
|
end
|
262
349
|
|
263
350
|
|
264
|
-
def find_inner_tags(el)
|
265
|
-
el.map do |e|
|
266
|
-
if e.is_a?(REXML::Element)
|
267
|
-
name = e.attributes["content_option"] || e.attributes["replace_option"]
|
268
|
-
[(name if name && !is_code_attribute?(name))] + find_inner_tags(e)
|
269
|
-
else
|
270
|
-
[]
|
271
|
-
end
|
272
|
-
end.flatten.compact
|
273
|
-
end
|
274
|
-
|
275
|
-
|
276
351
|
def tagbody_element(el)
|
277
352
|
dryml_exception("tagbody can only appear inside a <def>", el) unless
|
278
353
|
find_ancestor(el) {|e| e.name == 'def'}
|
279
354
|
dryml_exception("tagbody cannot appear inside a part", el) if
|
280
|
-
find_ancestor(el) {|e| e.attributes['
|
355
|
+
find_ancestor(el) {|e| e.attributes['part']}
|
281
356
|
tagbody_call(el)
|
282
357
|
end
|
283
|
-
|
358
|
+
|
284
359
|
|
285
360
|
def tagbody_call(el)
|
286
|
-
|
287
|
-
|
288
|
-
|
289
|
-
|
290
|
-
|
291
|
-
|
292
|
-
|
361
|
+
attributes = []
|
362
|
+
with = el.attributes['with']
|
363
|
+
field = el.attributes['field']
|
364
|
+
attributes << ":with => #{attribute_to_ruby(with)}" if with
|
365
|
+
attributes << ":field => #{attribute_to_ruby(field)}" if field
|
366
|
+
|
367
|
+
default_body = if el.children.empty?
|
368
|
+
"nil"
|
369
|
+
else
|
370
|
+
"proc { %>#{children_to_erb(el)}<% }"
|
371
|
+
end
|
372
|
+
|
373
|
+
call = apply_control_attributes("do_tagbody(tagbody, {#{attributes * ', '}}, #{default_body})", el)
|
374
|
+
"<% _output(#{call}) %>"
|
375
|
+
end
|
376
|
+
|
377
|
+
|
378
|
+
def default_tagbody_element(el)
|
379
|
+
name = el.attributes['for'] || @containing_tag_name
|
380
|
+
"<% #{name}_default_tagbody && #{name}_default_tagbody.call %>"
|
293
381
|
end
|
294
382
|
|
295
383
|
|
296
384
|
def part_element(el, content)
|
297
|
-
require_attribute(el, "
|
298
|
-
part_name = el.attributes['
|
385
|
+
require_attribute(el, "part", DRYML_NAME_RX)
|
386
|
+
part_name = el.attributes['part']
|
299
387
|
dom_id = el.attributes['id'] || part_name
|
300
388
|
|
301
|
-
dryml_exception("dupplicate part name: #{part_name}", el) if
|
302
|
-
(part_name + "_part").in?(@environment.instance_methods)
|
303
|
-
|
304
389
|
part_src = "<% def #{part_name}_part #{tag_newlines(el)}; new_context do %>" +
|
305
390
|
content +
|
306
391
|
"<% end; end %>"
|
307
|
-
|
392
|
+
@builder.add_part(part_name, restore_erb_scriptlets(part_src), element_line_num(el))
|
308
393
|
|
309
394
|
newlines = "\n" * part_src.count("\n")
|
310
|
-
|
395
|
+
"<%= call_part(#{attribute_to_ruby(dom_id)}, :#{part_name}) #{newlines} %>"
|
396
|
+
end
|
397
|
+
|
398
|
+
|
399
|
+
def get_param_name(el)
|
400
|
+
param_name = el.attributes["param"]
|
401
|
+
|
402
|
+
if param_name
|
403
|
+
def_tag = find_ancestor(el) {|e| e.name == "def"}
|
404
|
+
dryml_exception("param is not allowed outside of template definitions", el) if
|
405
|
+
def_tag.nil? || !template_name?(def_tag.attributes["tag"])
|
406
|
+
end
|
407
|
+
|
408
|
+
res = param_name == "&true" ? el.dryml_name : param_name
|
409
|
+
|
410
|
+
dryml_exception("param name for a template call must be capitalised", el) if
|
411
|
+
res && template_call?(el) && !template_name?(res)
|
412
|
+
dryml_exception("param name for a block-tag call must not be capitalised", el) if
|
413
|
+
res && !template_call?(el) && template_name?(res)
|
414
|
+
|
311
415
|
res
|
312
416
|
end
|
417
|
+
|
418
|
+
|
419
|
+
def call_name(el)
|
420
|
+
Hobo::Dryml.unreserve(el.dryml_name)
|
421
|
+
end
|
422
|
+
|
423
|
+
|
424
|
+
def polymorphic_call_type(el)
|
425
|
+
t = el.attributes['for_type']
|
426
|
+
if t.nil?
|
427
|
+
nil
|
428
|
+
elsif t == "&true"
|
429
|
+
'this_type'
|
430
|
+
elsif t =~ /^[A-Z]/
|
431
|
+
t
|
432
|
+
elsif t =~ /^[a-z]/
|
433
|
+
"Hobo.field_types[:#{t}]"
|
434
|
+
elsif is_code_attribute?(t)
|
435
|
+
t[1..-1]
|
436
|
+
else
|
437
|
+
dryml_exception("invalid for_type attribute", el)
|
438
|
+
end
|
439
|
+
end
|
440
|
+
|
441
|
+
|
442
|
+
def template_call(el)
|
443
|
+
name = call_name(el)
|
444
|
+
param_name = get_param_name(el)
|
445
|
+
attributes = tag_attributes(el)
|
446
|
+
newlines = tag_newlines(el)
|
447
|
+
|
448
|
+
parameters = tag_newlines(el) + tag_parameters(el)
|
449
|
+
|
450
|
+
is_param_default_call = el.attributes['restore']
|
451
|
+
|
452
|
+
call = if param_name
|
453
|
+
param_name = attribute_to_ruby(param_name, :symbolize => true)
|
454
|
+
args = "#{attributes}, #{parameters}, all_parameters[#{param_name}]"
|
455
|
+
to_call = if is_param_default_call
|
456
|
+
# The tag is available in a local variable
|
457
|
+
# holding a proc
|
458
|
+
el.dryml_name
|
459
|
+
elsif (call_type = polymorphic_call_type(el))
|
460
|
+
"find_polymorphic_template(:#{name}, #{call_type})"
|
461
|
+
else
|
462
|
+
":#{name}"
|
463
|
+
end
|
464
|
+
"call_template_parameter(#{to_call}, #{args})"
|
465
|
+
else
|
466
|
+
if is_param_default_call
|
467
|
+
# The tag is a proc available in a local variable
|
468
|
+
"#{name}__default.call(#{attributes}, #{parameters})"
|
469
|
+
elsif (call_type = polymorphic_call_type(el))
|
470
|
+
"send(find_polymorphic_template(:#{name}, #{call_type}), #{attributes}, #{parameters})"
|
471
|
+
else
|
472
|
+
"#{name}(#{attributes}, #{parameters})"
|
473
|
+
end
|
474
|
+
end
|
313
475
|
|
476
|
+
call = apply_control_attributes(call, el)
|
477
|
+
maybe_make_part_call(el, "<% _output(#{call}) %>")
|
478
|
+
end
|
479
|
+
|
480
|
+
|
481
|
+
def merge_attribute(el)
|
482
|
+
merge = el.attributes['merge']
|
483
|
+
dryml_exception("merge cannot have a RHS", el) if merge && merge != "&true"
|
484
|
+
merge
|
485
|
+
end
|
486
|
+
|
314
487
|
|
315
|
-
|
316
|
-
|
488
|
+
# Tag parameters are parameters to templates.
|
489
|
+
def tag_parameters(el)
|
490
|
+
dryml_exception("content is not allowed directly inside template calls", el) if
|
491
|
+
el.children.find { |e| e.is_a?(REXML::Text) && !e.to_s.blank? }
|
317
492
|
|
318
|
-
|
493
|
+
param_items = el.map do |node|
|
494
|
+
case node
|
495
|
+
when REXML::Text
|
496
|
+
# Just whitespace
|
497
|
+
node.to_s
|
498
|
+
when REXML::Element
|
499
|
+
e = node
|
500
|
+
param_name = get_param_name(e)
|
501
|
+
if param_name
|
502
|
+
if template_call?(e)
|
503
|
+
":#{e.name} => merge_template_parameter_procs(#{template_proc(e)}, all_parameters[:#{param_name}]), "
|
504
|
+
else
|
505
|
+
":#{e.name} => merge_option_procs(#{template_proc(e)}, all_parameters[:#{param_name}]), "
|
506
|
+
end
|
507
|
+
else
|
508
|
+
":#{e.name} => #{template_proc(e)}, "
|
509
|
+
end
|
510
|
+
end
|
511
|
+
end.join
|
512
|
+
|
513
|
+
|
514
|
+
merge_params = el.attributes['merge_params'] || merge_attribute(el)
|
515
|
+
if merge_params
|
516
|
+
extra_params = if merge_params == "&true"
|
517
|
+
"parameters"
|
518
|
+
elsif is_code_attribute?(merge_params)
|
519
|
+
merge_params[1..-1]
|
520
|
+
else
|
521
|
+
dryml_exception("invalid merge_params", el)
|
522
|
+
end
|
523
|
+
"{#{param_items}}.merge((#{extra_params}) || {})"
|
524
|
+
else
|
525
|
+
"{#{param_items}}"
|
526
|
+
end
|
319
527
|
end
|
320
528
|
|
321
529
|
|
322
|
-
def
|
323
|
-
|
324
|
-
|
325
|
-
|
326
|
-
|
327
|
-
empty_el = el.size == 0
|
328
|
-
|
329
|
-
# gather <:param_tags>, and remove them from the dom
|
330
|
-
compiled_param_tags = compile_parameter_tags(el)
|
530
|
+
def template_proc(el)
|
531
|
+
nl = tag_newlines(el)
|
532
|
+
if (repl = el.attribute("replace"))
|
533
|
+
dryml_exception("replace attribute must not have a value", el) if repl.has_rhs?
|
534
|
+
dryml_exception("replace parameters must not have attributes", el) if el.attributes.length > 1
|
331
535
|
|
332
|
-
|
333
|
-
options = tag_options(el, compiled_param_tags)
|
334
|
-
newlines = tag_newlines(el)
|
335
|
-
replace_option = el.attributes["replace_option"]
|
336
|
-
content_option = el.attributes["content_option"]
|
337
|
-
dryml_exception("both replace_option and content_option given") if replace_option && content_option
|
338
|
-
call = if replace_option
|
339
|
-
replace_option = attribute_to_ruby(replace_option)
|
340
|
-
"call_replaceable_tag(:#{name}, #{options}, inner_tag_options[#{replace_option}.to_sym])"
|
341
|
-
elsif content_option
|
342
|
-
content_option = attribute_to_ruby(content_option)
|
343
|
-
"call_replaceable_content_tag(:#{name}, #{options}, inner_tag_options[#{content_option}.to_sym])"
|
344
|
-
else
|
345
|
-
"#{name}(#{options})"
|
346
|
-
end
|
536
|
+
default_tag_name = "#{el.dryml_name}__default"
|
347
537
|
|
348
|
-
|
349
|
-
if empty_el
|
350
|
-
if part_id
|
351
|
-
"<span id='#{part_id}'>" + part_element(el, "<%= #{call} %>") + "</span>"
|
352
|
-
else
|
353
|
-
"<%= #{call} #{newlines}%>"
|
354
|
-
end
|
538
|
+
"proc {|#{default_tag_name}| new_context { %>#{children_to_erb(el)}<% } #{nl}}"
|
355
539
|
else
|
356
|
-
|
357
|
-
|
358
|
-
|
359
|
-
|
360
|
-
|
361
|
-
|
540
|
+
attributes = el.attributes.map do
|
541
|
+
|name, value| ":#{name} => #{attribute_to_ruby(value, el)}" unless name.in?(SPECIAL_ATTRIBUTES)
|
542
|
+
end.compact
|
543
|
+
|
544
|
+
if template_call?(el || modifiers.first)
|
545
|
+
parameters = el ? tag_parameters(el) : "{}"
|
546
|
+
"proc { [{#{attributes * ', '}}, #{parameters}] #{nl}}"
|
362
547
|
else
|
363
|
-
|
548
|
+
if el && el.has_end_tag?
|
549
|
+
old = @containing_tag_name
|
550
|
+
@containing_tag_name = el.dryml_name
|
551
|
+
body = children_to_erb(el)
|
552
|
+
@containing_tag_name = old
|
553
|
+
|
554
|
+
attributes << ":tagbody => proc {|#{el.dryml_name}_default_tagbody| new_context { %>#{body}<% } } "
|
555
|
+
end
|
556
|
+
"proc { {#{attributes * ', '}} #{nl}}"
|
364
557
|
end
|
365
558
|
end
|
366
559
|
end
|
367
560
|
|
368
561
|
|
369
|
-
def
|
370
|
-
|
371
|
-
|
372
|
-
|
373
|
-
|
374
|
-
last = el.children.reverse.ofind{is_a?(REXML::Element) && name.starts_with?(":")}
|
375
|
-
return "" if last.nil?
|
376
|
-
param_section = el.children[0..el.index(last)]
|
377
|
-
|
378
|
-
dryml_exception("invalid content before parameter tag", el) unless param_section.all? do |e|
|
379
|
-
(e.is_a?(REXML::Element) && e.name.starts_with?(":")) ||
|
380
|
-
(e.is_a?(REXML::Text) && e.to_s.blank?) ||
|
381
|
-
e.is_a?(REXML::Comment)
|
562
|
+
def default_tag_name(el)
|
563
|
+
if template_call?(el)
|
564
|
+
"Default#{el.dryml_name}"
|
565
|
+
else
|
566
|
+
"default_#{el.dryml_name}"
|
382
567
|
end
|
568
|
+
end
|
569
|
+
|
570
|
+
|
571
|
+
def tag_call(el)
|
572
|
+
name = call_name(el)
|
573
|
+
param_name = get_param_name(el)
|
574
|
+
attributes = tag_attributes(el)
|
575
|
+
newlines = tag_newlines(el)
|
383
576
|
|
384
|
-
|
385
|
-
|
386
|
-
|
387
|
-
|
388
|
-
|
389
|
-
|
390
|
-
|
391
|
-
|
392
|
-
|
393
|
-
|
394
|
-
|
395
|
-
|
396
|
-
|
397
|
-
|
398
|
-
|
399
|
-
|
400
|
-
|
401
|
-
|
402
|
-
|
403
|
-
|
404
|
-
|
405
|
-
|
406
|
-
|
407
|
-
# If there is content to, that goes in the hash under the key :content
|
408
|
-
if e.size > 0
|
409
|
-
pairs << "#{tag_newlines(el)}:content => (capture do %>#{children_to_erb(e)}<%; end)"
|
410
|
-
end
|
411
|
-
"{" + pairs.join(",") + "}"
|
412
|
-
elsif e.size > 0
|
413
|
-
"#{tag_newlines(el)}(capture do %>#{children_to_erb(e)}<%; end)"
|
414
|
-
else
|
415
|
-
"''"
|
416
|
-
end
|
417
|
-
pair = "#{param_name} => #{param_value}"
|
418
|
-
pair << ", " unless e == last
|
419
|
-
pair
|
420
|
-
|
421
|
-
when REXML::Text
|
422
|
-
e.to_s
|
423
|
-
|
424
|
-
when REXML::Comment
|
425
|
-
REXML::Comment::START + node.to_s + REXML::Comment::STOP
|
426
|
-
|
427
|
-
end
|
428
|
-
end
|
577
|
+
is_param_default_call = el.attributes['restore']
|
578
|
+
|
579
|
+
call = if param_name
|
580
|
+
to_call = if is_param_default_call
|
581
|
+
# The tag is available in a local variable
|
582
|
+
# holding a proc
|
583
|
+
el.dryml_name
|
584
|
+
elsif (call_type = polymorphic_call_type(el))
|
585
|
+
"find_polymorphic_tag(:#{name}, #{call_type})"
|
586
|
+
else
|
587
|
+
":#{name}"
|
588
|
+
end
|
589
|
+
param_name = attribute_to_ruby(param_name, :symbolize => true)
|
590
|
+
"call_block_tag_parameter(#{to_call}, #{attributes}, all_parameters[#{param_name}])"
|
591
|
+
else
|
592
|
+
if is_param_default_call
|
593
|
+
"#{name}__default.call_with_block(#{attributes})"
|
594
|
+
elsif (call_type = polymorphic_call_type(el))
|
595
|
+
"send(find_polymorphic_tag(:#{name}, #{call_type}), #{attributes})"
|
596
|
+
else
|
597
|
+
"#{name}(#{attributes unless attributes == '{}'})"
|
598
|
+
end
|
599
|
+
end
|
429
600
|
|
430
|
-
|
601
|
+
if el.children.empty?
|
602
|
+
call = apply_control_attributes(call, el)
|
603
|
+
maybe_make_part_call(el, "<%= #{call} #{newlines}%>")
|
604
|
+
else
|
605
|
+
old = @containing_tag_name
|
606
|
+
@containing_tag_name = el.dryml_name
|
607
|
+
children = children_to_erb(el)
|
608
|
+
@containing_tag_name = old
|
609
|
+
|
610
|
+
call_statement = "#{call} do |#{el.dryml_name}_default_tagbody| #{newlines}%>#{children}<% end"
|
611
|
+
call = "<% _output(" + apply_control_attributes(call_statement, el) + ") %>"
|
612
|
+
maybe_make_part_call(el, call)
|
613
|
+
end
|
431
614
|
end
|
432
|
-
|
433
615
|
|
434
|
-
def
|
435
|
-
|
436
|
-
|
437
|
-
|
438
|
-
|
616
|
+
def maybe_make_part_call(el, call)
|
617
|
+
part_name = el.attributes['part']
|
618
|
+
if part_name
|
619
|
+
part_id = part_name && "<%= #{attribute_to_ruby(el.attributes['id'] || part_name)} %>"
|
620
|
+
"<span class='part_wrapper' id='#{part_id}'>" + part_element(el, call) + "</span>"
|
439
621
|
else
|
440
|
-
|
622
|
+
call
|
441
623
|
end
|
442
624
|
end
|
443
625
|
|
444
626
|
|
445
|
-
def
|
627
|
+
def tag_attributes(el)
|
446
628
|
attributes = el.attributes
|
447
|
-
|
448
|
-
|
449
|
-
|
450
|
-
param_value = attribute_to_ruby(v)
|
451
|
-
"#{param_name} => #{param_value}"
|
452
|
-
end
|
629
|
+
items = attributes.map do |n,v|
|
630
|
+
":#{n} => #{attribute_to_ruby(v)}" unless n.in?(SPECIAL_ATTRIBUTES)
|
631
|
+
end.compact
|
453
632
|
|
454
|
-
|
455
|
-
|
633
|
+
# if there's a ':' el.name is just the part after the ':'
|
634
|
+
items << ":field => \"#{el.name}\"" if el.name != el.expanded_name
|
456
635
|
|
457
|
-
|
458
|
-
|
459
|
-
|
460
|
-
|
461
|
-
|
462
|
-
|
636
|
+
items = items.join(", ")
|
637
|
+
|
638
|
+
merge_attrs = attributes['merge_attrs'] || merge_attribute(el)
|
639
|
+
if merge_attrs
|
640
|
+
extra_attributes = if merge_attrs == "&true"
|
641
|
+
"attributes"
|
642
|
+
elsif is_code_attribute?(merge_attrs)
|
643
|
+
merge_attrs[1..-1]
|
463
644
|
else
|
464
|
-
dryml_exception("invalid
|
645
|
+
dryml_exception("invalid merge_attrs", el)
|
465
646
|
end
|
466
|
-
"{#{
|
647
|
+
"{#{items}}.merge((#{extra_attributes}) || {})"
|
467
648
|
else
|
468
|
-
"{#{
|
649
|
+
"{#{items}}"
|
469
650
|
end
|
470
651
|
end
|
471
652
|
|
472
|
-
|
473
|
-
|
474
|
-
|
475
|
-
|
476
|
-
|
477
|
-
|
478
|
-
|
479
|
-
|
480
|
-
|
481
|
-
|
482
|
-
|
483
|
-
else
|
484
|
-
dryml_exception("invalid xattrs", el)
|
485
|
-
end
|
486
|
-
class_attr = el.attributes["class"]
|
487
|
-
if class_attr
|
488
|
-
raise HoboError.new("invalid class attribute with xattrs: '#{class_attr}'") if
|
489
|
-
class_attr =~ /'|\[!\[HOBO-ERB/
|
490
|
-
|
491
|
-
attr_args.concat(", '#{class_attr}'")
|
492
|
-
start_tag_src.sub!(/\s*class\s*=\s*('[^']*?'|"[^"]*?")/, "")
|
493
|
-
end
|
494
|
-
start_tag_src.sub!(/\s*xattrs\s*=\s*('[^']*?'|"[^"]*?")/, " <%= xattrs(#{attr_args}) %>")
|
653
|
+
def static_tag_to_method_call(el)
|
654
|
+
part = el.attributes["part"]
|
655
|
+
attrs = el.attributes.map do |n, v|
|
656
|
+
next if n.in? SPECIAL_ATTRIBUTES
|
657
|
+
val = restore_erb_scriptlets(v).gsub('"', '\"').gsub(/<%=(.*?)%>/, '#{\1}')
|
658
|
+
%(:#{n} => "#{val}")
|
659
|
+
end.compact
|
660
|
+
|
661
|
+
# If there's a part but no id, the id defaults to the part name
|
662
|
+
if part && !el.attributes["id"]
|
663
|
+
attrs << ":id => '#{part}'"
|
495
664
|
end
|
665
|
+
|
666
|
+
# Convert the attributes hash to a call to merge_attrs if
|
667
|
+
# there's a merge_attrs attribute
|
668
|
+
attrs = if (merge_attrs = el.attributes['merge_attrs'])
|
669
|
+
dryml_exception("merge_attrs was given a string", el) unless is_code_attribute?(merge_attrs)
|
670
|
+
|
671
|
+
"merge_attrs({#{attrs * ', '}}, " +
|
672
|
+
"((__merge_attrs__ = (#{merge_attrs[1..-1]})) == true ? attributes : __merge_attrs__))"
|
673
|
+
else
|
674
|
+
"{" + attrs.join(', ') + "}"
|
675
|
+
end
|
676
|
+
|
677
|
+
if el.children.empty?
|
678
|
+
dryml_exception("part attribute on empty static tag", el) if part
|
496
679
|
|
497
|
-
|
498
|
-
start_tag_src
|
680
|
+
"<%= " + apply_control_attributes("tag(:#{el.name}, #{attrs} #{tag_newlines(el)})", el) + " %>"
|
499
681
|
else
|
500
|
-
if
|
682
|
+
if part
|
501
683
|
body = part_element(el, children_to_erb(el))
|
502
|
-
|
503
|
-
|
504
|
-
|
505
|
-
id_expr = attribute_to_ruby(el.attributes['id'])
|
506
|
-
start_tag_src.sub!(/id\s*=\s*('[^']*?'|"[^"]*?")/, "id='<%= #{id_expr} %>'")
|
507
|
-
else
|
508
|
-
# rename part_id to id
|
509
|
-
start_tag_src.sub!(/part_id\s*=\s*('[^']*?'|"[^"]*?")/, "id=\\1")
|
510
|
-
end
|
511
|
-
dryml_exception("multiple part ids", el) if start_tag_src.index("part_id=")
|
684
|
+
else
|
685
|
+
body = children_to_erb(el)
|
686
|
+
end
|
512
687
|
|
688
|
+
output_tag = "tag(:#{el.name}, #{attrs}, true) + new_context { %>#{body}</#{el.name}><% }"
|
689
|
+
"<% _output(" + apply_control_attributes(output_tag, el) + ") %>"
|
690
|
+
end
|
691
|
+
end
|
692
|
+
|
693
|
+
|
694
|
+
def static_element_to_erb(el)
|
695
|
+
if %w(part merge_attrs if unless repeat).any? {|x| el.attributes[x]}
|
696
|
+
static_tag_to_method_call(el)
|
697
|
+
else
|
698
|
+
start_tag_src = el.start_tag_source.gsub(REXML::CData::START, "").gsub(REXML::CData::STOP, "")
|
699
|
+
|
700
|
+
# Allow #{...} as an alternate to <%= ... %>
|
701
|
+
start_tag_src.gsub!(/=\s*('.*?'|".*?")/) do |s|
|
702
|
+
s.gsub(/#\{(.*?)\}/, '<%= \1 %>')
|
703
|
+
end
|
513
704
|
|
514
|
-
|
515
|
-
else
|
705
|
+
if el.has_end_tag?
|
516
706
|
start_tag_src + children_to_erb(el) + "</#{el.name}>"
|
707
|
+
else
|
708
|
+
start_tag_src
|
517
709
|
end
|
518
710
|
end
|
519
711
|
end
|
712
|
+
|
713
|
+
|
714
|
+
def apply_control_attributes(expression, el)
|
715
|
+
if_, unless_, repeat = controls = %w(if unless repeat).map {|x| el.attributes[x]}
|
716
|
+
controls.compact!
|
717
|
+
|
718
|
+
dryml_exception("You can't have multiple control attributes on the same element", el) if
|
719
|
+
controls.length > 1
|
720
|
+
|
721
|
+
val = controls.first
|
722
|
+
if val.nil?
|
723
|
+
expression
|
724
|
+
else
|
725
|
+
control = if repeat && val == "&true"
|
726
|
+
"this"
|
727
|
+
elsif is_code_attribute?(val)
|
728
|
+
"#{val[1..-1]}"
|
729
|
+
else
|
730
|
+
"this.#{val}"
|
731
|
+
end
|
732
|
+
|
733
|
+
x = gensym
|
734
|
+
if if_
|
735
|
+
"(if !(#{control}).blank?; (#{x} = #{expression}; Hobo::Dryml.last_if = true; #{x}) " +
|
736
|
+
"else (Hobo::Dryml.last_if = false; ''); end)"
|
737
|
+
elsif unless_
|
738
|
+
"(if (#{control}).blank?; (#{x} = #{expression}; Hobo::Dryml.last_if = true; #{x}) " +
|
739
|
+
"else (Hobo::Dryml.last_if = false; ''); end)"
|
740
|
+
elsif repeat
|
741
|
+
"repeat_attribute(#{control}) { #{expression} }"
|
742
|
+
end
|
743
|
+
end
|
744
|
+
end
|
745
|
+
|
746
|
+
|
747
|
+
def attribute_to_ruby(*args)
|
748
|
+
options = extract_options_from_args!(args)
|
749
|
+
attr, el = args
|
750
|
+
|
751
|
+
dryml_exception('erb scriptlet not allowed in this attribute (use #{ ... } instead)', el) if
|
752
|
+
attr.is_a?(String) && attr.index("[![HOBO-ERB")
|
520
753
|
|
521
|
-
|
522
|
-
|
523
|
-
if attr.nil?
|
524
|
-
"nil"
|
525
|
-
elsif is_code_attribute?(attr)
|
526
|
-
"(#{attr[1..-1]})"
|
754
|
+
if options[:symbolize] && attr =~ /^[a-zA-Z_][^a-zA-Z0-9_]*[\?!]?/
|
755
|
+
":#{attr}"
|
527
756
|
else
|
528
|
-
|
529
|
-
|
530
|
-
elsif
|
531
|
-
"
|
757
|
+
res = if attr.nil?
|
758
|
+
"nil"
|
759
|
+
elsif is_code_attribute?(attr)
|
760
|
+
"(#{attr[1..-1]})"
|
532
761
|
else
|
533
|
-
|
534
|
-
|
535
|
-
|
762
|
+
if attr !~ /"/
|
763
|
+
'"' + attr + '"'
|
764
|
+
elsif attr !~ /'/
|
765
|
+
"'#{attr}'"
|
766
|
+
else
|
767
|
+
dryml_exception("invalid quote(s) in attribute value")
|
768
|
+
end
|
769
|
+
#attr.starts_with?("++") ? "attr_extension(#{str})" : str
|
770
|
+
end
|
771
|
+
options[:symbolize] ? (res + ".to_sym") : res
|
536
772
|
end
|
537
773
|
end
|
538
774
|
|
@@ -545,41 +781,48 @@ module Hobo::Dryml
|
|
545
781
|
return nil
|
546
782
|
end
|
547
783
|
|
548
|
-
def require_toplevel(el)
|
549
|
-
|
784
|
+
def require_toplevel(el, message=nil)
|
785
|
+
message ||= "can only be at the top level"
|
786
|
+
dryml_exception("<#{el.dryml_name}> #{message}", el) if el.parent != @doc.root
|
550
787
|
end
|
551
788
|
|
552
789
|
def require_attribute(el, name, rx=nil, optional=false)
|
553
790
|
val = el.attributes[name]
|
554
791
|
if val
|
555
|
-
dryml_exception("invalid #{name}=\"#{val}\" attribute on <#{el.
|
792
|
+
dryml_exception("invalid #{name}=\"#{val}\" attribute on <#{el.dryml_name}>", el) unless rx && val =~ rx
|
556
793
|
else
|
557
|
-
dryml_exception("missing #{name} attribute on <#{el.
|
794
|
+
dryml_exception("missing #{name} attribute on <#{el.dryml_name}>", el) unless optional
|
558
795
|
end
|
559
796
|
end
|
560
797
|
|
561
798
|
def dryml_exception(message, el=nil)
|
562
799
|
el ||= @last_element
|
563
|
-
raise DrymlException.new(message
|
800
|
+
raise DrymlException.new(message, template_path, element_line_num(el))
|
564
801
|
end
|
565
802
|
|
566
803
|
def element_line_num(el)
|
567
|
-
offset = el.
|
804
|
+
offset = el.source_offset
|
568
805
|
line_no = @xmlsrc[0..offset].count("\n") + 1
|
569
806
|
end
|
570
807
|
|
571
808
|
def tag_newlines(el)
|
572
|
-
src = el.
|
809
|
+
src = el.start_tag_source
|
573
810
|
"\n" * src.count("\n")
|
574
811
|
end
|
575
812
|
|
576
813
|
def is_code_attribute?(attr_value)
|
577
|
-
attr_value
|
814
|
+
attr_value =~ /^\&/ && attr_value !~ /^\&\S+;/
|
578
815
|
end
|
579
816
|
|
580
817
|
def logger
|
581
818
|
ActionController::Base.logger rescue nil
|
582
819
|
end
|
820
|
+
|
821
|
+
def gensym(name="__tmp")
|
822
|
+
@gensym_counter ||= 0
|
823
|
+
@gensym_counter += 1
|
824
|
+
"#{name}_#{@gensym_counter}"
|
825
|
+
end
|
583
826
|
|
584
827
|
end
|
585
828
|
|