fortitude 0.9.4-java → 0.9.5-java

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 (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 +90 -58
@@ -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