hanami-view 2.0.0.alpha7 → 2.1.0.beta1

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 (44) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +38 -0
  3. data/README.md +15 -3
  4. data/hanami-view.gemspec +5 -3
  5. data/lib/hanami/view/cache.rb +16 -0
  6. data/lib/hanami/view/context.rb +12 -70
  7. data/lib/hanami/view/context_helpers/content_helpers.rb +5 -5
  8. data/lib/hanami/view/decorated_attributes.rb +2 -2
  9. data/lib/hanami/view/erb/engine.rb +27 -0
  10. data/lib/hanami/view/erb/filters/block.rb +44 -0
  11. data/lib/hanami/view/erb/filters/trimming.rb +42 -0
  12. data/lib/hanami/view/erb/parser.rb +161 -0
  13. data/lib/hanami/view/erb/template.rb +30 -0
  14. data/lib/hanami/view/errors.rb +5 -7
  15. data/lib/hanami/view/exposure.rb +23 -17
  16. data/lib/hanami/view/exposures.rb +22 -13
  17. data/lib/hanami/view/helpers/escape_helper.rb +221 -0
  18. data/lib/hanami/view/helpers/number_formatting_helper.rb +182 -0
  19. data/lib/hanami/view/helpers/tag_helper/tag_builder.rb +230 -0
  20. data/lib/hanami/view/helpers/tag_helper.rb +210 -0
  21. data/lib/hanami/view/html.rb +104 -0
  22. data/lib/hanami/view/html_safe_string_buffer.rb +46 -0
  23. data/lib/hanami/view/part.rb +13 -15
  24. data/lib/hanami/view/part_builder.rb +68 -108
  25. data/lib/hanami/view/path.rb +4 -31
  26. data/lib/hanami/view/renderer.rb +36 -44
  27. data/lib/hanami/view/rendering.rb +42 -0
  28. data/lib/hanami/view/{render_environment_missing.rb → rendering_missing.rb} +8 -13
  29. data/lib/hanami/view/scope.rb +15 -16
  30. data/lib/hanami/view/scope_builder.rb +42 -78
  31. data/lib/hanami/view/tilt/haml_adapter.rb +40 -0
  32. data/lib/hanami/view/tilt/slim_adapter.rb +40 -0
  33. data/lib/hanami/view/tilt.rb +22 -46
  34. data/lib/hanami/view/version.rb +1 -1
  35. data/lib/hanami/view.rb +351 -26
  36. metadata +64 -29
  37. data/LICENSE +0 -20
  38. data/lib/hanami/view/application_configuration.rb +0 -77
  39. data/lib/hanami/view/application_context.rb +0 -98
  40. data/lib/hanami/view/render_environment.rb +0 -62
  41. data/lib/hanami/view/standalone_view.rb +0 -400
  42. data/lib/hanami/view/tilt/erb.rb +0 -26
  43. data/lib/hanami/view/tilt/erbse.rb +0 -21
  44. data/lib/hanami/view/tilt/haml.rb +0 -26
@@ -0,0 +1,210 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Based on ActionView::Helpers::TagHelper, also released under the MIT licence.
4
+ #
5
+ # Copyright (c) David Heinemeier Hansson
6
+
7
+ module Hanami
8
+ class View
9
+ module Helpers
10
+ # Helper methods for generating HTML tags.
11
+ #
12
+ # When using full Hanami apps, these helpers will be automatically available in your view
13
+ # templates, part classes and scope classes.
14
+ #
15
+ # When using hanami-view standalone, include this module directly in your base part and scope
16
+ # classes, or in specific classes as required.
17
+ #
18
+ # @example Standalone usage
19
+ # class BasePart < Hanami::View::Part
20
+ # include Hanami::View::Helpers::TagHelper
21
+ # end
22
+ #
23
+ # class BaseScope < Hanami::View::Scope
24
+ # include Hanami::View::Helpers::TagHelper
25
+ # end
26
+ #
27
+ # class BaseView < Hanami::View
28
+ # config.part_class = BasePart
29
+ # config.scope_class = BaseScope
30
+ # end
31
+ #
32
+ # @api public
33
+ # @since 2.0.0
34
+ module TagHelper
35
+ module_function
36
+
37
+ # Returns a tag builder for building HTML tag strings.
38
+ #
39
+ # @example General usage
40
+ # tag.div # => <div></div>
41
+ # tag.img # => <img>
42
+ #
43
+ # tag.div("hello") # => <div>hello</div>
44
+ # tag.div { "hello" } # => <div>hello</div>
45
+ # tag.div(tag.p("hello")) # => <div><p>hello</p></div>
46
+ #
47
+ # tag.div(class: ["a", "b"]) # => <div class="a b"></div>
48
+ # tag.div(class: {"a": true, "b": false}) # => <div class="a"></div>
49
+ #
50
+ # tag.div(id: "el", data: {x: "y"}) # => <div id="el" data-x="y"></div>
51
+ # tag.div(id: "el", aria: {x: "y"}) # => <div id="el" aria-x="y"></div>
52
+ #
53
+ # tag.custom_tag("hello") # => <custom-tag>hello</custom-tag>
54
+ #
55
+ # @example Escaping
56
+ # tag.p("<script>alert()</script>") # => <p>&lt;script&gt;alert()&lt;/script&gt;</p>
57
+ # tag.p(class: "<script>alert()</script>") # => <p class="&lt;script&gt;alert()&lt;/script&gt;"></p>
58
+ # tag.p("<em>safe content</em>".html_safe) # => <p><em>safe content</em></p>
59
+ #
60
+ # @example Within templates
61
+ # <%= tag.div(id: "el") do %>
62
+ # <p>Template content can be mixed in.</p>
63
+ # <%= tag.p("Also nested tag builders.") %>
64
+ # <% end %>
65
+ #
66
+ # @api public
67
+ # @since 2.0.0
68
+ def tag
69
+ tag_builder
70
+ end
71
+
72
+ # Returns an anchor tag for the given contents and URL.
73
+ #
74
+ # The tag's contents are automatically escaped (unless marked as HTML safe).
75
+ #
76
+ # Uses the {#tag} builder to prepare the tag, so all tag builder options are also used.
77
+ #
78
+ # @overload link_to(content, url, **attributes)
79
+ # Returns a tag using a given string as the contents.
80
+ #
81
+ # @param content [String] content used in the a tag
82
+ # @param url [String] URL to be used in the `href` attribute
83
+ # @param attributes [Hash] HTML attributes to include in the tag
84
+ #
85
+ # @overload link_to(url, **attributes, &block)
86
+ # Returns a tag using the given block's return value as the contents.
87
+ #
88
+ # @param url [String] URL to be used in the `href` attribute
89
+ # @param attributes [Hash] HTML attributes to include in the tag
90
+ # @param block [Proc] block that returns the contents of the tag
91
+ #
92
+ # @return [String] HTML markup for the anchor tag
93
+ #
94
+ # @example
95
+ # link_to("Home", "/")
96
+ # # => <a href="/">Home</a>
97
+ #
98
+ # link_to("/") { "Home" }
99
+ # # => <a href="/">Home</a>
100
+ #
101
+ # link_to("Home", "/", class: "button") %>
102
+ # # => <a href="/" class="button">Home</a>
103
+ #
104
+ # @example Escaping
105
+ # link_to("<script>alert('xss')</script>", "/")
106
+ # # => <a href="/">&lt;script&gt;alert(&#39;xss&#39;)&lt;/script&gt;</a>
107
+ #
108
+ # link_to("/") { "<script>alert('xss')</script>" }
109
+ # # => <a href="/">&lt;script&gt;alert(&#39;xss&#39;)&lt;/script&gt;</a>
110
+ #
111
+ # @see #tag
112
+ #
113
+ # @api public
114
+ # @since 2.0.0
115
+ def link_to(content, url = nil, **attributes, &block)
116
+ if block
117
+ raise ArgumentError if url && content
118
+
119
+ url = content
120
+ content = nil
121
+ end
122
+
123
+ attributes[:href] = url or raise ArgumentError
124
+
125
+ tag.a(content, **attributes, &block)
126
+ end
127
+
128
+ # Returns a string of space-separated tokens from a range of given arguments.
129
+ #
130
+ # This is intended for building an HTML tag attribute value, such as a list of class names.
131
+ #
132
+ # @return [String]
133
+ #
134
+ # @example
135
+ # token_list("foo", "bar")
136
+ # # => "foo bar"
137
+ #
138
+ # token_list("foo", "foo bar")
139
+ # # => "foo bar"
140
+ #
141
+ # token_list({ foo: true, bar: false })
142
+ # # => "foo"
143
+ #
144
+ # token_list(nil, false, 123, "", "foo", { bar: true })
145
+ # # => "123 foo bar"
146
+ #
147
+ # @api public
148
+ # @since 2.0.0
149
+ def token_list(*args)
150
+ tokens = build_tag_values(*args).flat_map { |value|
151
+ safe = value.html_safe?
152
+ value.split(/\s+/).map { |s| safe ? s.html_safe : s }
153
+ }
154
+
155
+ EscapeHelper.escape_join(tokens, " ")
156
+ end
157
+
158
+ # @see #token_list
159
+ #
160
+ # @api public
161
+ # @since 2.0.0
162
+ def class_names(...)
163
+ token_list(...)
164
+ end
165
+
166
+ # @api private
167
+ # @since 2.0.0
168
+ def build_tag_values(*args)
169
+ tag_values = []
170
+
171
+ args.each do |tag_value|
172
+ case tag_value
173
+ when Hash
174
+ tag_value.each do |key, val|
175
+ tag_values << key.to_s if val && !key.to_s.empty?
176
+ end
177
+ when Array
178
+ tag_values.concat build_tag_values(*tag_value)
179
+ else
180
+ tag_values << tag_value.to_s unless tag_value.to_s.empty?
181
+ end
182
+ end
183
+
184
+ tag_values
185
+ end
186
+
187
+ # @api private
188
+ # @since 2.0.0
189
+ def tag_builder
190
+ @tag_builder ||= begin
191
+ TagBuilder.new(inflector: tag_builder_inflector)
192
+ end
193
+ end
194
+
195
+ # @api private
196
+ # @since 2.0.0
197
+ def tag_builder_inflector
198
+ if respond_to?(:_context)
199
+ return _context.inflector
200
+ end
201
+
202
+ # TODO: When hanami-view moves to Zeitwerk (and the only external require is for
203
+ # "dry/view"), remove this.
204
+ require "dry/inflector"
205
+ Dry::Inflector.new
206
+ end
207
+ end
208
+ end
209
+ end
210
+ end
@@ -0,0 +1,104 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Hanami
4
+ class View
5
+ module HTML
6
+ # A string that has been marked as "HTML safe", ensuring that it is not automatically escaped
7
+ # when used in HTML view templates.
8
+ #
9
+ # A SafeString is frozen when initialized to ensure it cannot be mutated after being marked as
10
+ # safe, which could be an avenue for injection of unsafe content.
11
+ #
12
+ # @see String#html_safe
13
+ #
14
+ # @api public
15
+ # @since 2.0.0
16
+ class SafeString < String
17
+ # @api public
18
+ # @since 2.0.0
19
+ def initialize(string)
20
+ super(string)
21
+ freeze
22
+ end
23
+
24
+ # @api private
25
+ private def initialize_copy(other)
26
+ super
27
+ freeze
28
+ end
29
+
30
+ # @return [true]
31
+ #
32
+ # @api public
33
+ # @since 2.0.0
34
+ def html_safe?
35
+ true
36
+ end
37
+
38
+ # @return [self]
39
+ #
40
+ # @api public
41
+ # @since 2.0.0
42
+ def html_safe
43
+ self
44
+ end
45
+
46
+ # @return [self]
47
+ #
48
+ # @api public
49
+ # @since 2.0.0
50
+ def to_s
51
+ self
52
+ end
53
+ end
54
+
55
+ # @api private
56
+ # @since 2.0.0
57
+ module StringExtensions
58
+ # Returns the string as a {Hanami::View::HTML::SafeString}, ensuring the string is not
59
+ # automatically escaped when used in HTML view templates.
60
+ #
61
+ # @return [Hanami::View::HTML::SafeString]
62
+ #
63
+ # @api public
64
+ # @since 2.0.0
65
+ def html_safe
66
+ Hanami::View::HTML::SafeString.new(self)
67
+ end
68
+ end
69
+ end
70
+ end
71
+ end
72
+
73
+ class String
74
+ # Prepend our `#html_safe` method so that it takes precedence over Active Support's. When both
75
+ # methods are loaded, the more likely scenario is that the user will want Hanami's, since in the
76
+ # context of a Hanami app, Active Support is more likely to be loaded incidentally, as a
77
+ # transitive dependency of another gem.
78
+ #
79
+ # Having our `#html_safe` available via this module also means that a user can also choose to
80
+ # _undefine_ this method within the module if they'd rather use Active Support's.
81
+ prepend Hanami::View::HTML::StringExtensions
82
+ end
83
+
84
+ class Object
85
+ # @return [false]
86
+ #
87
+ # @api public
88
+ # @since 2.0.0
89
+ def html_safe?
90
+ false
91
+ end
92
+ end
93
+
94
+ class Numeric
95
+ # @return [true]
96
+ #
97
+ # @api public
98
+ # @since 2.0.0
99
+ def html_safe?
100
+ true
101
+ end
102
+ end
103
+
104
+
@@ -0,0 +1,46 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "temple"
4
+
5
+ module Hanami
6
+ class View
7
+ # Specialized Temple buffer class that marks block-captured strings as HTML safe.
8
+ #
9
+ # This is important for any scope or part methods that receive a string from a yielded block
10
+ # and then determine whether to escape that string based on its `.html_safe?` value.
11
+ #
12
+ # In this case, since blocks captured templates _intentionally_ contain HTML (this is the
13
+ # purpose of the template after all), it makes sense to mark the entire captured block string as
14
+ # HTML safe.
15
+ #
16
+ # This is compatible with escaping of values interpolated into the template, since those will
17
+ # have already been automatically escaped by the template engine when they are evaluated, before
18
+ # the overall block is captured.
19
+ #
20
+ # This filter is included in all three of our supported HTML template engines (ERB, Haml and
21
+ # Slim) to provide consistent behavior across all.
22
+ #
23
+ # @see Hanami::View::ERB::Engine
24
+ # @see Hanami::View::HamlAdapter::Template
25
+ # @see Hanami::View::SlimAdapter::Template
26
+ #
27
+ # @api private
28
+ # @since 2.0.0
29
+ class HTMLSafeStringBuffer < Temple::Generators::StringBuffer
30
+ # Replace `Temple::Generator::ArrayBuffer#call` (which is used via the superclass of
31
+ # `StringBuffer`) with the standard implementation from the base `Temple::Generator`.
32
+ #
33
+ # This avoids certain specialisations in `ArrayBuffer#call` that prevent `#return_buffer` from
34
+ # being called. For our needs, `#return_buffer` must be called at all times in order to ensure
35
+ # the captured string is consistently marked as `.html_safe`.
36
+ def call(exp)
37
+ [preamble, compile(exp), postamble].flatten.compact.join('; ')
38
+ end
39
+
40
+ # Marks the string returned from the captured buffer as HTML safe.
41
+ def return_buffer
42
+ "#{buffer}.html_safe"
43
+ end
44
+ end
45
+ end
46
+ end
@@ -1,8 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require "dry/core/equalizer"
4
- require_relative "decorated_attributes"
5
- require_relative "render_environment_missing"
6
4
 
7
5
  module Hanami
8
6
  class View
@@ -25,7 +23,7 @@ module Hanami
25
23
  value
26
24
  ].freeze
27
25
 
28
- include Dry::Equalizer(:_name, :_value, :_render_env)
26
+ include Dry::Equalizer(:_name, :_value, :_rendering)
29
27
  include DecoratedAttributes
30
28
 
31
29
  # The part's name. This comes from the exposure supplying the value.
@@ -48,12 +46,12 @@ module Hanami
48
46
  # @api public
49
47
  attr_reader :_value
50
48
 
51
- # The current render environment
49
+ # The current rendering
52
50
  #
53
- # @return [RenderEnvironment] render environment
51
+ # @return [Rendering]
54
52
  #
55
53
  # @api private
56
- attr_reader :_render_env
54
+ attr_reader :_rendering
57
55
 
58
56
  # Determins a part name (when initialized without one). Intended for use
59
57
  # only while unit testing Parts.
@@ -67,17 +65,17 @@ module Hanami
67
65
  #
68
66
  # @param name [Symbol] part name
69
67
  # @param value [Object] the value to decorate
70
- # @param render_env [RenderEnvironment] render environment
68
+ # @param rendering [Rendering] the current rendering
71
69
  #
72
70
  # @api public
73
71
  def initialize(
74
- render_env: RenderEnvironmentMissing.new,
75
- name: self.class.part_name(render_env.inflector),
72
+ rendering: RenderingMissing.new,
73
+ name: self.class.part_name(rendering.inflector),
76
74
  value:
77
75
  )
78
76
  @_name = name
79
77
  @_value = value
80
- @_render_env = render_env
78
+ @_rendering = rendering
81
79
  end
82
80
 
83
81
  # The template format for the current render environment.
@@ -92,7 +90,7 @@ module Hanami
92
90
  #
93
91
  # @api public
94
92
  def _format
95
- _render_env.format
93
+ _rendering.format
96
94
  end
97
95
 
98
96
  # The context object for the current render environment
@@ -107,7 +105,7 @@ module Hanami
107
105
  #
108
106
  # @api public
109
107
  def _context
110
- _render_env.context
108
+ _rendering.context
111
109
  end
112
110
 
113
111
  # Renders a new partial with the part included in its locals.
@@ -128,7 +126,7 @@ module Hanami
128
126
  # @api public
129
127
  # rubocop:disable Naming/UncommunicativeMethodParamName
130
128
  def _render(partial_name, as: _name, **locals, &block)
131
- _render_env.partial(partial_name, _render_env.scope({as => self}.merge(locals)), &block)
129
+ _rendering.partial(partial_name, _rendering.scope({as => self}.merge(locals)), &block)
132
130
  end
133
131
  # rubocop:enable Naming/UncommunicativeMethodParamName
134
132
 
@@ -148,7 +146,7 @@ module Hanami
148
146
  #
149
147
  # @api public
150
148
  def _scope(scope_name = nil, **locals)
151
- _render_env.scope(scope_name, {_name => self}.merge(locals))
149
+ _rendering.scope(scope_name, {_name => self}.merge(locals))
152
150
  end
153
151
 
154
152
  # Returns a string representation of the value
@@ -180,7 +178,7 @@ module Hanami
180
178
  klass.new(
181
179
  name: name,
182
180
  value: value,
183
- render_env: _render_env,
181
+ rendering: _rendering,
184
182
  **options
185
183
  )
186
184
  end
@@ -1,139 +1,99 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "dry/core/cache"
4
- require "dry/core/equalizer"
5
- require_relative "part"
6
-
7
3
  module Hanami
8
4
  class View
9
5
  # Decorates exposure values with matching parts
10
6
  #
11
7
  # @api private
12
8
  class PartBuilder
13
- extend Dry::Core::Cache
14
- include Dry::Equalizer(:namespace)
15
-
16
- attr_reader :namespace
17
- attr_reader :render_env
18
-
19
- # Returns a new instance of PartBuilder
20
- #
21
- # @api private
22
- def initialize(namespace: nil, render_env: nil)
23
- @namespace = namespace
24
- @render_env = render_env
25
- end
26
-
27
- # @api private
28
- def for_render_env(render_env)
29
- return self if render_env == self.render_env
30
-
31
- self.class.new(namespace: namespace, render_env: render_env)
32
- end
33
-
34
- # Decorates an exposure value
35
- #
36
- # @param name [Symbol] exposure name
37
- # @param value [Object] exposure value
38
- # @param options [Hash] exposure options
39
- #
40
- # @return [Hanami::View::Part] decorated value
41
- #
42
- # @api private
43
- def call(name, value, **options)
44
- builder = value.respond_to?(:to_ary) ? :build_collection_part : :build_part
45
-
46
- send(builder, name, value, **options)
47
- end
48
-
49
- private
50
-
51
- def build_part(name, value, **options)
52
- klass = part_class(name: name, **options)
9
+ class << self
10
+ # Decorates an exposure value
11
+ #
12
+ # @param name [Symbol] exposure name
13
+ # @param value [Object] exposure value
14
+ # @param as [Symbol, nil] alternative name to use for part class resolution
15
+ #
16
+ # @return [Hanami::View::Part] decorated value
17
+ #
18
+ # @api private
19
+ def call(name, value, as: nil, rendering:)
20
+ builder = value.respond_to?(:to_ary) ? :build_collection_part : :build_part
21
+
22
+ send(builder, name: name, value: value, as: as, rendering: rendering)
23
+ end
53
24
 
54
- klass.new(
55
- name: name,
56
- value: value,
57
- render_env: render_env
58
- )
59
- end
25
+ private
60
26
 
61
- def build_collection_part(name, value, **options)
62
- collection_as = collection_options(name: name, **options)[:as]
63
- item_name, item_as = collection_item_options(name: name, **options).values_at(:name, :as)
27
+ def build_part(name:, value:, as:, rendering:)
28
+ klass = part_class(name: name, as: as, rendering: rendering)
64
29
 
65
- arr = value.to_ary.map { |obj|
66
- build_part(item_name, obj, **options.merge(as: item_as))
67
- }
30
+ klass.new(name: name, value: value, rendering: rendering)
31
+ end
68
32
 
69
- build_part(name, arr, **options.merge(as: collection_as))
70
- end
33
+ def build_collection_part(name:, value:, as: nil, rendering:)
34
+ item_name, item_as = collection_item_name_as(name, as, inflector: rendering.inflector)
35
+ item_part_class = part_class(name: item_name, as: item_as, rendering: rendering)
71
36
 
72
- # rubocop:disable Lint/UnusedMethodArgument
73
- def collection_options(name:, **options)
74
- collection_as = options[:as].is_a?(Array) ? options[:as].first : nil
37
+ arr = value.to_ary.map { |item|
38
+ item_part_class.new(name: item_name, value: item, rendering: rendering)
39
+ }
75
40
 
76
- options.merge(as: collection_as)
77
- end
78
- # rubocop:enable Lint/UnusedMethodArgument
41
+ collection_as = as.is_a?(Array) ? as.first : nil
42
+ build_part(name: name, value: arr, as: collection_as, rendering: rendering)
43
+ end
79
44
 
80
- def collection_item_options(name:, **options)
81
- singular_name = inflector.singularize(name).to_sym
82
- singular_as =
83
- if options[:as].is_a?(Array)
84
- options[:as].last if options[:as].length > 1
85
- else
86
- options[:as]
45
+ def collection_item_name_as(name, as, inflector:)
46
+ singular_name = inflector.singularize(name).to_sym
47
+ singular_as =
48
+ if as.is_a?(Array)
49
+ as.last if as.length > 1
50
+ else
51
+ as
52
+ end
53
+
54
+ if singular_as && !singular_as.is_a?(Class)
55
+ singular_as = inflector.singularize(singular_as.to_s)
87
56
  end
88
57
 
89
- if singular_as && !singular_as.is_a?(Class)
90
- singular_as = inflector.singularize(singular_as.to_s)
58
+ [singular_name, singular_as]
91
59
  end
92
60
 
93
- options.merge(
94
- name: singular_name,
95
- as: singular_as
96
- )
97
- end
98
-
99
- def part_class(name:, fallback_class: Part, **options)
100
- name = options[:as] || name
61
+ def part_class(name:, as:, rendering:)
62
+ name = as || name
101
63
 
102
- if name.is_a?(Class)
103
- name
104
- else
105
- fetch_or_store(namespace, name, fallback_class) do
106
- resolve_part_class(name: name, fallback_class: fallback_class)
64
+ if name.is_a?(Class)
65
+ name
66
+ else
67
+ View.cache.fetch_or_store(:part_class, name, rendering.config) do
68
+ resolve_part_class(name: name, rendering: rendering)
69
+ end
107
70
  end
108
71
  end
109
- end
110
72
 
111
- # rubocop:disable Metrics/PerceivedComplexity
112
- def resolve_part_class(name:, fallback_class:)
113
- return fallback_class unless namespace
73
+ # rubocop:disable Metrics/PerceivedComplexity
74
+ def resolve_part_class(name:, rendering:)
75
+ namespace = rendering.config.part_namespace
76
+ return rendering.config.part_class unless namespace
114
77
 
115
- name = inflector.camelize(name.to_s)
78
+ name = rendering.inflector.camelize(name.to_s)
116
79
 
117
- # Give autoloaders a chance to act
118
- begin
119
- klass = namespace.const_get(name)
120
- rescue NameError # rubocop:disable Lint/HandleExceptions
121
- end
80
+ # Give autoloaders a chance to act
81
+ begin
82
+ klass = namespace.const_get(name)
83
+ rescue NameError # rubocop:disable Lint/HandleExceptions
84
+ end
122
85
 
123
- if !klass && namespace.const_defined?(name, false)
124
- klass = namespace.const_get(name)
125
- end
86
+ if !klass && namespace.const_defined?(name, false)
87
+ klass = namespace.const_get(name)
88
+ end
126
89
 
127
- if klass && klass < Part
128
- klass
129
- else
130
- fallback_class
90
+ if klass && klass < Part
91
+ klass
92
+ else
93
+ rendering.config.part_class
94
+ end
131
95
  end
132
- end
133
- # rubocop:enable Metrics/PerceivedComplexity
134
-
135
- def inflector
136
- render_env.inflector
96
+ # rubocop:enable Metrics/PerceivedComplexity
137
97
  end
138
98
  end
139
99
  end