hobo 0.5.3 → 0.6
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.
- 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
|
|