hanami-view 0.0.0 → 0.6.0
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 +96 -0
- data/LICENSE.md +22 -0
- data/README.md +826 -7
- data/hanami-view.gemspec +17 -12
- data/lib/hanami-view.rb +1 -0
- data/lib/hanami/layout.rb +142 -0
- data/lib/hanami/presenter.rb +126 -0
- data/lib/hanami/view.rb +259 -2
- data/lib/hanami/view/configuration.rb +464 -0
- data/lib/hanami/view/dsl.rb +346 -0
- data/lib/hanami/view/errors.rb +47 -0
- data/lib/hanami/view/escape.rb +180 -0
- data/lib/hanami/view/inheritable.rb +54 -0
- data/lib/hanami/view/rendering.rb +265 -0
- data/lib/hanami/view/rendering/layout_finder.rb +128 -0
- data/lib/hanami/view/rendering/layout_registry.rb +63 -0
- data/lib/hanami/view/rendering/layout_scope.rb +240 -0
- data/lib/hanami/view/rendering/null_layout.rb +52 -0
- data/lib/hanami/view/rendering/null_template.rb +83 -0
- data/lib/hanami/view/rendering/partial.rb +29 -0
- data/lib/hanami/view/rendering/partial_finder.rb +73 -0
- data/lib/hanami/view/rendering/registry.rb +128 -0
- data/lib/hanami/view/rendering/scope.rb +88 -0
- data/lib/hanami/view/rendering/template.rb +67 -0
- data/lib/hanami/view/rendering/template_finder.rb +53 -0
- data/lib/hanami/view/rendering/template_name.rb +37 -0
- data/lib/hanami/view/rendering/templates_finder.rb +129 -0
- data/lib/hanami/view/rendering/view_finder.rb +37 -0
- data/lib/hanami/view/template.rb +43 -0
- data/lib/hanami/view/version.rb +4 -1
- metadata +91 -16
- data/.gitignore +0 -9
- data/Gemfile +0 -4
- data/Rakefile +0 -2
- data/bin/console +0 -14
- data/bin/setup +0 -8
@@ -0,0 +1,128 @@
|
|
1
|
+
require 'hanami/view/rendering/layout_registry'
|
2
|
+
require 'hanami/view/rendering/view_finder'
|
3
|
+
|
4
|
+
module Hanami
|
5
|
+
module View
|
6
|
+
module Rendering
|
7
|
+
# Holds all the references of all the registered subclasses of a view.
|
8
|
+
# We have one registry for each superclass view.
|
9
|
+
#
|
10
|
+
# @api private
|
11
|
+
# @since 0.1.0
|
12
|
+
#
|
13
|
+
# @see Hanami::View::Rendering::LayoutRegistry
|
14
|
+
# @see Hanami::View::Rendering#registry
|
15
|
+
#
|
16
|
+
# @example
|
17
|
+
# require 'hanami/view'
|
18
|
+
#
|
19
|
+
# module Articles
|
20
|
+
# class Index
|
21
|
+
# include Hanami::View
|
22
|
+
# end
|
23
|
+
#
|
24
|
+
# class Show
|
25
|
+
# include Hanami::View
|
26
|
+
# end
|
27
|
+
#
|
28
|
+
# class JsonShow < Show
|
29
|
+
# format :json
|
30
|
+
# end
|
31
|
+
#
|
32
|
+
# class XmlShow < Show
|
33
|
+
# format :xml
|
34
|
+
#
|
35
|
+
# def render
|
36
|
+
# ArticleSerializer.new(article).to_xml
|
37
|
+
# end
|
38
|
+
# end
|
39
|
+
# end
|
40
|
+
#
|
41
|
+
# # We have the following templates:
|
42
|
+
# #
|
43
|
+
# # * articles/index.html.erb
|
44
|
+
# # * articles/index.atom.erb
|
45
|
+
# # * articles/show.html.erb
|
46
|
+
# # * articles/show.json.erb
|
47
|
+
#
|
48
|
+
# # One registry per superclass view
|
49
|
+
# Articles::Index.send(:registry).object_id # => 70135342862240
|
50
|
+
#
|
51
|
+
# Articles::Show.send(:registry).object_id # => 70135342110540
|
52
|
+
# Articles::XmlShow.send(:registry).object_id # => 70135342110540
|
53
|
+
# Articles::JsonShow.send(:registry).object_id # => 70135342110540
|
54
|
+
#
|
55
|
+
#
|
56
|
+
#
|
57
|
+
# # It holds the references for all the templates and the views
|
58
|
+
# Articles::Index.send(:registry).inspect
|
59
|
+
# # => { :all => [Articles::Index, nil],
|
60
|
+
# # :atom => [Articles::Index, #<Hanami::View::Template ... @file="/path/to/templates/articles/index.atom.erb"],
|
61
|
+
# # :html => [Articles::Index, #<Hanami::View::Template ... @file="/path/to/templates/articles/index.html.erb"] }
|
62
|
+
#
|
63
|
+
# Articles::Show.send(:registry).inspect
|
64
|
+
# # => { :all => [Articles::Show, nil],
|
65
|
+
# # :html => [Articles::Show, #<Hanami::View::Template ... @file="/path/to/templates/articles/show.html.erb"],
|
66
|
+
# # :json => [Articles::JsonShow, #<Hanami::View::Template ... @file="/path/to/templates/articles/show.json.erb"],
|
67
|
+
# # :xml => [Articles::XmlShow, nil] }
|
68
|
+
class Registry < LayoutRegistry
|
69
|
+
# Default format for views without an explicit format.
|
70
|
+
#
|
71
|
+
# @api private
|
72
|
+
# @since 0.1.0
|
73
|
+
#
|
74
|
+
# @see Hanami::View::Dsl#format
|
75
|
+
DEFAULT_FORMAT = :all
|
76
|
+
|
77
|
+
# Returns the view for the given context.
|
78
|
+
#
|
79
|
+
# @param context [Hash] the rendering context
|
80
|
+
# @option context [Symbol] :format the requested format
|
81
|
+
#
|
82
|
+
# @return [Hanami::View] the view associated with the given context
|
83
|
+
#
|
84
|
+
# @raise [Hanami::View::MissingFormatError] if the given context doesn't
|
85
|
+
# have the :format key
|
86
|
+
#
|
87
|
+
# @api private
|
88
|
+
# @since 0.1.0
|
89
|
+
#
|
90
|
+
# @see Hanami::View::Rendering#render
|
91
|
+
def resolve(context)
|
92
|
+
view, template = @registry.fetch(format(context)) { @registry[DEFAULT_FORMAT] }
|
93
|
+
view.new(template, context)
|
94
|
+
end
|
95
|
+
|
96
|
+
private
|
97
|
+
def prepare!
|
98
|
+
prepare_views!
|
99
|
+
prepare_templates!
|
100
|
+
end
|
101
|
+
|
102
|
+
def prepare_views!
|
103
|
+
views.each do |view|
|
104
|
+
@registry.merge! view.format || DEFAULT_FORMAT => [ view, template_for(view) ]
|
105
|
+
end
|
106
|
+
end
|
107
|
+
|
108
|
+
def prepare_templates!
|
109
|
+
templates.each do |template|
|
110
|
+
@registry.merge! template.format => [ view_for(template), template ]
|
111
|
+
end
|
112
|
+
end
|
113
|
+
|
114
|
+
def views
|
115
|
+
@view.subclasses + [ @view ]
|
116
|
+
end
|
117
|
+
|
118
|
+
def view_for(template)
|
119
|
+
ViewFinder.new(@view).find(template)
|
120
|
+
end
|
121
|
+
|
122
|
+
def template_for(view)
|
123
|
+
templates.find {|template| template.format == view.format }
|
124
|
+
end
|
125
|
+
end
|
126
|
+
end
|
127
|
+
end
|
128
|
+
end
|
@@ -0,0 +1,88 @@
|
|
1
|
+
require 'hanami/utils/escape'
|
2
|
+
require 'hanami/view/rendering/layout_scope'
|
3
|
+
require 'hanami/view/rendering/template'
|
4
|
+
require 'hanami/view/rendering/partial'
|
5
|
+
|
6
|
+
module Hanami
|
7
|
+
module View
|
8
|
+
module Rendering
|
9
|
+
# Rendering scope
|
10
|
+
#
|
11
|
+
# @since 0.1.0
|
12
|
+
#
|
13
|
+
# @see Hanami::View::Rendering::LayoutScope
|
14
|
+
class Scope < LayoutScope
|
15
|
+
# Initialize the scope
|
16
|
+
#
|
17
|
+
# @param view [Class] the view
|
18
|
+
# @param locals [Hash] a set of objects available during the rendering
|
19
|
+
# @option locals [Symbol] :format the requested format
|
20
|
+
#
|
21
|
+
# @api private
|
22
|
+
# @since 0.1.0
|
23
|
+
def initialize(view, locals = {})
|
24
|
+
@view, @locals, @layout = view, locals, layout
|
25
|
+
end
|
26
|
+
|
27
|
+
# Returns an inspect String
|
28
|
+
#
|
29
|
+
# @return [String] inspect String (contains classname, objectid in hex, available ivars)
|
30
|
+
#
|
31
|
+
# @since 0.3.0
|
32
|
+
def inspect
|
33
|
+
base = "#<#{ self.class }: #{'%x' % (self.object_id << 1)}"
|
34
|
+
base << " @view=\"#{@view}\"" if @view
|
35
|
+
base << " @locals=\"#{@locals}\"" if @locals
|
36
|
+
base << ">"
|
37
|
+
end
|
38
|
+
|
39
|
+
# Returns the requested format.
|
40
|
+
#
|
41
|
+
# @return [Symbol] the requested format (eg. :html, :json, :xml, etc..)
|
42
|
+
#
|
43
|
+
# @since 0.1.0
|
44
|
+
def format
|
45
|
+
locals[:format]
|
46
|
+
end
|
47
|
+
|
48
|
+
# Implements "respond to" logic
|
49
|
+
#
|
50
|
+
# @return [TrueClass,FalseClass]
|
51
|
+
#
|
52
|
+
# @since 0.3.0
|
53
|
+
# @api private
|
54
|
+
#
|
55
|
+
# @see http://ruby-doc.org/core/Object.html#method-i-respond_to_missing-3F
|
56
|
+
def respond_to_missing?(m, include_all)
|
57
|
+
@view.respond_to?(m) ||
|
58
|
+
@locals.key?(m)
|
59
|
+
end
|
60
|
+
|
61
|
+
protected
|
62
|
+
def method_missing(m, *args, &block)
|
63
|
+
::Hanami::View::Escape.html(
|
64
|
+
if @view.respond_to?(m)
|
65
|
+
@view.__send__ m, *args, &block
|
66
|
+
elsif @locals.key?(m)
|
67
|
+
@locals[m]
|
68
|
+
else
|
69
|
+
super
|
70
|
+
end
|
71
|
+
)
|
72
|
+
end
|
73
|
+
|
74
|
+
private
|
75
|
+
|
76
|
+
# @since 0.4.2
|
77
|
+
# @api private
|
78
|
+
def layout
|
79
|
+
if @view.class.respond_to?(:layout)
|
80
|
+
@view.class.layout.new(self, "")
|
81
|
+
else
|
82
|
+
nil
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|
@@ -0,0 +1,67 @@
|
|
1
|
+
require 'hanami/view/rendering/template_finder'
|
2
|
+
|
3
|
+
module Hanami
|
4
|
+
module View
|
5
|
+
module Rendering
|
6
|
+
# Rendering template
|
7
|
+
#
|
8
|
+
# It's used when a template wants to render another template.
|
9
|
+
#
|
10
|
+
# @api private
|
11
|
+
# @since 0.1.0
|
12
|
+
#
|
13
|
+
# @see Hanami::View::Rendering::LayoutScope#render
|
14
|
+
#
|
15
|
+
# @example
|
16
|
+
# # We have an application template (templates/application.html.erb)
|
17
|
+
# # that uses the following line:
|
18
|
+
#
|
19
|
+
# <%= render template: 'articles/show' %>
|
20
|
+
class Template
|
21
|
+
# Initialize a template
|
22
|
+
#
|
23
|
+
# @param view [Hanami::View] the current view
|
24
|
+
# @param options [Hash] the rendering informations
|
25
|
+
# @option options [Symbol] :format the current format
|
26
|
+
# @option options [Hash] :locals the set of objects available within
|
27
|
+
# the rendering context
|
28
|
+
#
|
29
|
+
# @api private
|
30
|
+
# @since 0.1.0
|
31
|
+
def initialize(view, options)
|
32
|
+
@view, @options = view, options
|
33
|
+
end
|
34
|
+
|
35
|
+
# Render the template.
|
36
|
+
#
|
37
|
+
# @return [String] the output of the rendering process.
|
38
|
+
#
|
39
|
+
# @raise [Hanami::View::MissingTemplateError] if template can't be found
|
40
|
+
#
|
41
|
+
# @api private
|
42
|
+
# @since 0.1.0
|
43
|
+
def render
|
44
|
+
(template or raise_missing_template_error).render(scope)
|
45
|
+
end
|
46
|
+
|
47
|
+
protected
|
48
|
+
def template
|
49
|
+
TemplateFinder.new(@view.class, @options).find
|
50
|
+
end
|
51
|
+
|
52
|
+
def scope
|
53
|
+
Scope.new(@view, @options[:locals])
|
54
|
+
end
|
55
|
+
|
56
|
+
# @since 0.5.0
|
57
|
+
# @api private
|
58
|
+
def raise_missing_template_error
|
59
|
+
raise MissingTemplateError.new(
|
60
|
+
@options.fetch(:template) { @options.fetch(:partial, nil) },
|
61
|
+
@options[:format]
|
62
|
+
)
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
@@ -0,0 +1,53 @@
|
|
1
|
+
require 'hanami/view/rendering/templates_finder'
|
2
|
+
|
3
|
+
module Hanami
|
4
|
+
module View
|
5
|
+
module Rendering
|
6
|
+
# Find a template for the current view context.
|
7
|
+
# It's used when a template wants to render another template.
|
8
|
+
#
|
9
|
+
# @see Hanami::View::Rendering::Template
|
10
|
+
# @see Hanami::View::Rendering::TemplatesFinder
|
11
|
+
#
|
12
|
+
# @api private
|
13
|
+
# @since 0.1.0
|
14
|
+
class TemplateFinder < TemplatesFinder
|
15
|
+
# Initialize a finder
|
16
|
+
#
|
17
|
+
# @param view [Class] a view
|
18
|
+
# @param options [Hash] the informations about the context
|
19
|
+
# @option options [String] :template the template file name
|
20
|
+
# @option options [Symbol] :format the requested format
|
21
|
+
#
|
22
|
+
# @api private
|
23
|
+
# @since 0.1.0
|
24
|
+
def initialize(view, options)
|
25
|
+
super(view)
|
26
|
+
@options = options
|
27
|
+
end
|
28
|
+
|
29
|
+
# Find a template for the current view context
|
30
|
+
#
|
31
|
+
# @return [Hanami::View::Template] the requested template
|
32
|
+
#
|
33
|
+
# @api private
|
34
|
+
# @since 0.1.0
|
35
|
+
#
|
36
|
+
# @see Hanami::View::Rendering::TemplatesFinder#find
|
37
|
+
# @see Hanami::View::Rendering::Template#render
|
38
|
+
def find
|
39
|
+
super.first
|
40
|
+
end
|
41
|
+
|
42
|
+
protected
|
43
|
+
def template_name
|
44
|
+
@options[:template]
|
45
|
+
end
|
46
|
+
|
47
|
+
def format
|
48
|
+
@options[:format]
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
require 'hanami/utils/string'
|
2
|
+
|
3
|
+
module Hanami
|
4
|
+
module View
|
5
|
+
module Rendering
|
6
|
+
# @since 0.2.0
|
7
|
+
class TemplateName
|
8
|
+
NAMESPACE_SEPARATOR = '::'.freeze
|
9
|
+
|
10
|
+
def initialize(name, namespace)
|
11
|
+
@name = name
|
12
|
+
compile!(namespace)
|
13
|
+
end
|
14
|
+
|
15
|
+
def to_s
|
16
|
+
@name
|
17
|
+
end
|
18
|
+
|
19
|
+
private
|
20
|
+
def compile!(namespace)
|
21
|
+
tokens(namespace) {|token| replace!(token) }
|
22
|
+
@name = Utils::String.new(@name).underscore
|
23
|
+
end
|
24
|
+
|
25
|
+
def tokens(namespace)
|
26
|
+
namespace.to_s.split(NAMESPACE_SEPARATOR).each do |token|
|
27
|
+
yield token
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
def replace!(token)
|
32
|
+
@name.gsub!(%r{\A#{ token }#{ NAMESPACE_SEPARATOR }}, '')
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
@@ -0,0 +1,129 @@
|
|
1
|
+
require 'hanami/view/template'
|
2
|
+
|
3
|
+
module Hanami
|
4
|
+
module View
|
5
|
+
module Rendering
|
6
|
+
# Find templates for a view
|
7
|
+
#
|
8
|
+
# @api private
|
9
|
+
# @since 0.1.0
|
10
|
+
#
|
11
|
+
# @see View::Template
|
12
|
+
class TemplatesFinder
|
13
|
+
# Default format
|
14
|
+
#
|
15
|
+
# @api private
|
16
|
+
# @since 0.1.0
|
17
|
+
FORMAT = '*'.freeze
|
18
|
+
|
19
|
+
# Default template engines
|
20
|
+
#
|
21
|
+
# @api private
|
22
|
+
# @since 0.1.0
|
23
|
+
ENGINES = '*'.freeze
|
24
|
+
|
25
|
+
# Recursive pattern
|
26
|
+
#
|
27
|
+
# @api private
|
28
|
+
# @since 0.2.0
|
29
|
+
RECURSIVE = '**'.freeze
|
30
|
+
|
31
|
+
# Initialize a finder
|
32
|
+
#
|
33
|
+
# @param view [Class] the view
|
34
|
+
#
|
35
|
+
# @api private
|
36
|
+
# @since 0.1.0
|
37
|
+
def initialize(view)
|
38
|
+
@view = view
|
39
|
+
end
|
40
|
+
|
41
|
+
# Find all the associated templates to the view.
|
42
|
+
# It recursively looks for templates under the root path of the view,
|
43
|
+
# that are matching the template name
|
44
|
+
#
|
45
|
+
# @return [Array<Hanami::View::Template>] the templates
|
46
|
+
#
|
47
|
+
# @api private
|
48
|
+
# @since 0.1.0
|
49
|
+
#
|
50
|
+
# @see Hanami::View::Dsl#root
|
51
|
+
# @see Hanami::View::Dsl#template
|
52
|
+
#
|
53
|
+
# @example
|
54
|
+
# require 'hanami/view'
|
55
|
+
#
|
56
|
+
# module Articles
|
57
|
+
# class Show
|
58
|
+
# include Hanami::View
|
59
|
+
# end
|
60
|
+
# end
|
61
|
+
#
|
62
|
+
# Articles::Show.root # => "/path/to/templates"
|
63
|
+
# Articles::Show.template # => "articles/show"
|
64
|
+
#
|
65
|
+
# # This view has a template:
|
66
|
+
# #
|
67
|
+
# # "/path/to/templates/articles/show.html.erb"
|
68
|
+
#
|
69
|
+
# Hanami::View::Rendering::TemplatesFinder.new(Articles::Show).find
|
70
|
+
# # => [#<Hanami::View::Template:0x007f8a0a86a970 ... @file="/path/to/templates/articles/show.html.erb">]
|
71
|
+
def find
|
72
|
+
_find.map do |template|
|
73
|
+
View::Template.new(template, @view.configuration.default_encoding)
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
protected
|
78
|
+
|
79
|
+
# @api private
|
80
|
+
# @since 0.4.3
|
81
|
+
def _find(lookup = search_path)
|
82
|
+
Dir.glob( "#{ [root, lookup, template_name].join(separator) }.#{ format }.#{ engines }" )
|
83
|
+
end
|
84
|
+
|
85
|
+
# @api private
|
86
|
+
# @since 0.1.0
|
87
|
+
def template_name
|
88
|
+
@view.template
|
89
|
+
end
|
90
|
+
|
91
|
+
# @api private
|
92
|
+
# @since 0.1.0
|
93
|
+
def root
|
94
|
+
@view.root
|
95
|
+
end
|
96
|
+
|
97
|
+
# @api private
|
98
|
+
# @since 0.4.3
|
99
|
+
def search_path
|
100
|
+
recursive
|
101
|
+
end
|
102
|
+
|
103
|
+
# @api private
|
104
|
+
# @since 0.2.0
|
105
|
+
def recursive
|
106
|
+
RECURSIVE
|
107
|
+
end
|
108
|
+
|
109
|
+
# @api private
|
110
|
+
# @since 0.1.0
|
111
|
+
def separator
|
112
|
+
::File::SEPARATOR
|
113
|
+
end
|
114
|
+
|
115
|
+
# @api private
|
116
|
+
# @since 0.1.0
|
117
|
+
def format
|
118
|
+
FORMAT
|
119
|
+
end
|
120
|
+
|
121
|
+
# @api private
|
122
|
+
# @since 0.1.0
|
123
|
+
def engines
|
124
|
+
ENGINES
|
125
|
+
end
|
126
|
+
end
|
127
|
+
end
|
128
|
+
end
|
129
|
+
end
|