fortitude 0.0.4-java → 0.0.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 (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)