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
@@ -2,48 +2,48 @@ module MasterView
2
2
 
3
3
  # Namespace module for built-in directive implementations
4
4
  # that are standard with the MasterView template engine.
5
- #
5
+ #
6
6
  module Directives
7
7
  end
8
8
 
9
9
  # Base class for directive implementations.
10
- #
10
+ #
11
11
  # The standard technique for implementing a directive
12
12
  # is to subclass DirectiveBase. The builtin
13
13
  # MasterView directives are implemented in
14
- # module namespace MasterView::Directives.
15
- #
14
+ # module namespace MasterView::Directives and
15
+ # uses the mv: namespace in directive attribute markup.
16
+ #
17
+ # Custom directive implementations should be created
18
+ # in your own module namespace, according to what's
19
+ # appropriate for your application or component.
20
+ # By default, directive extensions will use the
21
+ # mvx: namespace to distinguish extensions from the
22
+ # builtin MasterView directives.
23
+ #
16
24
  # If you create a directive implementation class
17
- # elsewhere in the class hierarchy, it is recommended
18
- # that you include the DirectiveHelpers mixin.
25
+ # elsewhere in the class hierarchy, you include the
26
+ # DirectiveMetadata mixin class. It is also is recommended
27
+ # that you include DirectiveHelpers and DirectiveDSL.
19
28
  # If you do not include the PluginLoadTracking mixin,
20
29
  # you will need to manually register your directive
21
30
  # class using the MasterView.register_directive service.
22
31
  # either the PluginLoadTracking mixin or
23
- #
24
- #
32
+ #
25
33
  #--
26
34
  #TODO: add docs here on responsibilities and techniques for
27
35
  # implementing directives. Subclass DirectiveBase, define
28
36
  # within MasterView::Directives module namespace.
29
- #
30
- # mumble - class methods:
31
- # attr_name is the directive's attribute markup name - def. is class name
32
- # full_attr_name(ns) is the qualified name (name-space prefixed)
33
- #
34
- # Directive can optionally implement class method :on_load to initialize
35
- # itself prior to processed for the directive registry
36
- #
37
- #TODO: establish protocol convention for additional namespaces
38
37
  #
39
- # mumble: :global_directive? predicate indicates... what?? (inline erb)
40
- #
41
- # Directives implement <code>priority</code> to control the processing
42
- # order when multiple directive attributes are used on a template
43
- # document element.
44
- #
38
+ # A directive can optionally implement class method :on_load to
39
+ # initialize itself prior to being added to the directive registry.
40
+ #
41
+ # Directives may specify a <code>priority</code> in their metadata
42
+ # to control the processing order when multiple directive attributes
43
+ # are used on a template document element.
44
+ #
45
45
  #TODO: document the priority hierarchy and convention for level usage
46
- #
46
+ #
47
47
  # Discuss operational context: describe how/when MasterView parser
48
48
  # invokes a directive handler in the course of parsing the elements
49
49
  # and attributes of a template document node hierarchy.
@@ -51,25 +51,20 @@ module MasterView
51
51
  # information is available to a directive when invoked;
52
52
  # what services are available in order to produce some effect
53
53
  # on the results of the template parse.
54
- #
54
+ #
55
55
  # Two general flavors on content directives: those which operate on attribute
56
56
  # values of the containing element and those which operate on the
57
57
  # entire containing element, sometimes by supplying or modifying its
58
58
  # content, in other cases by replacing the template element with something
59
- # else.
60
- #
59
+ # else.
60
+ #
61
61
  # Third flavor: eval-only directive. Expression which is evaluate for its
62
62
  # effect on the directive processing context or the overall state of processing
63
63
  # the template document element tree.
64
- #
65
- # Directive implementation typically wants to implement either or both of
66
- # the methods <code>stag(dcs)</code> and <code>etag(dcs)</code> to hook
67
- # up its processing on the start/end tags of the element on which the
68
- # attribute is defined.
69
- #
64
+ #
70
65
  # When a directive attribute is used on a template document element,
71
66
  # the directive class is instantiated with the attribute_value provided
72
- # to its constructure. All directives used on an element are sorted
67
+ # to its constructure. All directives used on an element are sorted
73
68
  # into processing order according to their <code>priority</code>
74
69
  # (default is <code>Medium</code>. The directive processor is invoked
75
70
  # when the element start tag is encountered and when the element end
@@ -77,58 +72,25 @@ module MasterView
77
72
  # how its processing is hooked up to effect the template output.
78
73
  #++
79
74
  #
80
- class DirectiveBase
75
+ class DirectiveBase
81
76
  include PluginLoadTracking
77
+ include DirectiveMetadata
82
78
  include DirectiveHelpers
79
+ include DirectiveDSL
83
80
 
84
81
  # Register a class manually without regard to whether it inherits from DirectiveBase.
85
82
  # Classes which derive from DirectiveBase will automatically be registered as they are
86
83
  # loaded.
87
84
  def self.register_directive(directive_class)
88
- #ISSUE: do we really need both PluginLoadTracking.register_class
85
+ #ISSUE: do we really need both PluginLoadTracking.register_class
89
86
  #and DirectiveBase.register_directive, in addition to MasterView.register_directive???
90
87
  #[DJL 04-Jul-2006]
91
88
  MasterView.register_directive(directive_class)
92
89
  end
93
90
 
94
- # Returns the fully qualified attribute name of the directive,
95
- # consisting of the directive namespace and the directive attribute name.
96
- #
97
- # The default MasterView namespace_prefix is used if the directive does not
98
- # specify a separate namespace.
99
- #
100
- #--
101
- #TODO: clarify the mechanism by which alternate namespaces are defined.
102
- # Is this done by a code value or configured as part of the directory
103
- # path specifications, or some combination thereof to allow ovverides
104
- # in the event of namespace collisions?
105
- #++
106
- #
107
- def self.full_attr_name( namespace_prefix )
108
- #TODO: fix this so that directives can override to define their own namespace separate from mv:
109
- namespace_prefix + self.attr_name
110
- end
111
-
112
- # Returns the attribute name of the directive.
113
- #
114
- # Use full_attr_name to obtain the fully-qualified name
115
- # of the directive attribute with the qualifying namespace prefix.
116
- #
117
- # The default attribute name of a directive is formed
118
- # from the simple class name (without any module prefix qualifier),
119
- # with the first character downcased.
120
- #
121
- def self.attr_name()
122
- self.default_attr_name()
123
- end
124
-
125
- def self.default_attr_name() #:nodoc:
126
- self.name.split(':').last.downcase_first_letter
127
- end
128
-
129
- # Construct a directive processor for the attribute_value
91
+ # Construct a directive processor with the attribute_value
130
92
  # of a directive attribute on a document template element.
131
- #
93
+ #
132
94
  def initialize(attribute_value)
133
95
  @attribute_value = attribute_value
134
96
  end
@@ -145,88 +107,39 @@ module MasterView
145
107
 
146
108
  # Set the directive's attribute value string to be processed.
147
109
  def attr_value=(attribute_value)
110
+ #ISSUE: is this useful/necessary? Believe we only want initialzer to set this [DJL 23-Sep-2006]
148
111
  @attribute_value = attribute_value
149
112
  end
150
113
 
151
- #get attribute hash from tag
152
- def attrs
114
+ # Returns the attributes hash containing the attributes
115
+ # defined on the template document element that is being processed.
116
+ def element_attrs
153
117
  @directive_call_stack.context[:tag].attributes
154
118
  end
155
119
 
156
- #set attribute hash for tag
157
- def attrs=(attributes)
120
+ # Set the attributes hash containing the attributes
121
+ # for the template document element that is being processed.
122
+ def element_attrs=(attributes)
158
123
  @directive_call_stack.context[:tag].attributes = attributes
159
124
  end
160
125
 
161
- #get attribute hash with lowercased keys and values, and cache it
162
- def attrs_lckv
163
- @attrs_lckv ||= lowercase_attribute_keys_and_values(attrs)
164
- end
165
-
166
- #get attribute hash with lowercased keys (and original values) and cache it
167
- def attrs_lck
168
- @attrs_lck ||= lowercase_attribute_keys(attrs)
169
- end
170
-
171
- #returns true if the value for lckey of the attribute hash with lowercased keys and values
172
- #matches (lowercase) lcmatch string
173
- def attr_lckv_matches(lckey, lcmatch)
174
- (attrs_lckv[lckey] && attrs_lckv[lckey] == lcmatch.downcase) ? true : false
175
- end
176
-
177
- #DEPRECATED - going away
178
- #output '<% '+str+' %>'
179
- def erb(str) #:nodoc:
180
- #ISSUE: convert clients to erb_eval and drop. Ya oughta have a point of view. [DJL 04-Jul-2006]
181
- ERB_START + str + ERB_END
182
- end
183
-
184
- # Compose an Erb expression which produces content in the containing document.
185
- # The expression may also have effects on the processing context of
186
- # the template document
187
- #
188
- # output '<%= '+str+' %>
189
- #
190
- def erb_content(str)
191
- ERB_CONTENT_START + str + ERB_CONTENT_END
192
- end
193
-
194
- # Compose an Erb expression which is evaluated for its effect on the processing context
195
- # but does not produce content in the containing document.
196
- #
197
- # output '<% '+str+' %>'
198
- #
199
- def erb_eval(str)
200
- ERB_EVAL_START + str + ERB_EVAL_END
201
- end
202
-
203
- #get tag_name
204
- def tag_name
126
+ # Returns the tag of the template document element that is being processed.
127
+ def element_tag
205
128
  @directive_call_stack.context[:tag].tag_name
206
129
  end
207
130
 
208
- #set tag_name
209
- def tag_name=(tag_name)
131
+ # Set the tag of the template document element that is being processed.
132
+ def element_tag=(tag_name)
210
133
  @directive_call_stack.context[:tag].tag_name = tag_name
211
134
  end
212
135
 
213
- #inside characters, cdata, or comment you can call this to get the characters passed
214
- def data
215
- @directive_call_stack.context[:content_part]
216
- end
217
-
218
- #set the data that will be passed to characters, cdata, or comment directives
219
- def data=(data)
220
- @directive_call_stack.context[:content_part]=data
221
- end
222
-
223
136
  # rolled up content from all children of the tag, note this will not be complete until hitting the end tag method :etag
224
- def content
137
+ def content_array
225
138
  @directive_call_stack.context[:tag].content
226
139
  end
227
140
 
228
141
  #return rolled up content from all children as string, note this will not be complete until hitting the end tag method :etag
229
- def content_str
142
+ def content_string
230
143
  content = @directive_call_stack.context[:tag].content
231
144
  content = content.join if content.respond_to? :join
232
145
  content
@@ -237,63 +150,172 @@ module MasterView
237
150
  @directive_call_stack.context[:tag].content = content
238
151
  end
239
152
 
240
- #add single quotes around string
241
- def quote(str)
242
- '\''+str+'\''
243
- end
244
-
245
- # adds single quotes around string if it is a simple
246
- # word [a-zA-Z0-9_]* otherwise return existing string
247
- # also quote if empty string
248
- def quote_if(str)
249
- (str =~ /^\w*$/) ? quote(str) : str
153
+ # Compose an Erb expression which produces content in the containing document.
154
+ # The expression may also have effects on the processing context of
155
+ # the template document.
156
+ #
157
+ # Enhanced allowing args to be passed in. If args are present then they will
158
+ # be assumed to be parameters for the method. A space will be appended to str
159
+ # and each argument will be appended joined with commas. Additionally if
160
+ # argument is a symbol then it will check for the existence of an instance variable
161
+ # with that name and if so substitutes its value. If any non-nil values are
162
+ # passed after it, then it will use its :default if that was specified
163
+ # in arg call, otherwise simply nil.
164
+ #
165
+ # output '<%= '+str+' %>
166
+ #
167
+ # erb_content(content, :a, :b, :c) where @a = 1, @b = nil (with optional_default of {}) and @c = {:hello => :world}
168
+ # becomes '<%= '+content+' 1, {}, {:hello => :world}'+' %>
169
+ #
170
+ def erb_content(str, *args)
171
+ ERB_CONTENT_START + prepare_output(str,*args) + ERB_CONTENT_END
250
172
  end
251
173
 
252
- def remove_strings_from_attr_value!
253
- self.attr_value = remove_prepended_strings(attr_value)
174
+ # Compose an Erb expression which is evaluated for its effect on the processing context
175
+ # but does not produce content in the containing document.
176
+ #
177
+ # Enhanced allowing args to be passed in. If args are present then they will
178
+ # be assumed to be parameters for the method. A space will be appended to str
179
+ # and each argument will be appended joined with commas. Additionally if
180
+ # argument is a symbol then it will check for the existence of an instance variable
181
+ # with that name and if so substitutes its value. If any non-nil values are
182
+ # passed after it, then it will use its :default if that was specified
183
+ # in arg call, otherwise simply nil.
184
+ #
185
+ # output '<% '+str+' %>
186
+ #
187
+ # erb_eval(str, :a, :b, :c) where @a = 1, @b = nil (with optional_default of {}) and @c = {:hello => :world}
188
+ # becomes '<% '+str+' 1, {}, {:hello => :world}'+' %>
189
+ #
190
+ def erb_eval(str, *args)
191
+ ERB_EVAL_START + prepare_output(str,*args) + ERB_EVAL_END
254
192
  end
255
193
 
256
- #prepend string to attribute value adding a comma if attribute value was not empty
257
- def prepend_to_attr_value!(str)
258
- return attr_value if str.nil? || str.strip.empty?
259
- av = str
260
- av << ', ' << attr_value unless attr_value.strip.empty?
261
- self.attr_value = av
194
+ # convert arg symbols to instance_variable values if exist
195
+ # Enhanced allowing args to be passed in. If args are present then they will
196
+ # be assumed to be parameters for the method. A space will be appended to str
197
+ # and each argument will be appended joined with commas. Additionally if
198
+ # argument is a symbol then it will check for the existence of an instance variable
199
+ # with that name and if so substitutes its value. If any non-nil values are
200
+ # passed after it, then it will use its :default if that was specified
201
+ # in arg call, otherwise simply nil.
202
+ #
203
+ # output '<%= '+str+' %>
204
+ #
205
+ # prepare_output(content, :a, :b, :c) where @a = 1, @b = nil (with optional_default of {}) and @c = {:hello => :world}
206
+ # becomes '<%= '+content+' 1, {}, {:hello => :world}'+' %>
207
+ #
208
+ # returns string of output
209
+ def prepare_output(str, *args)
210
+ out = str
211
+ non_nil_found = false
212
+ arg_values = []
213
+ args.reverse_each do |arg|
214
+ value = nil
215
+ invar_name = name_to_instance_var_name(arg)
216
+ var_sym_name = (invar_name.to_s.starts_with?('@')) ? invar_name.to_s[1..-1].to_sym : invar_name
217
+ attr_arg_def = self.class.directive_class_def.find_attr_arg_def_by_name(var_sym_name)
218
+ if instance_variable_exists?(invar_name)
219
+ value = self.instance_variable_get(invar_name)
220
+ else # symbol instance variable not found, assume need to use literal arg value
221
+ value = arg
222
+ end
223
+ if attr_arg_def and attr_arg_def.options and attr_arg_def.options[:varargs] # arg contains array of values that need to be expanded
224
+ if value.nil? or value.empty?
225
+ value = nil
226
+ else
227
+ value = value.join(', ') # expand these values out to foo, bar, baz
228
+ end
229
+ end
230
+ if non_nil_found # insure that the preceding values are specified
231
+ if value.nil?
232
+ value = attr_arg_def.optional_default unless attr_arg_def.nil? #try to use optional_default if exists
233
+ end
234
+
235
+ value = 'nil' if value.nil? or (value.is_a?(String) and value.strip.empty?) # finally we will be outputing the string word nil if value is still nil
236
+ if(value.is_a?(String))
237
+ value = '{ '+value+' }' if value =~ /^[^\{]+=>/
238
+ end
239
+ else # no non_nil_found previously, check this one
240
+ value = nil if value.is_a?(String) and value.strip.empty?
241
+ non_nil_found = true unless value.nil?
242
+ end
243
+ unless value.nil?
244
+ value = value.inspect unless value.is_a?(String) # insure is string inspect works for many things
245
+ arg_values.unshift(value) # if values are non-nil at this point add them to arg_values (to end first)
246
+ end
247
+ end
248
+ out = out + '( ' + arg_values.join(', ') + ' )' unless arg_values.empty?
249
+ out
262
250
  end
263
251
 
264
- #append string to attribute value adding a comma if attribute value was not empty
265
- def append_to_attr_value!(str)
266
- return attr_value if str.nil? || str.strip.empty?
267
- av = attr_value
268
- av << ', ' unless av.strip.empty?
269
- av << str
270
- self.attr_value = av
252
+ # check for common html options and return the hash
253
+ def common_html_options
254
+ options = {}
255
+ if (v = element_attrs[:id]) : options[:id] = v; end
256
+ if (v = element_attrs[:class]) : options[:class] = v; end
257
+ if (v = element_attrs[:style]) : options[:style] = v; end
258
+ if (v = element_attrs[:tabindex]) : options[:tabindex] = v; end
259
+ if (v = element_attrs[:accesskey]) : options[:accesskey] = v; end
260
+ if (element_attrs[:disabled]) : options[:disabled] = true; end
261
+ if (element_attrs[:readonly]) : options[:readonly] = true; end
262
+
263
+ # these are not as common but take care of them anyway for convenience
264
+ if (v = element_attrs[:alt]) : options[:alt] = v; end
265
+ if (v = element_attrs[:width]) : options[:width] = v.to_i; end
266
+ if (v = element_attrs[:height]) : options[:height] = v.to_i; end
267
+ if (v = element_attrs[:rows]) : options[:rows] = v.to_i; end
268
+ if (v = element_attrs[:cols]) : options[:cols] = v.to_i; end
269
+ if (v = element_attrs[:size]) : options[:size] = v.to_i; end
270
+ if (v = element_attrs[:maxlength]) : options[:maxlength] = v.to_i; end
271
+ options
271
272
  end
272
273
 
273
- #merge merge_hash into hashes stored in attribute_value string
274
- #hash_index is the zero based index of the hash you want to add to
275
- def merge_hash_attr_value!(hash_index, merge_hash)
276
- self.attr_value = merge_into_embedded_hash(attr_value, hash_index, merge_hash)
274
+ # merge a hash into a string representation of a hash,
275
+ # this method basically appends the hash values to the end of the
276
+ # string taking into account whether the hash has brackets or is
277
+ # open. When ruby reads a hash it will keep the last value set
278
+ # so later values override previous values
279
+ def merge_hash_into_str(hash_to_merge, str_to_merge_into)
280
+ str_to_merge_into = '' if str_to_merge_into.nil?
281
+ sorted_hash_to_merge = hash_to_merge.sort { |a,b| a[0].to_s <=> b[0].to_s } #sort, remember the keys might be symbols so use to_s
282
+ str_to_merge = sorted_hash_to_merge.collect{ |h,v| "#{h.inspect} => #{v.inspect}" }.join(', ')
283
+ str = str_to_merge_into.strip
284
+ if(str[0,1] == '{') # has {} surrounding
285
+ str = str[1..-2].strip # get just contents stripping {}
286
+ str += ', ' unless str.empty? or str_to_merge.empty?
287
+ str += str_to_merge
288
+ str = '{'+str+'}'
289
+ else
290
+ str += ', ' unless str.empty? or str_to_merge.empty?
291
+ str += str_to_merge
292
+ end
293
+ str
277
294
  end
278
295
 
279
- #calls non-evaling parse to split into string arguments
280
- def parse_attr_value
281
- parse(attr_value)
296
+ # find the first parent directive instance that matches the parentClass and if block is provided
297
+ # then evaluate the block as well allowing further filtering, if block is false then
298
+ # it won't be used as a match.
299
+ # This method is especially useful for providing a mechanism for children to locate
300
+ # parent directives and communicate with them potentially influencing their output.
301
+ # Once a parent directive instance is found, public methods on the parent directive
302
+ # can be called, so the child might add itself to some list, etc.
303
+ def find_parent_directive(parentClass, &block)
304
+ parent_dir = nil
305
+ tag = @directive_call_stack.context[:tag]
306
+ while( parent_dir.nil? )
307
+ tag = tag.parent
308
+ break if tag.nil?
309
+ parent_dir = tag.directives.directives.find { |d| d.is_a?(parentClass) and (block.nil? or block.call(d)) }
310
+ end
311
+ parent_dir
282
312
  end
283
313
 
284
- # check for common html options and return the hash
285
- def common_html_options(attrs_lck)
286
- options = {}
287
- options[:id] = attrs_lck['id'] if attrs_lck['id']
288
- options[:class] = attrs_lck['class'] if attrs_lck['class']
289
- options[:style] = attrs_lck['style'] if attrs_lck['style']
290
- options[:tabindex] = attrs_lck['tabindex'] if attrs_lck['tabindex']
291
- options[:accesskey] = attrs_lck['accesskey'] if attrs_lck['accesskey']
292
- options[:disabled] = true if attrs_lck['disabled']
293
- options[:readonly] = true if attrs_lck['readonly']
294
- options
314
+ # convenience method to get to current renderer
315
+ def renderer
316
+ @directive_call_stack.context[:tag].renderer
295
317
  end
296
318
 
297
319
  end
298
-
299
320
  end
321
+