hanami-view 2.0.0.alpha8 → 2.1.0.beta2

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 (42) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +36 -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 +15 -55
  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 +8 -2
  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/rendered.rb +31 -0
  27. data/lib/hanami/view/renderer.rb +36 -44
  28. data/lib/hanami/view/rendering.rb +42 -0
  29. data/lib/hanami/view/{render_environment_missing.rb → rendering_missing.rb} +8 -13
  30. data/lib/hanami/view/scope.rb +14 -15
  31. data/lib/hanami/view/scope_builder.rb +42 -78
  32. data/lib/hanami/view/tilt/haml_adapter.rb +40 -0
  33. data/lib/hanami/view/tilt/slim_adapter.rb +40 -0
  34. data/lib/hanami/view/tilt.rb +22 -46
  35. data/lib/hanami/view/version.rb +1 -1
  36. data/lib/hanami/view.rb +58 -99
  37. metadata +64 -26
  38. data/LICENSE +0 -20
  39. data/lib/hanami/view/render_environment.rb +0 -62
  40. data/lib/hanami/view/tilt/erb.rb +0 -26
  41. data/lib/hanami/view/tilt/erbse.rb +0 -21
  42. data/lib/hanami/view/tilt/haml.rb +0 -26
@@ -1,13 +1,11 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require "pathname"
4
- require "dry/core/cache"
5
4
 
6
5
  module Hanami
7
6
  class View
8
7
  # @api private
9
8
  class Path
10
- extend Dry::Core::Cache
11
9
  include Dry::Equalizer(:dir, :root)
12
10
 
13
11
  attr_reader :dir, :root
@@ -25,12 +23,10 @@ module Hanami
25
23
  @root = Pathname(root)
26
24
  end
27
25
 
28
- def lookup(name, format, child_dirs: [], parent_dir: false)
29
- fetch_or_store(dir, root, name, format, child_dirs, parent_dir) do
30
- lookup_template(name, format) ||
31
- lookup_in_child_dirs(name, format, child_dirs: child_dirs) ||
32
- parent_dir && lookup_in_parent_dir(name, format, child_dirs: child_dirs)
33
- end
26
+ # Searches for a template using a wildcard for the engine extension
27
+ def lookup(prefix, name, format)
28
+ glob = dir.join(prefix, "#{name}.#{format}.*")
29
+ Dir[glob].first
34
30
  end
35
31
 
36
32
  def chdir(dirname)
@@ -40,29 +36,6 @@ module Hanami
40
36
  def to_s
41
37
  dir.to_s
42
38
  end
43
-
44
- private
45
-
46
- def root?
47
- dir == root
48
- end
49
-
50
- # Search for a template using a wildcard for the engine extension
51
- def lookup_template(name, format)
52
- glob = dir.join("#{name}.#{format}.*")
53
- Dir[glob].first
54
- end
55
-
56
- def lookup_in_child_dirs(name, format, child_dirs:)
57
- child_dirs.reduce(nil) { |_, dir|
58
- template = chdir(dir).lookup(name, format)
59
- break template if template
60
- }
61
- end
62
-
63
- def lookup_in_parent_dir(name, format, child_dirs:)
64
- !root? && chdir("..").lookup(name, format, child_dirs: child_dirs, parent_dir: true)
65
- end
66
39
  end
67
40
  end
68
41
  end
@@ -7,6 +7,7 @@ module Hanami
7
7
  # Output of a View rendering
8
8
  #
9
9
  # @api public
10
+ # @since 2.1.0
10
11
  class Rendered
11
12
  include Dry::Equalizer(:output, :locals)
12
13
 
@@ -15,6 +16,7 @@ module Hanami
15
16
  # @return [String]
16
17
  #
17
18
  # @api public
19
+ # @since 2.1.0
18
20
  attr_reader :output
19
21
 
20
22
  # Returns the hash of locals used to render the view
@@ -22,9 +24,11 @@ module Hanami
22
24
  # @return [Hash[<Symbol, Hanami::View::Part>] locals hash
23
25
  #
24
26
  # @api public
27
+ # @since 2.1.0
25
28
  attr_reader :locals
26
29
 
27
30
  # @api private
31
+ # @since 2.1.0
28
32
  def initialize(output:, locals:)
29
33
  @output = output
30
34
  @locals = locals
@@ -37,6 +41,7 @@ module Hanami
37
41
  # @return [Hanami::View::Part]
38
42
  #
39
43
  # @api public
44
+ # @since 2.1.0
40
45
  def [](name)
41
46
  locals[name]
42
47
  end
@@ -46,10 +51,36 @@ module Hanami
46
51
  # @return [String]
47
52
  #
48
53
  # @api public
54
+ # @since 2.1.0
49
55
  def to_s
50
56
  output
51
57
  end
52
58
  alias_method :to_str, :to_s
59
+
60
+ # Matches given input with the rendered view
61
+ #
62
+ # @param matcher [String, Regexp] matcher
63
+ #
64
+ # @return [TrueClass,FalseClass]
65
+ #
66
+ # @api public
67
+ # @since 2.1.0
68
+ def match?(matcher)
69
+ output.match?(matcher)
70
+ end
71
+ alias_method :match, :match?
72
+
73
+ # Checks if given string is included in the rendered view
74
+ #
75
+ # @param string [String] string
76
+ #
77
+ # @return [TrueClass,FalseClass]
78
+ #
79
+ # @api public
80
+ # @since 2.1.0
81
+ def include?(string)
82
+ output.include?(string)
83
+ end
53
84
  end
54
85
  end
55
86
  end
@@ -1,9 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "dry/core/cache"
4
- require "dry/core/equalizer"
5
3
  require_relative "errors"
6
- require_relative "tilt"
7
4
 
8
5
  module Hanami
9
6
  class View
@@ -11,67 +8,62 @@ module Hanami
11
8
  class Renderer
12
9
  PARTIAL_PREFIX = "_"
13
10
  PATH_DELIMITER = "/"
11
+ CURRENT_PATH_PREFIX = "."
14
12
 
15
- extend Dry::Core::Cache
13
+ attr_reader :config, :prefixes
16
14
 
17
- include Dry::Equalizer(:paths, :format, :engine_mapping, :options)
18
-
19
- attr_reader :paths, :format, :engine_mapping, :options
20
-
21
- def initialize(paths, format:, engine_mapping: nil, **options)
22
- @paths = paths
23
- @format = format
24
- @engine_mapping = engine_mapping || {}
25
- @options = options
15
+ def initialize(config)
16
+ @config = config
17
+ @prefixes = [CURRENT_PATH_PREFIX]
26
18
  end
27
19
 
28
- def template(name, scope, **lookup_options, &block)
29
- path = lookup(name, **lookup_options)
20
+ def template(name, format, scope, &block)
21
+ old_prefixes = @prefixes.dup
30
22
 
31
- if path
32
- render(path, scope, &block)
33
- else
34
- raise TemplateNotFoundError.new(name, paths)
35
- end
36
- end
23
+ template_path = lookup(name, format)
37
24
 
38
- def partial(name, scope, &block)
39
- template(
40
- name_for_partial(name),
41
- scope,
42
- child_dirs: %w[shared],
43
- parent_dir: true,
44
- &block
45
- )
46
- end
25
+ raise TemplateNotFoundError.new(name, format, config.paths) unless template_path
47
26
 
48
- def render(path, scope, &block)
49
- tilt(path).render(scope, {locals: scope._locals}, &block)
50
- end
27
+ new_prefix = File.dirname(name)
28
+ @prefixes << new_prefix unless @prefixes.include?(new_prefix)
51
29
 
52
- def chdir(dirname)
53
- new_paths = paths.map { |path| path.chdir(dirname) }
30
+ render(template_path, scope, &block)
31
+ ensure
32
+ @prefixes = old_prefixes
33
+ end
54
34
 
55
- self.class.new(new_paths, format: format, **options)
35
+ def partial(name, format, scope, &block)
36
+ template(name_for_partial(name), format, scope, &block)
56
37
  end
57
38
 
58
39
  private
59
40
 
60
- def lookup(name, **options)
61
- paths.inject(nil) { |_, path|
62
- result = path.lookup(name, format, **options)
63
- break result if result
41
+ def lookup(name, format)
42
+ View.cache.fetch_or_store(:lookup, name, format, config, prefixes) {
43
+ catch :found do
44
+ config.paths.reduce(nil) do |_, path|
45
+ prefixes.reduce(nil) do |_, prefix|
46
+ result = path.lookup(prefix, name, format)
47
+ throw :found, result if result
48
+ end
49
+ end
50
+ end
64
51
  }
65
52
  end
66
53
 
67
54
  def name_for_partial(name)
68
- name_segments = name.to_s.split(PATH_DELIMITER)
69
- name_segments[0..-2].push("#{PARTIAL_PREFIX}#{name_segments[-1]}").join(PATH_DELIMITER)
55
+ segments = name.to_s.split(PATH_DELIMITER)
56
+ segments[-1] = "#{PARTIAL_PREFIX}#{segments[-1]}"
57
+ segments.join(PATH_DELIMITER)
58
+ end
59
+
60
+ def render(path, scope, &block)
61
+ tilt(path).render(scope, {locals: scope._locals}, &block).html_safe
70
62
  end
71
63
 
72
64
  def tilt(path)
73
- fetch_or_store(:engine, path, engine_mapping, options) {
74
- Tilt[path, engine_mapping, **options]
65
+ View.cache.fetch_or_store(:tilt, path, config) {
66
+ Hanami::View::Tilt[path, config.renderer_engine_mapping, config.renderer_options]
75
67
  }
76
68
  end
77
69
  end
@@ -0,0 +1,42 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Hanami
4
+ class View
5
+ # @api private
6
+ class Rendering
7
+ attr_reader :config, :format
8
+
9
+ attr_reader :inflector, :part_builder, :scope_builder
10
+
11
+ attr_reader :context, :renderer
12
+
13
+ def initialize(config:, format:, context:)
14
+ @config = config
15
+ @format = format
16
+
17
+ @inflector = config.inflector
18
+ @part_builder = config.part_builder
19
+ @scope_builder = config.scope_builder
20
+
21
+ @context = context.dup_for_rendering(self)
22
+ @renderer = Renderer.new(config)
23
+ end
24
+
25
+ def template(name, scope, &block)
26
+ renderer.template(name, format, scope, &block)
27
+ end
28
+
29
+ def partial(name, scope, &block)
30
+ renderer.partial(name, format, scope, &block)
31
+ end
32
+
33
+ def part(name, value, as: nil)
34
+ part_builder.(name, value, as: as, rendering: self)
35
+ end
36
+
37
+ def scope(name = nil, locals) # rubocop:disable Style/OptionalArguments
38
+ scope_builder.(name, locals: locals, rendering: self)
39
+ end
40
+ end
41
+ end
42
+ end
@@ -1,39 +1,34 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require "dry/inflector"
4
+ require_relative "errors"
4
5
 
5
6
  module Hanami
6
7
  class View
7
8
  # @api private
8
- class RenderEnvironmentMissing
9
- class MissingEnvironmentError < StandardError
10
- def message
11
- "a +render_env+ must be provided"
12
- end
13
- end
14
-
9
+ class RenderingMissing
15
10
  def format
16
- raise MissingEnvironmentError
11
+ raise RenderingMissingError
17
12
  end
18
13
 
19
14
  def context
20
- raise MissingEnvironmentError
15
+ raise RenderingMissingError
21
16
  end
22
17
 
23
18
  def part(_name, _value, **_options)
24
- raise MissingEnvironmentError
19
+ raise RenderingMissingError
25
20
  end
26
21
 
27
22
  def scope(_name = nil, _locals) # rubocop:disable Style/OptionalArguments
28
- raise MissingEnvironmentError
23
+ raise RenderingMissingError
29
24
  end
30
25
 
31
26
  def template(_name, _scope)
32
- raise MissingEnvironmentError
27
+ raise RenderingMissingError
33
28
  end
34
29
 
35
30
  def partial(_name, _scope)
36
- raise MissingEnvironmentError
31
+ raise RenderingMissingError
37
32
  end
38
33
 
39
34
  def inflector
@@ -2,7 +2,6 @@
2
2
 
3
3
  require "dry/core/equalizer"
4
4
  require "dry/core/constants"
5
- require_relative "render_environment_missing"
6
5
 
7
6
  module Hanami
8
7
  class View
@@ -21,7 +20,7 @@ module Hanami
21
20
  # @api private
22
21
  CONVENIENCE_METHODS = %i[format context locals].freeze
23
22
 
24
- include Dry::Equalizer(:_name, :_locals, :_render_env)
23
+ include Dry::Equalizer(:_name, :_locals, :_rendering)
25
24
 
26
25
  # The scope's name
27
26
  #
@@ -43,18 +42,18 @@ module Hanami
43
42
  # @api public
44
43
  attr_reader :_locals
45
44
 
46
- # The current render environment
45
+ # The current rendering
47
46
  #
48
- # @return [RenderEnvironment] render environment
47
+ # @return [Rendering]
49
48
  #
50
49
  # @api private
51
- attr_reader :_render_env
50
+ attr_reader :_rendering
52
51
 
53
52
  # Returns a new Scope instance
54
53
  #
55
54
  # @param name [Symbol, nil] scope name
56
55
  # @param locals [Hash<Symbol, Object>] template locals
57
- # @param render_env [RenderEnvironment] render environment
56
+ # @param rendering [Rendering] the current rendering
58
57
  #
59
58
  # @return [Scope]
60
59
  #
@@ -62,11 +61,11 @@ module Hanami
62
61
  def initialize(
63
62
  name: nil,
64
63
  locals: Dry::Core::Constants::EMPTY_HASH,
65
- render_env: RenderEnvironmentMissing.new
64
+ rendering: RenderingMissing.new
66
65
  )
67
66
  @_name = name
68
67
  @_locals = locals
69
- @_render_env = render_env
68
+ @_rendering = rendering
70
69
  end
71
70
 
72
71
  # @overload render(partial_name, **locals, &block)
@@ -96,7 +95,7 @@ module Hanami
96
95
  partial_name = _inflector.underscore(_inflector.demodulize(partial_name.to_s))
97
96
  end
98
97
 
99
- _render_env.partial(partial_name, _render_scope(**locals), &block)
98
+ _rendering.partial(partial_name, _render_scope(**locals), &block)
100
99
  end
101
100
 
102
101
  # Build a new scope using a scope class matching the provided name
@@ -108,7 +107,7 @@ module Hanami
108
107
  #
109
108
  # @api public
110
109
  def scope(name = nil, **locals)
111
- _render_env.scope(name, locals)
110
+ _rendering.scope(name, locals)
112
111
  end
113
112
 
114
113
  # The template format for the current render environment.
@@ -123,7 +122,7 @@ module Hanami
123
122
  #
124
123
  # @api public
125
124
  def _format
126
- _render_env.format
125
+ _rendering.format
127
126
  end
128
127
 
129
128
  # The context object for the current render environment
@@ -138,7 +137,7 @@ module Hanami
138
137
  #
139
138
  # @api public
140
139
  def _context
141
- _render_env.context
140
+ _rendering.context
142
141
  end
143
142
 
144
143
  private
@@ -164,7 +163,7 @@ module Hanami
164
163
 
165
164
  def respond_to_missing?(name, include_private = false)
166
165
  _locals.key?(name) ||
167
- _render_env.context.respond_to?(name) ||
166
+ _rendering.context.respond_to?(name) ||
168
167
  CONVENIENCE_METHODS.include?(name) ||
169
168
  super
170
169
  end
@@ -176,13 +175,13 @@ module Hanami
176
175
  self.class.new(
177
176
  # FIXME: what about `name`?
178
177
  locals: locals,
179
- render_env: _render_env
178
+ rendering: _rendering
180
179
  )
181
180
  end
182
181
  end
183
182
 
184
183
  def _inflector
185
- _render_env.inflector
184
+ _rendering.inflector
186
185
  end
187
186
  end
188
187
  end
@@ -1,97 +1,61 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "dry/core/cache"
4
- require "dry/core/equalizer"
5
- require_relative "scope"
6
-
7
3
  module Hanami
8
4
  class View
9
5
  # Builds scope objects via matching classes
10
6
  #
11
7
  # @api private
12
8
  class ScopeBuilder
13
- extend Dry::Core::Cache
14
- include Dry::Equalizer(:namespace)
15
-
16
- # The view's configured `scope_namespace`
17
- #
18
- # @api private
19
- attr_reader :namespace
20
-
21
- # @return [RenderEnvironment]
22
- #
23
- # @api private
24
- attr_reader :render_env
25
-
26
- # Returns a new instance of ScopeBuilder
27
- #
28
- # @api private
29
- def initialize(namespace: nil, render_env: nil)
30
- @namespace = namespace
31
- @render_env = render_env
32
- end
33
-
34
- # @api private
35
- def for_render_env(render_env)
36
- return self if render_env == self.render_env
37
-
38
- self.class.new(namespace: namespace, render_env: render_env)
39
- end
40
-
41
- # Returns a new scope using a class matching the name
42
- #
43
- # @param name [Symbol, Class] scope name
44
- # @param locals [Hash<Symbol, Object>] locals hash
45
- #
46
- # @return [Hanami::View::Scope]
47
- #
48
- # @api private
49
- def call(name = nil, locals) # rubocop:disable Style/OptionalArguments
50
- scope_class(name).new(
51
- name: name,
52
- locals: locals,
53
- render_env: render_env
54
- )
55
- end
56
-
57
- private
58
-
59
- DEFAULT_SCOPE_CLASS = Scope
9
+ class << self
10
+ # Returns a new scope using a class matching the name
11
+ #
12
+ # @param name [Symbol, Class] scope name
13
+ # @param locals [Hash<Symbol, Object>] locals hash
14
+ #
15
+ # @return [Hanami::View::Scope]
16
+ #
17
+ # @api private
18
+ def call(name = nil, locals:, rendering:) # rubocop:disable Style/OptionalArguments
19
+ klass = scope_class(name, rendering: rendering)
20
+
21
+ klass.new(name: name, locals: locals, rendering: rendering)
22
+ end
60
23
 
61
- def scope_class(name = nil)
62
- if name.nil?
63
- DEFAULT_SCOPE_CLASS
64
- elsif name.is_a?(Class)
65
- name
66
- else
67
- fetch_or_store(namespace, name) do
68
- resolve_scope_class(name: name)
24
+ private
25
+
26
+ def scope_class(name = nil, rendering:)
27
+ if name.nil?
28
+ rendering.config.scope_class
29
+ elsif name.is_a?(Class)
30
+ name
31
+ else
32
+ View.cache.fetch_or_store(:scope_class, rendering.config) do
33
+ resolve_scope_class(name: name, rendering: rendering)
34
+ end
69
35
  end
70
36
  end
71
- end
72
37
 
73
- def resolve_scope_class(name:)
74
- name = inflector.camelize(name.to_s)
38
+ def resolve_scope_class(name:, rendering:)
39
+ name = rendering.inflector.camelize(name.to_s)
75
40
 
76
- # Give autoloaders a chance to act
77
- begin
78
- klass = namespace.const_get(name)
79
- rescue NameError # rubocop:disable Lint/HandleExceptions
80
- end
41
+ namespace = rendering.config.scope_namespace
81
42
 
82
- if !klass && namespace.const_defined?(name, false)
83
- klass = namespace.const_get(name)
84
- end
43
+ # Give autoloaders a chance to act
44
+ begin
45
+ klass = namespace.const_get(name)
46
+ rescue NameError # rubocop:disable Lint/HandleExceptions
47
+ end
85
48
 
86
- if klass && klass < Scope
87
- klass
88
- else
89
- DEFAULT_SCOPE_CLASS
90
- end
91
- end
49
+ if !klass && namespace.const_defined?(name, false)
50
+ klass = namespace.const_get(name)
51
+ end
92
52
 
93
- def inflector
94
- render_env.inflector
53
+ if klass && klass < Scope
54
+ klass
55
+ else
56
+ rendering.config.scope_class
57
+ end
58
+ end
95
59
  end
96
60
  end
97
61
  end
@@ -0,0 +1,40 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "haml"
4
+
5
+ module Hanami
6
+ class View
7
+ module Tilt
8
+ # @api private
9
+ module HamlAdapter
10
+ # Add options to Haml::Engine to match the options from its default generator.
11
+ #
12
+ # The default generator for Haml::Engine is configurable via an engine option, like so:
13
+ #
14
+ # use :Generator, -> { options[:generator] }
15
+ #
16
+ # Because this Temple filter is set as a proc, the resulting effect within Temple's EngineDSL
17
+ # is that the generator's valid options are not merged into the full set of options available
18
+ # on Haml::Engine itself. This means we receive a "Option :capture_generator is invalid"
19
+ # warning when we set our `capture_generator:` below.
20
+ #
21
+ # However, this option is perfectly valid, so here we merge all the options for Haml's default
22
+ # generator into the top-level engine's options, avoiding the warning.
23
+ ::Haml::Engine.define_options(::Haml::Engine.options[:generator].options.valid_keys)
24
+
25
+ # Haml template renderer for Hanami::View.
26
+ #
27
+ # This differs from the standard Haml::Template by automatically escaping HTML based on a
28
+ # given string's `#html_safe?`, regardless of when "hanami/view/html" is required.
29
+ #
30
+ # @see Hanami::View::Tilt
31
+ # @api private
32
+ Template = Temple::Templates::Tilt(
33
+ ::Haml::Engine,
34
+ use_html_safe: true,
35
+ capture_generator: HTMLSafeStringBuffer,
36
+ )
37
+ end
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,40 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "slim"
4
+
5
+ module Hanami
6
+ class View
7
+ module Tilt
8
+ # @api private
9
+ module SlimAdapter
10
+ # Add options to Slim::Engine to match the options from its default generator.
11
+ #
12
+ # The default generator for Slim::Engine is configurable via an engine option, like so:
13
+ #
14
+ # use(:Generator) { options[:generator] }
15
+ #
16
+ # Because this Temple filter is set as a proc, the resulting effect within Temple's EngineDSL
17
+ # is that the generator's valid options are not merged into the full set of options available
18
+ # on Slim::Engine itself. This means we receive a "Option :capture_generator is invalid"
19
+ # warning when we set our `capture_generator:` below.
20
+ #
21
+ # However, this option is perfectly valid, so here we merge all the options for Slim's default
22
+ # generator into the top-level engine's options, avoiding the warning.
23
+ ::Slim::Engine.define_options(::Slim::Engine.options[:generator].options.valid_keys)
24
+
25
+ # Slim template renderer for Hanami::View.
26
+ #
27
+ # This differs from the standard Slim::Template by automatically escaping HTML based on a
28
+ # given string's `#html_safe?`, regardless of when "hanami/view/html" is required.
29
+ #
30
+ # @see Hanami::View::Tilt
31
+ # @api private
32
+ Template = Temple::Templates::Tilt(
33
+ ::Slim::Engine,
34
+ use_html_safe: true,
35
+ capture_generator: HTMLSafeStringBuffer,
36
+ )
37
+ end
38
+ end
39
+ end
40
+ end