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
@@ -10,25 +10,146 @@ module MasterView
10
10
  #
11
11
  class DirectiveRegistry
12
12
 
13
- # Answer the list of directive classes
13
+ DEBUG_TRACE_LOADING = false #:nodoc: ##DEBUG##
14
+ TRACE_MD_HARDENING_SUBJECT = nil #'MasterView::DirectiveTests::TestEventsDirective'
15
+ DEBUG_BUILTIN_DIRECTIVE_REGISTRATION = true #:nodoc:
16
+ DEBUG_CURRENT = false #:nodoc:
17
+
18
+ # The DirectiveRegistry for managing the loaded directives available
19
+ # for processing template markup.
20
+ # #
21
+ # DirectiveRegistry.current is ordinarily configured to load
22
+ # the directives registered on the DirectiveLoadPath.current.
23
+ def self.current
24
+ @@current
25
+ end
26
+
27
+ # Set the current directives registry for template processing.
28
+ # Ordinarily done once during masterview initialization.
29
+ def self.current=(registry) #:nodoc:
30
+ if self.class_variables.include?('@@current') && @@current && registry
31
+ # HACK: need to preserve the list of loaded classes.
32
+ # this shouldn't ordinarily happen, but repeated class loading
33
+ # occurs during, ah, say... test suite runs, yes!!
34
+ registry.update_loaded_classes_hack( @@current.loaded_classes )
35
+ end
36
+ @@current = registry
37
+ if DEBUG_CURRENT
38
+ STDOUT.puts "\n****#{self.name}.current set: #{current.object_id}"
39
+ STDOUT.puts "...#{current.inspect}\n"
40
+ end
41
+ end
42
+
43
+ def update_loaded_classes_hack(already_loaded_classes) #:nodoc:
44
+ @loaded_classes.concat( already_loaded_classes )
45
+ end
46
+
47
+ # Register default namespaces for directive metadata
48
+ # Should be invoked once during MasterView initialization
49
+ def self.register_default_namespaces(ns_prefix_masterview, ns_prefix_extensions) #:nodoc:
50
+ raise ArgumentError, "Invalid masterview namespace prefix '#{ns_prefix_masterview}'" if ns_prefix_masterview[-1..-1] != ':'
51
+ raise ArgumentError, "Invalid extensions namespace prefix '#{ns_prefix_extensions}'" if ns_prefix_extensions[-1..-1] != ':'
52
+ @@metadata_defaults_masterview = {
53
+ :namespace => ns_prefix_masterview[0...-1],
54
+ :namespace_prefix => ns_prefix_masterview,
55
+ }
56
+ @@metadata_defaults_extensions = {
57
+ :namespace => ns_prefix_extensions[0...-1],
58
+ :namespace_prefix => ns_prefix_extensions,
59
+ }
60
+ #assert NotReallyNecessary, 'because we'll just make sure we code up the right stuff here'
61
+ DirectiveMetadata.validate_metadata_props! @@metadata_defaults_masterview
62
+ DirectiveMetadata.validate_metadata_props! @@metadata_defaults_extensions
63
+ end
64
+
65
+ # Answer the namespace prefix of directives in the standard MasterView namespace.
66
+ def mv_namespace_prefix
67
+ ##ISSUE: needs work?? Does this need to be an inst var of the registry??
68
+ @@metadata_defaults_masterview[:namespace_prefix]
69
+ end
70
+
71
+ # Answer the namespace prefix of directives in the default MasterView
72
+ # extensions directives namespace.
73
+ def mv_extensions_namespace_prefix
74
+ ##ISSUE: needs work?? Does this need to be an inst var of the registry??
75
+ @@metadata_defaults_extensions[:namespace_prefix]
76
+ end
77
+
78
+ # Answer the list of directive classes which are loaded in the current configuration.
14
79
  attr_reader :loaded_classes
15
80
 
16
- def initialize() #:nodoc:
81
+ # Answer a list of all namespaces in use
82
+ def loaded_namespaces
83
+ @directive_namespaces #??.clone for paranoid safety?
84
+ end
85
+
86
+ # Answer the fully-qualified names of the registered directives
87
+ def registered_directive_names
88
+ @directive_classes.keys
89
+ end
90
+
91
+ # Answer the the registered directives
92
+ def registered_directives
93
+ @directive_classes.values
94
+ end
95
+
96
+ # Hash containing default directive metadata properties
97
+ # Used during directive path loading to exploit default config specs.
98
+ def metadata_defaults #:nodoc:
99
+ @@metadata_defaults_masterview
100
+ end
101
+
102
+ # Hash containing default directive metadata properties
103
+ # Used during directive path loading to exploit default config specs.
104
+ def metadata_defaults_extensions #:nodoc:
105
+ @@metadata_defaults_extensions
106
+ end
107
+
108
+ # internal mechanism to provide directory/path load context during directive class loading
109
+ attr_reader :load_context #:nodoc:
110
+ def set_load_context(context_settings, check_for_conflict=true) #:nodoc:
111
+ if check_for_conflict && context_settings && @load_context
112
+ raise RuntimeError, "Directive load operation already in progress: current context=#{@load_context.inspect}, requested context=#{context_settings.inspect}"
113
+ end
114
+ @load_context = context_settings
115
+ end
116
+
117
+ def initialize()
17
118
  @loaded_classes = []
119
+ set_load_context( nil )
18
120
  clear_directive_maps()
121
+ #STDOUT.puts "\n###Created DirectiveRegistry=#{self.object_id}"
122
+ #STDOUT.puts "...loaded_classes=#{self.loaded_classes.inspect}"
123
+ #STDOUT.puts "...load_context=#{self.load_context.inspect}"
124
+ end
125
+
126
+ protected
127
+ def clear_directive_maps() #:nodoc:
128
+ @directive_namespaces = []
129
+ @directive_classes = {} # map fully qualified directive attr name to directive_class
130
+ @global_directives = [] # conditional global directives
131
+ @auto_directives = [] # unconditional global directives
19
132
  end
133
+ public #nasty modal access specification mechanism
20
134
 
21
135
  # Register a directive implementation.
22
136
  #
23
137
  # A directive is ordinarily a subclass of MasterView::DirectiveBase.
24
138
  #
25
- #--
26
- #TODO: A directive implementation must support the following services:....
27
- #++
28
- #
29
139
  def register_directive(directive_class)
30
- #TODO: ensure that the directive impl supports required prototcol (i/f check...)?
140
+ # harden the DirectiveMetadata by filling in unspecified values from defaults
141
+ #assert directive_class.ancestors.include? DirectiveMetadata
142
+ ##AARGH: can't do this yet, the timing is premature [DJL 06-Oct-2006]
143
+ #PUNT: md_defaults = load_context.nil? ? metadata_defaults_extensions : load_context[:metadata_defaults]
144
+ #PUNT: directive_class.harden_metadata( md_defaults )
31
145
  @loaded_classes << directive_class
146
+ ##DEBUG##
147
+ if TRACE_MD_HARDENING_SUBJECT and directive_class.name == TRACE_MD_HARDENING_SUBJECT
148
+ STDOUT.puts "\nREGISTERED DIRECTIVE #{directive_class.name}"
149
+ STDOUT.puts "...TOO SOON TO HARDEN METADATA"
150
+ STDOUT.puts "...#{directive_class.metadata_values.object_id}: #{directive_class.metadata_values.inspect}\n"
151
+ end
152
+ ##DEBUG##
32
153
  end
33
154
 
34
155
  # Answer the (base) names of the loaded directive classes.
@@ -47,109 +168,178 @@ module MasterView
47
168
  dc.name.split(':').last
48
169
  end
49
170
 
50
- # Answer the attribute name implemented by a directive class
51
- def directive_attr_name( dc )
52
- dc.respond_to?(:attr_name) ? dc.attr_name : simple_class_name(dc).downcase_first_letter
53
- end
54
-
55
- # Answer the fully qualified name of the attribute implemented by a directive class.
56
- # attr_qname ::= <ns_name>:<attr_name>
57
- def directive_full_attr_name( dc, mv_ns )
58
- (dc.respond_to? :full_attr_name) ? dc.full_attr_name(mv_ns) : build_full_attribute_name(mv_ns, dc)
59
- end
60
-
61
- # this method is invoked to build the full attribute name (the attribute which will be watched for in
62
- # html attibutes. It concatenates the namespace prefix to the class name after first removing any module
63
- # prefixes and then downcasing the first letter
64
- def build_full_attribute_name(mv_ns, dc) #:nodoc:
65
- #ISSUE: need to allow directives to control their name space
66
- mv_ns + simple_class_name(dc).downcase_first_letter
67
- end
68
-
69
171
  # Ensure that all directives on the load path are loaded.
70
172
  # Build the directive processing tables used by the template parser.
71
173
  #
72
- def process_directives_load_path( directive_paths, mv_ns=nil )
73
- load_directives( directive_paths )
74
- build_directive_maps( mv_ns )
174
+ def process_directives_load_path( load_path=nil )
175
+ load_path = MasterView::DirectiveLoadPath.current if load_path.nil?
176
+ load_directives( load_path )
177
+ build_directive_maps
75
178
  end
76
179
 
77
180
  # Ensure that all directives on the load path are loaded.
78
181
  #
79
182
  # require {directives_dir}/foo_directive.rb
80
- def load_directives( directive_paths=nil ) #:nodoc:
81
- directive_paths = MasterView::DefaultDirectiveLoadPaths if directive_paths.nil?
82
- directive_paths.each do | dir |
83
- # MV::Initializer now handles bad dir path entries, but leave old checks for now [DJL 04-Jul-2006]
84
- next if dir.nil?
85
- if File.directory?(dir)
86
- Dir.open( dir ).each { |fn| require "#{dir}/#{fn}" if fn =~ /[.]rb$/ }
183
+ def load_directives( load_path=nil ) #:nodoc:
184
+ load_path = MasterView::DirectiveLoadPath.current if load_path.nil?
185
+ STDOUT.puts "\n-------- LOAD DIRECTIVES ON PATH -------" if DEBUG_TRACE_LOADING
186
+ load_path.each do | dpe |
187
+ STDOUT.puts "DIRECTORY PATH ENTRY: #{dpe.inspect}" if DEBUG_TRACE_LOADING
188
+ if dpe.exists?
189
+ self.load_from_directive_path_entry( dpe )
87
190
  else
88
- #raise InvalidPathError.new('Directive load path dir does not exist:'+dir) unless File.directory? dir
89
- Log.error "Directive load path dir does not exist: '#{dir}'" if MasterView.const_defined?(:Log) #backstop for test case startup
191
+ #raise InvalidPathError.new('Directive load path dir does not exist:'+dir_path)
192
+ Log.error "Directive load path dir does not exist: '#{dpe.dir_path}'" if MasterView.const_defined?(:Log) #backstop for test case startup
90
193
  end
91
194
  end
92
- clear_directive_maps() # ensure we take a clean point of view on the matter at hand
195
+ clear_directive_maps() # ensure we take a clean point of view on whatever just got loaded
196
+ STDOUT.puts "------ END LOAD DIRECTIVES ------" if DEBUG_TRACE_LOADING
197
+ end
198
+
199
+ # Load a specific directive implementation class
200
+ #
201
+ # Mainly provided for use by unit tests
202
+ def load_directive_file(directive_file_path)
203
+ dir_path = File.dirname(directive_file_path)
204
+ dpe = MasterView::DirectiveLoadPath::PathEntry.new(dir_path)
205
+ load_from_directive_path_entry( dpe, directive_file_path )
93
206
  end
94
207
 
208
+ def load_from_directive_path_entry(dpe, directive_file_path=nil) #:nodoc:
209
+ dir_md_specs = dpe.load_metadata_specs # :create_if_not_defined => true
210
+ is_mv_directives_dir = dir_md_specs.fetch(:use_masterview_namespace, false)
211
+ app_md_defaults = is_mv_directives_dir ? metadata_defaults : metadata_defaults_extensions
212
+ md_defaults = MasterView::DirectiveLoadPath.compute_md_defaults(
213
+ app_md_defaults,
214
+ dir_md_specs[:default],
215
+ dpe.metadata_defaults )
216
+ begin
217
+ dir_load_context = { :metadata_defaults => md_defaults, }
218
+ set_load_context( dir_load_context )
219
+ if directive_file_path
220
+ # load specific file from a directive path entry directory
221
+ dir_load_context[:directive_file] = directive_file_path
222
+ load_single_directive_file(directive_file_path, md_defaults) #WAS: require directive_file_path
223
+ else
224
+ # load all directives in the directory
225
+ dir_path = dpe.dir_path
226
+ #??Log.debug { "Loading directives from #{dir_path} with metadata defaults #{md_defaults.inspect}" }
227
+ #??load_context[:synonyms] = dir_md_specs.fetch(:synonyms, {})??
228
+ #??files_to_ignore = dir_md_specs.fetch(:ignore, []).collect { |fn| fn.ends_with?('.rb') ? fn : "#{fn}.rb" }
229
+ Dir.entries( dir_path ).each { |fn|
230
+ # we assume all .rb files in a directives dir are directive impls
231
+ if fn =~ /[.]rb$/ #?? && ! files_to_ignore.include?(fn)
232
+ directive_file_path = "#{dir_path}/#{fn}"
233
+ dir_load_context[:directive_file] = directive_file_path
234
+ #??@load_context[:directive_synonyms] = dir_synonyms_specs.fetch(fn, [])??
235
+ load_single_directive_file(directive_file_path, md_defaults) #WAS: require directive_file_path
236
+ end
237
+ }
238
+ end
239
+ ensure
240
+ set_load_context( nil )
241
+ end
242
+ end
243
+
244
+ protected
245
+ # we can't to this in register_directive as desired because
246
+ # the class defn hasn't been processed yet at that point, it seems.
247
+ def load_single_directive_file(directive_file_path, md_defaults)
248
+ last_class_loaded = @loaded_classes.last
249
+ require directive_file_path
250
+ if @loaded_classes.last != last_class_loaded
251
+ directive_class = @loaded_classes.last
252
+ directive_class.harden_metadata( md_defaults )
253
+ ##DEBUG##
254
+ if TRACE_MD_HARDENING_SUBJECT and directive_class.name == TRACE_MD_HARDENING_SUBJECT
255
+ STDOUT.puts "\nFINISHED LOADING DIRECTIVE #{directive_class.name}"
256
+ STDOUT.puts "...SHOULD HAVE HARDENED METADATA NOW:"
257
+ STDOUT.puts "...#{directive_class.metadata_values.object_id}: #{directive_class.metadata_values.inspect}\n"
258
+ STDOUT.puts ""
259
+ end
260
+ ##DEBUG##
261
+ end
262
+ end
263
+ public #nasty modal access specification mechanism
264
+
95
265
  # Build the directive processing tables used by the template parser.
96
- def build_directive_maps( mv_ns=nil ) #:nodoc:
266
+ def build_directive_maps() #:nodoc:
97
267
 
98
- mv_ns = MasterView::NamespacePrefix if mv_ns.nil?
99
268
  clear_directive_maps() # ensure we take a clean point of view on the matter at hand
100
269
 
101
270
  Log.debug { 'directive plugins loaded:' + loaded_class_names.inspect } if MasterView.const_defined?(:Log) #backstop for test case startup
102
271
  loaded_classes.each do |dc|
103
- dc.on_load if dc.respond_to?(:on_load)
104
- attr_qname = directive_full_attr_name( dc, mv_ns )
105
- qname_parts = attr_qname.split(':')
106
- raise NameError, "Directive qname requires namespace: '#{attr_qname} - #{dc.name}" if qname_parts.length != 2
107
- ns_name, attr_name = qname_parts
272
+ dc.on_load if dc.respond_to?(:on_load) #ISSUE: needs review (timing, intent) [DJL 08-Oct-2006]
273
+ attr_qname = dc.attribute_qname
274
+ ns_name = dc.namespace_name
275
+ raise NameError, "Directive qname requires namespace: '#{attr_qname} - #{dc.name}" if ns_name.nil? || ns_name.strip().empty?
276
+ if DEBUG_BUILTIN_DIRECTIVE_REGISTRATION and dc.name.starts_with?('MasterView::Directives::')
277
+ if ns_name != 'mv' #! attr_qname.starts_with(MasterView.mv_namespace_prefix)
278
+ STDOUT.puts "****BUILTIN DIRECTIVE NAMESPACE PROBLEM: #{dc.name} qname='#{attr_qname}'"
279
+ raise RuntimeError, "BUILTIN DIRECTIVE NS PROBLEM: #{dc.name} qname='#{attr_qname}"
280
+ end
281
+ end
108
282
  @directive_namespaces << ns_name if ! @directive_namespaces.include?( ns_name )
109
283
  @directive_classes[attr_qname] = dc
110
- ###WHAT IS THIS NOTION of auto/global directive??? currently used (only) by inline_erb_directive
111
- dc_instance = dc.new(nil)
112
- @auto_directives << dc if dc_instance.respond_to?(:global_directive?) && dc_instance.global_directive?
284
+ # global directives can be automatically attached to all elements (e.g., generated comments)
285
+ # or conditionally attached (e.g., a directive that wants to glom onto all elements of a specific type)
286
+ dc_global_spec = dc.metadata_values.fetch(:global_directive?, false)
287
+ if dc_global_spec == true # unconditional global directive - applied to all elements
288
+ @auto_directives << dc
289
+ elsif dc_global_spec # conditional global directive - applied when match condition satisfied
290
+ @global_directives << dc
291
+ end
113
292
  end
114
- Log.debug { 'directives='+@directive_classes.keys().sort!().inspect } if MasterView.const_defined?(:Log) #backstop for test case startup
115
- Log.debug { 'auto_directives='+@auto_directives.inspect } if MasterView.const_defined?(:Log) #backstop for test case startup
116
293
 
294
+ if MasterView.const_defined?(:Log) #backstop for test case startup
295
+ Log.debug { "directives=#{@directive_classes.keys().sort!().inspect}" }
296
+ Log.debug { "global_directives=#{@global_directives.inspect}" }
297
+ Log.debug { "auto_directives=#{@auto_directives.inspect}" }
298
+ end
117
299
  end
118
300
 
119
301
  # Construct directive processors needed to handle the
120
302
  # attributes defined on a template document element.
121
303
  #
122
304
  # Constructs processors for all global directives
123
- # and for any directive attributes.
305
+ # and for any directive attributes defined on the element.
124
306
  #
307
+ # Removes all directive attributes from the element's attributes.
125
308
  #
126
- def construct_directive_processors( attributes )
309
+ def construct_directive_processors( tag_name, element_attrs )
127
310
  directive_processors = []
128
311
  # always instantiate global directive handlers
129
312
  @auto_directives.each do | dc |
130
313
  directive_processors << dc.new(nil)
131
314
  end
315
+ # conditionally instantiate global directives which are interested in this element
316
+ @global_directives.each do | dc |
317
+ attr_value = dc.metadata_values[:global_directive?].call( tag_name, element_attrs )
318
+ if attr_value
319
+ directive_processors << dc.new(attr_value)
320
+ end
321
+ end
132
322
  # instantiate the directive processor on the attribute value if its attr present
133
- # remove the MV attribute from the document so that it's only effect is from the processor action
323
+ # remove the MV attribute from the document so that its only effect is from the processor action
134
324
  @directive_classes.each do | attr_qname, dc |
135
- directive_processors << dc.new(attributes.delete(attr_qname)) if attributes[attr_qname]
325
+ directive_attr_value = element_attrs[attr_qname]
326
+ if directive_attr_value
327
+ # convert the directive into a processing handler for this element
328
+ element_attrs.delete(attr_qname)
329
+ directive_processors << dc.new(directive_attr_value)
330
+ end
136
331
  end
137
332
  directive_processors
138
333
  end
139
334
 
140
- protected
141
- def clear_directive_maps() #:nodoc:
142
- @directive_namespaces = []
143
- @directive_classes = {} #map fully qualified directive attr name to directive_class
144
- @auto_directives = []
145
- end
335
+ # class initialization
336
+ self.current = self.new()
337
+ #STDOUT.puts "INITIALIZED DirectiveRegistry.current=#{DirectiveRegistry.current.object_id}" ##DEBUG##
338
+ #STDOUT.puts "...DirectiveRegistry.current=#{DirectiveRegistry.current.inspect}" ##DEBUG##
339
+ #STDOUT.puts "END DirectiveRegistry definition\n" ##DEBUG##
146
340
 
147
341
  end
148
342
 
149
- # The DirectivesRegistry manages the loaded directives available
150
- # for processing template markup
151
- DirectivesRegistry = DirectiveRegistry.new()
152
-
153
343
  # Register a directive implementation.
154
344
  #
155
345
  # Registration is handled automatically for directives
@@ -158,12 +348,12 @@ module MasterView
158
348
  # module in a directive implementation class.
159
349
  #
160
350
  # Directive registration ordinarily occurs during MasterView
161
- # initialization, when directive classes on the configured
162
- # <code>directive_paths</code> directories are automatically
351
+ # initialization, when directive classes from the configured
352
+ # <code>directive_load_path</code> directories are automatically
163
353
  # loaded and registered with the template engine.
164
354
  #
165
355
  def self.register_directive(directive_class)
166
- DirectivesRegistry.register_directive(directive_class)
356
+ DirectiveRegistry.current.register_directive(directive_class)
167
357
  end
168
358
 
169
- end
359
+ end
@@ -0,0 +1,16 @@
1
+ # MasterView directive metadata specifications
2
+ # Built-in MasterView directives
3
+
4
+ # default metadata values for directives loaded from this directory
5
+ default:
6
+ namespace: mv
7
+ description: Built-in MasterView directive
8
+
9
+ # optionally specify any .rb files in this directory to ignore
10
+ #ignore: []
11
+
12
+ # optionally specify synonyms to register directives
13
+ # Mainly intended to provide backwards compat (hopefully for short-term transition only...)
14
+ # Synonym spec ::= [ <directive attribute name>, <synonym> [, <synonym]...
15
+ #synonyms:
16
+ # - [ foo, oldfoo ]
@@ -1,27 +1,28 @@
1
1
  module MasterView
2
2
  module Directives
3
+
3
4
  # takes the attr_value and parses it as a hash :foo => 'bar', :baz => 'cat'
4
5
  # it sets/overrides the tag's attribute values for each value.
5
6
  # To use erb output simply wrap the content in #{ foo } for example :foo => #{h product.name}
7
+ #
6
8
  class Attr < MasterView::DirectiveBase
7
9
  # substitution to make it easy to parse erb in attr
8
10
  SubstForErb = '#'+InlineErbStart+'= \1 '+InlineErbEnd+'#'
9
11
 
10
- def priority
11
- DirectivePriorities::MediumLow
12
- end
13
-
14
- def stag(dcs)
15
-
12
+ metadata :priority => 'MediumLow',
13
+ :category => 'general',
14
+ :description => 'Replaces attribute value(s) on an element with the value of an expression'
15
+
16
+ event :stag do
16
17
  attr_value.gsub!( /#\{([^}]*)\}/, SubstForErb ) #taking #{h product.name} and changing to #{{{= h product.name}}}# for easy parsing
17
18
  arr = attr_value.scan( /:(\w+)\s*=>\s*(['"#])([^,\2]*)\2,?/ )
18
19
  arr.each do |scn|
19
20
  n = scn[0]
20
21
  v = scn[2]
21
- dcs.context[:tag].attributes[n] = v
22
+ element_attrs[n] = v
22
23
  end
23
- dcs.render
24
24
  end
25
+
25
26
  end
26
27
  end
27
28
  end
@@ -1,27 +1,24 @@
1
1
  module MasterView
2
2
  module Directives
3
3
 
4
- #outputs a block around the text tags, if left bracket count is higher than right
5
- #assume that the end is a right bracket otherwise use end
4
+ # outputs a block around the text tags, if left bracket count is higher than right
5
+ # assume that the end is a right bracket otherwise use end
6
+ #
6
7
  class Block < MasterView::DirectiveBase
7
- def priority
8
- DirectivePriorities::MediumHigh
9
- end
10
8
 
11
- def stag(directive_call_stack)
9
+ metadata :priority => 'MediumHigh',
10
+ :category => 'erb',
11
+ :description => 'Expands to the equivalent rhtml (erb) block code around the element'
12
+
13
+ event :before_stag do
12
14
  count_left_brackets = attr_value.scan( /\{/ ).size
13
15
  count_right_brackets = attr_value.scan( /\}/ ).size
14
16
  @end_block = (count_left_brackets > count_right_brackets) ? '}' : 'end'
15
-
16
- ret = []
17
- ret << erb(attr_value)
18
- ret << directive_call_stack.render
17
+ render erb_eval(attr_value)
19
18
  end
20
19
 
21
- def etag(directive_call_stack)
22
- ret = []
23
- ret << directive_call_stack.render
24
- ret << erb(@end_block)
20
+ event :after_etag do
21
+ render erb_eval(@end_block)
25
22
  end
26
23
 
27
24
  end
@@ -3,27 +3,22 @@ module MasterView
3
3
 
4
4
  # create check_box helper, quoting object and method if necessary
5
5
  # merging in any html options specified
6
- class Check_box < MasterView::DirectiveBase
7
- def stag(dcs)
8
- #eat
9
- end
10
-
11
- def etag(dcs)
12
- args = parse_attr_value
13
- obj = args[0]
14
- method = args[1]
15
- options_and_on_off = args[2..-1].join(', ')
6
+ #
7
+ class CheckBox < MasterView::DirectiveBase
16
8
 
17
- obj = quote_if(obj)
18
- method = quote_if(method)
9
+ metadata :priority => :default,
10
+ :category => 'form',
11
+ :description => 'Replaces the element with a Rails check_box form helper',
12
+ :element_usage => 'input'
19
13
 
20
- options_and_on_off = merge_into_embedded_hash(options_and_on_off, 0, common_html_options(attrs_lck))
14
+ attr_arg :object_name, :quote => true
15
+ attr_arg :method, :quote => true
16
+ attr_arg :options, :default => {}, :append_element_attrs => [:common_html]
17
+ attr_arg :checked_value, :default => "'1'"
18
+ attr_arg :unchecked_value
21
19
 
22
- a = []
23
- a << 'check_box '+ obj
24
- a << method
25
- a << options_and_on_off if options_and_on_off && !options_and_on_off.strip.empty?
26
- erb_content(a.join(', '))
20
+ event :element do
21
+ render erb_content( 'check_box', :object_name, :method, :options, :checked_value, :unchecked_value )
27
22
  end
28
23
 
29
24
  end
@@ -5,38 +5,24 @@ module MasterView
5
5
  # html options specfied on element into any html options in attr_value
6
6
  # attr_value syntax:
7
7
  # object, method, collection, value_method, text_method, options = {}, html_options = {}
8
- class Collection_select < MasterView::DirectiveBase
9
- def stag(dcs)
10
- #eat
11
- end
12
-
13
- def etag(dcs)
14
- args = parse_attr_value
15
- obj = quote_if(args[0])
16
- method = quote_if(args[1])
17
- collection = quote_if(args[2])
18
- value_method = quote_if(args[3])
19
- text_method = quote_if(args[4])
20
- options = args[5]
21
- html_options = args[6]
8
+ #
9
+ class CollectionSelect < MasterView::DirectiveBase
22
10
 
23
- opt = {}
24
- opt[:size] = attrs_lck['size'].to_i if attrs_lck['size']
25
- opt.merge! common_html_options(attrs_lck)
26
- html_options = merge_into_embedded_hash(html_options, 0, opt)
27
- options = '{}' if options.to_s.empty? && !html_options.to_s.empty? # if we have html_options but no options, still need empty hash
11
+ metadata :priority => :default,
12
+ :category => 'form',
13
+ :description => 'Replaces the element with a Rails collection_select form helper.',
14
+ :element_usage => 'select'
28
15
 
29
- a = []
30
- a << 'collection_select '+ obj
31
- a << method
32
- a << collection
33
- a << value_method
34
- a << text_method
35
- a << options unless options.to_s.strip.empty?
36
- a << html_options unless html_options.to_s.strip.empty?
16
+ attr_arg :object_name, :quote => true
17
+ attr_arg :method, :quote => true
18
+ attr_arg :collection , :quote => true
19
+ attr_arg :value_method, :quote => true
20
+ attr_arg :text_method, :quote => true
21
+ attr_arg :options, :default => {}
22
+ attr_arg :html_options, :append_element_attrs => [:common_html]
37
23
 
38
- self.content = ''
39
- erb_content(a.join(', '))
24
+ event :element do
25
+ render erb_content( 'collection_select', :object_name, :method, :collection, :value_method, :text_method, :options, :html_options)
40
26
  end
41
27
 
42
28
  end
@@ -1,10 +1,16 @@
1
1
  module MasterView
2
2
  module Directives
3
3
  class Content < MasterView::DirectiveBase
4
- def etag(dcs)
5
- self.content = erb_content(attr_value)
6
- dcs.render
4
+
5
+ metadata :priority => :default,
6
+ :category => 'general',
7
+ :description => 'Replaces the content of the element with the value of the expression'
8
+
9
+ # replace the content of the element with <%= attr_value %>
10
+ event :content do
11
+ render erb_content( attr_value )
7
12
  end
13
+
8
14
  end
9
15
  end
10
16
  end
@@ -1,23 +1,25 @@
1
1
  module MasterView
2
2
  module Directives
3
- #outputs an else/end block around the text tags removing previous end tag (from if/elsif)
3
+
4
+ # outputs an else/end block around the text tags removing previous end tag (from if/elsif)
5
+ #
4
6
  class Else < MasterView::DirectiveBase
5
- def priority
6
- DirectivePriorities::High
7
- end
8
7
 
9
- def stag(directive_call_stack)
8
+ metadata :priority => 'High',
9
+ :category => 'erb',
10
+ :description => 'Used in conjunction with the mv:if and mv:elsif directives to allow you to create if... elsif... else... end blocks'
11
+
12
+ # remove the last <% end -%> from the parent tag's content and add a <% else -%> instead.
13
+ # This is a continuation of previous if or elsif statement.
14
+ event :before_stag do
10
15
  tag = @directive_call_stack.context[:tag]
11
- delete_last_in_parent(tag, erb('end') )
12
- ret = []
13
- ret << erb('else')
14
- ret << directive_call_stack.render
16
+ delete_last_in_parent(tag, erb_eval('end') )
17
+ render erb_eval( 'else' )
15
18
  end
16
19
 
17
- def etag(directive_call_stack)
18
- ret = []
19
- ret << directive_call_stack.render
20
- ret << erb('end')
20
+ # output <% end -%>
21
+ event :after_etag do
22
+ render erb_eval( 'end' )
21
23
  end
22
24
 
23
25
  end