fortitude 0.0.4 → 0.0.5

Sign up to get free protection for your applications and to get access to all the features.
Files changed (56) hide show
  1. checksums.yaml +4 -4
  2. data/.travis.yml +19 -19
  3. data/CHANGES.md +31 -0
  4. data/Gemfile +1 -0
  5. data/README-erector.md +1 -1
  6. data/lib/fortitude/erector.rb +32 -0
  7. data/lib/fortitude/method_templates/tag_method_template.rb.smpl +6 -6
  8. data/lib/fortitude/method_templates/text_method_template.rb.smpl +3 -3
  9. data/lib/fortitude/rails/railtie.rb +6 -4
  10. data/lib/fortitude/rails/renderer.rb +7 -4
  11. data/lib/fortitude/rails/template_handler.rb +18 -3
  12. data/lib/fortitude/rendering_context.rb +20 -2
  13. data/lib/fortitude/tags/render_widget_placeholder.rb +19 -0
  14. data/lib/fortitude/tags/tag.rb +18 -4
  15. data/lib/fortitude/tags/tag_return_value.rb +1 -1
  16. data/lib/fortitude/tags/tag_store.rb +4 -0
  17. data/lib/fortitude/tilt/fortitude_template.rb +6 -128
  18. data/lib/fortitude/version.rb +1 -1
  19. data/lib/fortitude/widget.rb +2 -0
  20. data/lib/fortitude/widget/files.rb +162 -0
  21. data/lib/fortitude/widget/integration.rb +5 -3
  22. data/lib/fortitude/widget/modules_and_subclasses.rb +17 -0
  23. data/lib/fortitude/widget/rendering.rb +12 -5
  24. data/lib/fortitude/widget/start_and_end_comments.rb +4 -2
  25. data/lib/fortitude/widget/tags.rb +6 -1
  26. data/lib/fortitude/widget/widget_class_inheritable_attributes.rb +7 -0
  27. data/spec/helpers/rails_server.rb +4 -0
  28. data/spec/rails/basic_rails_system_spec.rb +4 -0
  29. data/spec/rails/erector_coexistence_system_spec.rb +33 -0
  30. data/spec/rails/rendering_context_system_spec.rb +19 -3
  31. data/spec/rails/rendering_system_spec.rb +6 -0
  32. data/spec/rails/templates/basic_rails_system_spec/app/controllers/basic_rails_system_spec_controller.rb +5 -0
  33. data/spec/rails/templates/basic_rails_system_spec/app/views/basic_rails_system_spec/double_render_one.rb +5 -0
  34. data/spec/rails/templates/basic_rails_system_spec/app/views/basic_rails_system_spec/double_render_three.rb +5 -0
  35. data/spec/rails/templates/basic_rails_system_spec/app/views/basic_rails_system_spec/double_render_two.rb +9 -0
  36. data/spec/rails/templates/erector_coexistence_system_spec/app/controllers/erector_coexistence_system_spec_controller.rb +19 -0
  37. data/spec/rails/templates/erector_coexistence_system_spec/app/v/views/erector_coexistence_system_spec/erector_widget_in_app_v_views.rb +7 -0
  38. data/spec/rails/templates/erector_coexistence_system_spec/app/v/views/erector_coexistence_system_spec/fortitude_widget_in_app_v_views.rb +7 -0
  39. data/spec/rails/templates/erector_coexistence_system_spec/app/views/erector_coexistence_system_spec/erector_widget_in_app_views.rb +7 -0
  40. data/spec/rails/templates/erector_coexistence_system_spec/app/views/erector_coexistence_system_spec/fortitude_widget_in_app_views.rb +7 -0
  41. data/spec/rails/templates/erector_coexistence_system_spec/config/application.rb +25 -0
  42. data/spec/rails/templates/rendering_context_system_spec/app/controllers/rendering_context_system_spec_controller.rb +2 -2
  43. data/spec/rails/templates/rendering_context_system_spec/app/views/rendering_context_system_spec/_current_element_nesting_intermediate.html.erb +3 -0
  44. data/spec/rails/templates/rendering_context_system_spec/app/views/rendering_context_system_spec/current_element_nesting_child.rb +13 -0
  45. data/spec/rails/templates/rendering_context_system_spec/app/views/rendering_context_system_spec/current_element_nesting_toplevel.rb +9 -0
  46. data/spec/rails/templates/rendering_context_system_spec/app/views/rendering_context_system_spec/start_end_widget_through_partials.rb +2 -2
  47. data/spec/system/convenience_methods_system_spec.rb +22 -0
  48. data/spec/system/inline_system_spec.rb +2 -2
  49. data/spec/system/record_tag_emission_system_spec.rb +71 -0
  50. data/spec/system/rendering_context_system_spec.rb +21 -0
  51. data/spec/system/setting_inheritance_system_spec.rb +52 -0
  52. data/spec/system/tag_return_value_system_spec.rb +7 -0
  53. data/spec/system/tilt_system_spec.rb +13 -18
  54. data/spec/system/widget_class_from_spec.rb +240 -0
  55. data/spec/system/widget_method_system_spec.rb +52 -0
  56. metadata +37 -2
@@ -5,50 +5,17 @@ require 'fortitude/doctypes'
5
5
 
6
6
  module Fortitude
7
7
  module Tilt
8
- class CannotDetermineTemplateClassError < StandardError
9
- attr_reader :tried_class_names
10
-
11
- def initialize(tried_class_names)
12
- @tried_class_names = tried_class_names
13
- super %{Due to the way Tilt is designed, Fortitude unfortunately has to guess, given a template,
14
- what the actual class name of the widget it contains is. Despite our best efforts, we were unable to
15
- guess what the name of the class involved is.
16
-
17
- Given the source of the template, we tried the following class names:
18
-
19
- #{tried_class_names.join("\n")}
20
-
21
- You can correct this problem either by passing a :fortitude_class option to Tilt, giving
22
- either the name of the widget class or the actual widget Class object, or by adding a
23
- comment to the source of the template, like so:
24
-
25
- #!fortitude_tilt_class: Foo::Bar::MyWidget}
26
- end
27
- end
28
-
29
- class NotATemplateClassError < StandardError
30
- attr_reader :class_name, :actual_object
31
-
32
- def initialize(class_name, actual_object)
33
- @class_name = class_name
34
- @actual_object = actual_object
35
-
36
- super %{You explicitly told Fortitude's Tilt support (either with a comment in your template, or
37
- with a :fortitude_class option) that the class we should render for this template is
38
- #{class_name.inspect}, but that turns out to be #{actual_object.inspect},
39
- which is not a Class that inherits from Fortitude::Widget.}
40
- end
41
- end
42
-
43
8
  class FortitudeTemplate < ::Tilt::Template
44
9
  def prepare
45
10
  ::Object.class_eval(data)
46
- @fortitude_class = explicit_fortitude_class || first_class_that_is_a_widget_class(all_possible_class_names)
47
11
 
48
12
  # 2014-06-19 ageweke -- Earlier versions of Tilt try to instantiate the engine with an empty tempate as a way
49
13
  # of making sure it can be created, so we have to support this case.
50
- if (! @fortitude_class) && (data != "")
51
- raise CannotDetermineTemplateClassError.new(all_possible_class_names)
14
+ if data.strip.length > 0
15
+ @fortitude_class = ::Fortitude::Widget.widget_class_from_source(
16
+ data,
17
+ :magic_comment_text => 'fortitude_tilt_class',
18
+ :class_names_to_try => Array(options[:fortitude_class]) + Array(options[:class_names_to_try]))
52
19
  end
53
20
  end
54
21
 
@@ -74,96 +41,7 @@ which is not a Class that inherits from Fortitude::Widget.}
74
41
  end
75
42
 
76
43
  private
77
- def fortitude_class
78
- @fortitude_class
79
- end
80
-
81
- def explicit_fortitude_class
82
- explicit_fortitude_class_from_option || explicit_fortitude_class_from_comment
83
- end
84
-
85
- def explicit_fortitude_class_from_option
86
- string_or_class_to_widget_class(options[:fortitude_class])
87
- end
88
-
89
- def explicit_fortitude_class_from_comment
90
- out = nil
91
- data.scan(/^\s*\#\s*\!\s*fortitude_tilt_class\s*:\s*(\S+)\s*$/) do |*args|
92
- out = args.first.first
93
- end
94
- string_or_class_to_widget_class(out)
95
- end
96
-
97
- def string_or_class_to_widget_class(string_or_class)
98
- out = string_or_class
99
-
100
- if out.kind_of?(String)
101
- begin
102
- out = out.constantize
103
- rescue NameError => ne
104
- raise NotATemplateClassError.new(string_or_class, nil)
105
- end
106
- end
107
-
108
- if out
109
- raise NotATemplateClassError.new(string_or_class, out) unless is_widget_class?(out)
110
- end
111
- out
112
- end
113
-
114
- def all_possible_class_names
115
- out = [ ]
116
- module_nesting = [ ]
117
-
118
- data.scan(/\bmodule\s+(\S+)/) do |module_name|
119
- module_nesting << module_name
120
- end
121
-
122
- data.scan(/\bclass\s+(\S+)/) do |*args|
123
- out << args.first.first
124
- end
125
-
126
- out.uniq!
127
-
128
- while module_nesting.length > 0
129
- possible_module_name = module_nesting.join("::")
130
- out.reverse.each do |class_name|
131
- out.unshift("#{possible_module_name}::#{class_name}")
132
- end
133
- module_nesting.pop
134
- end
135
-
136
- out
137
- end
138
-
139
- def first_class_that_is_a_widget_class(class_names)
140
- class_names.each do |class_name|
141
- begin
142
- klass = "::#{class_name}".constantize
143
- return klass if is_widget_class?(klass)
144
- rescue NameError => ne
145
- # ok, keep going
146
- end
147
- end
148
-
149
- nil
150
- end
151
-
152
- def is_widget_class?(klass)
153
- if (! klass)
154
- false
155
- elsif (! klass.kind_of?(Class))
156
- false
157
- elsif defined?(::BasicObject) && (klass == ::BasicObject)
158
- false
159
- elsif klass == Object
160
- false
161
- elsif klass == ::Fortitude::Widget
162
- true
163
- else
164
- is_widget_class?(klass.superclass)
165
- end
166
- end
44
+ attr_reader :fortitude_class
167
45
  end
168
46
  end
169
47
  end
@@ -1,3 +1,3 @@
1
1
  module Fortitude
2
- VERSION = "0.0.4"
2
+ VERSION = "0.0.5"
3
3
  end
@@ -14,6 +14,7 @@ require 'fortitude/widget/helpers'
14
14
  require 'fortitude/widget/capturing'
15
15
  require 'fortitude/widget/rendering'
16
16
  require 'fortitude/widget/temporary_overrides'
17
+ require 'fortitude/widget/files'
17
18
 
18
19
  require 'fortitude/doctypes'
19
20
 
@@ -38,6 +39,7 @@ module Fortitude
38
39
  include Fortitude::Widget::Capturing
39
40
  include Fortitude::Widget::Rendering
40
41
  include Fortitude::Widget::TemporaryOverrides
42
+ include Fortitude::Widget::Files
41
43
 
42
44
  if defined?(::Rails)
43
45
  require 'fortitude/rails/widget_methods'
@@ -0,0 +1,162 @@
1
+ require 'active_support'
2
+ require 'active_support/concern'
3
+
4
+ module Fortitude
5
+ class Widget
6
+ module Files
7
+ extend ActiveSupport::Concern
8
+
9
+ class CannotDetermineWidgetClassNameError < StandardError
10
+ attr_reader :tried_class_names, :filename, :magic_comment_texts
11
+
12
+ def initialize(tried_class_names, options = { })
13
+ options.assert_valid_keys(:filename, :magic_comment_texts)
14
+
15
+ @tried_class_names = tried_class_names
16
+ @filename = options[:filename]
17
+ @magic_comment_texts = options[:magic_comment_texts]
18
+ from_what = filename ? "from the file '#{filename}'" : "from some Fortitude source code"
19
+
20
+ super %{You asked for a Fortitude widget class #{from_what},
21
+ but we couldn't determine the class name of the widget that supposedly is inside.
22
+
23
+ We tried the following class names, in order:
24
+
25
+ #{tried_class_names.join("\n")}
26
+
27
+ ...but none of them both existed and were a class that eventually inherits from
28
+ ::Fortitude::Widget.
29
+
30
+ You can either pass the class name into this method via the :class_names_to_try option,
31
+ or add a "magic comment" to the source code of this widget that looks like this:
32
+
33
+ #!<token>: <class_name>
34
+
35
+ ...where <token> is one of: #{magic_comment_texts.join(", ")}}
36
+ end
37
+ end
38
+
39
+ module ClassMethods
40
+ def widget_class_from_file(filename, options = { })
41
+ options.assert_valid_keys(:root_dirs, :class_names_to_try, :magic_comment_text)
42
+ filename = File.expand_path(filename)
43
+ source = File.read(filename)
44
+
45
+ class_names_to_try = Array(options[:class_names_to_try])
46
+ root_dirs = Array(options[:root_dirs])
47
+
48
+ root_dirs.each do |root_dir|
49
+ root_dir = File.expand_path(root_dir)
50
+
51
+ if filename[0..(root_dir.length - 1)].downcase == root_dir.downcase
52
+ subpath = filename[(root_dir.length + 1)..-1]
53
+ subpath = $1 if subpath =~ /^(.*)\.rb$/i
54
+ class_names_to_try << subpath.camelize if subpath && subpath.length > 1
55
+ end
56
+ end
57
+
58
+ widget_class_from_source(source,
59
+ :class_names_to_try => class_names_to_try, :magic_comment_text => options[:magic_comment_text],
60
+ :filename => filename)
61
+ end
62
+
63
+ def widget_class_from_source(source, options = { })
64
+ options.assert_valid_keys(:class_names_to_try, :magic_comment_text, :filename)
65
+
66
+ magic_comment_texts = Array(options[:magic_comment_text]) + DEFAULT_MAGIC_COMMENT_TEXTS
67
+ all_class_names =
68
+ magic_comment_class_from(source, magic_comment_texts) +
69
+ Array(options[:class_names_to_try]) +
70
+ scan_source_for_possible_class_names(source)
71
+
72
+ out = widget_class_from_class_names(all_class_names)
73
+
74
+ unless out
75
+ if options[:filename]
76
+ require options[:filename]
77
+ else
78
+ ::Object.class_eval(source)
79
+ end
80
+
81
+ out = widget_class_from_class_names(all_class_names)
82
+ end
83
+
84
+ out || (
85
+ raise CannotDetermineWidgetClassNameError.new(all_class_names, :magic_comment_texts => magic_comment_texts,
86
+ :filename => options[:filename]))
87
+ end
88
+
89
+ private
90
+ DEFAULT_MAGIC_COMMENT_TEXTS = %w{fortitude_class}
91
+
92
+ def magic_comment_class_from(source, magic_comment_texts)
93
+ magic_comment_texts = magic_comment_texts.map { |c| c.to_s.strip.downcase }.uniq
94
+
95
+ out = [ ]
96
+ source.scan(/^\s*\#\s*\!\s*(\S+)\s*:\s*([A-Za-z0-9_:]+)\s*$/) do |(comment_text, class_name)|
97
+ out << class_name if magic_comment_texts.include?(comment_text.strip.downcase)
98
+ end
99
+ out
100
+ end
101
+
102
+ def scan_source_for_possible_class_names(source)
103
+ out = [ ]
104
+ module_nesting = [ ]
105
+
106
+ source.scan(/\bmodule\s+([A-Za-z0-9_:]+)/) do |match_data|
107
+ module_name = match_data[0]
108
+ module_nesting << module_name
109
+ end
110
+
111
+ source.scan(/\bclass\s+([A-Za-z0-9_:]+)/) do |match_data|
112
+ class_name = match_data[0]
113
+ out << class_name
114
+ end
115
+
116
+ out.uniq!
117
+
118
+ while module_nesting.length > 0
119
+ possible_module_name = module_nesting.join("::")
120
+ out.reverse.each do |class_name|
121
+ out.unshift("#{possible_module_name}::#{class_name}")
122
+ end
123
+ module_nesting.pop
124
+ end
125
+
126
+ out
127
+ end
128
+
129
+ def widget_class_from_class_names(class_names)
130
+ out = nil
131
+
132
+ class_names.each do |class_name|
133
+ klass = begin
134
+ "::#{class_name}".constantize
135
+ rescue NameError => ne
136
+ nil
137
+ end
138
+
139
+ if is_widget_class?(klass)
140
+ out = klass
141
+ break
142
+ end
143
+ end
144
+
145
+ out
146
+ end
147
+
148
+ def is_widget_class?(klass)
149
+ if (! klass)
150
+ false
151
+ elsif (! klass.kind_of?(Class))
152
+ false
153
+ elsif klass == ::Fortitude::Widget
154
+ true
155
+ else
156
+ is_widget_class?(klass.superclass)
157
+ end
158
+ end
159
+ end
160
+ end
161
+ end
162
+ end
@@ -18,7 +18,9 @@ module Fortitude
18
18
  # INTERNAL USE ONLY
19
19
  def rebuild_text_methods!(why, klass = self)
20
20
  rebuilding(:text_methods, why, klass) do
21
- class_eval(Fortitude::MethodTemplates::SimpleTemplate.template('text_method_template').result(:format_output => format_output, :needs_element_rules => self.enforce_element_nesting_rules))
21
+ class_eval(Fortitude::MethodTemplates::SimpleTemplate.template('text_method_template').result(
22
+ :format_output => format_output,
23
+ :record_emitting_tag => self._fortitude_record_emitting_tag?))
22
24
  direct_subclasses.each { |s| s.rebuild_text_methods!(why, klass) }
23
25
  end
24
26
  end
@@ -40,13 +42,13 @@ module Fortitude
40
42
 
41
43
  included do
42
44
  _fortitude_on_class_inheritable_attribute_change(
43
- :format_output, :enforce_element_nesting_rules) do |attribute_name, old_value, new_value|
45
+ :format_output, :enforce_element_nesting_rules, :record_tag_emission) do |attribute_name, old_value, new_value|
44
46
  rebuild_text_methods!(:"#{attribute_name}_changed")
45
47
  end
46
48
 
47
49
  _fortitude_on_class_inheritable_attribute_change(
48
50
  :format_output, :close_void_tags, :enforce_element_nesting_rules,
49
- :enforce_attribute_rules, :enforce_id_uniqueness) do |attribute_name, old_value, new_value|
51
+ :enforce_attribute_rules, :enforce_id_uniqueness, :record_tag_emission) do |attribute_name, old_value, new_value|
50
52
  rebuild_tag_methods!(:"#{attribute_name}_changed")
51
53
  end
52
54
 
@@ -9,6 +9,23 @@ module Fortitude
9
9
  extend ActiveSupport::Concern
10
10
 
11
11
  module ClassMethods
12
+ def all_fortitude_superclasses
13
+ @all_fortitude_superclasses ||= begin
14
+ if self.name == ::Fortitude::Widget.name
15
+ [ ]
16
+ else
17
+ out = [ ]
18
+ klass = superclass
19
+ while true
20
+ out << klass
21
+ break if klass.name == ::Fortitude::Widget.name
22
+ klass = klass.superclass
23
+ end
24
+ out
25
+ end
26
+ end
27
+ end
28
+
12
29
  # INTERNAL USE ONLY
13
30
  def direct_subclasses
14
31
  @direct_subclasses || [ ]
@@ -11,13 +11,13 @@ module Fortitude
11
11
  # PUBLIC API
12
12
  def render(*args, &block)
13
13
  call_through = lambda do
14
- @_fortitude_rendering_context.record_widget(args) do
14
+ @_fortitude_rendering_context.record_render(args) do
15
15
  tag_rawtext(invoke_helper(:render, *args, &block))
16
16
  end
17
17
  end
18
18
 
19
- if self.class.enforce_element_nesting_rules && args[0].kind_of?(Hash) && args[0].has_key?(:partial)
20
- @_fortitude_rendering_context.record_tag(self, Fortitude::Tags::PartialTagPlaceholder.instance, &call_through)
19
+ if self.class._fortitude_record_emitting_tag? && args[0].kind_of?(Hash) && args[0].has_key?(:partial)
20
+ @_fortitude_rendering_context.emitting_tag!(self, Fortitude::Tags::PartialTagPlaceholder.instance, nil, nil, &call_through)
21
21
  else
22
22
  call_through.call
23
23
  end
@@ -50,8 +50,15 @@ module Fortitude
50
50
  end
51
51
 
52
52
  # PUBLIC API
53
- def widget(w)
54
- w.render_to(@_fortitude_rendering_context)
53
+ def widget(w, hash = nil)
54
+ if w.respond_to?(:render_to)
55
+ w.render_to(@_fortitude_rendering_context)
56
+ elsif w.kind_of?(Class)
57
+ hash ||= { }
58
+ w.new(hash).render_to(@_fortitude_rendering_context)
59
+ else
60
+ raise "You tried to render a widget, but this is not valid: #{w.inspect}(#{hash.inspect})"
61
+ end
55
62
  end
56
63
 
57
64
  # PUBLIC API
@@ -22,7 +22,9 @@ module Fortitude
22
22
  if self.class.start_and_end_comments
23
23
  fo = self.class.format_output
24
24
 
25
- comment_text = "BEGIN #{self.class.name || '(anonymous widget class)'} depth #{widget_nesting_depth}"
25
+ class_name = self.class.name
26
+ class_name = "(anonymous widget class)" if (class_name || "") == ""
27
+ comment_text = "BEGIN #{class_name} depth #{widget_nesting_depth}"
26
28
 
27
29
  assign_keys = assigns.keys
28
30
  if assign_keys.length > 0
@@ -58,7 +60,7 @@ module Fortitude
58
60
  end
59
61
  tag_comment comment_text
60
62
  yield
61
- tag_comment "END #{self.class.name} depth #{widget_nesting_depth}"
63
+ tag_comment "END #{class_name} depth #{widget_nesting_depth}"
62
64
  else
63
65
  yield
64
66
  end
@@ -14,6 +14,7 @@ module Fortitude
14
14
  class << self
15
15
  # INTERNAL USE ONLY
16
16
  def tags_changed!(tags)
17
+ super
17
18
  rebuild_tag_methods!(:tags_declared, tags)
18
19
  end
19
20
  private :tags_changed!
@@ -30,6 +31,10 @@ module Fortitude
30
31
  end
31
32
  end
32
33
 
34
+ def validate_can_enclose!(widget, tag_object)
35
+ # ok, nothing here
36
+ end
37
+
33
38
  module ClassMethods
34
39
  # INTERNAL USE ONLY
35
40
  def rebuild_tag_methods!(why, which_tags_in = nil, klass = self)
@@ -40,7 +45,7 @@ module Fortitude
40
45
  which_tags.each do |tag_object|
41
46
  tag_object.define_method_on!(tags_module,
42
47
  :enable_formatting => self.format_output,
43
- :enforce_element_nesting_rules => self.enforce_element_nesting_rules,
48
+ :record_emitting_tag => self._fortitude_record_emitting_tag?,
44
49
  :enforce_attribute_rules => self.enforce_attribute_rules,
45
50
  :enforce_id_uniqueness => self.enforce_id_uniqueness,
46
51
  :close_void_tags => self.close_void_tags)