masterview 0.2.5 → 0.3.0

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 (155) hide show
  1. data/CHANGELOG +31 -1
  2. data/README +70 -69
  3. data/RELEASE_NOTES +70 -64
  4. data/Rakefile +26 -27
  5. data/TODO +13 -29
  6. data/doc/about.html +246 -0
  7. data/doc/configuration.html +49 -36
  8. data/doc/developer.html +423 -41
  9. data/doc/directives.html +139 -51
  10. data/doc/guide.html +19 -9
  11. data/doc/index.html +90 -224
  12. data/doc/installation.html +36 -28
  13. data/doc/media_list.html +30 -20
  14. data/doc/simple_diagram.html +3 -5
  15. data/doc/stylesheets/masterview.css +16 -1
  16. data/examples/rails_app_config/masterview/settings.rb +2 -1
  17. data/init.rb +1 -1
  18. data/lib/#ChangeLog# +6 -0
  19. data/lib/masterview/analyzer.rb +48 -34
  20. data/lib/masterview/attr_string_parser.rb +5 -1
  21. data/lib/masterview/case_insensitive_hash.rb +69 -0
  22. data/lib/masterview/{pathname_extensions.rb → core_ext/pathname.rb} +0 -0
  23. data/lib/masterview/{string_extensions.rb → core_ext/string.rb} +0 -0
  24. data/lib/masterview/deprecated/directive_base.rb +362 -0
  25. data/lib/masterview/directive_base.rb +201 -179
  26. data/lib/masterview/directive_dsl.rb +457 -0
  27. data/lib/masterview/directive_helpers.rb +28 -141
  28. data/lib/masterview/directive_load_path.rb +388 -0
  29. data/lib/masterview/directive_metadata.rb +377 -0
  30. data/lib/masterview/directive_registry.rb +259 -69
  31. data/lib/masterview/directives/.metadata +16 -0
  32. data/lib/masterview/directives/attr.rb +9 -8
  33. data/lib/masterview/directives/block.rb +11 -14
  34. data/lib/masterview/directives/check_box.rb +13 -18
  35. data/lib/masterview/directives/collection_select.rb +15 -29
  36. data/lib/masterview/directives/content.rb +9 -3
  37. data/lib/masterview/directives/else.rb +15 -13
  38. data/lib/masterview/directives/elsif.rb +14 -13
  39. data/lib/masterview/directives/eval.rb +20 -0
  40. data/lib/masterview/directives/form.rb +56 -9
  41. data/lib/masterview/directives/form_remote.rb +26 -0
  42. data/lib/masterview/directives/global_inline_erb.rb +10 -14
  43. data/lib/masterview/directives/hidden_field.rb +11 -20
  44. data/lib/masterview/directives/if.rb +13 -12
  45. data/lib/masterview/directives/image_tag.rb +20 -28
  46. data/lib/masterview/directives/import.rb +5 -12
  47. data/lib/masterview/directives/import_render.rb +7 -19
  48. data/lib/masterview/directives/insert_generated_comment.rb +8 -11
  49. data/lib/masterview/directives/javascript_include.rb +21 -12
  50. data/lib/masterview/directives/link_to.rb +14 -8
  51. data/lib/masterview/directives/link_to_function.rb +22 -0
  52. data/lib/masterview/directives/link_to_if.rb +15 -13
  53. data/lib/masterview/directives/link_to_remote.rb +13 -8
  54. data/lib/masterview/directives/omit_tag.rb +32 -16
  55. data/lib/masterview/directives/password_field.rb +10 -22
  56. data/lib/masterview/directives/radio_button.rb +11 -22
  57. data/lib/masterview/directives/replace.rb +7 -8
  58. data/lib/masterview/directives/select.rb +11 -24
  59. data/lib/masterview/directives/stylesheet_link.rb +20 -12
  60. data/lib/masterview/directives/submit.rb +11 -5
  61. data/lib/masterview/directives/text_area.rb +10 -23
  62. data/lib/masterview/directives/text_field.rb +10 -22
  63. data/lib/masterview/exceptions.rb +21 -0
  64. data/lib/masterview/extras/app/controllers/masterview_controller.rb +102 -75
  65. data/lib/masterview/extras/app/views/layouts/masterview_admin.rhtml +24 -23
  66. data/lib/masterview/extras/app/views/layouts/masterview_admin_config.rhtml +81 -0
  67. data/lib/masterview/extras/app/views/masterview/admin/configuration.rhtml +5 -1
  68. data/lib/masterview/extras/app/views/masterview/admin/create.rhtml +2 -2
  69. data/lib/masterview/extras/app/views/masterview/admin/directives.rhtml +5 -0
  70. data/lib/masterview/extras/app/views/masterview/admin/features.rhtml +5 -79
  71. data/lib/masterview/extras/app/views/masterview/admin/interact.rhtml +5 -0
  72. data/lib/masterview/extras/app/views/masterview/admin/list.rhtml +3 -71
  73. data/lib/masterview/extras/init_mv_admin_pages.rb +42 -23
  74. data/lib/masterview/filter_helpers.rb +26 -0
  75. data/lib/masterview/initializer.rb +99 -53
  76. data/lib/masterview/io.rb +19 -15
  77. data/lib/masterview/keyword_expander.rb +7 -2
  78. data/lib/masterview/masterview_info.rb +229 -23
  79. data/lib/masterview/masterview_version.rb +2 -2
  80. data/lib/masterview/parser.rb +275 -105
  81. data/lib/masterview/parser_helpers.rb +54 -0
  82. data/lib/masterview/rails_ext/action_controller_erb_direct.rb +29 -0
  83. data/lib/masterview/rails_ext/action_controller_reparse_checking.rb +27 -0
  84. data/lib/masterview/{extras/init_rails_erb_mv_direct.rb → rails_ext/action_view_erb_direct.rb} +12 -59
  85. data/lib/masterview/template_spec.rb +3 -2
  86. data/lib/masterview.rb +21 -12
  87. data/lib/rexml/parsers/baseparser_with_doctype_fix.rb +473 -0
  88. data/lib/rexml/parsers/sax2parser_with_doctype_fix.rb +243 -0
  89. data/test/directive_test_helper.rb +135 -0
  90. data/test/fixtures/directives/id_check.rb +18 -0
  91. data/test/fixtures/directives/test_directive_events.rb +70 -0
  92. data/test/test_helper.rb +18 -5
  93. data/test/tmp/views/layouts/product.rhtml +10 -10
  94. data/test/tmp/views/product/_form.rhtml +4 -4
  95. data/test/tmp/views/product/_product.rhtml +3 -3
  96. data/test/tmp/views/product/destroy.rhtml +5 -5
  97. data/test/tmp/views/product/edit.rhtml +4 -4
  98. data/test/tmp/views/product/list.rhtml +3 -3
  99. data/test/tmp/views/product/new.rhtml +4 -4
  100. data/test/tmp/views/product/show.rhtml +2 -2
  101. data/test/unit/attr_string_parser_test.rb +105 -0
  102. data/test/unit/case_insensitive_hash_mod_test.rb +104 -0
  103. data/test/unit/config_settings_test.rb +13 -1
  104. data/test/unit/default_generate_mio_filter_test.rb +3 -3
  105. data/test/unit/deprecated_directive_base_test.rb +30 -0
  106. data/test/unit/directive_attr_test.rb +111 -35
  107. data/test/unit/directive_base_test.rb +520 -1
  108. data/test/unit/directive_block_test.rb +30 -22
  109. data/test/unit/directive_content_test.rb +24 -11
  110. data/test/unit/directive_else_test.rb +18 -15
  111. data/test/unit/directive_elsif_test.rb +17 -15
  112. data/test/unit/directive_form_remote_test.rb +59 -0
  113. data/test/unit/directive_form_test.rb +31 -39
  114. data/test/unit/directive_global_inline_erb_test.rb +28 -17
  115. data/test/unit/directive_grid_test_notready.rb +38 -0
  116. data/test/unit/directive_helpers_test.rb +39 -0
  117. data/test/unit/directive_hidden_field_test.rb +44 -29
  118. data/test/unit/directive_if_test.rb +10 -7
  119. data/test/unit/directive_image_tag_test.rb +69 -61
  120. data/test/unit/directive_import_render_test.rb +28 -38
  121. data/test/unit/directive_import_test.rb +16 -14
  122. data/test/unit/directive_insert_generated_comment_test.rb +32 -0
  123. data/test/unit/directive_javascript_include_test.rb +40 -43
  124. data/test/unit/directive_link_to_function_test.rb +40 -0
  125. data/test/unit/directive_link_to_if_test.rb +52 -12
  126. data/test/unit/directive_link_to_remote_test.rb +58 -0
  127. data/test/unit/directive_link_to_test.rb +46 -31
  128. data/test/unit/directive_load_path_test.rb +257 -0
  129. data/test/unit/directive_metadata_test.rb +313 -0
  130. data/test/unit/directive_omit_tag_test.rb +73 -21
  131. data/test/unit/directive_password_field_test.rb +44 -38
  132. data/test/unit/directive_registry_test.rb +44 -0
  133. data/test/unit/directive_replace_test.rb +28 -12
  134. data/test/unit/directive_stylesheet_link_test.rb +43 -36
  135. data/test/unit/directive_submit_test.rb +29 -30
  136. data/test/unit/directive_text_area_test.rb +40 -36
  137. data/test/unit/directive_text_field_test.rb +44 -38
  138. data/test/unit/example_directive_child_events_test.rb +41 -0
  139. data/test/unit/example_test.rb +31 -4
  140. data/test/unit/file_mio_test.rb +18 -13
  141. data/test/unit/filter_helpers_test.rb +10 -8
  142. data/test/unit/find_directive_parent_test.rb +174 -0
  143. data/test/unit/keyword_expander_test.rb +4 -2
  144. data/test/unit/mio_test.rb +18 -11
  145. data/test/unit/mtime_string_hash_mio_tree_test.rb +5 -1
  146. data/test/unit/parser_test.rb +41 -29
  147. data/test/unit/pathname_extensions_test.rb +1 -1
  148. data/test/unit/run_parser_test.rb +2 -2
  149. data/test/unit/simplified_directive_base_test.rb +256 -0
  150. data/test/unit/string_hash_mio_test.rb +5 -1
  151. data/test/unit/template_file_watcher_test.rb +2 -2
  152. data/test/unit/template_test.rb +221 -46
  153. metadata +86 -45
  154. data/lib/masterview/directives/testfilter.rb +0 -55
  155. data/lib/masterview/extras/init_rails_reparse_checking.rb +0 -62
@@ -0,0 +1,457 @@
1
+ =begin
2
+
3
+ Directive DSL
4
+
5
+ class MyDirective < DirectiveBase
6
+ attr_arg :obj, :quote => true
7
+ attr_arg :method { 'override' }
8
+ attr_arg :foo { |x| x.downcase }
9
+ attr_arg :collection, :quote => true
10
+ attr_arg :value_method, :quote => true
11
+ attr_arg :text_method, :quote => true
12
+ attr_arg :options, :default => {}
13
+ attr_arg :html_options, :append_element_attrs => [:common_html, :size]
14
+ attr_arg :name do |value, args|
15
+ if value.respond_to?(:include?) and value.include?('=>')
16
+ args.unshift value
17
+ nil
18
+ else
19
+ value
20
+ end
21
+ end
22
+ attr_arg :params, :varargs => true #collect remaining args (if any) put in array, like *params
23
+
24
+ event :before_stag do
25
+ render erb_content( 'text_area', :obj, :method, :foo )
26
+ end
27
+
28
+ event :content, :render => :nothing #rolls up all content between after_stag and before_etag
29
+
30
+ also can be done
31
+
32
+ event :content do
33
+ render :nothing # causes nothing to be rendered
34
+ end
35
+
36
+ event :stag do
37
+ # unless render something or render :nothing is called, then dcs.render will still occur implicitely
38
+ end
39
+
40
+ event :etag do
41
+ con = render_result() # get the dcs.render result so we can modify it
42
+ con.gsub!( /foo/, 'bar' )
43
+ render con
44
+ end
45
+
46
+ event :element do # this will allow replacement of entire element and children
47
+ render erb_eval( 'execute something' )
48
+ end
49
+ end
50
+
51
+ event listing
52
+ ----------------
53
+ before_stag
54
+ stag
55
+ after_stag
56
+ content
57
+ before_etag
58
+ etag
59
+ after_etag
60
+ element
61
+
62
+ these might not be exposed as events but still using direct methods
63
+ characters
64
+ comment
65
+
66
+
67
+ =end
68
+
69
+ module MasterView
70
+
71
+ # The DirectiveProcessing module contains the internal mechanisms
72
+ # which support the DirectiveDSL notation for attribute argument definition
73
+ # and document processing event processing definition in directive
74
+ # implementation classes.
75
+ module DirectiveProcessing
76
+
77
+ # holds the attr_arg definition, how a positional subargument will be
78
+ # parsed out of the directive attr_value
79
+ class AttrArgDef
80
+ attr_reader :name, :options, :proc
81
+ def initialize(name, options, proc)
82
+ @name = name
83
+ @options = options
84
+ @proc = proc
85
+ end
86
+
87
+ # returns the optional default if one was specified
88
+ def optional_default
89
+ (@options.nil?) ? nil : @options[:default]
90
+ end
91
+ end
92
+
93
+ # holds the event definition which contains the meat of what will happen
94
+ # for each parser event. The events are more finely grained than the rexml
95
+ # listener events.
96
+ class EventDef
97
+ BaseEventMapping = {
98
+ /^(before_|after_)?(child_.*|descendant_.*)?stag$/ => ['\2stag'],
99
+ /^(before_|after_)?(child_.*|descendant_.*)?etag$/ => ['\2etag'],
100
+ /^(child_.*|descendant_.*)?content$/ => ['\1etag'],
101
+ /^(child_.*|descendant_.*)?element$/ => ['\1stag', '\1etag']
102
+ }
103
+
104
+ # true if is valid event name
105
+ def self.valid_event_mapping?(event_name_sym)
106
+ event_name_str = event_name_sym.to_s
107
+ BaseEventMapping.keys.any? { |x| x =~ event_name_str }
108
+ end
109
+
110
+ # returns base method sym array for event_name_sym
111
+ # :before_stag => :stag,
112
+ # :stag => :stag,
113
+ # :after_stag => :stag,
114
+ # :content => :etag,
115
+ # :before_etag => :etag,
116
+ # :etag => :etag,
117
+ # :after_etag => :etag,
118
+ # :element => :stag, :etag
119
+ def self.base_methods_from_event(event_name_sym)
120
+ event_name_str = event_name_sym.to_s
121
+ base_pair = BaseEventMapping.find {|k,v| k =~ event_name_str }
122
+ base_name_unsub_array = base_pair[1]
123
+ base_name_unsub_array.collect { |x| event_name_str.sub(base_pair[0], x).to_sym }
124
+ end
125
+
126
+ # return array of events to check for the base
127
+ # :stag => [:before_stag, :stag, :after_stag],
128
+ # :etag => [:content, :before_etag, :etag, :after_etag, :element]
129
+ def self.method_events_from_base(base_sym)
130
+ base_str = base_sym.to_s
131
+ if match = base_str.match( /(.*)stag/ )
132
+ [
133
+ ('before_'+base_str).to_sym,
134
+ base_sym,
135
+ ('after_'+base_str).to_sym
136
+ ]
137
+ elsif match = base_str.match( /(.*)etag/ )
138
+ [
139
+ (match[1]+'content').to_sym,
140
+ ('before_'+base_str).to_sym,
141
+ base_sym,
142
+ ('after_'+base_str).to_sym,
143
+ (match[1]+'element').to_sym
144
+ ]
145
+ end
146
+ end
147
+
148
+ attr_reader :name, :options, :proc
149
+
150
+ def initialize(name, options, proc)
151
+ raise "Invalid event name=#{name}" unless self.class.valid_event_mapping?(name)
152
+ @name = name
153
+ @options = options
154
+ @proc = proc
155
+ end
156
+
157
+ # execute proc/block in the context of an instance object (instance_eval)
158
+ def exec(instance_obj)
159
+ instance_obj.instance_eval &proc unless proc.nil?
160
+ end
161
+ end
162
+
163
+ class DirectiveClassDef
164
+ attr_reader :attr_arg_defs, :event_defs
165
+ def initialize
166
+ @attr_arg_defs = [] #list in order of position
167
+ @attr_arg_def_map = {} #map to def from symname
168
+ @event_defs = {}
169
+ end
170
+
171
+ def add_attr_arg_def(attr_arg_def)
172
+ if @attr_arg_def_map[attr_arg_def.name].nil? # prevent multiple calls to this, rake calling multiple times?
173
+ @attr_arg_def_map[attr_arg_def.name] = attr_arg_def
174
+ @attr_arg_defs.push attr_arg_def
175
+ end
176
+ end
177
+
178
+ def set_event_def(event_def)
179
+ @event_defs[event_def.name] = event_def
180
+ end
181
+
182
+ def find_attr_arg_def_by_name(name)
183
+ @attr_arg_def_map[name]
184
+ end
185
+ end
186
+
187
+ class RenderAccumulator
188
+ attr_accessor :render_call_found
189
+ attr_writer :render_result, :render_result_block
190
+ def initialize
191
+ @method_level_accumulator = []
192
+ reset_event_level_vars
193
+ end
194
+
195
+ def reset_event_level_vars
196
+ reset_event_level_content_array
197
+ @render_call_found = false
198
+ @render_result = nil
199
+ @render_result_block = nil
200
+ end
201
+
202
+ def reset_event_level_content_array
203
+ @event_level_accumulator = []
204
+ end
205
+
206
+ def reset_event_level_content_array_render_nothing
207
+ reset_event_level_content_array
208
+ @render_call_found = true # we don't want to render anything
209
+ end
210
+
211
+ def method_level_content_array
212
+ @method_level_accumulator
213
+ end
214
+
215
+ def event_level_content_array
216
+ @event_level_accumulator
217
+ end
218
+
219
+ def add_event_content_to_method_content
220
+ @method_level_accumulator.concat @event_level_accumulator
221
+ @event_level_accumulator = []
222
+ end
223
+
224
+ def replace_all_method_content_with_event_content
225
+ @method_level_accumulator = @event_level_accumulator
226
+ @event_level_accumulator = []
227
+ end
228
+
229
+ def call_render_result_block
230
+ result = nil
231
+ unless @render_result_block.nil?
232
+ result = @render_result_block.call
233
+ @render_result_block = nil #only allow this call once
234
+ end
235
+ result
236
+ end
237
+
238
+ def add_to_event_content(value)
239
+ @render_call_found = true
240
+ @event_level_accumulator.push(value)
241
+ end
242
+
243
+ end
244
+
245
+ end
246
+
247
+ # Mixin for directive implementation classes to support the DSL for
248
+ # attr_arg attribute value argument parsing and event handler
249
+ # registration for document template event processing.
250
+ #
251
+ # Relies on the mixing class to provide accessor services to
252
+ # element processing context information (element_attrs, element_tag)
253
+ # and the attr_value directive attribute value string,
254
+ # along with erb output services (erb_content, erb_eval).
255
+ # Assumes DirectiveHelpers also mixed in.
256
+ #
257
+ module DirectiveDSL
258
+
259
+ module ClassMethods
260
+ # retrieve my class specific DirectiveClassDef, creating if necessary
261
+ def directive_class_def
262
+ @@directive_class_defs ||= {}
263
+ @@directive_class_defs[object_id] ||= DirectiveProcessing::DirectiveClassDef.new
264
+ end
265
+
266
+ # add a postional subargument parsing def which will be applied to attr_value
267
+ def attr_arg(name, options={}, &block)
268
+ self.directive_class_def.add_attr_arg_def(DirectiveProcessing::AttrArgDef.new(name, options, block))
269
+ end
270
+
271
+ # set the definition for an event which will be invoked at appropriate time in
272
+ # directive rendering lifecycle, also create the base method if it does not
273
+ # already exist.
274
+ def event(name, options={}, &block)
275
+ directive_class_def = self.directive_class_def
276
+ directive_class_def.set_event_def(DirectiveProcessing::EventDef.new(name, options, block))
277
+ base_event_name_sym_array = DirectiveProcessing::EventDef.base_methods_from_event(name)
278
+ base_event_name_sym_array.each do |base_event_name_sym|
279
+ base_event_name = base_event_name_sym.to_s
280
+ directive_class_def.set_event_def(DirectiveProcessing::EventDef.new(base_event_name_sym,{},nil)) if directive_class_def.event_defs[base_event_name_sym].nil?
281
+ event_mapping = DirectiveProcessing::EventDef.method_events_from_base(base_event_name_sym)
282
+ unless self.class.respond_to?( base_event_name_sym ) or event_mapping.nil? # unless already defined method or no mapping
283
+ event_eval_code = <<-END
284
+ def #{base_event_name}(dcs)
285
+ @method_content_ref ||= {} # create references to content created by method
286
+ prepare_arg_instance_vars
287
+ @render_accumulator = DirectiveProcessing::RenderAccumulator.new
288
+ #{event_mapping.inspect}.each do |full_event_name|
289
+ event_def = self.class.directive_class_def.event_defs[full_event_name]
290
+ unless event_def.nil?
291
+ prepare(event_def)
292
+ event_def.exec(self)
293
+ handle_render(event_def)
294
+ end
295
+ end
296
+ @method_content_ref[:#{base_event_name}] = @render_accumulator.method_level_content_array # add reference
297
+ @method_content_ref[:#{base_event_name}] # return the reference so we can empty later if necessary
298
+ end
299
+ END
300
+ self.class_eval event_eval_code # todo could pass in filename, but would have to use self.to_s.downcase and trim off leading modules
301
+ end
302
+ end
303
+ end
304
+ end
305
+
306
+ # add class methods for DSL declarations to class which is mixing in DirectiveDSL
307
+ def self.included(mixing_class) #:nodoc:
308
+ mixing_class.extend(ClassMethods)
309
+ end
310
+
311
+ def prepare_arg_instance_vars
312
+ attr_arg_defs = self.class.directive_class_def.attr_arg_defs
313
+ unless attr_arg_defs.empty? # if we have any attr_arg_defs, prepare the parsed instance vars for these args
314
+ args = parse(self.attr_value)
315
+ attr_arg_defs.each do |attr_arg_def|
316
+ eval_attr_arg(args, attr_arg_def)
317
+ end
318
+ end
319
+ end
320
+
321
+ def prepare(event_def)
322
+ @render_accumulator.reset_event_level_vars
323
+ case event_def.name.to_s
324
+ when /^before_(.*_)?stag$/
325
+ when /^after_(.*_)?stag$/
326
+ when /^(.*_)?stag$/
327
+ @render_accumulator.render_result_block = lambda { @render_accumulator.render_result = @directive_call_stack.render }
328
+ when /^(.*_)?content$/
329
+ @render_accumulator.render_result_block = lambda { @directive_call_stack.context[:tag].content.join }
330
+ when /^before_(.*_)?etag$/
331
+ when /^after_(.*_)?etag$/
332
+ when /^(.*_)?etag$/
333
+ @render_accumulator.render_result_block = lambda { @render_accumulator.render_result = @directive_call_stack.render }
334
+ when /^(.*_)?element$/
335
+ @render_accumulator.render_result_block = lambda { (
336
+ @method_content_ref[event_def.name.to_s.sub(/^(.*_)?element$/, '\1stag').to_sym] + # fine proper stag content ref
337
+ @directive_call_stack.context[:tag].content +
338
+ @render_accumulator.event_level_content_array).join }
339
+ end
340
+ end
341
+
342
+ def render_result
343
+ @render_accumulator.call_render_result_block
344
+ end
345
+
346
+ def render(value)
347
+ if value == :nothing
348
+ @render_accumulator.reset_event_level_content_array_render_nothing
349
+ else
350
+ @render_accumulator.add_to_event_content(value)
351
+ end
352
+ end
353
+
354
+ def handle_render(event_def)
355
+ return if event_def.options[:render] == :nothing and (event_def.name != :content and event_def.name != :element)
356
+ case event_def.name.to_s
357
+ when /^before_(.*_)?stag$/
358
+ @render_accumulator.add_event_content_to_method_content
359
+ when /^after_(.*_)?stag$/
360
+ @render_accumulator.add_event_content_to_method_content
361
+ when /^(.*_)?stag$/
362
+ render(render_result) unless @render_accumulator.render_call_found # call default dcs.render if render call not made
363
+ @render_accumulator.add_event_content_to_method_content
364
+ when /^(.*_)?content$/
365
+ if event_def.options[:render] == :nothing
366
+ @directive_call_stack.context[:tag].content = nil
367
+ else
368
+ @directive_call_stack.context[:tag].content = @render_accumulator.event_level_content_array
369
+ end
370
+ when /^before_(.*_)?etag$/
371
+ @render_accumulator.add_event_content_to_method_content
372
+ when /^after_(.*_)?etag$/
373
+ @render_accumulator.add_event_content_to_method_content
374
+ when /^(.*_)?etag$/
375
+ render(render_result) unless @render_accumulator.render_call_found # call default dcs.render if render call not made
376
+ @render_accumulator.add_event_content_to_method_content
377
+ when /^(.*_)?element$/ # we need to affect only things on and below our callstack so surrounging directives will render (like if)
378
+ @method_content_ref[event_def.name.to_s.sub(/^(.*_)?element$/, '\1stag').to_sym].clear # we will zero the stag reference which will clear it from the output
379
+ @directive_call_stack.context[:tag].content = nil
380
+ @render_accumulator.reset_event_level_content_array if event_def.options[:render] == :nothing # zero content if render :nothing
381
+ @render_accumulator.replace_all_method_content_with_event_content # replace everything with event content
382
+ end
383
+ end
384
+
385
+ # convert symbol name to instance var name, it appends a @ if not
386
+ # already starting with it. Handles strings or symbols.
387
+ # returns symbol of complete name :@name
388
+ def name_to_instance_var_name(name)
389
+ str_name = name.to_s
390
+ str_name = '@'+str_name unless str_name[0] == '@'
391
+ str_name.to_sym
392
+ end
393
+
394
+ # evaluate the attr_arg using the current attr_value, attributes, etc.
395
+ # This is the method that takes the definition, determines the instance value
396
+ # and stores it.
397
+ def eval_attr_arg(args, attr_arg_def)
398
+ name = attr_arg_def.name
399
+ options = attr_arg_def.options
400
+ proc = attr_arg_def.proc
401
+ instance_var_name = name_to_instance_var_name(name)
402
+ if(options and options[:varargs])
403
+ value = args.clone # value set to array of all remaining args
404
+ args.clear
405
+ else
406
+ value = args.shift
407
+ if options
408
+ value = quote_if(value) if options[:quote]
409
+ if merge_array = options[:append_element_attrs]
410
+ merge_array = [merge_array] unless merge_array.is_a? Array
411
+ merge_opts = {}
412
+ merge_array.each do |merge_item|
413
+ if merge_item == :common_html
414
+ merge_opts.merge! common_html_options
415
+ else
416
+ merge_item_sym = merge_item.to_sym
417
+ if (v = element_attrs[merge_item_sym]) : merge_opts[merge_item_sym] = v; end
418
+ end
419
+ end
420
+ value = merge_hash_into_str(merge_opts, value)
421
+ end
422
+ end
423
+ end
424
+ unless proc.nil?
425
+ if proc.arity < 1 # simply set value to block's return
426
+ value = proc.call
427
+ elsif proc.arity == 1 # |x| - pass value into block to allow it to manipulate
428
+ value = proc.call(value)
429
+ elsif proc.arity == 2 # |value, args| - pass value and remaining args array
430
+ value = proc.call(value, args)
431
+ elsif proc.arity == 3 # |value, args, instance| - pass value, remaining args, and directive instance
432
+ value = proc.call(value, args, self)
433
+ end
434
+ end
435
+ self.instance_variable_set(instance_var_name, value) # set @foo = value
436
+ end
437
+
438
+ # safely checks for existence of instance variable without throwing NameError
439
+ # true if instance_variable exists
440
+ def instance_variable_exists?(instance_variable_name)
441
+ self.instance_variables.include?(instance_variable_name.to_s)
442
+ end
443
+
444
+ #inside characters, cdata, or comment you can call this to get the characters passed
445
+ def data
446
+ @directive_call_stack.context[:content_part]
447
+ end
448
+
449
+ #set the data that will be passed to characters, cdata, or comment directives
450
+ def data=(data)
451
+ @directive_call_stack.context[:content_part]=data
452
+ end
453
+
454
+
455
+ end
456
+
457
+ end
@@ -1,9 +1,9 @@
1
1
  module MasterView
2
2
 
3
3
  # Mixin services for directive implementation classes.
4
- #
4
+ #
5
5
  # Subclasses of MasterView::DirectiveBase inherit this mixin.
6
- #
6
+ #
7
7
  module DirectiveHelpers
8
8
 
9
9
  CRLF = "\r\n"
@@ -18,27 +18,6 @@ module MasterView
18
18
  # end of ERB whose evaluation results in content in the document
19
19
  ERB_CONTENT_END = ' %>'
20
20
 
21
- ####OBSOLETE: SWEEP AND REMOVE
22
- ERB_START = ERB_EVAL_START #:nodoc: #WAS: '<% '
23
- ERB_EVAL = ERB_CONTENT_START #:nodoc: #WAS: '<%= '
24
- ERB_END = ERB_CONTENT_END #:nodoc: #WAS: ' %>'
25
-
26
- #convenience constants defined to allow priority to directives
27
- #higher priority (lower value) will be executed first in chain
28
- module DirectivePriorities
29
- Highest = 0
30
- UltraHigh = 0x3FFFFFFF/16
31
- VeryHigh = 0x3FFFFFFF/8
32
- High = 0x3FFFFFFF/4
33
- MediumHigh = 0x3FFFFFFF/3
34
- Medium = 0x3FFFFFFF/2
35
- MediumLow = (0x3FFFFFFF/3)*2
36
- Low = (0x3FFFFFFF/4)*3
37
- VeryLow = (0x3FFFFFFF/8)*7
38
- UltraLow = (0x3FFFFFFF/16)*15
39
- Lowest = 0x3FFFFFFF
40
- end
41
-
42
21
  # convert render_partial_name to file_name, ex foo/bar to foo/_bar.rhtml
43
22
  def render_partial_name_to_file_name(render_partial_name, default_extension)
44
23
  pathname = Pathname.for_path(render_partial_name)
@@ -49,22 +28,32 @@ module MasterView
49
28
  path = (dir_pathname+filename).to_s
50
29
  end
51
30
 
52
-
53
- # find the last string that fully matches exactly the
31
+
32
+ # find the last string that fully matches exactly the
54
33
  # parent tags content string array
55
- # It looks for something that has been output as a unit in
34
+ # It looks for something that has been output as a unit in
56
35
  # the array not a substring
57
- # returns the ref to the string which you can operate on
36
+ # returns the ref to the string which you can operate on
58
37
  # using replace
59
38
  def find_last_in_parent(tag, full_string)
60
39
  ret = nil
61
40
  parent = tag.parent
62
41
  unless parent.nil?
63
42
  parent.content.reverse.each do |str|
64
- if str == full_string
65
- ret = str
66
- break
67
- end
43
+ if str.kind_of? Array # if it is a nested array check inside of it
44
+ str.reverse.each do |s|
45
+ if s == full_string
46
+ ret = s
47
+ break
48
+ end
49
+ end
50
+ break if ret
51
+ else
52
+ if str == full_string
53
+ ret = str
54
+ break
55
+ end
56
+ end
68
57
  end
69
58
  end
70
59
  ret
@@ -89,124 +78,22 @@ module MasterView
89
78
  m[1]
90
79
  end
91
80
 
92
- #returns an array of args by parsing and evaling the str value passed in
93
- #uses evaling, so can't have any variables only simple strings, numbers, booleans
94
- def parse_eval_into_array(value)
95
- return [] if value.nil? || value.empty?
96
- val = value.strip
97
- args = []
98
- until val.empty?
99
- if val =~ /^[:'"%\[{&*]/ #starts with quote or ruby lang char
100
- v = nil
101
- val = '{'+val+'}' if val =~ /^:/ #starts with colon, assume hash so wrap with brackets
102
- eval 'v = '+ val #rest is all evaled
103
- if v.is_a? Array
104
- args += v
105
- else
106
- args << v
107
- end
108
- break
109
- else
110
- unquoted_string = val.slice!( /^[^,]+/ ) #pull off everything up to a comma
111
- unquoted_string.strip!
112
- args.push unquoted_string
113
- val.slice!( /^,/ ) #strip off comma if exists
114
- val.strip!
115
- end
116
- end
117
- args
118
- end
119
-
120
- #returns a hash, for values that are not already part of hash it adds them using default_key
121
- #uses evaling so it cannot have any variables or non-simple types
122
- def parse_eval_into_hash(value, default_key)
123
- h = {}
124
- a = parse_eval_into_array(value)
125
- a.each do |v|
126
- if v.is_a?(Hash)
127
- h.merge!(v)
128
- else #it adds any additional non-hash args using default key, if key,val exists, it changes to array and appends
129
- prev = h[default_key]
130
- if prev.nil? #nil just add it
131
- h[default_key] = v
132
- elsif prev.is_a?(Array) #was array, concat
133
- h[default_key] = prev+v
134
- else #anything else, make it into array
135
- h[default_key] = [prev, v]
136
- end
137
- end
138
- end
139
- h
140
- end
141
-
142
81
  #parse into array of strings, containing the various arguments without evaling
143
82
  #looks for %q{}, %q[], hash, array, function call using (), values deliminated by commas,
144
83
  def parse(str)
145
84
  AttrStringParser.parse(str)
146
85
  end
147
86
 
148
-
149
- #remove any strings that were prepended to the hashes, typically these are overridden by other values, so
150
- # we need to strip them off leaving only the hashes, returns a string with only hashes
151
- def remove_prepended_strings(full_string)
152
- return full_string if full_string.nil? || full_string.strip.empty?
153
- hashes = full_string.scan( /(\{?)\s*(\S+\s*=>.*)/ ).flatten
154
- hashes.join.strip
155
- end
156
-
157
- #merge hash_to_merge values into the hash contained in the full_string, hash_arg is zero based index of which
158
- #hash this needes to be merged to if there are multiple ones.
159
- def merge_into_embedded_hash(full_string, hash_arg, hash_to_merge)
160
- return full_string if hash_to_merge.empty?
161
- full_string ||= ""
162
- sorted_hash_to_merge = hash_to_merge.sort { |a,b| a.to_s <=> b.to_s } #sort, remember the keys might be symbols so use to_s
163
- str_to_merge = sorted_hash_to_merge.collect{ |h,v| "#{h.inspect} => #{v.inspect}" }.join(', ')
164
-
165
- hashes = full_string.scan( /(\{?[^{}]+=>[^{}]+\}?)\s*,?\s*/ ).flatten
166
- hash_str = hashes[hash_arg] #be careful to use methods to update string in place or else put back in hash
167
-
168
- if hash_str.nil?
169
- hashes.each do |v| #make sure each prior hash has brackets, since we are adding a hash
170
- unless v.index '}'
171
- v.insert(0, '{')
172
- v.insert(-1, '}')
173
- end
174
- end
175
- hashes[hash_arg] = hash_str = ""
176
- end
177
-
178
- closing_brack = hash_str.index '}'
179
- if closing_brack
180
- hash_str.insert(closing_brack, ', '+str_to_merge)
181
- else
182
- hash_str << ', ' unless hash_str.empty?
183
- hash_str << str_to_merge
184
- end
185
-
186
- hashes.join(', ')
187
- end
188
-
189
- #return attributes with lowercase keys
190
- def lowercase_attribute_keys(attributes)
191
- lcattrs = {}
192
- attributes.each { |k,v| lcattrs[k.downcase] = v }
193
- lcattrs
194
- end
195
-
196
- #return attributes with lowercase keys and values
197
- def lowercase_attribute_keys_and_values(attributes)
198
- lcattrs = {}
199
- attributes.each { |k,v| lcattrs[k.downcase] = v.downcase }
200
- lcattrs
87
+ #add single quotes around string
88
+ def quote(str, quote_char='\'')
89
+ quote_char+str+quote_char
201
90
  end
202
91
 
203
- # using hash, symbolize keys, sort keys, serialize to string
204
- def symbolize_sort_and_serialize_hash_to_str(hash)
205
- symbolized = {}
206
- hash.each{ |k,v| symbolized[k.to_sym] = v } #symbolize
207
- sorted = symbolized.sort{ |a,b| a[0].to_s <=> b[0].to_s } # sort the keys
208
- sorted_strings = sorted.collect{ |k,v| "#{k.inspect} => '#{v}'"} # create strings
209
- sorted_strings.join(', ') # finally combine them
92
+ # adds single quotes around string if it is a simple
93
+ # word [a-zA-Z0-9_]* otherwise return existing string
94
+ # also quote if empty string
95
+ def quote_if(str)
96
+ (str =~ /^\w*$/) ? quote(str) : str
210
97
  end
211
98
 
212
99
  end