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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +38 -0
- data/README.md +15 -3
- data/hanami-view.gemspec +5 -3
- data/lib/hanami/view/cache.rb +16 -0
- data/lib/hanami/view/context.rb +12 -70
- data/lib/hanami/view/context_helpers/content_helpers.rb +5 -5
- data/lib/hanami/view/decorated_attributes.rb +2 -2
- data/lib/hanami/view/erb/engine.rb +27 -0
- data/lib/hanami/view/erb/filters/block.rb +44 -0
- data/lib/hanami/view/erb/filters/trimming.rb +42 -0
- data/lib/hanami/view/erb/parser.rb +161 -0
- data/lib/hanami/view/erb/template.rb +30 -0
- data/lib/hanami/view/errors.rb +5 -7
- data/lib/hanami/view/exposure.rb +23 -17
- data/lib/hanami/view/exposures.rb +22 -13
- data/lib/hanami/view/helpers/escape_helper.rb +221 -0
- data/lib/hanami/view/helpers/number_formatting_helper.rb +182 -0
- data/lib/hanami/view/helpers/tag_helper/tag_builder.rb +230 -0
- data/lib/hanami/view/helpers/tag_helper.rb +210 -0
- data/lib/hanami/view/html.rb +104 -0
- data/lib/hanami/view/html_safe_string_buffer.rb +46 -0
- data/lib/hanami/view/part.rb +13 -15
- data/lib/hanami/view/part_builder.rb +68 -108
- data/lib/hanami/view/path.rb +4 -31
- data/lib/hanami/view/renderer.rb +36 -44
- data/lib/hanami/view/rendering.rb +42 -0
- data/lib/hanami/view/{render_environment_missing.rb → rendering_missing.rb} +8 -13
- data/lib/hanami/view/scope.rb +15 -16
- data/lib/hanami/view/scope_builder.rb +42 -78
- data/lib/hanami/view/tilt/haml_adapter.rb +40 -0
- data/lib/hanami/view/tilt/slim_adapter.rb +40 -0
- data/lib/hanami/view/tilt.rb +22 -46
- data/lib/hanami/view/version.rb +1 -1
- data/lib/hanami/view.rb +351 -26
- metadata +64 -29
- data/LICENSE +0 -20
- data/lib/hanami/view/application_configuration.rb +0 -77
- data/lib/hanami/view/application_context.rb +0 -98
- data/lib/hanami/view/render_environment.rb +0 -62
- data/lib/hanami/view/standalone_view.rb +0 -400
- data/lib/hanami/view/tilt/erb.rb +0 -26
- data/lib/hanami/view/tilt/erbse.rb +0 -21
- data/lib/hanami/view/tilt/haml.rb +0 -26
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 547759271a98bf19bed148a823827ddb668f8034ce9673a0db01a989f10d8b4a
|
4
|
+
data.tar.gz: 5fbab49653a3efd335a8b67ce8910a6a077023b57466b0f84f61a71ffc8cc328
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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
|
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
|
-
##
|
51
|
+
## Versioning
|
52
52
|
|
53
|
-
|
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", "~>
|
32
|
-
spec.add_runtime_dependency "dry-core", "~>
|
33
|
-
spec.add_runtime_dependency "dry-inflector", "~> 0
|
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"
|
data/lib/hanami/view/context.rb
CHANGED
@@ -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
|
-
|
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
|
-
|
33
|
-
|
34
|
-
|
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(
|
58
|
-
@_render_env = render_env
|
59
|
-
@_options = options
|
29
|
+
def initialize(**)
|
60
30
|
end
|
61
31
|
|
62
32
|
# @api private
|
63
|
-
def
|
64
|
-
|
65
|
-
|
66
|
-
|
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(
|
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
|
-
|
14
|
+
@content_for[key] = yield
|
15
15
|
elsif value
|
16
|
-
|
16
|
+
@content_for[key] = value
|
17
17
|
else
|
18
|
-
output =
|
18
|
+
output = @content_for[key]
|
19
19
|
end
|
20
20
|
|
21
21
|
output
|
@@ -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
|
data/lib/hanami/view/errors.rb
CHANGED
@@ -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
|
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
|
-
|
50
|
-
|
51
|
-
|
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
|
data/lib/hanami/view/exposure.rb
CHANGED
@@ -30,35 +30,41 @@ module Hanami
|
|
30
30
|
end
|
31
31
|
|
32
32
|
def dependency_names
|
33
|
-
|
34
|
-
proc
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
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
|
-
|
44
|
-
proc
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
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
|
59
|
+
options.fetch(:layout, false)
|
54
60
|
end
|
55
61
|
|
56
62
|
def decorate?
|
57
|
-
options.fetch(:decorate
|
63
|
+
options.fetch(:decorate, true)
|
58
64
|
end
|
59
65
|
|
60
66
|
def private?
|
61
|
-
options.fetch(:private
|
67
|
+
options.fetch(:private, false)
|
62
68
|
end
|
63
69
|
|
64
70
|
def default_value
|