hanami-view 2.0.0.alpha8 → 2.1.0.beta2

Sign up to get free protection for your applications and to get access to all the features.
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
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: d49c664876b44d1be9f65e7d9c472a3fe50ab40d42002b170a4a2121ddf2f095
4
- data.tar.gz: 51508250526dd6af93f2f8d1af2031e37c39109896a0b92fac038b99a65e1512
3
+ metadata.gz: d15d51188db92065fac65432748c393654adc894fb1fbb41547eb3226af3736d
4
+ data.tar.gz: b4af5a77b310e6a32fd33f61359429c5dfe4ef269638f7aa0623db4171797784
5
5
  SHA512:
6
- metadata.gz: e08aff9c75a5c412922f6e7669078e0a88258d8aa6d80b7b56569ce3732a15e703861e7a713381c1e928cbe5e5a67f1ac51e2473a9de85868ca9c895184e838e
7
- data.tar.gz: cdad93f677870279dad0fb101dbfc03b4da79d78f78f3f1d6de5fdc50b7e7b0f4e69aef6b9b360049aaa2ff45d2e15beb24b2afe6116a87c07a17eb9eb0219eb
6
+ metadata.gz: 3792f9a42a835e7e3441d9cc5544689543f33d7575d8e1e872ba26bcae1b5c59a441499c4c1b334a4f40abc049aceb1c8725753f9e71185d788804281579069d
7
+ data.tar.gz: 280eb1a5752838d0ffab053ff7dac261ed5529a481caceda9a8dda54784009c49592caa7ec463087932d325cbcd98c8f15d942c7e0bff81dd13ee9ebed6223b1
data/CHANGELOG.md CHANGED
@@ -1,6 +1,42 @@
1
1
  # Hanami::View
2
+
2
3
  View layer for Hanami
3
4
 
5
+ ## v2.1.0.beta2 - 2023-10-04
6
+
7
+ ### Added
8
+ - [Luca Guidi] Add `Hanami::View::Rendered#match?`, `#match`, and `#include?` to make it more specs friendly.
9
+ - [Philip Arndt] Make `Hanami::View#call` to accept `layout:` keyword argument to specify the layout to use during the rendering.
10
+
11
+ ## v2.1.0.beta1 - 2023-06-29
12
+
13
+ ### Added
14
+ - [Tim Riley] Introduce new ERB engine, `Hanami::View::ERB`, now used by default for ERB templates;
15
+ the erbse gem is no longer a requirement (#226)
16
+ - [Tim Riley] Introduce `Hanami::View::HTML::SafeString`, returned when calling `String#html_safe`,
17
+ also introduced via a `Hanami::View::HTML::StringExtensions` module prepended onto `String`. (#226)
18
+ - [Tim Riley] Auto-escape HTML based on whether it is `#html_safe?` in ERB, Haml and Slim templates (#226)
19
+ - [Tim Riley] Add `part_class` and `scope_class` settings, used by the standard `part_builder` and
20
+ `scope_builder` as the default class to use when no value-specific part or scope classes can be
21
+ found. These settings default to `Hanami::View::Part` and `Hanami::View::Scope` respectively (#227)
22
+ - [Tim Riley] Introduce `Hanami::View::Helpers::EscapeHelper`, `Hanami::View::Helpers::TagHelper`,
23
+ `Hanami::View::Helpers::LinkToHelper`, `Hanami::View::Helpers::NumberFormattingHelper`. These
24
+ helper modules may optionally be mixed into your Part and Scope classes to provide additional
25
+ conveniences when authoring your views. To do this by default, create your own
26
+ `Hanaim::View::Part` and `Hanami::View::Scope` subclasses that include these modules, and then
27
+ configure these as the `part_class` and `scope_class` for your views. (#229)
28
+
29
+ ### Changed
30
+ - [Tim Riley] Use Zeitwerk for code loading; you should now require `require "hanami/view"` just
31
+ once (#233)
32
+ - [Tim Riley] Change `Context` interface: custom context subclasses now have complete control over
33
+ their `#initialize` (no longer need to receive `**options` or call `super`); though any mutable
34
+ ivars should be duped in a custom `#initialize_copy` as required. (#223)
35
+ - [Tim Riley] Change `PartBuilder` and `ScopeBuilder` interfaces to consist of static methods only (#223)
36
+ - [Tim Riley] Finalize the view class config when calling `.new` for the first time (#223)
37
+ - [Tim Riley] Consolidate all internal caches to a single `View.cache` (#223)
38
+ - [Tim Riley] [Internal] Various refactorings to improve rendering performance (#223)
39
+
4
40
  ## v2.0.0.alpha8 - 2022-05-19
5
41
 
6
42
  ### Added
data/README.md CHANGED
@@ -33,7 +33,7 @@ __Hanami::view__ supports Ruby (MRI) 3.0+
33
33
  Add this line to your application's Gemfile:
34
34
 
35
35
  ```ruby
36
- gem "hanami/view"
36
+ gem "hanami-view"
37
37
  ```
38
38
 
39
39
  And then execute:
@@ -48,6 +48,18 @@ Or install it yourself as:
48
48
  $ gem install hanami-view
49
49
  ```
50
50
 
51
- ## License
51
+ ## Versioning
52
52
 
53
- See `LICENSE` file.
53
+ __Hanami::View__ uses [Semantic Versioning 2.0.0](http://semver.org)
54
+
55
+ ## Contributing
56
+
57
+ 1. Fork it
58
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
59
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
60
+ 4. Push to the branch (`git push origin my-new-feature`)
61
+ 5. Create new Pull Request
62
+
63
+ ## Copyright
64
+
65
+ Copyright © 2014 Hanami Team – Released under MIT License
data/hanami-view.gemspec CHANGED
@@ -28,10 +28,12 @@ Gem::Specification.new do |spec|
28
28
  spec.required_ruby_version = ">= 3.0"
29
29
 
30
30
  spec.add_runtime_dependency "concurrent-ruby", "~> 1.0"
31
- spec.add_runtime_dependency "dry-configurable", "~> 0.13", ">= 0.13.0"
32
- spec.add_runtime_dependency "dry-core", "~> 0.5", ">= 0.5"
33
- spec.add_runtime_dependency "dry-inflector", "~> 0.1"
31
+ spec.add_runtime_dependency "dry-configurable", "~> 1.0"
32
+ spec.add_runtime_dependency "dry-core", "~> 1.0"
33
+ spec.add_runtime_dependency "dry-inflector", "~> 1.0", "< 2"
34
+ spec.add_runtime_dependency "temple", "~> 0.10.0", ">= 0.10.2"
34
35
  spec.add_runtime_dependency "tilt", "~> 2.0", ">= 2.0.6"
36
+ spec.add_runtime_dependency "zeitwerk", "~> 2.6"
35
37
 
36
38
  spec.add_development_dependency "bundler"
37
39
  spec.add_development_dependency "rake"
@@ -0,0 +1,16 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "dry/core/cache"
4
+
5
+ module Hanami
6
+ class View
7
+ # @api private
8
+ class Cache
9
+ extend Dry::Core::Cache
10
+
11
+ def self.clear
12
+ cache.clear
13
+ end
14
+ end
15
+ end
16
+ end
@@ -1,8 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "dry/core/equalizer"
4
- require_relative "decorated_attributes"
5
-
6
3
  module Hanami
7
4
  class View
8
5
  # Provides a baseline environment across all the templates, parts and scopes
@@ -13,67 +10,30 @@ module Hanami
13
10
  #
14
11
  # @api public
15
12
  class Context
16
- include Dry::Equalizer(:_options)
17
13
  include DecoratedAttributes
18
14
 
19
- attr_reader :_render_env, :_options
15
+ # @api private
16
+ attr_reader :_rendering
17
+
18
+ # @api private
19
+ def self.new(rendering: RenderingMissing.new, **args)
20
+ allocate.tap do |obj|
21
+ obj.instance_variable_set(:@_rendering, rendering)
22
+ obj.send(:initialize, **args)
23
+ end
24
+ end
20
25
 
21
26
  # Returns a new instance of Context
22
27
  #
23
- # In subclasses, you should include an `**options` parameter and pass _all
24
- # arguments_ to `super`. This allows Context to make copies of itself
25
- # while preserving your dependencies.
26
- #
27
- # @example
28
- # class MyContext < Hanami::View::Context
29
- # # Injected dependency
30
- # attr_reader :assets
31
- #
32
- # def initialize(assets:, **options)
33
- # @assets = assets
34
- # super
35
- # end
36
- # end
37
- #
38
28
  # @api public
39
- def initialize(render_env: nil, **options)
40
- @_render_env = render_env
41
- @_options = options
29
+ def initialize(**)
42
30
  end
43
31
 
44
32
  # @api private
45
- def for_render_env(render_env)
46
- return self if render_env == _render_env
47
-
48
- self.class.new(**_options.merge(render_env: render_env))
49
- end
50
-
51
- # Returns a copy of the Context with new options merged in.
52
- #
53
- # This may be useful to supply values for dependencies that are _optional_
54
- # when initializing your custom Context subclass.
55
- #
56
- # @example
57
- # class MyContext < Hanami::View::Context
58
- # # Injected dependencies (request is optional)
59
- # attr_reader :assets, :request
60
- #
61
- # def initialize(assets:, request: nil, **options)
62
- # @assets = assets
63
- # @request = reuqest
64
- # super
65
- # end
66
- # end
67
- #
68
- # my_context = MyContext.new(assets: assets)
69
- # my_context_with_request = my_context.with(request: request)
70
- #
71
- # @api public
72
- def with(**new_options)
73
- self.class.new(
74
- render_env: _render_env,
75
- **_options.merge(new_options)
76
- )
33
+ def dup_for_rendering(rendering)
34
+ dup.tap do |obj|
35
+ obj.instance_variable_set(:@_rendering, rendering)
36
+ end
77
37
  end
78
38
  end
79
39
  end
@@ -2,20 +2,20 @@ module Hanami
2
2
  class View
3
3
  module ContextHelpers
4
4
  module ContentHelpers
5
- def initialize(content: {}, **options)
5
+ def initialize(**)
6
+ @content_for = {}
6
7
  super
7
8
  end
8
9
 
9
10
  def content_for(key, value = nil, &block)
10
- content = _options[:content]
11
11
  output = nil
12
12
 
13
13
  if block
14
- content[key] = yield
14
+ @content_for[key] = yield
15
15
  elsif value
16
- content[key] = value
16
+ @content_for[key] = value
17
17
  else
18
- output = content[key]
18
+ output = @content_for[key]
19
19
  end
20
20
 
21
21
  output
@@ -63,8 +63,8 @@ module Hanami
63
63
  define_method name do
64
64
  attribute = super()
65
65
 
66
- if _render_env && attribute
67
- _render_env.part(name, attribute, **options)
66
+ if _rendering && attribute
67
+ _rendering.part(name, attribute, **options)
68
68
  else
69
69
  attribute
70
70
  end
@@ -0,0 +1,27 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "temple"
4
+
5
+ module Hanami
6
+ class View
7
+ module ERB
8
+ # Hanami::View ERB engine.
9
+ #
10
+ # @api private
11
+ # @since 2.0.0
12
+ class Engine < Temple::Engine
13
+ define_options capture_generator: Hanami::View::HTMLSafeStringBuffer
14
+
15
+ use Parser
16
+ use Filters::Block
17
+ use Filters::Trimming
18
+ filter :Escapable, use_html_safe: true
19
+ filter :StringSplitter
20
+ filter :StaticAnalyzer
21
+ filter :MultiFlattener
22
+ filter :StaticMerger
23
+ generator :ArrayBuffer
24
+ end
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,44 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Hanami
4
+ class View
5
+ module ERB
6
+ module Filters
7
+ # Implicitly captures and outputs the content inside blocks opened in ERB expression tags,
8
+ # such as `<%= form_for(:post) do %>`.
9
+ #
10
+ # Inspired by Slim's Slim::Controls::Filter#on_slim_output.
11
+ #
12
+ # @since 2.0.0
13
+ # @api private
14
+ class Block < Temple::Filter
15
+ END_LINE_RE = /\bend\b/
16
+
17
+ def on_erb_block(escape, code, content)
18
+ tmp = unique_name
19
+
20
+ # Remove the last `end` :code sexp, since this is technically "outside" the block
21
+ # contents, which we want to capture separately below. This `end` is added back after
22
+ # capturing the content below.
23
+ case content.last
24
+ in [:code, c] if c =~ END_LINE_RE
25
+ content.pop
26
+ end
27
+
28
+ [:multi,
29
+ # Capture the result of the code in a variable. We can't do `[:dynamic, code]` because
30
+ # it's probably not a complete expression (which is a requirement for Temple).
31
+ [:code, "#{tmp} = #{code}"],
32
+ # Capture the content of a block in a separate buffer. This means that `yield` will
33
+ # not output the content to the current buffer, but rather return the output.
34
+ [:capture, unique_name, compile(content)],
35
+ [:code, "end"],
36
+ # Output the content.
37
+ [:escape, escape, [:dynamic, tmp]]
38
+ ]
39
+ end
40
+ end
41
+ end
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,42 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Based on Temple::ERB::Trimming, also released under the MIT licence.
4
+ #
5
+ # Copyright (c) 2010-2023 Magnus Holm.
6
+
7
+ module Hanami
8
+ class View
9
+ module ERB
10
+ module Filters
11
+ # Trims spurious spaces from ERB-generated text.
12
+ #
13
+ # Deletes spaces around "<% %>" and leave spaces around "<%= %>".
14
+ #
15
+ # This is a copy of Temple::ERB::Trimming, with the one difference being that it descends
16
+ # into the sexp-tree by running `compile(e)` for any non-`:static` sexps. This is necessary
17
+ # for our implementation of ERB, because we capture block content by creating additional
18
+ # `:multi` sexps with their own nested content.
19
+ #
20
+ # @api private
21
+ # @since 2.0.0
22
+ class Trimming < Temple::Filter
23
+ define_options trim: true
24
+
25
+ def on_multi(*exps)
26
+ exps = exps.each_with_index.map do |e,i|
27
+ if e.first == :static && i > 0 && exps[i-1].first == :code
28
+ [:static, e.last.lstrip]
29
+ elsif e.first == :static && i < exps.size-1 && exps[i+1].first == :code
30
+ [:static, e.last.rstrip]
31
+ else
32
+ compile(e)
33
+ end
34
+ end if options[:trim]
35
+
36
+ [:multi, *exps]
37
+ end
38
+ end
39
+ end
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,161 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Hanami::View::ERB is based on Temple::ERB::Parser, also released under the MIT licence.
4
+ #
5
+ # Copyright (c) 2010-2023 Magnus Holm.
6
+
7
+ require "temple"
8
+
9
+ module Hanami
10
+ class View
11
+ module ERB
12
+ # ERB parser for Hanami views.
13
+ #
14
+ # This is a [Temple][temple] parser that prepares a Temple [s-expression][expression] (sexp)
15
+ # for later generating as HTML via {ERB::Engine}.
16
+ #
17
+ # [temple]: https://github.com/judofyr/temple
18
+ # [expressions]: https://github.com/judofyr/temple/blob/master/EXPRESSIONS.md
19
+ #
20
+ # The key features of this parser are:
21
+ #
22
+ # - Auto-escaping any non-`html_safe?` values given to `<%=` ERB expression tags, with
23
+ # auto-escaping disabled when using `<%==` tags.
24
+ # - Implicitly capturing and correctly outputting block content without the need for special
25
+ # helpers. This allows helpers like `<%= form_for(:post) do %>` to be used, with the
26
+ # `form_for` helper itself doing nothing more special than a `yield`.
27
+ #
28
+ # To support implicit block capture, this parser differs somewhat from Temple's example ERB
29
+ # parser, as well as most other Temple parsers. Typical parsers prepare a single `result` sexp
30
+ # up front, like so:
31
+ #
32
+ # result = [:multi]
33
+ #
34
+ # As parsing occurs, new Temple sexps are then pushed onto this `result`.
35
+ #
36
+ # In this parser, however, we prepare a _stack_ of results:
37
+ #
38
+ # results = [[:multi]]
39
+ #
40
+ # The first item in the stack (`[:multi]`) is the final result that will be returned at the
41
+ # end of parsing. Every sexp that is generated during parsing will still be added to this
42
+ # result, directly or indirectly.
43
+ #
44
+ # How this happens is that during parsing, every new sexp is push to the _last_ result in this
45
+ # stack, representing the "current" result.
46
+ #
47
+ # The nature of stack becomes important when we encounter an ERB expression tag that opens a
48
+ # code block, such as `<%= form_for(:post) do %>`.
49
+ #
50
+ # In this case, we push an `[:erb, :block, ..., [:multi]]` sexp to the last result, with its
51
+ # `[:multi]` representing the _contents_ of that code block. We then also push this particular
52
+ # `[:multi]` onto the `results` stack, so that any subsequent sexps are added to the block's
53
+ # own contents.
54
+ #
55
+ # Then, when we encounter the `<% end %>` closing tag for that block, we pop the block's
56
+ # `[:multi]` off the results stack. This `[:multi]` isn't lost, however, because it is still
57
+ # referenced inside the `[:erb, :block, ..., [:multi]]` sexp.
58
+ #
59
+ # Taking this approach (along with the `on_erb_block` sexp-handling code in
60
+ # `Hanami::View::ERB::Filters::Block`) allows us to implicitly capture the contents of the
61
+ # block and output it in place. This means that helpers that expect blocks do not need to
62
+ # explicitly call a `capture` helper (or similar) internally. Instead they can just `yield`,
63
+ # per idiomatic Ruby.
64
+ #
65
+ # In fact, we pop a result off the stack _every_ time we encounter an `<% end %>` tag. To
66
+ # acount for this, every time we encounter an ERB code tag that will have a matching closing
67
+ # tag (such as `<% if some_cond %>` or `<% 5.times do %>`), we push another reference to the
68
+ # _current_ last result onto the `results` stack. This allows subsequent sexps to be added to
69
+ # the same item on the stack (they need no special handling; the special handling is for
70
+ # blocks with ERB expression tags only) while still allowing it to be popped again when the
71
+ # matching `<% end %>` is encountered.
72
+ #
73
+ # In this way, this stack of results will grow every time a new scope requiring an `end` is
74
+ # opened, and then will shrink again as each `end` is encountered; think of it as matching the
75
+ # level of LHS indentation if you were writing such code by hand. This allows each new
76
+ # generated sexp to be pushed onto `results.last`, knowing that it will go into the right
77
+ # place in the overall sexp tree. By the time we finish parsing, just a single result will
78
+ # remain, which is the value returned.
79
+ #
80
+ # @api private
81
+ # @since 2.0.0
82
+ class Parser < Temple::Parser
83
+ ERB_PATTERN = /(\n|<%%|%%>)|<%(==?|\#)?(.*?)?-?%>/m
84
+
85
+ IF_UNLESS_CASE_LINE_RE = /\A\s*(if|unless|case)\b/
86
+ BLOCK_LINE_RE = /\s*((\s+|\))do|\{)(\s*\|[^|]*\|)?\s*\Z/
87
+ END_LINE_RE = /\bend\b/
88
+
89
+ def call(input)
90
+ results = [[:multi]]
91
+ pos = 0
92
+
93
+ input.scan(ERB_PATTERN) do |token, indicator, code|
94
+ # Capture any text between the last ERB tag and the current one, and update the position
95
+ # to match the end of the current tag for the next iteration of text collection.
96
+ text = input[pos...$~.begin(0)]
97
+ pos = $~.end(0)
98
+
99
+ if token
100
+ # First, handle certain static tokens picked up by our ERB_PATTERN regexp. These are
101
+ # newlines as well as the special codes for literal `<%` and `%>` values.
102
+ case token
103
+ when "\n"
104
+ results.last << [:static, "#{text}\n"] << [:newline]
105
+ when "<%%", "%%>"
106
+ results.last << [:static, text] unless text.empty?
107
+ token.slice!(1)
108
+ results.last << [:static, token]
109
+ end
110
+ else
111
+ # Next, handle actual ERB tags. Start by adding any static text between this match and
112
+ # the last.
113
+ results.last << [:static, text] unless text.empty?
114
+
115
+ case indicator
116
+ when "#"
117
+ # Comment tags: <%# this is a comment %>
118
+ results.last << [:code, "\n" * code.count("\n")]
119
+ when %r{=}
120
+ # Expression tags: <%= "hello (auto-escaped)" %> or <%== "hello (not escaped)" %>
121
+ if code =~ BLOCK_LINE_RE
122
+ # See Hanami::View::Erb::Filters::Block for the processing of `:erb, :block` sexps
123
+ block_node = [:erb, :block, indicator.size == 1, code, (block_content = [:multi])]
124
+ results.last << block_node
125
+
126
+ # For blocks opened in ERB expression tags, push this `[:multi]` sexp
127
+ # (representing the content of the block) onto the stack of resuts. This allows
128
+ # subsequent results to be appropriately added inside the block, until its closing
129
+ # tag is encountered, and this `block_content` multi is subsequently popped off
130
+ # the results stack.
131
+ results << block_content
132
+ else
133
+ results.last << [:escape, indicator.size == 1, [:dynamic, code]]
134
+ end
135
+ else
136
+ # Code tags: <% if some_cond %>
137
+ if code =~ BLOCK_LINE_RE || code =~ IF_UNLESS_CASE_LINE_RE
138
+ results.last << [:code, code]
139
+
140
+ # For ERB code tags that will result in a matching `end`, push the last result
141
+ # back onto the stack of results. This might seem redundant, but it allows
142
+ # subsequent sexps to continue to be pushed onto the same result while also
143
+ # allowing it to be safely popped again when the matching `end` is encountered.
144
+ results << results.last
145
+ elsif code =~ END_LINE_RE
146
+ results.last << [:code, code]
147
+ results.pop
148
+ else
149
+ results.last << [:code, code]
150
+ end
151
+ end
152
+ end
153
+ end
154
+
155
+ # Add any text after the final ERB tag
156
+ results.last << [:static, input[pos..-1]]
157
+ end
158
+ end
159
+ end
160
+ end
161
+ end
@@ -0,0 +1,30 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "temple"
4
+ require_relative "engine"
5
+
6
+ module Hanami
7
+ class View
8
+ # Hanami::View ERB template renderer for Tilt.
9
+ #
10
+ # The key features of this ERB implementation are:
11
+ #
12
+ # - Auto-escaping any non-`html_safe?` values given to `<%=` ERB expression tags, with
13
+ # auto-escaping disabled when using `<%==` tags.
14
+ # - Implicitly capturing and correctly outputting block content without the need for special
15
+ # helpers. This allows helpers like `<%= form_for(:post) do %>` to be used, with the
16
+ # `form_for` helper itself doing nothing more special than a `yield`.
17
+ #
18
+ # See [Tilt](https://github.com/rtomayko/tilt) for rendering options.
19
+ #
20
+ # @example
21
+ # Hanami::View::ERB::Template.new { "<%= 'Hello, world!' %>" }.render
22
+ #
23
+ # @api private
24
+ # @since 2.0.0
25
+ module ERB
26
+ # ERB Template class
27
+ Template = Temple::Templates::Tilt(Engine)
28
+ end
29
+ end
30
+ end
@@ -21,9 +21,9 @@ module Hanami
21
21
  #
22
22
  # @api private
23
23
  class TemplateNotFoundError < StandardError
24
- def initialize(template_name, lookup_paths)
24
+ def initialize(template_name, format, lookup_paths)
25
25
  msg = [
26
- "Template +#{template_name}+ could not be found in paths:",
26
+ "Template `#{template_name}' for format `#{format}' could not be found in paths:",
27
27
  lookup_paths.map { |path| " - #{path}" }
28
28
  ].join("\n\n")
29
29
 
@@ -45,5 +45,11 @@ module Hanami
45
45
  super(msg)
46
46
  end
47
47
  end
48
+
49
+ class RenderingMissingError < Error
50
+ def message
51
+ "a +rendering+ must be provided"
52
+ end
53
+ end
48
54
  end
49
55
  end
@@ -30,35 +30,41 @@ module Hanami
30
30
  end
31
31
 
32
32
  def dependency_names
33
- if proc
34
- proc.parameters.each_with_object([]) { |(type, name), names|
35
- names << name if EXPOSURE_DEPENDENCY_PARAMETER_TYPES.include?(type)
36
- }
37
- else
38
- []
39
- end
33
+ @dependency_names ||=
34
+ if proc
35
+ proc.parameters.each_with_object([]) { |(type, name), names|
36
+ names << name if EXPOSURE_DEPENDENCY_PARAMETER_TYPES.include?(type)
37
+ }
38
+ else
39
+ []
40
+ end
41
+ end
42
+
43
+ def dependencies?
44
+ !dependency_names.empty?
40
45
  end
41
46
 
42
47
  def input_keys
43
- if proc
44
- proc.parameters.each_with_object([]) { |(type, name), keys|
45
- keys << name if INPUT_PARAMETER_TYPES.include?(type)
46
- }
47
- else
48
- []
49
- end
48
+ @input_keys ||=
49
+ if proc
50
+ proc.parameters.each_with_object([]) { |(type, name), keys|
51
+ keys << name if INPUT_PARAMETER_TYPES.include?(type)
52
+ }
53
+ else
54
+ []
55
+ end
50
56
  end
51
57
 
52
58
  def for_layout?
53
- options.fetch(:layout) { false }
59
+ options.fetch(:layout, false)
54
60
  end
55
61
 
56
62
  def decorate?
57
- options.fetch(:decorate) { true }
63
+ options.fetch(:decorate, true)
58
64
  end
59
65
 
60
66
  def private?
61
- options.fetch(:private) { false }
67
+ options.fetch(:private, false)
62
68
  end
63
69
 
64
70
  def default_value