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.
@@ -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