hanami-view 1.3.1 → 2.0.0.alpha3

Sign up to get free protection for your applications and to get access to all the features.
Files changed (61) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +11 -0
  3. data/LICENSE +20 -0
  4. data/README.md +17 -835
  5. data/hanami-view.gemspec +26 -16
  6. data/lib/hanami/view/application_configuration.rb +77 -0
  7. data/lib/hanami/view/application_context.rb +35 -0
  8. data/lib/hanami/view/application_view.rb +89 -0
  9. data/lib/hanami/view/context.rb +97 -0
  10. data/lib/hanami/view/context_helpers/content_helpers.rb +26 -0
  11. data/lib/hanami/view/decorated_attributes.rb +82 -0
  12. data/lib/hanami/view/errors.rb +31 -53
  13. data/lib/hanami/view/exposure.rb +126 -0
  14. data/lib/hanami/view/exposures.rb +74 -0
  15. data/lib/hanami/view/part.rb +217 -0
  16. data/lib/hanami/view/part_builder.rb +140 -0
  17. data/lib/hanami/view/path.rb +68 -0
  18. data/lib/hanami/view/render_environment.rb +62 -0
  19. data/lib/hanami/view/render_environment_missing.rb +44 -0
  20. data/lib/hanami/view/rendered.rb +55 -0
  21. data/lib/hanami/view/renderer.rb +79 -0
  22. data/lib/hanami/view/scope.rb +189 -0
  23. data/lib/hanami/view/scope_builder.rb +98 -0
  24. data/lib/hanami/view/standalone_view.rb +400 -0
  25. data/lib/hanami/view/tilt/erb.rb +26 -0
  26. data/lib/hanami/view/tilt/erbse.rb +21 -0
  27. data/lib/hanami/view/tilt/haml.rb +26 -0
  28. data/lib/hanami/view/tilt.rb +78 -0
  29. data/lib/hanami/view/version.rb +5 -5
  30. data/lib/hanami/view.rb +208 -223
  31. data/lib/hanami-view.rb +3 -1
  32. metadata +120 -70
  33. data/LICENSE.md +0 -22
  34. data/lib/hanami/layout.rb +0 -190
  35. data/lib/hanami/presenter.rb +0 -98
  36. data/lib/hanami/view/configuration.rb +0 -504
  37. data/lib/hanami/view/dsl.rb +0 -347
  38. data/lib/hanami/view/escape.rb +0 -225
  39. data/lib/hanami/view/inheritable.rb +0 -54
  40. data/lib/hanami/view/rendering/layout_finder.rb +0 -128
  41. data/lib/hanami/view/rendering/layout_registry.rb +0 -69
  42. data/lib/hanami/view/rendering/layout_scope.rb +0 -274
  43. data/lib/hanami/view/rendering/null_layout.rb +0 -52
  44. data/lib/hanami/view/rendering/null_local.rb +0 -82
  45. data/lib/hanami/view/rendering/null_template.rb +0 -83
  46. data/lib/hanami/view/rendering/null_view.rb +0 -26
  47. data/lib/hanami/view/rendering/options.rb +0 -24
  48. data/lib/hanami/view/rendering/partial.rb +0 -31
  49. data/lib/hanami/view/rendering/partial_file.rb +0 -29
  50. data/lib/hanami/view/rendering/partial_finder.rb +0 -75
  51. data/lib/hanami/view/rendering/partial_templates_finder.rb +0 -73
  52. data/lib/hanami/view/rendering/registry.rb +0 -134
  53. data/lib/hanami/view/rendering/scope.rb +0 -108
  54. data/lib/hanami/view/rendering/subscope.rb +0 -56
  55. data/lib/hanami/view/rendering/template.rb +0 -69
  56. data/lib/hanami/view/rendering/template_finder.rb +0 -55
  57. data/lib/hanami/view/rendering/template_name.rb +0 -50
  58. data/lib/hanami/view/rendering/templates_finder.rb +0 -144
  59. data/lib/hanami/view/rendering/view_finder.rb +0 -37
  60. data/lib/hanami/view/rendering.rb +0 -294
  61. data/lib/hanami/view/template.rb +0 -57
@@ -1,128 +0,0 @@
1
- require 'hanami/utils/string'
2
- require 'hanami/utils/class'
3
- require 'hanami/view/rendering/null_layout'
4
-
5
- module Hanami
6
- module View
7
- module Rendering
8
- # Defines the logic to find a layout
9
- #
10
- # @api private
11
- # @since 0.1.0
12
- #
13
- # @see Hanami::Layout
14
- class LayoutFinder
15
- # Layout class name suffix
16
- #
17
- # @api private
18
- # @since 0.1.0
19
- SUFFIX = 'Layout'.freeze
20
-
21
- # Find a layout from the given name.
22
- #
23
- # @param layout [Symbol,String,NilClass] layout name or nil if you want
24
- # to fallback to the framework defaults (see `Hanami::View.layout`).
25
- #
26
- # @param namespace [Class,Module] a Ruby namespace where to lookup
27
- #
28
- # @return [Hanami::Layout] the layout for the given name or
29
- # `Hanami::View.layout`
30
- #
31
- # @api private
32
- # @since 0.1.0
33
- #
34
- # @example With given name
35
- # require 'hanami/view'
36
- #
37
- # Hanami::View::Rendering::LayoutFinder.find(:article) # =>
38
- # ArticleLayout
39
- #
40
- # @example With a class
41
- # require 'hanami/view'
42
- #
43
- # Hanami::View::Rendering::LayoutFinder.find(ArticleLayout) # =>
44
- # ArticleLayout
45
- #
46
- # @example With namespace
47
- # require 'hanami/view'
48
- #
49
- # Hanami::View::Rendering::LayoutFinder.find(:application, CardDeck) # =>
50
- # CardDeck::ApplicationLayout
51
- #
52
- # @example With nil
53
- # require 'hanami/view'
54
- #
55
- # Hanami::View::Rendering::LayoutFinder.find(nil) # =>
56
- # Hanami::View::Rendering::NullLayout
57
- #
58
- # @example With unknown layout
59
- # require 'hanami/view'
60
- #
61
- # Hanami::View::Rendering::LayoutFinder.find(:unknown) # =>
62
- # Hanami::View::Rendering::NullLayout
63
- #
64
- def self.find(layout, namespace = Object)
65
- case layout
66
- when Symbol, String
67
- # TODO Move this low level logic into a Hanami::Utils solution
68
- class_name = "#{ Utils::String.classify(layout) }#{ SUFFIX }"
69
- namespace = Utils::Class.load!(namespace)
70
- namespace.const_get(class_name)
71
- when Class
72
- layout
73
- end || NullLayout
74
- end
75
-
76
- # Initialize the finder
77
- #
78
- # @param view [Class, #layout]
79
- #
80
- # @api private
81
- # @since 0.1.0
82
- def initialize(view)
83
- @view = view
84
- end
85
-
86
- # Find the layout for the view
87
- #
88
- # @return [Hanami::Layout] the layout associated to the view
89
- #
90
- # @see Hanami::View::Rendering::LayoutFinder.find
91
- # @see Hanami::View::Rendering::LayoutFinder#initialize
92
- #
93
- # @api private
94
- # @since 0.1.0
95
- #
96
- # @example With layout
97
- # require 'hanami/view'
98
- #
99
- # module Articles
100
- # class Show
101
- # include Hanami::View
102
- # layout :article
103
- # end
104
- # end
105
- #
106
- # Hanami::View::Rendering::LayoutFinder.new(Articles::Show) # =>
107
- # ArticleLayout
108
- #
109
- # @example Without layout
110
- # require 'hanami/view'
111
- #
112
- # module Dashboard
113
- # class Index
114
- # include Hanami::View
115
- # end
116
- # end
117
- #
118
- # Hanami::View.layout # => :application
119
- #
120
- # Hanami::View::Rendering::LayoutFinder.new(Dashboard::Index) # =>
121
- # ApplicationLayout
122
- def find
123
- self.class.find(@view.layout)
124
- end
125
- end
126
- end
127
- end
128
- end
@@ -1,69 +0,0 @@
1
- require 'hanami/view/rendering/null_template'
2
- require 'hanami/view/rendering/templates_finder'
3
-
4
- module Hanami
5
- module View
6
- module Rendering
7
- # Holds the references of all the registered layouts.
8
- # As now the registry is unique at the level of the framework.
9
- #
10
- # @api private
11
- # @since 0.1.0
12
- #
13
- # @see Hanami::Layout::ClassMethods#registry
14
- class LayoutRegistry
15
- # Initialize the registry
16
- #
17
- # @param view [Class] the view
18
- #
19
- # @api private
20
- # @since 0.1.0
21
- def initialize(view)
22
- @registry = {}
23
- @view = view
24
- prepare!
25
- end
26
-
27
- # Returns the layout for the given context.
28
- #
29
- # @param context [Hash] the rendering context
30
- # @option context [Symbol] :format the requested format
31
- #
32
- # @return [Hanami::Layout, Hanami::View::Rendering::NullTemplate]
33
- # the layout associated with the given context or a `NullTemplate` if
34
- # it can't be found.
35
- #
36
- # @raise [Hanami::View::MissingFormatError] if the given context doesn't
37
- # have the :format key
38
- #
39
- # @api private
40
- # @since 0.1.0
41
- def resolve(context)
42
- @registry.fetch(format(context)) { NullTemplate.new }
43
- end
44
-
45
- protected
46
- # @api private
47
- # @since 0.1.0
48
- def prepare!
49
- templates.each do |template|
50
- @registry.merge! template.format => template
51
- end
52
- @registry.any? or raise MissingTemplateLayoutError.new(@view)
53
- end
54
-
55
- # @api private
56
- # @since 0.1.0
57
- def templates
58
- TemplatesFinder.new(@view).find
59
- end
60
-
61
- # @api private
62
- # @since 0.1.0
63
- def format(context)
64
- context.fetch(:format) { raise MissingFormatError }
65
- end
66
- end
67
- end
68
- end
69
- end
@@ -1,274 +0,0 @@
1
- require 'hanami/view/rendering/null_local'
2
- require 'hanami/view/rendering/options'
3
- require 'hanami/utils/escape'
4
-
5
- module Hanami
6
- module View
7
- module Rendering
8
- # List of render types that exactly one of must be included when calling `#render`.
9
- # For example, when calling `<%= render something: 'my_thing', locals: {} %>`,
10
- # 'something' must be one of the values listed here.
11
- #
12
- # @since 1.1.0
13
- # @api private
14
- KNOWN_RENDER_TYPES = [:partial, :template].freeze
15
-
16
- # Scope for layout rendering
17
- #
18
- # @since 0.1.0
19
- class LayoutScope < BasicObject
20
- # Initialize the scope
21
- #
22
- # @param layout [Hanami::Layout] the layout to render
23
- # @param scope [Hanami::View::Rendering::Scope] the scope of the current
24
- # view
25
- #
26
- # @api private
27
- # @since 0.1.0
28
- def initialize(layout, scope)
29
- @layout = layout
30
- @scope = scope
31
- @view = nil
32
- @locals = nil
33
- end
34
-
35
- # Returns the classname as string
36
- #
37
- # @return classname
38
- #
39
- # @since 0.3.0
40
- def class
41
- (class << self; self end).superclass
42
- end
43
-
44
- # Returns an inspect String
45
- #
46
- # @return [String] inspect String (contains classname, objectid in hex, available ivars)
47
- #
48
- # @since 0.3.0
49
- def inspect
50
- base = "#<#{ self.class }:#{'%x' % (self.object_id << 1)}"
51
- base << " @layout=\"#{@layout.inspect}\"" if @layout
52
- base << " @scope=\"#{@scope.inspect}\"" if @scope
53
- base << ">"
54
- end
55
-
56
- # Render a partial or a template within a layout template.
57
- #
58
- # @param options [Hash]
59
- # @option options [String] :partial the partial template to render
60
- # @option options [String] :template the template to render
61
- #
62
- # @return [String] the output of the rendering process
63
- #
64
- # @raise [Hanami::Error::UnknownRenderTypeError] if the given type to
65
- # be rendered is unknown
66
- #
67
- # @since 0.1.0
68
- #
69
- # @example Rendering partial
70
- # # Given a partial under:
71
- # # templates/shared/_sidebar.html.erb
72
- # #
73
- # # In the layout template:
74
- # # templates/application.html.erb
75
- # #
76
- # # Use like this:
77
- # <%= render partial: 'shared/sidebar' %>
78
- #
79
- # @example Rendering template
80
- # # Given a template under:
81
- # # templates/articles/index.html.erb
82
- # #
83
- # # In the layout template:
84
- # # templates/application.html.erb
85
- # #
86
- # # Use like this:
87
- # <%= render template: 'articles/index' %>
88
- #
89
- # @example Rendering partial, using optional :locals
90
- # # Given a partial under:
91
- # # templates/shared/_sidebar.html.erb
92
- # #
93
- # # In the layout template:
94
- # # templates/application.html.erb
95
- # #
96
- # # Use like this:
97
- # <%= render partial: 'shared/sidebar', { user: current_user } %>
98
- #
99
- # #
100
- # # `user` will be available in the scope of the sidebar rendering
101
- def render(options)
102
- renderer(options).render
103
- end
104
-
105
- # Returns the requested format.
106
- #
107
- # @return [Symbol] the requested format (eg. :html, :json, :xml, etc..)
108
- #
109
- # @since 0.1.0
110
- def format
111
- @scope.format
112
- end
113
-
114
- # The current view.
115
- #
116
- # @return [Hanami::View] the current view
117
- #
118
- # @since 0.1.0
119
- def view
120
- @view || @scope.view
121
- end
122
-
123
- # The current locals.
124
- #
125
- # @return [Hash] the current locals
126
- #
127
- # @since 0.1.0
128
- def locals
129
- (@locals || @scope.locals).dup
130
- end
131
-
132
- # It tries to invoke a method for the view or a local for the given key.
133
- # If the lookup fails, it returns a null object.
134
- #
135
- # @return [Object,Hanami::View::Rendering::NullLocal] the returning value
136
- #
137
- # @since 0.7.0
138
- #
139
- # @example Safe method navigation
140
- # <% if local(:plan).overdue? %>
141
- # <h2>Your plan is overdue.</h2>
142
- # <% end %>
143
- #
144
- # @example Optional Contents
145
- # # Given the following layout template
146
- #
147
- # <!doctype HTML>
148
- # <html>
149
- # <!-- ... -->
150
- # <body>
151
- # <!-- ... -->
152
- # <%= local :footer %>
153
- # </body>
154
- # </html>
155
- #
156
- # # Case 1:
157
- # # Products::Index doesn't respond to #footer, local will return nil
158
- # #
159
- # # Case 2:
160
- # # Products::Show responds to #footer, local will send back
161
- # # #footer returning value
162
- #
163
- # module Products
164
- # class Index
165
- # include Hanami::View
166
- # end
167
- #
168
- # class Show
169
- # include Hanami::View
170
- #
171
- # def footer
172
- # "contents for footer"
173
- # end
174
- # end
175
- # end
176
- def local(key)
177
- if respond_to?(key)
178
- __send__(key)
179
- else
180
- locals.fetch(key) { NullLocal.new(key) }
181
- end
182
- end
183
-
184
- # Implements "respond to" logic
185
- #
186
- # @return [TrueClass,FalseClass]
187
- #
188
- # @since 0.3.0
189
- # @api private
190
- #
191
- # @see http://ruby-doc.org/core/Object.html#method-i-respond_to-3F
192
- def respond_to?(m, include_all = false)
193
- respond_to_missing?(m, include_all)
194
- end
195
-
196
- # Implements "respond to" logic
197
- #
198
- # @return [TrueClass,FalseClass]
199
- #
200
- # @since 0.3.0
201
- # @api private
202
- #
203
- # @see http://ruby-doc.org/core/Object.html#method-i-respond_to_missing-3F
204
- def respond_to_missing?(m, include_all)
205
- @layout.respond_to?(m, include_all) ||
206
- @scope.respond_to?(m, include_all)
207
- end
208
-
209
- protected
210
-
211
- # Forward all the missing methods to the view scope or to the layout.
212
- #
213
- # @api private
214
- # @since 0.1.0
215
- #
216
- # @see Hanami::View::Rendering::Scope
217
- # @see Hanami::Layout
218
- #
219
- # @example
220
- # # In the layout template:
221
- # # templates/application.html.erb
222
- # #
223
- # # Use like this:
224
- # <title><%= article.title %></title>
225
- #
226
- # # `article` will be looked up in the view scope first.
227
- # # If not found, it will be searched within the layout.
228
- def method_missing(m, *args, &blk)
229
- # FIXME: this isn't compatible with Hanami 2.0, as it extends a view
230
- # that we want to be frozen in the future
231
- #
232
- # See https://github.com/hanami/view/issues/130#issuecomment-319326236
233
- if @scope.respond_to?(m, true) && @scope.locals.has_key?(m) && layout.respond_to?(m, true)
234
- layout.__send__(m, *args, &blk)
235
- elsif @scope.respond_to?(m, true)
236
- @scope.__send__(m, *args, &blk)
237
- elsif layout.respond_to?(m, true)
238
- layout.__send__(m, *args, &blk)
239
- else
240
- ::Hanami::View::Escape.html(super)
241
- end
242
- end
243
-
244
- # @api private
245
- def renderer(options)
246
- if options[:partial]
247
- Rendering::Partial
248
- elsif options[:template]
249
- Rendering::Template
250
- else
251
- ::Kernel.raise UnknownRenderTypeError.new(KNOWN_RENDER_TYPES, options)
252
- end.new(view, _options(options))
253
- end
254
-
255
- private
256
-
257
- # @api private
258
- def _options(options)
259
- current_locals = locals.reject do |key, _|
260
- @scope.respond_to?(key, true) &&
261
- (layout.respond_to?(key, true) || @scope.view.respond_to?(:name, true))
262
- end
263
- Options.build(options, current_locals, format)
264
- end
265
-
266
- # @since 0.4.2
267
- # @api private
268
- def layout
269
- @layout || @layout.class.layout.new(@scope, "")
270
- end
271
- end
272
- end
273
- end
274
- end
@@ -1,52 +0,0 @@
1
- module Hanami
2
- module View
3
- module Rendering
4
- # Null Object pattern for Layout.
5
- # It's used when a view doesn't require a layout.
6
- #
7
- # @api private
8
- # @since 0.1.0
9
- #
10
- # @example
11
- # require 'hanami/view'
12
- #
13
- # module Articles
14
- # class Show
15
- # include Hanami::View
16
- # layout false
17
- # end
18
- # end
19
- #
20
- # # In this scenario we will use a `NullLayout`.
21
- class NullLayout
22
-
23
- # Initialize a layout
24
- #
25
- # @param scope [Hanami::View::Rendering::Scope] view rendering scope
26
- # @param rendered [String] the output of the view rendering process
27
- #
28
- # @api private
29
- # @since 0.1.0
30
- #
31
- # @see Hanami::Layout#initialize
32
- # @see Hanami::View::Rendering#render
33
- def initialize(scope, rendered)
34
- @rendered = rendered
35
- end
36
-
37
- # Render the layout
38
- #
39
- # @return [String] the output of the rendering process
40
- #
41
- # @api private
42
- # @since 0.1.0
43
- #
44
- # @see Hanami::Layout#render
45
- # @see Hanami::View::Rendering#render
46
- def render
47
- @rendered
48
- end
49
- end
50
- end
51
- end
52
- end
@@ -1,82 +0,0 @@
1
- require 'hanami/utils/basic_object'
2
-
3
- module Hanami
4
- module View
5
- module Rendering
6
- # Null local
7
- #
8
- # @since 0.7.0
9
- #
10
- # @see Hanami::View::Rendering#local
11
- class NullLocal < Utils::BasicObject
12
- # @since 0.7.0
13
- # @api private
14
- TO_STR = "".freeze
15
-
16
- # @since 0.7.0
17
- # @api private
18
- def initialize(local)
19
- @local = local
20
- end
21
-
22
- # @since 0.7.0
23
- # @api private
24
- def all?
25
- false
26
- end
27
-
28
- # @since 0.7.0
29
- # @api private
30
- def any?
31
- false
32
- end
33
-
34
- # @since 0.7.0
35
- # @api private
36
- def empty?
37
- true
38
- end
39
-
40
- # @since 0.7.0
41
- # @api private
42
- def nil?
43
- true
44
- end
45
-
46
- # @since 0.7.0
47
- # @api private
48
- def to_str
49
- TO_STR
50
- end
51
-
52
- # @since 0.8.0
53
- # @api private
54
- alias to_s to_str
55
-
56
- # @since 0.7.0
57
- # @api private
58
- def method_missing(m, *)
59
- if m.match(/\?\z/)
60
- false
61
- else
62
- self.class.new("#{ @local }.#{ m }")
63
- end
64
- end
65
-
66
- private
67
-
68
- # @since 0.7.0
69
- # @api private
70
- def respond_to_missing?(method_name, include_all)
71
- true
72
- end
73
-
74
- # @since 0.7.0
75
- # @api private
76
- def __inspect
77
- " :#{ @local }"
78
- end
79
- end
80
- end
81
- end
82
- end
@@ -1,83 +0,0 @@
1
- module Hanami
2
- module View
3
- module Rendering
4
- # Null Object pattern for layout template
5
- #
6
- # It's used when a layout doesn't have an associated template.
7
- #
8
- # A common scenario is for non-html requests.
9
- # Usually we have a template for the application layout
10
- # (eg `templates/application.html.erb`), but we don't use to have the
11
- # template for JSON requests (eg `templates/application.json.erb`).
12
- # Because most of the times, we only return the output of the view.
13
- #
14
- # @api private
15
- # @since 0.1.0
16
- #
17
- # @example
18
- # require 'hanami/view'
19
- #
20
- # # We have an ApplicationLayout (views/application_layout.rb):
21
- # class ApplicationLayout
22
- # include Hanami::Layout
23
- # end
24
- #
25
- # # Our layout has a template for HTML requests, located at:
26
- # # templates/application.html.erb
27
- #
28
- # # We set it as global layout
29
- # Hanami::View.layout = :application
30
- #
31
- # # We have two views for HTML and JSON articles.
32
- # # They have a template each:
33
- # #
34
- # # * templates/articles/show.html.erb
35
- # # * templates/articles/show.json.erb
36
- # module Articles
37
- # class Show
38
- # include Hanami::View
39
- # format :html
40
- # end
41
- #
42
- # class JsonShow < Show
43
- # format :json
44
- # end
45
- # end
46
- #
47
- # # We initialize the framework
48
- # Hanami::View.load!
49
- #
50
- #
51
- #
52
- # # When we ask for a HTML rendering, it will use `Articles::Show` and
53
- # # ApplicationLayout. The output will be a composition of:
54
- # #
55
- # # * templates/articles/show.html.erb
56
- # # * templates/application.html.erb
57
- #
58
- # # When we ask for a JSON rendering, it will use `Articles::JsonShow`
59
- # # and ApplicationLayout. Since, the layout doesn't have any associated
60
- # # template for JSON, the output will be a composition of:
61
- # #
62
- # # * templates/articles/show.json.erb
63
- class NullTemplate
64
- # Render the layout template
65
- #
66
- # @param scope [Hanami::View::Scope] the rendering scope
67
- # @param locals [Hash] a set of objects available during the rendering
68
- # @yield [Proc] yields the given block
69
- #
70
- # @return [String] the output of the rendering process
71
- #
72
- # @api private
73
- # @since 0.1.0
74
- #
75
- # @see Hanami::Layout#render
76
- # @see Hanami::View::Rendering#render
77
- def render(scope, locals = {})
78
- yield
79
- end
80
- end
81
- end
82
- end
83
- end
@@ -1,26 +0,0 @@
1
- module Hanami
2
- module View
3
- module Rendering
4
- # Null Object pattern for view
5
- #
6
- # It's used when a layout is rendered direcly for testing purposes
7
- #
8
- # @api private
9
- # @since 1.2.1
10
- class NullView
11
- # Render the layout template
12
- #
13
- # @return [String] an empty string
14
- #
15
- # @api private
16
- # @since 1.2.1
17
- #
18
- # @see Hanami::Layout#render
19
- # @see Hanami::View::Rendering#render
20
- def render
21
- ""
22
- end
23
- end
24
- end
25
- end
26
- end