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