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,464 @@
|
|
1
|
+
require 'set'
|
2
|
+
require 'hanami/utils/class'
|
3
|
+
require 'hanami/utils/kernel'
|
4
|
+
require 'hanami/utils/string'
|
5
|
+
require 'hanami/utils/load_paths'
|
6
|
+
require 'hanami/view/rendering/layout_finder'
|
7
|
+
|
8
|
+
module Hanami
|
9
|
+
module View
|
10
|
+
# Configuration for the framework, controllers and actions.
|
11
|
+
#
|
12
|
+
# Hanami::Controller has its own global configuration that can be manipulated
|
13
|
+
# via `Hanami::View.configure`.
|
14
|
+
#
|
15
|
+
# Every time that `Hanami::View` and `Hanami::Layout` are included, that
|
16
|
+
# global configuration is being copied to the recipient. The copy will
|
17
|
+
# inherit all the settings from the original, but all the subsequent changes
|
18
|
+
# aren't reflected from the parent to the children, and viceversa.
|
19
|
+
#
|
20
|
+
# This architecture allows to have a global configuration that capture the
|
21
|
+
# most common cases for an application, and let views and layouts
|
22
|
+
# layouts to specify exceptions.
|
23
|
+
#
|
24
|
+
# @since 0.2.0
|
25
|
+
class Configuration
|
26
|
+
# Default root
|
27
|
+
#
|
28
|
+
# @since 0.2.0
|
29
|
+
# @api private
|
30
|
+
DEFAULT_ROOT = '.'.freeze
|
31
|
+
|
32
|
+
# Default encoding
|
33
|
+
#
|
34
|
+
# @since 0.5.0
|
35
|
+
# @api private
|
36
|
+
DEFAULT_ENCODING = Encoding::UTF_8
|
37
|
+
|
38
|
+
attr_reader :load_paths
|
39
|
+
attr_reader :views
|
40
|
+
attr_reader :layouts
|
41
|
+
attr_reader :modules
|
42
|
+
|
43
|
+
# Return the original configuration of the framework instance associated
|
44
|
+
# with the given class.
|
45
|
+
#
|
46
|
+
# When multiple instances of Hanami::View are used in the same application,
|
47
|
+
# we want to make sure that a controller or an action will receive the
|
48
|
+
# expected configuration.
|
49
|
+
#
|
50
|
+
# @param base [Class] a view or a layout
|
51
|
+
#
|
52
|
+
# @return [Hanami::Controller::Configuration] the configuration associated
|
53
|
+
# to the given class.
|
54
|
+
#
|
55
|
+
# @since 0.2.0
|
56
|
+
# @api private
|
57
|
+
#
|
58
|
+
# @example Direct usage of the framework
|
59
|
+
# require 'hanami/view'
|
60
|
+
#
|
61
|
+
# class Show
|
62
|
+
# include Hanami::View
|
63
|
+
# end
|
64
|
+
#
|
65
|
+
# Hanami::View::Configuration.for(Show)
|
66
|
+
# # => will return from Hanami::View
|
67
|
+
#
|
68
|
+
# @example Multiple instances of the framework
|
69
|
+
# require 'hanami/view'
|
70
|
+
#
|
71
|
+
# module MyApp
|
72
|
+
# View = Hanami::View.duplicate(self)
|
73
|
+
#
|
74
|
+
# module Views::Dashboard
|
75
|
+
# class Index
|
76
|
+
# include MyApp::View
|
77
|
+
# end
|
78
|
+
# end
|
79
|
+
# end
|
80
|
+
#
|
81
|
+
# class Show
|
82
|
+
# include Hanami::Action
|
83
|
+
# end
|
84
|
+
#
|
85
|
+
# Hanami::View::Configuration.for(Show)
|
86
|
+
# # => will return from Hanami::View
|
87
|
+
#
|
88
|
+
# Hanami::View::Configuration.for(MyApp::Views::Dashboard::Index)
|
89
|
+
# # => will return from MyApp::View
|
90
|
+
def self.for(base)
|
91
|
+
# TODO this implementation is similar to Hanami::Controller::Configuration consider to extract it into Hanami::Utils
|
92
|
+
namespace = Utils::String.new(base).namespace
|
93
|
+
framework = Utils::Class.load_from_pattern!("(#{namespace}|Hanami)::View")
|
94
|
+
framework.configuration
|
95
|
+
end
|
96
|
+
|
97
|
+
# Initialize a configuration instance
|
98
|
+
#
|
99
|
+
# @return [Hanami::View::Configuration] a new configuration's instance
|
100
|
+
#
|
101
|
+
# @since 0.2.0
|
102
|
+
def initialize
|
103
|
+
@namespace = Object
|
104
|
+
reset!
|
105
|
+
end
|
106
|
+
|
107
|
+
# Set the Ruby namespace where to lookup for views.
|
108
|
+
#
|
109
|
+
# When multiple instances of the framework are used, we want to make sure
|
110
|
+
# that if a `MyApp` wants a `Dashboard::Index` view, we are loading the
|
111
|
+
# right one.
|
112
|
+
#
|
113
|
+
# If not set, this value defaults to `Object`.
|
114
|
+
#
|
115
|
+
# This is part of a DSL, for this reason when this method is called with
|
116
|
+
# an argument, it will set the corresponding instance variable. When
|
117
|
+
# called without, it will return the already set value, or the default.
|
118
|
+
#
|
119
|
+
# @overload namespace(value)
|
120
|
+
# Sets the given value
|
121
|
+
# @param value [Class, Module, String] a valid Ruby namespace identifier
|
122
|
+
#
|
123
|
+
# @overload namespace
|
124
|
+
# Gets the value
|
125
|
+
# @return [Class, Module, String]
|
126
|
+
#
|
127
|
+
# @since 0.2.0
|
128
|
+
#
|
129
|
+
# @example Getting the value
|
130
|
+
# require 'hanami/view'
|
131
|
+
#
|
132
|
+
# Hanami::View.configuration.namespace # => Object
|
133
|
+
#
|
134
|
+
# @example Setting the value
|
135
|
+
# require 'hanami/view'
|
136
|
+
#
|
137
|
+
# Hanami::View.configure do
|
138
|
+
# namespace 'MyApp::Views'
|
139
|
+
# end
|
140
|
+
def namespace(value = nil)
|
141
|
+
if value
|
142
|
+
@namespace = value
|
143
|
+
else
|
144
|
+
@namespace
|
145
|
+
end
|
146
|
+
end
|
147
|
+
|
148
|
+
# Set the root path where to search for templates
|
149
|
+
#
|
150
|
+
# If not set, this value defaults to the current directory.
|
151
|
+
#
|
152
|
+
# This is part of a DSL, for this reason when this method is called with
|
153
|
+
# an argument, it will set the corresponding instance variable. When
|
154
|
+
# called without, it will return the already set value, or the default.
|
155
|
+
#
|
156
|
+
# @overload root(value)
|
157
|
+
# Sets the given value
|
158
|
+
# @param value [String,Pathname,#to_pathname] an object that can be
|
159
|
+
# coerced to Pathname
|
160
|
+
# @raise [Errno::ENOENT] if the given path doesn't exist
|
161
|
+
#
|
162
|
+
# @overload root
|
163
|
+
# Gets the value
|
164
|
+
# @return [Pathname]
|
165
|
+
#
|
166
|
+
# @since 0.2.0
|
167
|
+
#
|
168
|
+
# @see Hanami::View::Dsl#root
|
169
|
+
# @see http://www.ruby-doc.org/stdlib-2.1.2/libdoc/pathname/rdoc/Pathname.html
|
170
|
+
# @see http://rdoc.info/gems/hanami-utils/Hanami/Utils/Kernel#Pathname-class_method
|
171
|
+
#
|
172
|
+
# @example Getting the value
|
173
|
+
# require 'hanami/view'
|
174
|
+
#
|
175
|
+
# Hanami::View.configuration.root # => #<Pathname:.>
|
176
|
+
#
|
177
|
+
# @example Setting the value
|
178
|
+
# require 'hanami/view'
|
179
|
+
#
|
180
|
+
# Hanami::View.configure do
|
181
|
+
# root '/path/to/templates'
|
182
|
+
# end
|
183
|
+
#
|
184
|
+
# Hanami::View.configuration.root # => #<Pathname:/path/to/templates>
|
185
|
+
def root(value = nil)
|
186
|
+
if value
|
187
|
+
@root = Utils::Kernel.Pathname(value).realpath
|
188
|
+
else
|
189
|
+
@root
|
190
|
+
end
|
191
|
+
end
|
192
|
+
|
193
|
+
# Set the global layout
|
194
|
+
#
|
195
|
+
# If not set, this value defaults to `nil`, while at the rendering time
|
196
|
+
# it will use `Hanami::View::Rendering::NullLayout`.
|
197
|
+
#
|
198
|
+
# This is part of a DSL, for this reason when this method is called with
|
199
|
+
# an argument, it will set the corresponding instance variable. When
|
200
|
+
# called without, it will return the already set value, or the default.
|
201
|
+
#
|
202
|
+
# @overload layout(value)
|
203
|
+
# Sets the given value
|
204
|
+
# @param value [Symbol] the name of the layout
|
205
|
+
#
|
206
|
+
# @overload layout
|
207
|
+
# Gets the value
|
208
|
+
# @return [Class]
|
209
|
+
#
|
210
|
+
# @since 0.2.0
|
211
|
+
#
|
212
|
+
# @see Hanami::View::Dsl#layout
|
213
|
+
#
|
214
|
+
# @example Getting the value
|
215
|
+
# require 'hanami/view'
|
216
|
+
#
|
217
|
+
# Hanami::View.configuration.layout # => nil
|
218
|
+
#
|
219
|
+
# @example Setting the value
|
220
|
+
# require 'hanami/view'
|
221
|
+
#
|
222
|
+
# Hanami::View.configure do
|
223
|
+
# layout :application
|
224
|
+
# end
|
225
|
+
#
|
226
|
+
# Hanami::View.configuration.layout # => ApplicationLayout
|
227
|
+
#
|
228
|
+
# @example Setting the value in a namespaced app
|
229
|
+
# require 'hanami/view'
|
230
|
+
#
|
231
|
+
# module MyApp
|
232
|
+
# View = Hanami::View.duplicate(self) do
|
233
|
+
# layout :application
|
234
|
+
# end
|
235
|
+
# end
|
236
|
+
#
|
237
|
+
# MyApp::View.configuration.layout # => MyApp::ApplicationLayout
|
238
|
+
def layout(value = nil)
|
239
|
+
if value.nil?
|
240
|
+
Rendering::LayoutFinder.find(@layout, @namespace)
|
241
|
+
else
|
242
|
+
@layout = value
|
243
|
+
end
|
244
|
+
end
|
245
|
+
|
246
|
+
# Default encoding for templates
|
247
|
+
#
|
248
|
+
# This is part of a DSL, for this reason when this method is called with
|
249
|
+
# an argument, it will set the corresponding instance variable. When
|
250
|
+
# called without, it will return the already set value, or the default.
|
251
|
+
#
|
252
|
+
# @overload default_encoding(value)
|
253
|
+
# Sets the given value
|
254
|
+
# @param value [String,Encoding] a string representation of the encoding,
|
255
|
+
# or an Encoding constant
|
256
|
+
#
|
257
|
+
# @raise [ArgumentError] if the given value isn't a supported encoding
|
258
|
+
#
|
259
|
+
# @overload default_encoding
|
260
|
+
# Gets the value
|
261
|
+
# @return [Encoding]
|
262
|
+
#
|
263
|
+
# @since 0.5.0
|
264
|
+
#
|
265
|
+
# @example Set UTF-8 As A String
|
266
|
+
# require 'hanami/view'
|
267
|
+
#
|
268
|
+
# Hanami::View.configure do
|
269
|
+
# default_encoding 'utf-8'
|
270
|
+
# end
|
271
|
+
#
|
272
|
+
# @example Set UTF-8 As An Encoding Constant
|
273
|
+
# require 'hanami/view'
|
274
|
+
#
|
275
|
+
# Hanami::View.configure do
|
276
|
+
# default_encoding Encoding::UTF_8
|
277
|
+
# end
|
278
|
+
#
|
279
|
+
# @example Raise An Error For Unknown Encoding
|
280
|
+
# require 'hanami/view'
|
281
|
+
#
|
282
|
+
# Hanami::View.configure do
|
283
|
+
# default_encoding 'foo'
|
284
|
+
# end
|
285
|
+
#
|
286
|
+
# # => ArgumentError
|
287
|
+
def default_encoding(value = nil)
|
288
|
+
if value.nil?
|
289
|
+
@default_encoding
|
290
|
+
else
|
291
|
+
@default_encoding = Encoding.find(value)
|
292
|
+
end
|
293
|
+
end
|
294
|
+
|
295
|
+
# Prepare the views.
|
296
|
+
#
|
297
|
+
# The given block will be yielded when `Hanami::View` will be included by
|
298
|
+
# a view.
|
299
|
+
#
|
300
|
+
# This method can be called multiple times.
|
301
|
+
#
|
302
|
+
# @param blk [Proc] the code block
|
303
|
+
#
|
304
|
+
# @return [void]
|
305
|
+
#
|
306
|
+
# @raise [ArgumentError] if called without passing a block
|
307
|
+
#
|
308
|
+
# @since 0.3.0
|
309
|
+
#
|
310
|
+
# @see Hanami::View.configure
|
311
|
+
# @see Hanami::View.duplicate
|
312
|
+
#
|
313
|
+
# @example Including shared utilities
|
314
|
+
# require 'hanami/view'
|
315
|
+
#
|
316
|
+
# module UrlHelpers
|
317
|
+
# def comments_path
|
318
|
+
# '/'
|
319
|
+
# end
|
320
|
+
# end
|
321
|
+
#
|
322
|
+
# Hanami::View.configure do
|
323
|
+
# prepare do
|
324
|
+
# include UrlHelpers
|
325
|
+
# end
|
326
|
+
# end
|
327
|
+
#
|
328
|
+
# Hanami::View.load!
|
329
|
+
#
|
330
|
+
# module Comments
|
331
|
+
# class New
|
332
|
+
# # The following include will cause UrlHelpers to be included too.
|
333
|
+
# # This makes `comments_path` available in the view context
|
334
|
+
# include Hanami::View
|
335
|
+
#
|
336
|
+
# def form
|
337
|
+
# %(<form action="#{ comments_path }" method="POST"></form>)
|
338
|
+
# end
|
339
|
+
# end
|
340
|
+
# end
|
341
|
+
#
|
342
|
+
# @example Preparing multiple times
|
343
|
+
# require 'hanami/view'
|
344
|
+
#
|
345
|
+
# Hanami::View.configure do
|
346
|
+
# prepare do
|
347
|
+
# include UrlHelpers
|
348
|
+
# end
|
349
|
+
#
|
350
|
+
# prepare do
|
351
|
+
# format :json
|
352
|
+
# end
|
353
|
+
# end
|
354
|
+
#
|
355
|
+
# Hanami::View.configure do
|
356
|
+
# prepare do
|
357
|
+
# include FormattingHelpers
|
358
|
+
# end
|
359
|
+
# end
|
360
|
+
#
|
361
|
+
# Hanami::View.load!
|
362
|
+
#
|
363
|
+
# module Articles
|
364
|
+
# class Index
|
365
|
+
# # The following include will cause the inclusion of:
|
366
|
+
# # * UrlHelpers
|
367
|
+
# # * FormattingHelpers
|
368
|
+
# #
|
369
|
+
# # It also sets the view to render only JSON
|
370
|
+
# include Hanami::View
|
371
|
+
# end
|
372
|
+
# end
|
373
|
+
def prepare(&blk)
|
374
|
+
if block_given?
|
375
|
+
@modules.push(blk)
|
376
|
+
else
|
377
|
+
raise ArgumentError.new('Please provide a block')
|
378
|
+
end
|
379
|
+
end
|
380
|
+
|
381
|
+
# Add a view to the registry
|
382
|
+
#
|
383
|
+
# @since 0.2.0
|
384
|
+
# @api private
|
385
|
+
def add_view(view)
|
386
|
+
@views.add(view)
|
387
|
+
end
|
388
|
+
|
389
|
+
# Add a layout to the registry
|
390
|
+
#
|
391
|
+
# @since 0.2.0
|
392
|
+
# @api private
|
393
|
+
def add_layout(layout)
|
394
|
+
@layouts.add(layout)
|
395
|
+
end
|
396
|
+
|
397
|
+
# Duplicate by copying the settings in a new instance.
|
398
|
+
#
|
399
|
+
# @return [Hanami::View::Configuration] a copy of the configuration
|
400
|
+
#
|
401
|
+
# @since 0.2.0
|
402
|
+
# @api private
|
403
|
+
def duplicate
|
404
|
+
Configuration.new.tap do |c|
|
405
|
+
c.namespace = namespace
|
406
|
+
c.root = root
|
407
|
+
c.layout = @layout # lazy loading of the class
|
408
|
+
c.default_encoding = default_encoding
|
409
|
+
c.load_paths = load_paths.dup
|
410
|
+
c.modules = modules.dup
|
411
|
+
end
|
412
|
+
end
|
413
|
+
|
414
|
+
# Load the configuration for the current framework
|
415
|
+
#
|
416
|
+
# @since 0.2.0
|
417
|
+
# @api private
|
418
|
+
def load!
|
419
|
+
views.each { |v| v.__send__(:load!) }
|
420
|
+
layouts.each { |l| l.__send__(:load!) }
|
421
|
+
freeze
|
422
|
+
end
|
423
|
+
|
424
|
+
# Reset all the values to the defaults
|
425
|
+
#
|
426
|
+
# @since 0.2.0
|
427
|
+
# @api private
|
428
|
+
def reset!
|
429
|
+
root DEFAULT_ROOT
|
430
|
+
default_encoding DEFAULT_ENCODING
|
431
|
+
|
432
|
+
@views = Set.new
|
433
|
+
@layouts = Set.new
|
434
|
+
@load_paths = Utils::LoadPaths.new(root)
|
435
|
+
@layout = nil
|
436
|
+
@modules = []
|
437
|
+
end
|
438
|
+
|
439
|
+
# Copy the configuration for the given action
|
440
|
+
#
|
441
|
+
# @param base [Class] the target action
|
442
|
+
#
|
443
|
+
# @return void
|
444
|
+
#
|
445
|
+
# @since 0.3.0
|
446
|
+
# @api private
|
447
|
+
def copy!(base)
|
448
|
+
modules.each do |mod|
|
449
|
+
base.class_eval(&mod)
|
450
|
+
end
|
451
|
+
end
|
452
|
+
|
453
|
+
alias_method :unload!, :reset!
|
454
|
+
|
455
|
+
protected
|
456
|
+
attr_writer :namespace
|
457
|
+
attr_writer :root
|
458
|
+
attr_writer :load_paths
|
459
|
+
attr_writer :layout
|
460
|
+
attr_writer :default_encoding
|
461
|
+
attr_writer :modules
|
462
|
+
end
|
463
|
+
end
|
464
|
+
end
|