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
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 5f1426ce046073c71aeffe9a171b0b0343704686bb92b75046526858ca82f9e8
4
- data.tar.gz: 14a7f4078a42e3abd63e5411da1a2fa4b19fdfeaf405184c9c1f11bda7e78210
3
+ metadata.gz: 547759271a98bf19bed148a823827ddb668f8034ce9673a0db01a989f10d8b4a
4
+ data.tar.gz: 5fbab49653a3efd335a8b67ce8910a6a077023b57466b0f84f61a71ffc8cc328
5
5
  SHA512:
6
- metadata.gz: 1a0f2cca15e556ee2c93396f730c6b68d3a7799ec0c2cbebc574b5c73c0315d2192c89ad8d85ad1b273d03b80132b3acf2348befab3c7f22fff28b1ef7e82667
7
- data.tar.gz: 5032a5b1c0891a369bf518cf12d0baad00dcfb688ac41625dd61818b635b8ef6dea498a574c9558249a0beb2cd905553b253bb5a0a8b82245030664d0f7bd629
6
+ metadata.gz: 2f218ce967455774e6b00340a791b17e30accdee496c88f79c2b12721a97e2008d40341b8981f3144caba2a7bacb0eff9aa0e0c51b8f0c25b1406b55c0a68d18
7
+ data.tar.gz: 83cf8481266c6f0c8e4c76ec3c87b36b60e2e3035fc61db1ee9497675030008efde35fd5890e625a3560b9c6fc0b182956f55c9358e7752f5c6457ea5e4fb5a8
data/CHANGELOG.md CHANGED
@@ -1,6 +1,44 @@
1
1
  # Hanami::View
2
+
2
3
  View layer for Hanami
3
4
 
5
+ ## v2.1.0.beta1 - 2023-06-29
6
+
7
+ ### Added
8
+ - [Tim Riley] Introduce new ERB engine, `Hanami::View::ERB`, now used by default for ERB templates;
9
+ the erbse gem is no longer a requirement (#226)
10
+ - [Tim Riley] Introduce `Hanami::View::HTML::SafeString`, returned when calling `String#html_safe`,
11
+ also introduced via a `Hanami::View::HTML::StringExtensions` module prepended onto `String`. (#226)
12
+ - [Tim Riley] Auto-escape HTML based on whether it is `#html_safe?` in ERB, Haml and Slim templates (#226)
13
+ - [Tim Riley] Add `part_class` and `scope_class` settings, used by the standard `part_builder` and
14
+ `scope_builder` as the default class to use when no value-specific part or scope classes can be
15
+ found. These settings default to `Hanami::View::Part` and `Hanami::View::Scope` respectively (#227)
16
+ - [Tim Riley] Introduce `Hanami::View::Helpers::EscapeHelper`, `Hanami::View::Helpers::TagHelper`,
17
+ `Hanami::View::Helpers::LinkToHelper`, `Hanami::View::Helpers::NumberFormattingHelper`. These
18
+ helper modules may optionally be mixed into your Part and Scope classes to provide additional
19
+ conveniences when authoring your views. To do this by default, create your own
20
+ `Hanaim::View::Part` and `Hanami::View::Scope` subclasses that include these modules, and then
21
+ configure these as the `part_class` and `scope_class` for your views. (#229)
22
+
23
+ ### Changed
24
+ - [Tim Riley] Use Zeitwerk for code loading; you should now require `require "hanami/view"` just
25
+ once (#233)
26
+ - [Tim Riley] Change `Context` interface: custom context subclasses now have complete control over
27
+ their `#initialize` (no longer need to receive `**options` or call `super`); though any mutable
28
+ ivars should be duped in a custom `#initialize_copy` as required. (#223)
29
+ - [Tim Riley] Change `PartBuilder` and `ScopeBuilder` interfaces to consist of static methods only (#223)
30
+ - [Tim Riley] Finalize the view class config when calling `.new` for the first time (#223)
31
+ - [Tim Riley] Consolidate all internal caches to a single `View.cache` (#223)
32
+ - [Tim Riley] [Internal] Various refactorings to improve rendering performance (#223)
33
+
34
+ ## v2.0.0.alpha8 - 2022-05-19
35
+
36
+ ### Added
37
+ - [Tim Riley] Access a hash of template locals via accessing `locals` inside any template
38
+
39
+ ### Changed
40
+ - [Tim Riley] Removed automatic integration of `Hanami::View` subclasses with their surrounding Hanami application. View base classes within Hanami apps should inherit from `Hanami::Application::View` instead.
41
+
4
42
  ## v2.0.0.alpha7 - 2022-03-08
5
43
 
6
44
  ### 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,9 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "dry/core/equalizer"
4
- require_relative "application_context"
5
- require_relative "decorated_attributes"
6
-
7
3
  module Hanami
8
4
  class View
9
5
  # Provides a baseline environment across all the templates, parts and scopes
@@ -14,84 +10,30 @@ module Hanami
14
10
  #
15
11
  # @api public
16
12
  class Context
17
- include Dry::Equalizer(:_options)
18
13
  include DecoratedAttributes
19
14
 
20
- attr_reader :_render_env, :_options
21
-
22
- def self.inherited(subclass)
23
- super
24
-
25
- # When inheriting within an Hanami app, add application context behavior
26
- provider = application_provider(subclass)
27
- if provider
28
- subclass.include ApplicationContext.new(provider)
29
- end
30
- end
15
+ # @api private
16
+ attr_reader :_rendering
31
17
 
32
- def self.application_provider(subclass)
33
- if Hanami.respond_to?(:application?) && Hanami.application?
34
- Hanami.application.component_provider(subclass)
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)
35
23
  end
36
24
  end
37
- private_class_method :application_provider
38
25
 
39
26
  # Returns a new instance of Context
40
27
  #
41
- # In subclasses, you should include an `**options` parameter and pass _all
42
- # arguments_ to `super`. This allows Context to make copies of itself
43
- # while preserving your dependencies.
44
- #
45
- # @example
46
- # class MyContext < Hanami::View::Context
47
- # # Injected dependency
48
- # attr_reader :assets
49
- #
50
- # def initialize(assets:, **options)
51
- # @assets = assets
52
- # super
53
- # end
54
- # end
55
- #
56
28
  # @api public
57
- def initialize(render_env: nil, **options)
58
- @_render_env = render_env
59
- @_options = options
29
+ def initialize(**)
60
30
  end
61
31
 
62
32
  # @api private
63
- def for_render_env(render_env)
64
- return self if render_env == _render_env
65
-
66
- self.class.new(**_options.merge(render_env: render_env))
67
- end
68
-
69
- # Returns a copy of the Context with new options merged in.
70
- #
71
- # This may be useful to supply values for dependencies that are _optional_
72
- # when initializing your custom Context subclass.
73
- #
74
- # @example
75
- # class MyContext < Hanami::View::Context
76
- # # Injected dependencies (request is optional)
77
- # attr_reader :assets, :request
78
- #
79
- # def initialize(assets:, request: nil, **options)
80
- # @assets = assets
81
- # @request = reuqest
82
- # super
83
- # end
84
- # end
85
- #
86
- # my_context = MyContext.new(assets: assets)
87
- # my_context_with_request = my_context.with(request: request)
88
- #
89
- # @api public
90
- def with(**new_options)
91
- self.class.new(
92
- render_env: _render_env,
93
- **_options.merge(new_options)
94
- )
33
+ def dup_for_rendering(rendering)
34
+ dup.tap do |obj|
35
+ obj.instance_variable_set(:@_rendering, rendering)
36
+ end
95
37
  end
96
38
  end
97
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
 
@@ -46,11 +46,9 @@ module Hanami
46
46
  end
47
47
  end
48
48
 
49
- # @since 2.0.0
50
- # @api public
51
- class MissingProviderError < Error
52
- def initialize(provider)
53
- super("#{provider.inspect} is missing")
49
+ class RenderingMissingError < Error
50
+ def message
51
+ "a +rendering+ must be provided"
54
52
  end
55
53
  end
56
54
  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