hanami-view 0.0.0 → 0.6.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|