hobo 1.0.3 → 1.1.0.pre0

Sign up to get free protection for your applications and to get access to all the features.
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