hobo 1.0.3 → 1.1.0.pre0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (64) hide show
  1. data/CHANGES.txt +0 -66
  2. data/README +3 -0
  3. data/Rakefile +7 -7
  4. data/doctest/model.rdoctest +0 -2
  5. data/doctest/multi_model_forms.rdoctest +0 -2
  6. data/doctest/scopes.rdoctest +13 -21
  7. data/lib/active_record/association_collection.rb +7 -16
  8. data/lib/hobo.rb +10 -65
  9. data/lib/hobo/accessible_associations.rb +1 -5
  10. data/lib/hobo/authentication_support.rb +1 -1
  11. data/lib/hobo/controller.rb +5 -5
  12. data/lib/hobo/hobo_helper.rb +0 -84
  13. data/lib/hobo/lifecycles/lifecycle.rb +37 -31
  14. data/lib/hobo/lifecycles/transition.rb +1 -2
  15. data/lib/hobo/model.rb +13 -21
  16. data/lib/hobo/model_controller.rb +8 -8
  17. data/lib/hobo/rapid_helper.rb +12 -1
  18. data/lib/hobo/scopes/automatic_scopes.rb +26 -13
  19. data/lib/hobo/scopes/named_scope_extensions.rb +16 -28
  20. data/lib/hobo/user_controller.rb +1 -0
  21. data/lib/hobo/view_hints.rb +1 -5
  22. data/rails_generators/hobo/templates/initializer.rb +1 -1
  23. data/rails_generators/hobo_front_controller/templates/summary.dryml +4 -2
  24. data/rails_generators/hobo_model/hobo_model_generator.rb +12 -0
  25. data/rails_generators/hobo_model/templates/model.rb +9 -2
  26. data/rails_generators/hobo_rapid/templates/hobo-rapid.js +98 -23
  27. data/rails_generators/hobo_rapid/templates/themes/clean/public/stylesheets/clean.css +1 -1
  28. data/rails_generators/hobo_rapid/templates/themes/clean/public/stylesheets/rapid-ui.css +3 -1
  29. data/{dryml_generators → rapid_generators}/rapid/cards.dryml.erb +0 -0
  30. data/{dryml_generators → rapid_generators}/rapid/forms.dryml.erb +0 -0
  31. data/{dryml_generators → rapid_generators}/rapid/pages.dryml.erb +1 -1
  32. data/taglibs/rapid.dryml +2 -0
  33. data/taglibs/rapid_core.dryml +10 -9
  34. data/taglibs/rapid_forms.dryml +70 -35
  35. data/taglibs/rapid_lifecycles.dryml +17 -4
  36. data/taglibs/rapid_plus.dryml +3 -3
  37. data/taglibs/rapid_summary.dryml +11 -0
  38. data/taglibs/rapid_user_pages.dryml +39 -28
  39. data/tasks/hobo_tasks.rake +1 -1
  40. metadata +45 -61
  41. data/hobo.gemspec +0 -226
  42. data/lib/hobo/dryml.rb +0 -188
  43. data/lib/hobo/dryml/dryml_builder.rb +0 -140
  44. data/lib/hobo/dryml/dryml_doc.rb +0 -159
  45. data/lib/hobo/dryml/dryml_generator.rb +0 -263
  46. data/lib/hobo/dryml/dryml_support_controller.rb +0 -13
  47. data/lib/hobo/dryml/parser.rb +0 -3
  48. data/lib/hobo/dryml/parser/attribute.rb +0 -41
  49. data/lib/hobo/dryml/parser/base_parser.rb +0 -254
  50. data/lib/hobo/dryml/parser/document.rb +0 -57
  51. data/lib/hobo/dryml/parser/element.rb +0 -27
  52. data/lib/hobo/dryml/parser/elements.rb +0 -45
  53. data/lib/hobo/dryml/parser/source.rb +0 -58
  54. data/lib/hobo/dryml/parser/text.rb +0 -13
  55. data/lib/hobo/dryml/parser/tree_parser.rb +0 -67
  56. data/lib/hobo/dryml/part_context.rb +0 -137
  57. data/lib/hobo/dryml/scoped_variables.rb +0 -42
  58. data/lib/hobo/dryml/tag_parameters.rb +0 -36
  59. data/lib/hobo/dryml/taglib.rb +0 -123
  60. data/lib/hobo/dryml/template.rb +0 -1019
  61. data/lib/hobo/dryml/template_environment.rb +0 -613
  62. data/lib/hobo/dryml/template_handler.rb +0 -187
  63. data/lib/hobo/static_tags +0 -98
  64. data/taglibs/core.dryml +0 -104
@@ -1,36 +0,0 @@
1
- module Hobo
2
-
3
- module Dryml
4
-
5
- class NoParameterError < RuntimeError; end
6
-
7
- class TagParameters < Hash
8
-
9
- def initialize(parameters, exclude_names=nil)
10
- if exclude_names.blank?
11
- update(parameters)
12
- else
13
- parameters.each_pair { |k, v| self[k] = v unless k.in?(exclude_names) }
14
- end
15
- end
16
-
17
- def method_missing(name, default_content="")
18
- if name.to_s =~ /\?$/
19
- has_key?(name.to_s[0..-2].to_sym)
20
- else
21
- self[name]._?.call(default_content) || ""
22
- end
23
- end
24
-
25
- undef_method :default
26
-
27
- # Question: does this do anything? -Tom
28
- def [](param_name)
29
- fetch(param_name, nil)
30
- end
31
-
32
- end
33
-
34
- end
35
-
36
- end
@@ -1,123 +0,0 @@
1
- module Hobo
2
-
3
- module Dryml
4
-
5
- class Taglib
6
-
7
- @cache = {}
8
-
9
- class << self
10
-
11
- def get(options)
12
- src_file = taglib_filename(options)
13
- taglib = @cache[src_file]
14
- if taglib
15
- taglib.reload
16
- else
17
- taglib = Taglib.new(src_file)
18
- @cache[src_file] = taglib
19
- end
20
- taglib
21
- end
22
-
23
- def clear_cache
24
- @cache = {}
25
- end
26
-
27
- private
28
-
29
- def taglib_filename(options)
30
- plugin = options[:plugin]
31
- base = if plugin == "hobo"
32
- "#{HOBO_ROOT}/taglibs"
33
- elsif plugin
34
- "#{RAILS_ROOT}/vendor/plugins/#{plugin}/taglibs"
35
- elsif options[:src] =~ /\//
36
- "#{RAILS_ROOT}/app/views"
37
- elsif options[:template_dir] =~ /^#{HOBO_ROOT}/
38
- options[:template_dir]
39
- else
40
- "#{RAILS_ROOT}/#{options[:template_dir].gsub(/^\//, '')}" # remove leading / if there is one
41
- end
42
-
43
- src = options[:src] || plugin
44
- filename = "#{base}/#{src}.dryml"
45
- raise DrymlException, "No such taglib: #{base} #{options.inspect} #{filename}" unless File.exists?(filename)
46
- filename
47
- end
48
-
49
- end
50
-
51
- def initialize(src_file)
52
- @src_file = src_file
53
- load
54
- end
55
-
56
- def reload
57
- load if File.mtime(@src_file) > @last_load_time
58
- end
59
-
60
- def load
61
- @module = Module.new do
62
-
63
- @tag_attrs = {}
64
- @tag_aliases = []
65
-
66
- class << self
67
-
68
- def included(base)
69
- @tag_aliases.each do |tag, feature|
70
- if base.respond_to? :alias_method_chain_on_include
71
- base.alias_method_chain_on_include tag, feature
72
- else
73
- base.send(:alias_method_chain, tag, feature)
74
- end
75
- end
76
- end
77
-
78
- def _register_tag_attrs(tag, attrs)
79
- @tag_attrs[tag] = attrs
80
- end
81
- attr_reader :tag_attrs
82
-
83
- def alias_method_chain_on_include(tag, feature)
84
- @tag_aliases << [tag, feature]
85
- end
86
-
87
- end
88
-
89
- end
90
- template = Template.new(File.read(@src_file), @module, @src_file)
91
- template.compile([], [])
92
- @last_load_time = File.mtime(@src_file)
93
- end
94
-
95
- def import_into(class_or_module, as)
96
- if as
97
- # Define a method on class_or_module named whatever 'as'
98
- # is. The first time the method is called it creates and
99
- # returns an object that provides the taglib's tags as
100
- # methods. On subsequent calls the object is cached in an
101
- # instance variable "@_#{as}_taglib"
102
-
103
- taglib_module = @module
104
- ivar = "@_#{as}_taglib"
105
- class_or_module.send(:define_method, as) do
106
- instance_variable_get(ivar) or begin
107
- as_class = Class.new(TemplateEnvironment) { include taglib_module }
108
- as_object = as_class.new
109
- as_object.copy_instance_variables_from(self)
110
- instance_variable_set(ivar, as_object)
111
- end
112
- end
113
- else
114
- class_or_module.send(:include, @module)
115
- class_or_module.tag_attrs.update(@module.tag_attrs) if @module.respond_to?(:tag_attrs)
116
- end
117
- end
118
-
119
- end
120
-
121
- end
122
-
123
- end
@@ -1,1019 +0,0 @@
1
- require 'rexml/document'
2
- require 'pathname'
3
-
4
- module Hobo::Dryml
5
-
6
- class Template
7
-
8
- DRYML_NAME = "[a-zA-Z\-][a-zA-Z0-9\-]*"
9
- DRYML_NAME_RX = /^#{DRYML_NAME}$/
10
-
11
- RUBY_NAME = "[a-zA-Z_][a-zA-Z0-9_]*"
12
- RUBY_NAME_RX = /^#{RUBY_NAME}$/
13
-
14
- CODE_ATTRIBUTE_CHAR = "&"
15
-
16
- NO_METADATA_TAGS = %w(doctype if else unless repeat do with name type-name)
17
-
18
- SPECIAL_ATTRIBUTES = %w(param merge merge-params merge-attrs
19
- for-type
20
- if unless repeat
21
- part part-locals
22
- restore)
23
-
24
- VALID_PARAMETER_TAG_ATTRIBUTES = %w(param replace)
25
-
26
- @build_cache = {}
27
-
28
- class << self
29
- attr_reader :build_cache
30
-
31
- def clear_build_cache
32
- @build_cache.clear()
33
- end
34
- end
35
-
36
- def initialize(src, environment, template_path)
37
- @src = src
38
- @environment = environment # a class or a module
39
- @template_path = template_path.sub(%r(^#{Regexp.escape(RAILS_ROOT)}/), "")
40
-
41
- @builder = Template.build_cache[@template_path] || DRYMLBuilder.new(self)
42
- @builder.set_environment(environment)
43
-
44
- @last_element = nil
45
- end
46
-
47
- attr_reader :tags, :template_path
48
-
49
- def compile(local_names=[], auto_taglibs=[])
50
- now = Time.now
51
-
52
- unless @template_path.ends_with?(EMPTY_PAGE)
53
- p = Pathname.new template_path
54
- p = Pathname.new(RAILS_ROOT) + p unless p.absolute?
55
- mtime = p.mtime
56
-
57
- if !@builder.ready?(mtime)
58
- @builder.start
59
- parsed = true
60
- # parse the DRYML file creating a list of build instructions
61
- if is_taglib?
62
- process_src
63
- else
64
- create_render_page_method
65
- end
66
-
67
- # store build instructions in the cache
68
- Template.build_cache[@template_path] = @builder
69
- end
70
- end
71
-
72
- # compile the build instructions
73
- @builder.build(local_names, auto_taglibs, mtime)
74
-
75
- logger.info(" DRYML: Compiled #{template_path} in #{'%.2fs' % (Time.now - now)}") if parsed
76
- end
77
-
78
-
79
- def create_render_page_method
80
- erb_src = process_src
81
-
82
- @builder.add_build_instruction(:render_page, :src => erb_src, :line_num => 1)
83
- end
84
-
85
-
86
- def is_taglib?
87
- @environment.class == Module
88
- end
89
-
90
-
91
- def process_src
92
- @doc = Hobo::Dryml::Parser::Document.new(@src, @template_path)
93
- result = children_to_erb(@doc.root)
94
- restore_erb_scriptlets(result)
95
- end
96
-
97
-
98
- def restore_erb_scriptlets(src)
99
- @doc.restore_erb_scriptlets(src)
100
- end
101
-
102
-
103
- def children_to_erb(nodes)
104
- nodes.map { |x| node_to_erb(x) }.join
105
- end
106
-
107
-
108
- def node_to_erb(node)
109
- case node
110
-
111
- # v important this comes before REXML::Text, as REXML::CData < REXML::Text
112
- when REXML::CData
113
- REXML::CData::START + node.to_s + REXML::CData::STOP
114
-
115
- when REXML::Comment
116
- REXML::Comment::START + node.to_s + REXML::Comment::STOP
117
-
118
- when REXML::Text
119
- strip_suppressed_whiteaspace(node.to_s)
120
-
121
- when REXML::Element
122
- element_to_erb(node)
123
- end
124
- end
125
-
126
-
127
- def strip_suppressed_whiteaspace(s)
128
- s # s.gsub(/ -(\s*\n\s*)/, '<% \1 %>')
129
- end
130
-
131
-
132
- def element_to_erb(el)
133
- dryml_exception("old-style parameter tag (<#{el.name}>)", el) if el.name.starts_with?(":")
134
-
135
- @last_element = el
136
- case el.dryml_name
137
-
138
- when "include"
139
- include_element(el)
140
- # return just the newlines to keep line-number matching - the
141
- # include has no presence in the erb source
142
- tag_newlines(el)
143
-
144
- when "set-theme"
145
- require_attribute(el, "name", /^#{DRYML_NAME}$/)
146
- @builder.add_build_instruction(:set_theme, :name => el.attributes['name'])
147
-
148
- # return nothing - set_theme has no presence in the erb source
149
- tag_newlines(el)
150
-
151
- when "def"
152
- def_element(el)
153
-
154
- when "extend"
155
- extend_element(el)
156
-
157
- when "set"
158
- set_element(el)
159
-
160
- when "set-scoped"
161
- set_scoped_element(el)
162
-
163
- when "param-content"
164
- param_content_element(el)
165
-
166
- else
167
- if el.dryml_name.not_in?(Hobo.static_tags) || el.attributes['param'] || el.attributes['restore']
168
- tag_call(el)
169
- else
170
- static_element_to_erb(el)
171
- end
172
- end
173
- end
174
-
175
-
176
- def include_element(el)
177
- require_toplevel(el)
178
- require_attribute(el, "as", /^#{DRYML_NAME}$/, true)
179
- options = {}
180
- %w(src module plugin as).each do |attr|
181
- options[attr.to_sym] = el.attributes[attr] if el.attributes[attr]
182
- end
183
- @builder.add_build_instruction(:include, options)
184
- end
185
-
186
-
187
- def import_module(mod, as=nil)
188
- @builder.import_module(mod, as)
189
- end
190
-
191
-
192
- def set_element(el)
193
- assigns = el.attributes.map do |name, value|
194
- next if name.in?(SPECIAL_ATTRIBUTES)
195
- dryml_exception("invalid name in <set> (remember to use '-' rather than '_')", el) unless name =~ /^#{DRYML_NAME}(\.#{DRYML_NAME})*$/
196
- "#{ruby_name name} = #{attribute_to_ruby(value)}; "
197
- end.join
198
- code = apply_control_attributes("begin; #{assigns}; end", el)
199
- "<% #{code}#{tag_newlines(el)} %>"
200
- end
201
-
202
-
203
- def set_scoped_element(el)
204
- variables = el.attributes.map do |name, value|
205
- dryml_exception("invalid name in <set-scoped> (remember to use '-' rather than '_')", el) unless name =~ DRYML_NAME_RX
206
- ":#{ruby_name name} => #{attribute_to_ruby(value)} "
207
- end
208
- "<% scope.new_scope(#{variables * ', '}) { #{tag_newlines(el)} %>#{children_to_erb(el)}<% } %>"
209
- end
210
-
211
-
212
- def declared_attributes(def_element)
213
- attrspec = def_element.attributes["attrs"]
214
- attr_names = attrspec ? attrspec.split(/\s*,\s*/).map{ |n| n.underscore.to_sym } : []
215
- invalids = attr_names & ([:with, :field, :this] + SPECIAL_ATTRIBUTES.*.to_sym)
216
- dryml_exception("invalid attrs in def: #{invalids * ', '}", def_element) unless invalids.empty?
217
- attr_names
218
- end
219
-
220
-
221
- def ruby_name(dryml_name)
222
- dryml_name.gsub('-', '_')
223
- end
224
-
225
-
226
- def with_containing_tag_name(el)
227
- old = @containing_tag_name
228
- @containing_tag_name = el.dryml_name
229
- yield
230
- @containing_tag_name = old
231
- end
232
-
233
-
234
- def define_polymorphic_dispatcher(el, name)
235
- # FIXME: The new erb context ends up being set-up twice
236
- src = %(
237
- def #{name}(attributes={}, parameters={})
238
- _tag_context(attributes) do
239
- attributes.delete :with
240
- attributes.delete :field
241
- call_polymorphic_tag('#{name}', attributes, parameters) { #{name}__base(attributes.except, parameters) }
242
- end
243
- end
244
- )
245
- @builder.add_build_instruction(:eval, :src => src, :line_num => element_line_num(el))
246
- end
247
-
248
-
249
- def extend_element(el)
250
- def_element(el, true)
251
- end
252
-
253
-
254
- def type_specific_suffix
255
- el = @def_element
256
- for_type = el.attributes['for']
257
- if for_type
258
- type_name = if defined?(HoboFields) && for_type =~ /^[a-z]/
259
- # It's a symbolic type name - look up the Ruby type name
260
- klass = HoboFields.to_class(for_type) or
261
- dryml_exception("No such type in polymorphic tag definition: '#{for_type}'", el)
262
- klass.name
263
- else
264
- for_type
265
- end.underscore.gsub('/', '__')
266
- "__for_#{type_name}"
267
- end
268
- end
269
-
270
-
271
- def def_element(el, extend_tag=false)
272
- require_toplevel(el)
273
- require_attribute(el, "tag", DRYML_NAME_RX)
274
- require_attribute(el, "attrs", /^\s*#{DRYML_NAME}(\s*,\s*#{DRYML_NAME})*\s*$/, true)
275
- require_attribute(el, "alias-of", DRYML_NAME_RX, true)
276
-
277
- @def_element = el
278
-
279
- unsafe_name = el.attributes["tag"]
280
- name = Hobo::Dryml.unreserve(unsafe_name)
281
- suffix = type_specific_suffix
282
- if suffix
283
- name += suffix
284
- unsafe_name += suffix
285
- end
286
-
287
- if el.attributes['polymorphic']
288
- %w(for alias-of).each do |attr|
289
- dryml_exception("def cannot have both 'polymorphic' and '#{attr}' attributes") if el.attributes[attr]
290
- end
291
-
292
- define_polymorphic_dispatcher(el, ruby_name(name))
293
- name += "__base"
294
- unsafe_name += "__base"
295
- end
296
-
297
- alias_of = el.attributes['alias-of']
298
- dryml_exception("def with alias-of must be empty", el) if alias_of and el.size > 0
299
-
300
- alias_of and @builder.add_build_instruction(:alias_method,
301
- :new => ruby_name(name).to_sym,
302
- :old => ruby_name(Hobo::Dryml.unreserve(alias_of)).to_sym)
303
-
304
- res = if alias_of
305
- "<% #{tag_newlines(el)} %>"
306
- else
307
- src = tag_method(name, el, extend_tag) +
308
- "<% _register_tag_attrs(:#{ruby_name name}, #{declared_attributes(el).inspect.underscore}) %>"
309
-
310
- logger.debug(restore_erb_scriptlets(src)) if el.attributes["debug-source"]
311
-
312
- @builder.add_build_instruction(:def,
313
- :src => restore_erb_scriptlets(src),
314
- :line_num => element_line_num(el))
315
- # keep line numbers matching up
316
- "<% #{"\n" * src.count("\n")} %>"
317
- end
318
- @def_element = nil
319
- res
320
- end
321
-
322
- def self.descendents(el,&block)
323
- return if el.elements.empty?
324
- el.elements.each do |child|
325
- block.call(child)
326
- descendents(child,&block)
327
- end
328
- end
329
-
330
- # Using REXML::XPath is slow
331
- def self.descendent_select(el)
332
- result = []
333
- descendents(el) { |desc|
334
- result << desc if yield(desc)
335
- }
336
- result
337
- end
338
-
339
- def param_names_in_definition(el)
340
- self.class.descendent_select(el) { |el| el.attribute 'param' }.map do |e|
341
- name = get_param_name(e)
342
- dryml_exception("invalid param name: #{name.inspect}", e) unless
343
- is_code_attribute?(name) || name =~ RUBY_NAME_RX || name =~ /#\{/
344
- name.to_sym unless is_code_attribute?(name)
345
- end.compact
346
- end
347
-
348
-
349
- def tag_method(name, el, extend_tag=false)
350
- name = ruby_name name
351
- param_names = param_names_in_definition(el)
352
-
353
- if extend_tag
354
- @extend_key = 'a' + Digest::SHA1.hexdigest(el.to_s)[0..10]
355
- alias_statement = "; alias_method_chain_on_include :#{name}, :#{@extend_key}"
356
- name = "#{name}_with_#{@extend_key}"
357
- end
358
-
359
- src = "<% def #{name}(all_attributes={}, all_parameters={}); " +
360
- "parameters = Hobo::Dryml::TagParameters.new(all_parameters, #{param_names.inspect.underscore}); " +
361
- "all_parameters = Hobo::Dryml::TagParameters.new(all_parameters); " +
362
- tag_method_body(el) +
363
- "; end#{alias_statement} %>"
364
- @extend_key = nil
365
- src
366
- end
367
-
368
-
369
- def tag_method_body(el)
370
- attrs = declared_attributes(el)
371
-
372
- # A statement to assign values to local variables named after the tag's attrs
373
- # The trailing comma on `attributes` is supposed to be there!
374
- setup_locals = attrs.map{|a| "#{Hobo::Dryml.unreserve(a).underscore}, "}.join + "attributes, = " +
375
- "_tag_locals(all_attributes, #{attrs.inspect})"
376
-
377
- start = "_tag_context(all_attributes) do #{setup_locals}"
378
-
379
- "#{start} " +
380
- # reproduce any line breaks in the start-tag so that line numbers are preserved
381
- tag_newlines(el) + "%>" +
382
- wrap_tag_method_body_with_metadata(children_to_erb(el)) +
383
- "<% output_buffer; end"
384
- end
385
-
386
-
387
- def wrap_source_with_metadata(content, kind, name, *args)
388
- if (!include_source_metadata) || name.in?(NO_METADATA_TAGS)
389
- content
390
- else
391
- metadata = [kind, name] + args + [@template_path]
392
- "<!--[DRYML|#{metadata * '|'}[-->" + content + "<!--]DRYML]-->"
393
- end
394
- end
395
-
396
-
397
- def wrap_tag_method_body_with_metadata(content)
398
- name = @def_element.attributes['tag']
399
- for_ = @def_element.attributes['for']
400
- name += " for #{for_}" if for_
401
- wrap_source_with_metadata(content, "def", name, element_line_num(@def_element))
402
- end
403
-
404
-
405
- def wrap_tag_call_with_metadata(el, content)
406
- name = el.expanded_name
407
- param = el.attributes['param']
408
-
409
- if param == "&true"
410
- name += " param"
411
- elsif param
412
- name += " param='#{param}'"
413
- end
414
-
415
- wrap_source_with_metadata(content, "call", name, element_line_num(el))
416
- end
417
-
418
-
419
- def param_content_local_name(name)
420
- "_#{ruby_name name}__default_content"
421
- end
422
-
423
-
424
- def param_content_element(name_or_el)
425
- name = if name_or_el.is_a?(String)
426
- name_or_el
427
- else
428
- el = name_or_el
429
- el.attributes['for'] || @containing_tag_name
430
- end
431
- local_name = param_content_local_name(name)
432
- "<%= #{local_name} && #{local_name}.call %>"
433
- end
434
-
435
-
436
- def part_element(el, content)
437
- require_attribute(el, "part", DRYML_NAME_RX)
438
-
439
- if contains_param?(el)
440
- delegated_part_element(el, content)
441
- else
442
- simple_part_element(el, content)
443
- end
444
- end
445
-
446
-
447
- def simple_part_element(el, content)
448
- part_name = el.attributes['part']
449
- dom_id = el.attributes['id'] || part_name
450
- part_name = ruby_name(part_name)
451
- part_locals = el.attributes["part-locals"]
452
-
453
- part_src = "<% def #{part_name}_part(#{part_locals._?.gsub('@', '')}) #{tag_newlines(el)}; new_context do %>" +
454
- content +
455
- "<% end; end %>"
456
- @builder.add_part(part_name, restore_erb_scriptlets(part_src), element_line_num(el))
457
-
458
- newlines = "\n" * part_src.count("\n")
459
- args = [attribute_to_ruby(dom_id), ":#{part_name}", part_locals].compact
460
- "<%= call_part(#{args * ', '}) #{newlines} %>"
461
- end
462
-
463
-
464
- def delegated_part_element(el, content)
465
- # TODO
466
- end
467
-
468
-
469
- def contains_param?(el)
470
- # TODO
471
- false
472
- end
473
-
474
-
475
- def part_delegate_tag_name(el)
476
- "#{@def_name}_#{el.attributes['part']}__part_delegate"
477
- end
478
-
479
-
480
- def current_def_name
481
- @def_element && @def_element.attributes['tag']
482
- end
483
-
484
-
485
- def get_param_name(el)
486
- param_name = el.attributes["param"]
487
-
488
- if param_name
489
- def_tag = find_ancestor(el) {|e| e.name == "def" || e.name == "extend" }
490
- dryml_exception("param is not allowed outside of tag definitions", el) if def_tag.nil?
491
-
492
- ruby_name(param_name == "&true" ? el.dryml_name : param_name)
493
- else
494
- nil
495
- end
496
- end
497
-
498
-
499
- def inside_def_for_type?
500
- @def_element && @def_element.attributes['for']
501
- end
502
-
503
-
504
- def call_name(el)
505
- dryml_exception("invalid tag name (remember to use '-' rather than '_')", el) unless el.dryml_name =~ /^#{DRYML_NAME}(\.#{DRYML_NAME})*$/
506
-
507
- name = Hobo::Dryml.unreserve(ruby_name(el.dryml_name))
508
- if call_to_self_from_type_specific_def?(el)
509
- "#{name}__base"
510
- elsif old_tag_call?(el)
511
- name = name[4..-1] # remove 'old-' prefix
512
- name += type_specific_suffix if inside_def_for_type?
513
- "#{name}_without_#{@extend_key}"
514
- else
515
- name
516
- end
517
- end
518
-
519
-
520
- def old_tag_call?(el)
521
- @def_element && el.dryml_name == "old-#{current_def_name}"
522
- end
523
-
524
-
525
- def call_to_self_from_type_specific_def?(el)
526
- inside_def_for_type? && el.dryml_name == current_def_name &&!el.attributes['for-type']
527
- end
528
-
529
-
530
- def polymorphic_call_type(el)
531
- t = el.attributes['for-type']
532
- if t.nil?
533
- nil
534
- elsif t == "&true"
535
- 'this_type'
536
- elsif t =~ /^[A-Z]/
537
- t
538
- elsif t =~ /^[a-z]/ && defined? HoboFields.to_class
539
- klass = HoboFields.to_class(t)
540
- klass.name
541
- elsif is_code_attribute?(t)
542
- t[1..-1]
543
- else
544
- dryml_exception("invalid for-type attribute", el)
545
- end
546
- end
547
-
548
-
549
- def tag_call(el)
550
- name = call_name(el)
551
- param_name = get_param_name(el)
552
- attributes = tag_attributes(el)
553
- newlines = tag_newlines(el)
554
-
555
- parameters = tag_newlines(el) + parameter_tags_hash(el)
556
-
557
- is_param_restore = el.attributes['restore']
558
-
559
- call = if param_name
560
- param_name = attribute_to_ruby(param_name, :symbolize => true)
561
- args = "#{attributes}, #{parameters}, all_parameters, #{param_name}"
562
- to_call = if is_param_restore
563
- # The tag is available in a local variable
564
- # holding a proc
565
- param_restore_local_name(name)
566
- elsif (call_type = polymorphic_call_type(el))
567
- "find_polymorphic_tag(:#{ruby_name name}, #{call_type})"
568
- else
569
- ":#{ruby_name name}"
570
- end
571
- "call_tag_parameter(#{to_call}, #{args})"
572
- else
573
- if is_param_restore
574
- # The tag is a proc available in a local variable
575
- "#{param_restore_local_name(name)}.call(#{attributes}, #{parameters})"
576
- elsif (call_type = polymorphic_call_type(el))
577
- "send(find_polymorphic_tag(:#{ruby_name name}, #{call_type}), #{attributes}, #{parameters})"
578
- elsif attributes == "{}" && parameters == "{}"
579
- if name =~ /^[A-Z]/
580
- # it's a tag with a cap name - not a local
581
- "#{ruby_name name}()"
582
- else
583
- # could be a tag or a local variable
584
- "#{ruby_name name}.to_s"
585
- end
586
- else
587
- "#{ruby_name name}(#{attributes}, #{parameters})"
588
- end
589
- end
590
-
591
- call = apply_control_attributes(call, el)
592
- call = maybe_make_part_call(el, "<% concat(#{call}) %>")
593
- wrap_tag_call_with_metadata(el, call)
594
- end
595
-
596
-
597
- def merge_attribute(el)
598
- merge = el.attributes['merge']
599
- dryml_exception("merge cannot have a RHS", el) if merge && merge != "&true"
600
- merge
601
- end
602
-
603
-
604
- def parameter_tags_hash(el, containing_tag_name=nil)
605
- call_type = nil
606
-
607
- metadata_name = containing_tag_name || el.expanded_name
608
-
609
- param_items = el.map do |node|
610
- case node
611
- when REXML::Text
612
- text = node.to_s
613
- unless text.blank?
614
- dryml_exception("mixed content and parameter tags", el) if call_type == :named_params
615
- call_type = :default_param_only
616
- end
617
- text
618
- when REXML::Element
619
- e = node
620
- is_parameter_tag = e.parameter_tag?
621
-
622
- # Make sure there isn't a mix of parameter tags and normal content
623
- case call_type
624
- when nil
625
- call_type = is_parameter_tag ? :named_params : :default_param_only
626
- when :named_params
627
- dryml_exception("mixed parameter tags and non-parameter tags (did you forget a ':'?)", el) unless is_parameter_tag
628
- when :default_param_only
629
- dryml_exception("mixed parameter tags and non-parameter tags (did you forget a ':'?)", el) if is_parameter_tag
630
- end
631
-
632
- if is_parameter_tag
633
- parameter_tag_hash_item(e, metadata_name) + ", "
634
- end
635
- end
636
- end.join
637
-
638
- if call_type == :default_param_only || (call_type.nil? && param_items.length > 0) || (el.children.empty? && el.has_end_tag?)
639
- with_containing_tag_name(el) do
640
- param_items = " :default => #{default_param_proc(el, containing_tag_name)}, "
641
- end
642
- end
643
-
644
- param_items.concat without_parameters(el)
645
-
646
- merge_params = el.attributes['merge-params'] || merge_attribute(el)
647
- if merge_params
648
- extra_params = if merge_params == "&true"
649
- "parameters"
650
- elsif is_code_attribute?(merge_params)
651
- merge_params[1..-1]
652
- else
653
- merge_param_names = merge_params.split(/\s*,\s*/).*.gsub("-", "_")
654
- "all_parameters & #{merge_param_names.inspect}"
655
- end
656
- "merge_parameter_hashes({#{param_items}}, (#{extra_params}) || {})"
657
- else
658
- "{#{param_items}}"
659
- end
660
- end
661
-
662
-
663
- def without_parameters(el)
664
- without_names = el.attributes.keys.map { |name| name =~ /^without-(.*)/ and $1 }.compact
665
- without_names.map { |name| ":#{ruby_name name}_replacement => proc {|__discard__| '' }, " }.join
666
- end
667
-
668
-
669
- def parameter_tag_hash_item(el, metadata_name)
670
- name = el.name.dup
671
- if name.sub!(/^before-/, "")
672
- before_parameter_tag_hash_item(name, el, metadata_name)
673
- elsif name.sub!(/^after-/, "")
674
- after_parameter_tag_hash_item(name, el, metadata_name)
675
- elsif name.sub!(/^prepend-/, "")
676
- prepend_parameter_tag_hash_item(name, el, metadata_name)
677
- elsif name.sub!(/^append-/, "")
678
- append_parameter_tag_hash_item(name, el, metadata_name)
679
- else
680
- hash_key = ruby_name name
681
- hash_key += "_replacement" if el.attribute("replace")
682
- if (param_name = get_param_name(el))
683
- ":#{hash_key} => merge_tag_parameter(#{param_proc(el, metadata_name)}, all_parameters[:#{param_name}])"
684
- else
685
- ":#{hash_key} => #{param_proc(el, metadata_name)}"
686
- end
687
- end
688
- end
689
-
690
-
691
- def before_parameter_tag_hash_item(name, el, metadata_name)
692
- param_name = get_param_name(el)
693
- dryml_exception("param declaration not allowed on 'before' parameters", el) if param_name
694
- content = children_to_erb(el) + "<% concat(#{param_restore_local_name(name)}.call({}, {})) %>"
695
- ":#{ruby_name name}_replacement => #{replace_parameter_proc(el, metadata_name, content)}"
696
- end
697
-
698
-
699
- def after_parameter_tag_hash_item(name, el, metadata_name)
700
- param_name = get_param_name(el)
701
- dryml_exception("param declaration not allowed on 'after' parameters", el) if param_name
702
- content = "<% concat(#{param_restore_local_name(name)}.call({}, {})) %>" + children_to_erb(el)
703
- ":#{ruby_name name}_replacement => #{replace_parameter_proc(el, metadata_name, content)}"
704
- end
705
-
706
-
707
- def append_parameter_tag_hash_item(name, el, metadata_name)
708
- ":#{ruby_name name} => proc { [{}, { :default => proc { |#{param_content_local_name(name)}| new_context { %>" +
709
- param_content_element(name) + children_to_erb(el) +
710
- "<% } } } ] }"
711
- end
712
-
713
-
714
- def prepend_parameter_tag_hash_item(name, el, metadata_name)
715
- ":#{ruby_name name} => proc { [{}, { :default => proc { |#{param_content_local_name(name)}| new_context { %>" +
716
- children_to_erb(el) + param_content_element(name) +
717
- "<% } } } ] }"
718
- end
719
-
720
-
721
- def default_param_proc(el, containing_param_name=nil)
722
- content = children_to_erb(el)
723
- content = wrap_source_with_metadata(content, "param", containing_param_name,
724
- element_line_num(el)) if containing_param_name
725
- "proc { |#{param_content_local_name(el.dryml_name)}| new_context { %>#{content}<% } #{tag_newlines(el)}}"
726
- end
727
-
728
-
729
- def param_restore_local_name(name)
730
- "_#{ruby_name name}_restore"
731
- end
732
-
733
-
734
- def wrap_replace_parameter(el, name)
735
- wrap_source_with_metadata(children_to_erb(el), "replace", name, element_line_num(el))
736
- end
737
-
738
-
739
- def param_proc(el, metadata_name_prefix)
740
- metadata_name = "#{metadata_name_prefix}><#{el.name}"
741
-
742
- nl = tag_newlines(el)
743
-
744
- if (repl = el.attribute("replace"))
745
- dryml_exception("replace attribute must not have a value", el) if repl.has_rhs?
746
- dryml_exception("replace parameters must not have attributes", el) if el.attributes.length > 1
747
-
748
- replace_parameter_proc(el, metadata_name)
749
- else
750
- attributes = el.attributes.dup
751
- # Providing one of 'with' or 'field' but not the other should cancel out the other
752
- attributes[:with] = "&nil" if attributes.key?(:field) && !attributes.key?(:with)
753
- attributes[:field] = "&nil" if !attributes.key?(:field) && attributes.key?(:with)
754
- attribute_items = attributes.map do |name, value|
755
- if name.in?(VALID_PARAMETER_TAG_ATTRIBUTES)
756
- # just ignore
757
- elsif name.in?(SPECIAL_ATTRIBUTES)
758
- dryml_exception("attribute '#{name}' is not allowed on parameter tags (<#{el.name}:>)", el)
759
- else
760
- ":#{ruby_name name} => #{attribute_to_ruby(value, el)}"
761
- end
762
- end.compact
763
-
764
- nested_parameters_hash = parameter_tags_hash(el, metadata_name)
765
- "proc { [{#{attribute_items * ', '}}, #{nested_parameters_hash}] #{nl}}"
766
- end
767
- end
768
-
769
-
770
- def replace_parameter_proc(el, metadata_name, content=nil)
771
- content ||= wrap_replace_parameter(el, metadata_name)
772
- param_name = el.dryml_name.sub(/^(before|after|append|prepend)-/, "")
773
- "proc { |#{param_restore_local_name(param_name)}| new_context { %>#{content}<% } #{tag_newlines(el)}}"
774
- end
775
-
776
-
777
- def maybe_make_part_call(el, call)
778
- part_name = el.attributes['part']
779
- if part_name
780
- part_id = el.attributes['id'] || part_name
781
- "<div class='part-wrapper' id='<%= #{attribute_to_ruby part_id} %>'>#{part_element(el, call)}</div>"
782
- else
783
- call
784
- end
785
- end
786
-
787
-
788
- def field_shorthand_element?(el)
789
- el.expanded_name =~ /:./
790
- end
791
-
792
-
793
- def tag_attributes(el)
794
- attributes = el.attributes
795
- items = attributes.map do |n,v|
796
- dryml_exception("invalid attribute name '#{n}' (remember to use '-' rather than '_')", el) unless n =~ DRYML_NAME_RX
797
-
798
- next if n.in?(SPECIAL_ATTRIBUTES) || n =~ /^without-/
799
- next if el.attributes['part'] && n == 'id' # The id is rendered on the <div class="part-wrapper"> instead
800
-
801
- ":#{ruby_name n} => #{attribute_to_ruby(v)}"
802
- end.compact
803
-
804
- # if there's a ':' el.name is just the part after the ':'
805
- items << ":field => \"#{ruby_name el.name}\"" if field_shorthand_element?(el)
806
-
807
- hash = "{#{items.join(", ")}}"
808
-
809
- if merge_attribute(el)
810
- "merge_attrs(#{hash}, attributes)"
811
- elsif el.attributes['merge-attrs']
812
- merge_attrs = compile_merge_attrs(el)
813
- "merge_attrs(#{hash}, #{merge_attrs} || {})"
814
- else
815
- hash
816
- end
817
- end
818
-
819
-
820
- def compile_merge_attrs(el)
821
- merge_attrs = el.attributes['merge-attrs']
822
- if merge_attrs == "&true"
823
- "attributes"
824
- elsif is_code_attribute?(merge_attrs)
825
- "(#{merge_attrs[1..-1]})"
826
- else
827
- merge_attr_names = merge_attrs.split(/\s*,\s*/).*.gsub("-", "_").*.to_sym
828
- "(all_attributes & #{merge_attr_names.inspect})"
829
- end
830
- end
831
-
832
-
833
- def static_tag_to_method_call(el)
834
- part = el.attributes["part"]
835
- attrs = el.attributes.map do |n, v|
836
- next if n.in? SPECIAL_ATTRIBUTES
837
- val = restore_erb_scriptlets(v).gsub('"', '\"').gsub(/<%=(.*?)%>/, '#{\1}')
838
- %('#{n}' => "#{val}")
839
- end.compact
840
-
841
- # If there's a part but no id, the id defaults to the part name
842
- if part && !el.attributes["id"]
843
- attrs << ":id => '#{part}'"
844
- end
845
-
846
- # Convert the attributes hash to a call to merge_attrs if
847
- # there's a merge-attrs attribute
848
- attrs = if el.attributes['merge-attrs']
849
- merge_attrs = compile_merge_attrs(el)
850
- "merge_attrs({#{attrs * ', '}}, #{merge_attrs} || {})"
851
- else
852
- "{" + attrs.join(', ') + "}"
853
- end
854
-
855
- if el.children.empty?
856
- dryml_exception("part attribute on empty static tag", el) if part
857
-
858
- "<%= " + apply_control_attributes("element(:#{el.name}, #{attrs}, nil, true, #{!el.has_end_tag?} #{tag_newlines(el)})", el) + " %>"
859
- else
860
- if part
861
- body = part_element(el, children_to_erb(el))
862
- else
863
- body = children_to_erb(el)
864
- end
865
-
866
- output_tag = "element(:#{el.name}, #{attrs}, new_context { %>#{body}<% })"
867
- "<% concat(" + apply_control_attributes(output_tag, el) + ") %>"
868
- end
869
- end
870
-
871
-
872
- def static_element_to_erb(el)
873
- if promote_static_tag_to_method_call?(el)
874
- static_tag_to_method_call(el)
875
- else
876
- start_tag_src = el.start_tag_source.gsub(REXML::CData::START, "").gsub(REXML::CData::STOP, "")
877
-
878
- # Allow #{...} as an alternate to <%= ... %>
879
- start_tag_src.gsub!(/=\s*('.*?'|".*?")/) do |s|
880
- s.gsub(/#\{(.*?)\}/, '<%= \1 %>')
881
- end
882
-
883
- if el.has_end_tag?
884
- start_tag_src + children_to_erb(el) + "</#{el.name}>"
885
- else
886
- start_tag_src
887
- end
888
- end
889
- end
890
-
891
-
892
- def promote_static_tag_to_method_call?(el)
893
- %w(part merge-attrs if unless repeat).any? {|x| el.attributes[x]}
894
- end
895
-
896
-
897
- def apply_control_attributes(expression, el)
898
- controls = %w(if unless repeat).map_hash { |x| el.attributes[x] }.compact
899
-
900
- dryml_exception("You can't have multiple control attributes on the same element", el) if
901
- controls.length > 1
902
-
903
- attr = controls.keys.first
904
- val = controls.values.first
905
- if val.nil?
906
- expression
907
- else
908
- control = if !el.attribute(attr).has_rhs?
909
- "this"
910
- elsif is_code_attribute?(val)
911
- "#{val[1..-1]}"
912
- else
913
- val.gsub!('-', '_')
914
- attr == "repeat" ? %("#{val}") : "this.#{val}"
915
- end
916
-
917
- x = gensym
918
- case attr
919
- when "if"
920
- "(if !(#{control}).blank?; (#{x} = #{expression}; Hobo::Dryml.last_if = true; #{x}) " +
921
- "else (Hobo::Dryml.last_if = false; ''); end)"
922
- when "unless"
923
- "(if (#{control}).blank?; (#{x} = #{expression}; Hobo::Dryml.last_if = true; #{x}) " +
924
- "else (Hobo::Dryml.last_if = false; ''); end)"
925
- when "repeat"
926
- "repeat_attribute(#{control}) { #{expression} }"
927
- end
928
- end
929
- end
930
-
931
-
932
- def attribute_to_ruby(*args)
933
- options = args.extract_options!
934
- attr, el = args
935
-
936
- dryml_exception('erb scriptlet not allowed in this attribute (use #{ ... } instead)', el) if
937
- attr.is_a?(String) && attr.index("[![HOBO-ERB")
938
-
939
- if options[:symbolize] && attr =~ /^[a-zA-Z_][^a-zA-Z0-9_]*[\?!]?/
940
- ":#{attr}"
941
- else
942
- res = if attr.nil?
943
- "nil"
944
- elsif is_code_attribute?(attr)
945
- "(#{attr[1..-1]})"
946
- else
947
- if attr !~ /"/
948
- '"' + attr + '"'
949
- elsif attr !~ /'/
950
- "'#{attr}'"
951
- else
952
- dryml_exception("invalid quote(s) in attribute value")
953
- end
954
- end
955
- options[:symbolize] ? (res + ".to_sym") : res
956
- end
957
- end
958
-
959
- def find_ancestor(el)
960
- e = el.parent
961
- until e.is_a? REXML::Document
962
- return e if yield(e)
963
- e = e.parent
964
- end
965
- return nil
966
- end
967
-
968
- def require_toplevel(el, message=nil)
969
- message ||= "can only be at the top level"
970
- dryml_exception("<#{el.dryml_name}> #{message}", el) if el.parent != @doc.root
971
- end
972
-
973
- def require_attribute(el, name, rx=nil, optional=false)
974
- val = el.attributes[name]
975
- if val
976
- dryml_exception("invalid #{name}=\"#{val}\" attribute on <#{el.dryml_name}>", el) unless rx && val =~ rx
977
- else
978
- dryml_exception("missing #{name} attribute on <#{el.dryml_name}>", el) unless optional
979
- end
980
- end
981
-
982
- def dryml_exception(message, el=nil)
983
- el ||= @last_element
984
- raise DrymlException.new(message, template_path, element_line_num(el))
985
- end
986
-
987
- def element_line_num(el)
988
- @doc.element_line_num(el)
989
- end
990
-
991
- def tag_newlines(el)
992
- src = el.start_tag_source
993
- "\n" * src.count("\n")
994
- end
995
-
996
- def is_code_attribute?(attr_value)
997
- attr_value =~ /^\&/ && attr_value !~ /^\&\S+;/
998
- end
999
-
1000
- def logger
1001
- ActionController::Base.logger rescue nil
1002
- end
1003
-
1004
- def gensym(name="__tmp")
1005
- @gensym_counter ||= 0
1006
- @gensym_counter += 1
1007
- "#{name}_#{@gensym_counter}"
1008
- end
1009
-
1010
- def include_source_metadata
1011
- # disabled for now -- we're still getting broken rendering with this feature on
1012
- return false
1013
- @include_source_metadata = RAILS_ENV == "development" && !ENV['DRYML_EDITOR'].blank? if @include_source_metadata.nil?
1014
- @include_source_metadata
1015
- end
1016
-
1017
- end
1018
-
1019
- end