hanami-view 1.3.0.beta1 → 2.0.0.alpha2
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 +21 -0
- data/LICENSE +20 -0
- data/README.md +20 -862
- data/hanami-view.gemspec +26 -16
- data/lib/hanami-view.rb +3 -1
- data/lib/hanami/view.rb +208 -223
- data/lib/hanami/view/application_configuration.rb +77 -0
- data/lib/hanami/view/application_context.rb +35 -0
- data/lib/hanami/view/application_view.rb +89 -0
- data/lib/hanami/view/context.rb +97 -0
- data/lib/hanami/view/context_helpers/content_helpers.rb +26 -0
- data/lib/hanami/view/decorated_attributes.rb +82 -0
- data/lib/hanami/view/errors.rb +19 -56
- data/lib/hanami/view/exposure.rb +126 -0
- data/lib/hanami/view/exposures.rb +74 -0
- data/lib/hanami/view/part.rb +217 -0
- data/lib/hanami/view/part_builder.rb +140 -0
- data/lib/hanami/view/path.rb +68 -0
- data/lib/hanami/view/render_environment.rb +62 -0
- data/lib/hanami/view/render_environment_missing.rb +44 -0
- data/lib/hanami/view/rendered.rb +55 -0
- data/lib/hanami/view/renderer.rb +79 -0
- data/lib/hanami/view/scope.rb +189 -0
- data/lib/hanami/view/scope_builder.rb +98 -0
- data/lib/hanami/view/standalone_view.rb +396 -0
- data/lib/hanami/view/tilt.rb +78 -0
- data/lib/hanami/view/tilt/erb.rb +26 -0
- data/lib/hanami/view/tilt/erbse.rb +21 -0
- data/lib/hanami/view/tilt/haml.rb +26 -0
- data/lib/hanami/view/version.rb +5 -5
- metadata +113 -63
- data/LICENSE.md +0 -22
- data/lib/hanami/layout.rb +0 -172
- data/lib/hanami/presenter.rb +0 -98
- data/lib/hanami/view/configuration.rb +0 -504
- data/lib/hanami/view/dsl.rb +0 -347
- data/lib/hanami/view/escape.rb +0 -225
- data/lib/hanami/view/inheritable.rb +0 -54
- data/lib/hanami/view/rendering.rb +0 -294
- data/lib/hanami/view/rendering/layout_finder.rb +0 -128
- data/lib/hanami/view/rendering/layout_registry.rb +0 -69
- data/lib/hanami/view/rendering/layout_scope.rb +0 -274
- data/lib/hanami/view/rendering/null_layout.rb +0 -52
- data/lib/hanami/view/rendering/null_local.rb +0 -82
- data/lib/hanami/view/rendering/null_template.rb +0 -83
- data/lib/hanami/view/rendering/options.rb +0 -24
- data/lib/hanami/view/rendering/partial.rb +0 -31
- data/lib/hanami/view/rendering/partial_file.rb +0 -29
- data/lib/hanami/view/rendering/partial_finder.rb +0 -75
- data/lib/hanami/view/rendering/partial_templates_finder.rb +0 -73
- data/lib/hanami/view/rendering/registry.rb +0 -134
- data/lib/hanami/view/rendering/scope.rb +0 -108
- data/lib/hanami/view/rendering/subscope.rb +0 -56
- data/lib/hanami/view/rendering/template.rb +0 -69
- data/lib/hanami/view/rendering/template_finder.rb +0 -55
- data/lib/hanami/view/rendering/template_name.rb +0 -50
- data/lib/hanami/view/rendering/templates_finder.rb +0 -144
- data/lib/hanami/view/rendering/view_finder.rb +0 -37
- data/lib/hanami/view/template.rb +0 -57
@@ -0,0 +1,77 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "dry/configurable"
|
4
|
+
require_relative "../view"
|
5
|
+
|
6
|
+
module Hanami
|
7
|
+
class View
|
8
|
+
class ApplicationConfiguration
|
9
|
+
include Dry::Configurable
|
10
|
+
|
11
|
+
setting :parts_path, "views/parts"
|
12
|
+
|
13
|
+
def initialize(*)
|
14
|
+
super
|
15
|
+
|
16
|
+
@base_config = View.config.dup
|
17
|
+
|
18
|
+
configure_defaults
|
19
|
+
end
|
20
|
+
|
21
|
+
# Returns the list of available settings
|
22
|
+
#
|
23
|
+
# @return [Set]
|
24
|
+
#
|
25
|
+
# @since 2.0.0
|
26
|
+
# @api private
|
27
|
+
def settings
|
28
|
+
self.class.settings + View.settings - NON_FORWARDABLE_METHODS
|
29
|
+
end
|
30
|
+
|
31
|
+
def finalize!
|
32
|
+
return self if frozen?
|
33
|
+
|
34
|
+
base_config.finalize!
|
35
|
+
|
36
|
+
super
|
37
|
+
end
|
38
|
+
|
39
|
+
private
|
40
|
+
|
41
|
+
attr_reader :base_config
|
42
|
+
|
43
|
+
def configure_defaults
|
44
|
+
self.paths = ["web/templates"]
|
45
|
+
self.template_inference_base = "views"
|
46
|
+
self.layout = "application"
|
47
|
+
end
|
48
|
+
|
49
|
+
# An inflector for views is not configurable via `config.views.inflector` on an
|
50
|
+
# `Hanami::Application`. The application-wide inflector is already configurable
|
51
|
+
# there as `config.inflector` and will be used as the default inflector for views.
|
52
|
+
#
|
53
|
+
# A custom inflector may still be provided in an `Hanami::View` subclass, via
|
54
|
+
# `config.inflector=`.
|
55
|
+
NON_FORWARDABLE_METHODS = [:inflector, :inflector=].freeze
|
56
|
+
private_constant :NON_FORWARDABLE_METHODS
|
57
|
+
|
58
|
+
def method_missing(name, *args, &block)
|
59
|
+
return super if NON_FORWARDABLE_METHODS.include?(name)
|
60
|
+
|
61
|
+
if config.respond_to?(name)
|
62
|
+
config.public_send(name, *args, &block)
|
63
|
+
elsif base_config.respond_to?(name)
|
64
|
+
base_config.public_send(name, *args, &block)
|
65
|
+
else
|
66
|
+
super
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
def respond_to_missing?(name, _include_all = false)
|
71
|
+
return false if NON_FORWARDABLE_METHODS.include?(name)
|
72
|
+
|
73
|
+
config.respond_to?(name) || base_config.respond_to?(name) || super
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Hanami
|
4
|
+
class View
|
5
|
+
module ApplicationContext
|
6
|
+
def initialize(inflector: Hanami.application.inflector, **options)
|
7
|
+
@inflector = inflector
|
8
|
+
super
|
9
|
+
end
|
10
|
+
|
11
|
+
def inflector
|
12
|
+
@inflector
|
13
|
+
end
|
14
|
+
|
15
|
+
def request
|
16
|
+
_options.fetch(:request)
|
17
|
+
end
|
18
|
+
|
19
|
+
def session
|
20
|
+
request.session
|
21
|
+
end
|
22
|
+
|
23
|
+
def flash
|
24
|
+
response.flash
|
25
|
+
end
|
26
|
+
|
27
|
+
private
|
28
|
+
|
29
|
+
# TODO: create `Request#flash` so we no longer need the `response`
|
30
|
+
def response
|
31
|
+
_options.fetch(:response)
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
@@ -0,0 +1,89 @@
|
|
1
|
+
module Hanami
|
2
|
+
class View
|
3
|
+
class ApplicationView < Module
|
4
|
+
InheritedHook = Class.new(Module)
|
5
|
+
|
6
|
+
attr_reader :provider
|
7
|
+
attr_reader :application
|
8
|
+
attr_reader :inherited_hook
|
9
|
+
|
10
|
+
def initialize(provider)
|
11
|
+
@provider = provider
|
12
|
+
@application = provider.respond_to?(:application) ? provider.application : Hanami.application
|
13
|
+
@inherited_hook = InheritedHook.new
|
14
|
+
|
15
|
+
define_inherited_hook
|
16
|
+
end
|
17
|
+
|
18
|
+
def included(view_class)
|
19
|
+
configure_view view_class
|
20
|
+
view_class.extend inherited_hook
|
21
|
+
end
|
22
|
+
|
23
|
+
private
|
24
|
+
|
25
|
+
def configure_view(view_class)
|
26
|
+
view_class.settings.each do |setting|
|
27
|
+
if application.config.views.respond_to?(:"#{setting}")
|
28
|
+
application_value = application.config.views.public_send(:"#{setting}")
|
29
|
+
view_class.config.public_send :"#{setting}=", application_value
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
view_class.config.inflector = provider.inflector
|
34
|
+
view_class.config.paths = prepare_paths(provider, view_class.config.paths)
|
35
|
+
view_class.config.template = template_name(view_class)
|
36
|
+
|
37
|
+
if (part_namespace = namespace_from_path(application.config.views.parts_path))
|
38
|
+
view_class.config.part_namespace = part_namespace
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
def define_inherited_hook
|
43
|
+
template_name = method(:template_name)
|
44
|
+
|
45
|
+
inherited_hook.send :define_method, :inherited do |subclass|
|
46
|
+
super(subclass)
|
47
|
+
subclass.config.template = template_name.(subclass)
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
def prepare_paths(provider, configured_paths)
|
52
|
+
configured_paths.map { |path|
|
53
|
+
if path.dir.relative?
|
54
|
+
provider.root.join(path.dir)
|
55
|
+
else
|
56
|
+
path
|
57
|
+
end
|
58
|
+
}
|
59
|
+
end
|
60
|
+
|
61
|
+
def template_name(view_class)
|
62
|
+
provider
|
63
|
+
.inflector
|
64
|
+
.underscore(view_class.name)
|
65
|
+
.sub(/^#{provider.namespace_path}\//, "")
|
66
|
+
.sub(/^#{view_class.config.template_inference_base}\//, "")
|
67
|
+
end
|
68
|
+
|
69
|
+
def namespace_from_path(path)
|
70
|
+
path = "#{provider.namespace_path}/#{path}"
|
71
|
+
|
72
|
+
begin
|
73
|
+
require path
|
74
|
+
rescue LoadError => exception
|
75
|
+
raise exception unless exception.path == path
|
76
|
+
end
|
77
|
+
|
78
|
+
begin
|
79
|
+
inflector.constantize(inflector.camelize(path))
|
80
|
+
rescue NameError => exception
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
def inflector
|
85
|
+
provider.inflector
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|
89
|
+
end
|
@@ -0,0 +1,97 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "dry/core/equalizer"
|
4
|
+
require_relative "application_context"
|
5
|
+
require_relative "decorated_attributes"
|
6
|
+
|
7
|
+
module Hanami
|
8
|
+
class View
|
9
|
+
# Provides a baseline environment across all the templates, parts and scopes
|
10
|
+
# in a given rendering.
|
11
|
+
#
|
12
|
+
# @abstract Subclass this and add your own methods (along with a custom
|
13
|
+
# `#initialize` if you wish to inject dependencies)
|
14
|
+
#
|
15
|
+
# @api public
|
16
|
+
class Context
|
17
|
+
include Dry::Equalizer(:_options)
|
18
|
+
include DecoratedAttributes
|
19
|
+
|
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
|
+
if application_provider(subclass)
|
27
|
+
subclass.include ApplicationContext
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
def self.application_provider(subclass)
|
32
|
+
if Hanami.respond_to?(:application?) && Hanami.application?
|
33
|
+
Hanami.application.component_provider(subclass)
|
34
|
+
end
|
35
|
+
end
|
36
|
+
private_class_method :application_provider
|
37
|
+
|
38
|
+
# Returns a new instance of Context
|
39
|
+
#
|
40
|
+
# In subclasses, you should include an `**options` parameter and pass _all
|
41
|
+
# arguments_ to `super`. This allows Context to make copies of itself
|
42
|
+
# while preserving your dependencies.
|
43
|
+
#
|
44
|
+
# @example
|
45
|
+
# class MyContext < Hanami::View::Context
|
46
|
+
# # Injected dependency
|
47
|
+
# attr_reader :assets
|
48
|
+
#
|
49
|
+
# def initialize(assets:, **options)
|
50
|
+
# @assets = assets
|
51
|
+
# super
|
52
|
+
# end
|
53
|
+
# end
|
54
|
+
#
|
55
|
+
# @api public
|
56
|
+
def initialize(render_env: nil, **options)
|
57
|
+
@_render_env = render_env
|
58
|
+
@_options = options
|
59
|
+
end
|
60
|
+
|
61
|
+
# @api private
|
62
|
+
def for_render_env(render_env)
|
63
|
+
return self if render_env == _render_env
|
64
|
+
|
65
|
+
self.class.new(**_options.merge(render_env: render_env))
|
66
|
+
end
|
67
|
+
|
68
|
+
# Returns a copy of the Context with new options merged in.
|
69
|
+
#
|
70
|
+
# This may be useful to supply values for dependencies that are _optional_
|
71
|
+
# when initializing your custom Context subclass.
|
72
|
+
#
|
73
|
+
# @example
|
74
|
+
# class MyContext < Hanami::View::Context
|
75
|
+
# # Injected dependencies (request is optional)
|
76
|
+
# attr_reader :assets, :request
|
77
|
+
#
|
78
|
+
# def initialize(assets:, request: nil, **options)
|
79
|
+
# @assets = assets
|
80
|
+
# @request = reuqest
|
81
|
+
# super
|
82
|
+
# end
|
83
|
+
# end
|
84
|
+
#
|
85
|
+
# my_context = MyContext.new(assets: assets)
|
86
|
+
# my_context_with_request = my_context.with(request: request)
|
87
|
+
#
|
88
|
+
# @api public
|
89
|
+
def with(**new_options)
|
90
|
+
self.class.new(
|
91
|
+
render_env: _render_env,
|
92
|
+
**_options.merge(new_options)
|
93
|
+
)
|
94
|
+
end
|
95
|
+
end
|
96
|
+
end
|
97
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
module Hanami
|
2
|
+
class View
|
3
|
+
module ContextHelpers
|
4
|
+
module ContentHelpers
|
5
|
+
def initialize(content: {}, **options)
|
6
|
+
super
|
7
|
+
end
|
8
|
+
|
9
|
+
def content_for(key, value = nil, &block)
|
10
|
+
content = _options[:content]
|
11
|
+
output = nil
|
12
|
+
|
13
|
+
if block
|
14
|
+
content[key] = yield
|
15
|
+
elsif value
|
16
|
+
content[key] = value
|
17
|
+
else
|
18
|
+
output = content[key]
|
19
|
+
end
|
20
|
+
|
21
|
+
output
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,82 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "set"
|
4
|
+
|
5
|
+
module Hanami
|
6
|
+
class View
|
7
|
+
# Decorates attributes in Parts.
|
8
|
+
module DecoratedAttributes
|
9
|
+
# @api private
|
10
|
+
def self.included(klass)
|
11
|
+
klass.extend ClassInterface
|
12
|
+
end
|
13
|
+
|
14
|
+
# Decorated attributes class-level interface.
|
15
|
+
module ClassInterface
|
16
|
+
# @api private
|
17
|
+
MODULE_NAME = :DecoratedAttributes
|
18
|
+
|
19
|
+
# Decorates the provided attributes, wrapping them in Parts using the
|
20
|
+
# current render environment.
|
21
|
+
#
|
22
|
+
# @example
|
23
|
+
# class Article < Hanami::View::Part
|
24
|
+
# decorate :feature_image
|
25
|
+
# decorate :author as: :person
|
26
|
+
# end
|
27
|
+
#
|
28
|
+
# @param names [Array<Symbol>] the attribute names
|
29
|
+
# @param options [Hash] the options to pass to the Part Builder
|
30
|
+
# @option options [Symbol, Class] :as an alternative name or class to use when finding a
|
31
|
+
# matching Part
|
32
|
+
#
|
33
|
+
# @api public
|
34
|
+
def decorate(*names, **options)
|
35
|
+
decorated_attributes.decorate(*names, **options)
|
36
|
+
end
|
37
|
+
|
38
|
+
private
|
39
|
+
|
40
|
+
def decorated_attributes
|
41
|
+
if const_defined?(MODULE_NAME, false)
|
42
|
+
const_get(MODULE_NAME)
|
43
|
+
else
|
44
|
+
const_set(MODULE_NAME, Attributes.new).tap do |mod|
|
45
|
+
prepend mod
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
# @api private
|
52
|
+
class Attributes < Module
|
53
|
+
def initialize(*)
|
54
|
+
@names = Set.new
|
55
|
+
super
|
56
|
+
end
|
57
|
+
|
58
|
+
def decorate(*names, **options)
|
59
|
+
@names += names
|
60
|
+
|
61
|
+
class_eval do
|
62
|
+
names.each do |name|
|
63
|
+
define_method name do
|
64
|
+
attribute = super()
|
65
|
+
|
66
|
+
if _render_env && attribute
|
67
|
+
_render_env.part(name, attribute, **options)
|
68
|
+
else
|
69
|
+
attribute
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
def inspect
|
77
|
+
%(#<#{self.class.name}#{@names.to_a.sort.inspect}>)
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
data/lib/hanami/view/errors.rb
CHANGED
@@ -1,65 +1,28 @@
|
|
1
|
-
|
2
|
-
module View
|
3
|
-
# @since 0.5.0
|
4
|
-
class Error < ::StandardError
|
5
|
-
end
|
1
|
+
# frozen_string_literal: true
|
6
2
|
|
7
|
-
|
8
|
-
|
9
|
-
#
|
10
|
-
# the requested format.
|
11
|
-
#
|
12
|
-
# We can't raise this error during the loading phase, because at that time
|
13
|
-
# we don't know if a view implements its own rendering policy.
|
14
|
-
# A view is allowed to override `#render`, and this scenario can make the
|
15
|
-
# presence of a template useless. One typical example is the usage of a
|
16
|
-
# serializer that returns the output string, without rendering a template.
|
3
|
+
module Hanami
|
4
|
+
class View
|
5
|
+
# Error raised when critical settings are not configured
|
17
6
|
#
|
18
|
-
# @
|
19
|
-
class
|
20
|
-
|
21
|
-
|
22
|
-
def initialize(template, format)
|
23
|
-
super("Can't find template '#{ template }' for '#{ format }' format.")
|
7
|
+
# @api private
|
8
|
+
class UndefinedConfigError < StandardError
|
9
|
+
def initialize(key)
|
10
|
+
super("no +#{key}+ configured")
|
24
11
|
end
|
25
12
|
end
|
26
13
|
|
27
|
-
#
|
28
|
-
#
|
29
|
-
#
|
30
|
-
#
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
# Missing template layout error
|
39
|
-
#
|
40
|
-
# This is raised at the runtime when Hanami::Layout cannot find its template.
|
41
|
-
#
|
42
|
-
# @since 0.5.0
|
43
|
-
class MissingTemplateLayoutError < Error
|
44
|
-
# @since 0.5.0
|
45
|
-
# @api private
|
46
|
-
def initialize(template)
|
47
|
-
super("Can't find layout template '#{ template }'")
|
48
|
-
end
|
49
|
-
end
|
14
|
+
# Error raised when template could not be found within a view's configured
|
15
|
+
# paths
|
16
|
+
#
|
17
|
+
# @api private
|
18
|
+
class TemplateNotFoundError < StandardError
|
19
|
+
def initialize(template_name, lookup_paths)
|
20
|
+
msg = [
|
21
|
+
"Template +#{template_name}+ could not be found in paths:",
|
22
|
+
lookup_paths.map { |path| " - #{path}" }
|
23
|
+
].join("\n\n")
|
50
24
|
|
51
|
-
|
52
|
-
#
|
53
|
-
# This is raised at the runtime when `Hanami::Layout` doesn't recognize the render type.
|
54
|
-
#
|
55
|
-
# @since 1.1.0
|
56
|
-
class UnknownRenderTypeError < Error
|
57
|
-
# @since 1.1.0
|
58
|
-
# @api private
|
59
|
-
def initialize(known_types, supplied_options)
|
60
|
-
known_types_list = known_types.map{|t| "':#{t}'"}.join(', ')
|
61
|
-
supplied_options_list = supplied_options.keys.map{|t| "':#{t}'"}.join(', ')
|
62
|
-
super("Calls to `render` in a layout must include one of #{known_types_list}. Found #{supplied_options_list}.")
|
25
|
+
super(msg)
|
63
26
|
end
|
64
27
|
end
|
65
28
|
end
|