fortitude 0.9.4 → 0.9.5

Sign up to get free protection for your applications and to get access to all the features.
Files changed (67) hide show
  1. checksums.yaml +4 -4
  2. data/.fix_bundler_for_jruby_17 +26 -0
  3. data/.gitignore +1 -0
  4. data/.travis.yml +33 -40
  5. data/CHANGES.md +44 -0
  6. data/CONTRIBUTORS.md +26 -0
  7. data/Rakefile +1 -1
  8. data/ext/com/fortituderuby/ext/fortitude/FortitudeNativeLibrary.java +45 -33
  9. data/ext/fortitude_native_ext/fortitude_native_ext.c +23 -23
  10. data/fortitude.gemspec +25 -8
  11. data/lib/fortitude/erector.rb +26 -18
  12. data/lib/fortitude/errors.rb +15 -4
  13. data/lib/fortitude/extensions/fortitude_ruby_ext.rb +35 -10
  14. data/lib/fortitude/rails/helpers.rb +59 -2
  15. data/lib/fortitude/rails/railtie.rb +238 -157
  16. data/lib/fortitude/rails/renderer.rb +15 -0
  17. data/lib/fortitude/rails/rendering_methods.rb +46 -33
  18. data/lib/fortitude/rails/template_handler.rb +49 -18
  19. data/lib/fortitude/rails/yielded_object_outputter.rb +3 -2
  20. data/lib/fortitude/rendering_context.rb +14 -5
  21. data/lib/fortitude/support/method_overriding.rb +90 -0
  22. data/lib/fortitude/support/staticized_method.rb +12 -0
  23. data/lib/fortitude/version.rb +1 -1
  24. data/lib/fortitude/widget/content.rb +4 -2
  25. data/lib/fortitude/widget/files.rb +17 -11
  26. data/lib/fortitude/widget/helpers.rb +7 -1
  27. data/lib/fortitude/widget/integration.rb +4 -0
  28. data/lib/fortitude/widget/localization.rb +63 -4
  29. data/lib/fortitude/widget/rendering.rb +17 -10
  30. data/lib/fortitude_jruby_native_ext.jar +0 -0
  31. data/spec/helpers/fortitude_rails_helpers.rb +26 -4
  32. data/spec/rails/capture_system_spec.rb +1 -1
  33. data/spec/rails/class_loading_system_spec.rb +16 -2
  34. data/spec/rails/complex_helpers_system_spec.rb +29 -0
  35. data/spec/rails/data_passing_system_spec.rb +2 -2
  36. data/spec/rails/development_mode_system_spec.rb +1 -1
  37. data/spec/rails/erector_coexistence_system_spec.rb +1 -1
  38. data/spec/rails/helpers_system_spec.rb +20 -2
  39. data/spec/rails/layouts_system_spec.rb +1 -1
  40. data/spec/rails/rendering_system_spec.rb +4 -4
  41. data/spec/rails/rules_system_spec.rb +2 -2
  42. data/spec/rails/templates/class_loading_system_spec/app/views/some_namespace/some_other_namespace/placeholder.rb +5 -0
  43. data/spec/rails/templates/complex_helpers_system_spec/app/controllers/complex_helpers_system_spec_controller.rb +4 -0
  44. data/spec/rails/templates/complex_helpers_system_spec/app/views/complex_helpers_system_spec/label_block_test.rb +9 -0
  45. data/spec/rails/templates/helpers_system_spec/app/controllers/helpers_system_spec_controller.rb +4 -0
  46. data/spec/rails/templates/helpers_system_spec/app/controllers/home_controller.rb +9 -0
  47. data/spec/rails/templates/helpers_system_spec/app/views/helpers_system_spec/rails_helpers_without_automatic_helper_access.rb +37 -0
  48. data/spec/rails/templates/helpers_system_spec/app/views/helpers_system_spec/url_helpers_without_automatic_helper_access.rb +45 -0
  49. data/spec/rails/templates/helpers_system_spec/config/initializers/host.rb +1 -0
  50. data/spec/rails/templates/helpers_system_spec/config/routes.rb +7 -0
  51. data/spec/rails/templates/static_method_system_spec/app/views/static_method_system_spec/localization.rb +1 -1
  52. data/spec/rails/templates/view_paths_system_spec/app/controllers/view_paths_system_spec_controller.rb +15 -0
  53. data/spec/rails/templates/view_paths_system_spec/config/application.rb +30 -0
  54. data/spec/rails/templates/view_paths_system_spec/view_path_one/baseone/basetwo/base_class_one.rb +5 -0
  55. data/spec/rails/templates/view_paths_system_spec/view_path_one/view_paths_system_spec/added_view_path.html.rb +5 -0
  56. data/spec/rails/templates/view_paths_system_spec/view_path_one/view_paths_system_spec/autoloading_from_added_view_path.html.rb +5 -0
  57. data/spec/rails/templates/view_paths_system_spec/view_path_two/view_paths_system_spec/added_view_path_from_controller.html.rb +5 -0
  58. data/spec/rails/templates/view_paths_system_spec/view_path_two/view_paths_system_spec/added_view_path_from_controller_with_impossible_to_guess_name.html.rb +14 -0
  59. data/spec/rails/view_paths_system_spec.rb +19 -0
  60. data/spec/system/escaping_system_spec.rb +10 -2
  61. data/spec/system/helpers_system_spec.rb +37 -6
  62. data/spec/system/inline_system_spec.rb +19 -0
  63. data/spec/system/static_method_system_spec.rb +16 -0
  64. data/spec/system/tag_rendering_system_spec.rb +4 -4
  65. data/spec/system/widget_class_from_spec.rb +39 -0
  66. data/spec/system/yield_system_spec.rb +53 -1
  67. metadata +54 -22
@@ -10,6 +10,21 @@ module Fortitude
10
10
  module Rails
11
11
  class Renderer
12
12
  class << self
13
+ def render_file(template_identifier, view_paths, template_handler, local_assigns, &block)
14
+ expanded_view_paths = view_paths.map do |path|
15
+ File.expand_path(path.to_s, ::Rails.root.to_s)
16
+ end
17
+
18
+ valid_base_classes = [ ::Fortitude::Widget, ::Fortitude::Erector.erector_widget_base_class_if_available ].compact
19
+
20
+ widget_class = ::Fortitude::Widget.widget_class_from_file(template_identifier,
21
+ :root_dirs => expanded_view_paths, :valid_base_classes => valid_base_classes)
22
+
23
+ is_partial = !! File.basename(template_identifier) =~ /^_/
24
+
25
+ render(widget_class, template_handler, local_assigns, is_partial, &block)
26
+ end
27
+
13
28
  # TODO: Refactor this and render :widget => ... support into one method somewhere.
14
29
  def render(widget_class, template_handler, local_assigns, is_partial, &block)
15
30
  if ::Fortitude::Erector.is_erector_widget_class?(widget_class)
@@ -6,8 +6,12 @@ module Fortitude
6
6
  module RenderingMethods
7
7
  extend ActiveSupport::Concern
8
8
 
9
- included do
10
- alias_method_chain :render, :fortitude
9
+ class << self
10
+ def include_into!(target)
11
+ target.send(:include, self)
12
+ ::Fortitude::MethodOverriding.override_methods(
13
+ target, ::Fortitude::Rails::RenderingMethods::Overrides, :fortitude, [ :render ])
14
+ end
11
15
  end
12
16
 
13
17
  def fortitude_rendering_context_for(delegate_object, yield_block)
@@ -48,55 +52,64 @@ module Fortitude
48
52
 
49
53
  passed_options = options.dup
50
54
  passed_options.delete(:widget)
51
- passed_options[:text] = output_buffer.to_s
55
+
56
+ output_key = if ::Rails.version =~ /^(3\.)|(4\.0\.)/ then :text else :html end
57
+ passed_options[output_key] = output_buffer.to_s
52
58
  passed_options[:layout] = true unless passed_options.has_key?(:layout)
53
59
 
54
60
  return controller.render_to_string(passed_options)
55
61
  end
56
62
 
57
63
  def self._fortitude_register_renderer!
58
- ::ActionController.add_renderer_without_fortitude(:widget) do |widget, options|
64
+ ::ActionController.add_renderer(:_fortitude_widget) do |widget, options|
59
65
  ::Fortitude::Rails::RenderingMethods._fortitude_render_widget(self, widget, options)
60
66
  end
61
67
  end
62
68
 
63
- def render_with_fortitude(*args, &block)
64
- if (options = args[0]).kind_of?(Hash) && (widget_block = options[:inline]) && (options[:type] == :fortitude)
65
- options.delete(:inline)
66
-
67
- rendering_context = fortitude_rendering_context_for(self, nil)
68
- widget_class = Class.new(Fortitude::Widgets::Html5)
69
- widget_class.use_instance_variables_for_assigns(true)
70
- widget_class.extra_assigns(:use)
71
- widget_class.send(:define_method, :content, &widget_block)
72
-
73
- assigns = { }
74
- instance_variables.each do |ivar_name|
75
- value = instance_variable_get(ivar_name)
76
- assigns[$1.to_sym] = value if ivar_name =~ /^@([^_].*)$/
69
+ module Overrides
70
+ def render_uniwith_fortitude(original_method, *args, &block)
71
+ if (options = args[0]).kind_of?(Hash) && (widget_block = options[:inline]) && (options[:type] == :fortitude)
72
+ options.delete(:inline)
73
+
74
+ rendering_context = fortitude_rendering_context_for(self, nil)
75
+ widget_class = Class.new(Fortitude::Widgets::Html5)
76
+ widget_class.use_instance_variables_for_assigns(true)
77
+ widget_class.extra_assigns(:use)
78
+ widget_class.send(:define_method, :content, &widget_block)
79
+
80
+ assigns = { }
81
+ instance_variables.each do |ivar_name|
82
+ value = instance_variable_get(ivar_name)
83
+ assigns[$1.to_sym] = value if ivar_name =~ /^@([^_].*)$/
84
+ end
85
+ assigns = assigns.merge(options[:locals] || { })
86
+
87
+ widget = widget_class.new(assigns)
88
+ new_args = [ options.merge(:widget => widget) ] + args[1..-1]
89
+ return original_method.call(*new_args, &block)
77
90
  end
78
- assigns = assigns.merge(options[:locals] || { })
79
91
 
80
- widget = widget_class.new(assigns)
81
- new_args = [ options.merge(:widget => widget) ] + args[1..-1]
82
- return render_without_fortitude(*new_args, &block)
92
+ return original_method.call(*args, &block)
83
93
  end
94
+ end
84
95
 
85
- return render_without_fortitude(*args, &block)
96
+ module ActionControllerOverrides
97
+ def add_renderer_uniwith_fortitude(original_method, key, *args, &block)
98
+ if key == :_fortitude_widget
99
+ original_method.call(:widget, *args, &block)
100
+ else
101
+ original_method.call(key, *args, &block)
102
+ ::Fortitude::Rails::RenderingMethods._fortitude_register_renderer!
103
+ end
104
+ end
86
105
  end
87
106
  end
88
107
  end
89
108
  end
90
109
 
91
- ::ActionController.module_eval do
92
- class << self
93
- def add_renderer_with_fortitude(key, *args, &block)
94
- add_renderer_without_fortitude(key, *args, &block)
95
- ::Fortitude::Rails::RenderingMethods._fortitude_register_renderer!
96
- end
97
-
98
- alias_method_chain :add_renderer, :fortitude
99
- end
100
- end
110
+ eigenclass = ::ActionController.class_eval "class << self; self; end"
111
+ ::Fortitude::MethodOverriding.override_methods(
112
+ eigenclass, ::Fortitude::Rails::RenderingMethods::ActionControllerOverrides, :fortitude,
113
+ [ :add_renderer ])
101
114
 
102
115
  ::Fortitude::Rails::RenderingMethods._fortitude_register_renderer!
@@ -4,34 +4,65 @@ module Fortitude
4
4
  module Rails
5
5
  class TemplateHandler
6
6
  def call(template, &block)
7
- widget_class_name = "views/#{template.identifier =~ %r(views/([^.]*)(\..*)?\.rb) && $1}".camelize
8
- is_partial = !! (File.basename(template.identifier) =~ /^_/)
7
+ # This is a little funny. Under almost every single circumstance, we can, at template-compile time, deduce
8
+ # what class is inside the template file, and simply call Fortitude::Rails::Renderer.render with that class.
9
+ #
10
+ # However, there is one case under which we can't: if you've added to the view paths in the controller
11
+ # (using something like +append_view_path+), the template you're rendering is in an added view path,
12
+ # *and* that template has an "un-guessable" class name -- meaning you're doing something seriously strange
13
+ # in its source code. (See the view_paths_system_spec's test case with an "impossible-to-guess name" for the
14
+ # exact circumstances.) Under that case, the only way everything can work completely correctly is if we
15
+ # delay trying to figure out the class name from the template filename until rendering time, when we'll have
16
+ # the view paths available.
17
+ #
18
+ # This second path, however, is slower, because it has to do I/O to figure out that class name. (This adds
19
+ # about 1ms on my 2013 MacBook Pro with SSD.) So, we try the fast path first, and only fall back to the slow
20
+ # path if absolutely necessary.
21
+ expanded_view_paths = ::Rails.application.paths['app/views'].map { |path| File.expand_path(path.to_s, ::Rails.root.to_s) }
22
+ valid_base_classes = [ ::Fortitude::Widget, ::Fortitude::Erector.erector_widget_base_class_if_available ].compact
23
+ is_partial = !! File.basename(template.identifier) =~ /^_/
9
24
 
10
- <<-SRC
11
- Fortitude::Rails::Renderer.render(#{widget_class_name}, self, local_assigns, #{is_partial.inspect}) { |*args| yield *args }
12
- SRC
25
+ widget_class = nil
26
+
27
+ begin
28
+ widget_class = ::Fortitude::Widget.widget_class_from_file(template.identifier.to_s,
29
+ :root_dirs => expanded_view_paths, :valid_base_classes => valid_base_classes)
30
+
31
+ <<-SRC
32
+ Fortitude::Rails::Renderer.render(#{widget_class.name}, self, local_assigns, #{is_partial.inspect}) { |*args| yield *args }
33
+ SRC
34
+ rescue Fortitude::Widget::Files::CannotDetermineWidgetClassNameError => cdwcne
35
+ <<-SRC
36
+ Fortitude::Rails::Renderer.render_file(#{template.identifier.to_s.inspect}, view_paths, self, local_assigns) { |*args| yield *args }
37
+ SRC
38
+ end
13
39
  end
14
40
 
15
41
  def supports_streaming?
16
42
  true
17
43
  end
18
- end
19
- end
20
- end
21
44
 
22
- ::ActionView::Template.class_eval do
23
- class << self
24
- def _fortitude_register_template_handler!
25
- register_template_handler_without_fortitude(:rb, Fortitude::Rails::TemplateHandler.new)
45
+ class << self
46
+ def register!
47
+ ::ActionView::Template.register_template_handler(:rb, ::Fortitude::Rails::TemplateHandler.new)
48
+ end
49
+ end
26
50
  end
27
51
 
28
- def register_template_handler_with_fortitude(*args, &block)
29
- register_template_handler_without_fortitude(*args, &block)
30
- ActionView::Template._fortitude_register_template_handler!
31
- end
52
+ module RegisterTemplateHandlerOverrides
53
+ def register_template_handler_uniwith_fortitude(original_method, *args, &block)
54
+ original_method.call(*args, &block)
32
55
 
33
- alias_method_chain :register_template_handler, :fortitude
56
+ unless args[0] == :rb && args[1].instance_of?(::Fortitude::Rails::TemplateHandler)
57
+ original_method.call(:rb, ::Fortitude::Rails::TemplateHandler.new)
58
+ end
59
+ end
60
+ end
34
61
  end
35
62
  end
36
63
 
37
- ActionView::Template._fortitude_register_template_handler!
64
+ eigenclass = ::ActionView::Template.class_eval "class << self; self; end"
65
+ ::Fortitude::MethodOverriding.override_methods(
66
+ eigenclass, ::Fortitude::Rails::RegisterTemplateHandlerOverrides, :fortitude, [ :register_template_handler ])
67
+
68
+ ::Fortitude::Rails::TemplateHandler.register!
@@ -4,8 +4,9 @@ module Fortitude
4
4
  class YieldedObjectOutputter < YIELDED_OBJECT_OUTPUTTER_SUPERCLASS
5
5
  class << self
6
6
  def wrap_block_as_needed(output_target, for_method_name, original_block, yielded_methods_to_output)
7
- if original_block && yielded_methods_to_output
8
- lambda do |yielded_object, *args|
7
+ if original_block && yielded_methods_to_output && original_block.arity > 0
8
+ lambda do |*args|
9
+ yielded_object = args.shift
9
10
  outputter = new(output_target, yielded_object, for_method_name, yielded_methods_to_output)
10
11
  original_block.call(outputter, *args)
11
12
  end
@@ -5,6 +5,12 @@ module Fortitude
5
5
  class RenderingContext
6
6
  attr_reader :output_buffer_holder, :instance_variable_set, :helpers_object
7
7
 
8
+ class << self
9
+ def default_rendering_context
10
+ new({ })
11
+ end
12
+ end
13
+
8
14
  def initialize(options)
9
15
  options.assert_valid_keys(:delegate_object, :output_buffer_holder, :helpers_object, :instance_variables_object,
10
16
  :yield_block, :render_yield_result)
@@ -188,11 +194,14 @@ module Fortitude
188
194
 
189
195
  NEWLINE = "\n"
190
196
 
191
- def yield_from_widget(widget, *args)
192
- raise Fortitude::Errors::NoBlockToYieldTo.new(widget) unless @yield_block
193
- result = @yield_block.call(*args)
194
- @output_buffer_holder.output_buffer << result if @render_yield_result
195
- result
197
+ def effective_yield_block
198
+ if @yield_block
199
+ lambda do |*args|
200
+ result = @yield_block.call(*args)
201
+ @output_buffer_holder.output_buffer << result if @render_yield_result
202
+ result
203
+ end
204
+ end
196
205
  end
197
206
 
198
207
  def flush!
@@ -0,0 +1,90 @@
1
+ module Fortitude
2
+ module MethodOverriding
3
+ class << self
4
+ # This is Fortitude’s way of maintaining compatibility both with Ruby < 2.0 (no support for Module#prepend)
5
+ # and Ruby 2.0 and later (alias_method_chain is deprecated). Here’s how it works:
6
+ #
7
+ # * For a method 'foo' that you want to override using a 'feature name' of bar, you define a method called
8
+ # 'foo_uniwith_bar' -- but not in the target module or class (containing the method to be overridden); rather,
9
+ # it must be in a separate Module, and, importantly, not one that you have already Module#include'd into the
10
+ # target module or class. This method has the same signature as the original, except that it also takes, as a first
11
+ # parameter, a #call'able object (typically a Proc or lambda) that represents the original, un-overridden
12
+ # method. You use this, instead of calling 'foo_without_bar' or 'super', in order to invoke the original
13
+ # method.
14
+ # * You then call Fortitude::MethodOverriding.override_methods. +target_module+ is the module containing the
15
+ # method you want to override, +override_methods_module+ is the module containing your overriding method
16
+ # ('foo_uniwith_bar'), +feature_name+ is the name of the feature you're using ('bar'), and +method_names+
17
+ # is an Array of Symbols, each of which is the name of a method you want to override (_e.g._, 'foo').
18
+ #
19
+ # This class then performs the appropriate logic to use 'alias_method_chain' or Module#prepend appropriately.
20
+ #
21
+ # One exception: using Module#prepend seems to cause Fortitude all kinds of problems with JRuby 9.1.5.0 (as of
22
+ # this writing, the latest version of JRuby). In particular, you get things like a java.lang.BootstrapMethodError
23
+ # at "require at org/jruby/RubyKernel.java:956", and various java.lang.StackOverflowErrors that seem to make no
24
+ # sense at all -- and if you use alias_method_chain instead, everything seems to work perfectly. As a result,
25
+ # we currently fall back to using alias_method_chain on JRuby. (You only get deprecation warnings with this
26
+ # when running with Rails 5, which is not yet supported by JRuby anyway, at least as of this writing.)
27
+ def override_methods(target_module, override_methods_module, feature_name, method_names)
28
+ if RUBY_VERSION =~ /^2\./ && (! ((RUBY_ENGINE || '').to_s.downcase.strip == 'jruby'))
29
+ override_methods_using_prepend(target_module, override_methods_module, feature_name, method_names)
30
+ else
31
+ override_methods_using_alias_method_chain(target_module, override_methods_module, feature_name, method_names)
32
+ end
33
+ end
34
+
35
+ private
36
+ def override_methods_using_prepend(target_module, override_methods_module, feature_name, method_names)
37
+ method_names.each do |method_name|
38
+ universal_name = universal_method_name(method_name, feature_name)
39
+
40
+ override_methods_module.class_eval <<-EOS
41
+ def #{method_name}(*args, &block)
42
+ original_method = Proc.new { |*args, &block| super(*args, &block) }
43
+ #{universal_name}(original_method, *args, &block)
44
+ end
45
+ EOS
46
+ end
47
+
48
+ target_module.send(:prepend, override_methods_module)
49
+ end
50
+
51
+ def override_methods_using_alias_method_chain(target_module, override_methods_module, feature_name, method_names = nil)
52
+ method_names.each do |method_name|
53
+ universal_name = universal_method_name(method_name, feature_name)
54
+ with_name = with_feature_name(method_name, feature_name)
55
+ without_name = without_feature_name(method_name, feature_name)
56
+
57
+ override_methods_module.class_eval <<-EOS
58
+ def #{with_name}(*args, &block)
59
+ original_method = Proc.new { |*args, &block| #{without_name}(*args, &block) }
60
+ #{universal_name}(original_method, *args, &block)
61
+ end
62
+ EOS
63
+
64
+ target_module.send(:include, override_methods_module)
65
+ target_module.send(:alias_method_chain, method_name, feature_name)
66
+ end
67
+ end
68
+
69
+ def suffix_method_name(method_name, suffix)
70
+ if method_name.to_s =~ /^(.*?)([\?\_\!]+)$/i
71
+ "#{$1}#{suffix}#{$2}"
72
+ else
73
+ "#{method_name}#{suffix}"
74
+ end
75
+ end
76
+
77
+ def with_feature_name(method_name, feature_name)
78
+ suffix_method_name(method_name, "_with_#{feature_name}")
79
+ end
80
+
81
+ def without_feature_name(method_name, feature_name)
82
+ suffix_method_name(method_name, "_without_#{feature_name}")
83
+ end
84
+
85
+ def universal_method_name(method_name, feature_name)
86
+ suffix_method_name(method_name, "_uniwith_#{feature_name}")
87
+ end
88
+ end
89
+ end
90
+ end
@@ -28,6 +28,18 @@ module Fortitude
28
28
  end
29
29
 
30
30
  def create_method!
31
+ begin
32
+ widget_class.instance_method(method_name)
33
+ rescue NameError => ne
34
+ raise NameError, %{You declared the method #{method_name.inspect} in class
35
+ #{widget_class.name.inspect} to be a Fortitude static content method,
36
+ but there is no method declared on this class with that name.
37
+
38
+ (It's possible you simply tried to declare the method static before
39
+ it was defined on that class -- the call to 'static' must come
40
+ _after_ the definition of the method in the class's source code.)}
41
+ end
42
+
31
43
  unless widget_class.instance_methods.map(&:to_s).include?(dynamic_method_name.to_s)
32
44
  widget_class.send(:alias_method, dynamic_method_name, method_name)
33
45
  end
@@ -1,3 +1,3 @@
1
1
  module Fortitude
2
- VERSION = "0.9.4"
2
+ VERSION = "0.9.5"
3
3
  end
@@ -13,8 +13,8 @@ module Fortitude
13
13
  out
14
14
  end
15
15
 
16
- def inline_html(assigns = { }, &block)
17
- inline_subclass(&block).new(assigns).to_html
16
+ def inline_html(assigns = { }, rendering_context = nil, &block)
17
+ inline_subclass(&block).new(assigns).to_html(rendering_context)
18
18
  end
19
19
 
20
20
  # INTERNAL USE ONLY
@@ -43,6 +43,8 @@ module Fortitude
43
43
  text += " " + (" " * (acm.length - (index + 1))) + "end\n"
44
44
  end
45
45
  text += " out\n"
46
+ text += "rescue LocalJumpError => lje\n"
47
+ text += " raise Fortitude::Errors::NoBlockToYieldTo.new(self, lje)\n"
46
48
  text += "end"
47
49
 
48
50
  class_eval(text)
@@ -41,7 +41,7 @@ or add a "magic comment" to the source code of this widget that looks like this:
41
41
 
42
42
  module ClassMethods
43
43
  def widget_class_from_file(filename, options = { })
44
- options.assert_valid_keys(:root_dirs, :class_names_to_try, :magic_comment_text)
44
+ options.assert_valid_keys(:root_dirs, :class_names_to_try, :magic_comment_text, :valid_base_classes)
45
45
  filename = File.expand_path(filename)
46
46
  source = File.read(filename)
47
47
 
@@ -53,18 +53,18 @@ or add a "magic comment" to the source code of this widget that looks like this:
53
53
 
54
54
  if filename[0..(root_dir.length - 1)].downcase == root_dir.downcase
55
55
  subpath = filename[(root_dir.length + 1)..-1]
56
- subpath = $1 if subpath =~ /^(.*)\.rb$/i
56
+ subpath = $1 if subpath =~ %r{^(.*?)\.[^/]+$}i # remove all extensions
57
57
  class_names_to_try << subpath.camelize if subpath && subpath.length > 1
58
58
  end
59
59
  end
60
60
 
61
61
  widget_class_from_source(source,
62
62
  :class_names_to_try => class_names_to_try, :magic_comment_text => options[:magic_comment_text],
63
- :filename => filename)
63
+ :filename => filename, :valid_base_classes => options[:valid_base_classes])
64
64
  end
65
65
 
66
66
  def widget_class_from_source(source, options = { })
67
- options.assert_valid_keys(:class_names_to_try, :magic_comment_text, :filename)
67
+ options.assert_valid_keys(:class_names_to_try, :magic_comment_text, :filename, :valid_base_classes)
68
68
 
69
69
  magic_comment_texts = Array(options[:magic_comment_text]) + DEFAULT_MAGIC_COMMENT_TEXTS
70
70
  all_class_names =
@@ -72,7 +72,7 @@ or add a "magic comment" to the source code of this widget that looks like this:
72
72
  Array(options[:class_names_to_try]) +
73
73
  scan_source_for_possible_class_names(source)
74
74
 
75
- out = widget_class_from_class_names(all_class_names)
75
+ out = widget_class_from_class_names(all_class_names, :valid_base_classes => options[:valid_base_classes])
76
76
  resulting_objects = out[:resulting_objects]
77
77
 
78
78
  unless out[:widget_class]
@@ -82,7 +82,7 @@ or add a "magic comment" to the source code of this widget that looks like this:
82
82
  ::Object.class_eval(source)
83
83
  end
84
84
 
85
- out = widget_class_from_class_names(all_class_names)
85
+ out = widget_class_from_class_names(all_class_names, :valid_base_classes => options[:valid_base_classes])
86
86
  resulting_objects += out[:resulting_objects]
87
87
  end
88
88
 
@@ -133,7 +133,9 @@ or add a "magic comment" to the source code of this widget that looks like this:
133
133
  out
134
134
  end
135
135
 
136
- def widget_class_from_class_names(class_names)
136
+ def widget_class_from_class_names(class_names, options = { })
137
+ options.assert_valid_keys(:valid_base_classes)
138
+
137
139
  out = {
138
140
  :widget_class => nil,
139
141
  :resulting_objects => [ ]
@@ -147,7 +149,7 @@ or add a "magic comment" to the source code of this widget that looks like this:
147
149
  nil
148
150
  end
149
151
 
150
- if is_widget_class?(klass)
152
+ if is_widget_class?(klass, options)
151
153
  out[:widget_class] = klass
152
154
  break
153
155
  elsif klass
@@ -157,15 +159,19 @@ or add a "magic comment" to the source code of this widget that looks like this:
157
159
  out
158
160
  end
159
161
 
160
- def is_widget_class?(klass)
162
+ def is_widget_class?(klass, options = { })
163
+ options.assert_valid_keys(:valid_base_classes)
164
+
165
+ valid_base_classes = Array(options[:valid_base_classes] || ::Fortitude::Widget)
166
+
161
167
  if (! klass)
162
168
  false
163
169
  elsif (! klass.kind_of?(Class))
164
170
  false
165
- elsif klass == ::Fortitude::Widget
171
+ elsif valid_base_classes.include?(klass)
166
172
  true
167
173
  else
168
- is_widget_class?(klass.superclass)
174
+ is_widget_class?(klass.superclass, :valid_base_classes => valid_base_classes)
169
175
  end
170
176
  end
171
177
  end