hanami-view 1.3.3 → 2.0.0.alpha2

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.
Files changed (61) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +3 -10
  3. data/LICENSE +20 -0
  4. data/README.md +20 -862
  5. data/hanami-view.gemspec +26 -16
  6. data/lib/hanami-view.rb +3 -1
  7. data/lib/hanami/view.rb +208 -223
  8. data/lib/hanami/view/application_configuration.rb +77 -0
  9. data/lib/hanami/view/application_context.rb +35 -0
  10. data/lib/hanami/view/application_view.rb +89 -0
  11. data/lib/hanami/view/context.rb +97 -0
  12. data/lib/hanami/view/context_helpers/content_helpers.rb +26 -0
  13. data/lib/hanami/view/decorated_attributes.rb +82 -0
  14. data/lib/hanami/view/errors.rb +19 -56
  15. data/lib/hanami/view/exposure.rb +126 -0
  16. data/lib/hanami/view/exposures.rb +74 -0
  17. data/lib/hanami/view/part.rb +217 -0
  18. data/lib/hanami/view/part_builder.rb +140 -0
  19. data/lib/hanami/view/path.rb +68 -0
  20. data/lib/hanami/view/render_environment.rb +62 -0
  21. data/lib/hanami/view/render_environment_missing.rb +44 -0
  22. data/lib/hanami/view/rendered.rb +55 -0
  23. data/lib/hanami/view/renderer.rb +79 -0
  24. data/lib/hanami/view/scope.rb +189 -0
  25. data/lib/hanami/view/scope_builder.rb +98 -0
  26. data/lib/hanami/view/standalone_view.rb +396 -0
  27. data/lib/hanami/view/tilt.rb +78 -0
  28. data/lib/hanami/view/tilt/erb.rb +26 -0
  29. data/lib/hanami/view/tilt/erbse.rb +21 -0
  30. data/lib/hanami/view/tilt/haml.rb +26 -0
  31. data/lib/hanami/view/version.rb +5 -5
  32. metadata +114 -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.rb +0 -294
  41. data/lib/hanami/view/rendering/layout_finder.rb +0 -128
  42. data/lib/hanami/view/rendering/layout_registry.rb +0 -69
  43. data/lib/hanami/view/rendering/layout_scope.rb +0 -281
  44. data/lib/hanami/view/rendering/null_layout.rb +0 -52
  45. data/lib/hanami/view/rendering/null_local.rb +0 -82
  46. data/lib/hanami/view/rendering/null_template.rb +0 -83
  47. data/lib/hanami/view/rendering/null_view.rb +0 -26
  48. data/lib/hanami/view/rendering/options.rb +0 -24
  49. data/lib/hanami/view/rendering/partial.rb +0 -31
  50. data/lib/hanami/view/rendering/partial_file.rb +0 -29
  51. data/lib/hanami/view/rendering/partial_finder.rb +0 -75
  52. data/lib/hanami/view/rendering/partial_templates_finder.rb +0 -73
  53. data/lib/hanami/view/rendering/registry.rb +0 -134
  54. data/lib/hanami/view/rendering/scope.rb +0 -108
  55. data/lib/hanami/view/rendering/subscope.rb +0 -56
  56. data/lib/hanami/view/rendering/template.rb +0 -69
  57. data/lib/hanami/view/rendering/template_finder.rb +0 -55
  58. data/lib/hanami/view/rendering/template_name.rb +0 -50
  59. data/lib/hanami/view/rendering/templates_finder.rb +0 -144
  60. data/lib/hanami/view/rendering/view_finder.rb +0 -37
  61. data/lib/hanami/view/template.rb +0 -57
data/hanami-view.gemspec CHANGED
@@ -1,28 +1,38 @@
1
- # coding: utf-8
2
- lib = File.expand_path('../lib', __FILE__)
1
+ # frozen_string_literal: true
2
+
3
+ lib = File.expand_path('lib', __dir__)
3
4
  $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
5
  require 'hanami/view/version'
5
6
 
6
7
  Gem::Specification.new do |spec|
7
8
  spec.name = 'hanami-view'
8
- spec.version = Hanami::View::VERSION
9
- spec.authors = ['Luca Guidi']
10
- spec.email = ['me@lucaguidi.com']
11
- spec.description = %q{View layer for Hanami}
12
- spec.summary = %q{View layer for Hanami, with a separation between views and templates}
13
- spec.homepage = 'http://hanamirb.org'
9
+ spec.authors = ["Tim Riley", "Piotr Solnica"]
10
+ spec.email = ["tim@icelab.com.au", "piotr.solnica@gmail.com"]
14
11
  spec.license = 'MIT'
12
+ spec.version = Hanami::View::VERSION.dup
15
13
 
16
- spec.files = `git ls-files -- lib/* CHANGELOG.md LICENSE.md README.md hanami-view.gemspec`.split($/)
14
+ spec.summary = "A complete, standalone view rendering system that gives you everything you need to write well-factored view code"
15
+ spec.description = spec.summary
16
+ spec.homepage = 'https://dry-rb.org/gems/hanami-view'
17
+ spec.files = Dir["CHANGELOG.md", "LICENSE", "README.md", "hanami-view.gemspec", "lib/**/*"]
18
+ spec.bindir = 'bin'
17
19
  spec.executables = []
18
- spec.test_files = spec.files.grep(%r{^(test)/})
19
20
  spec.require_paths = ['lib']
20
- spec.required_ruby_version = '>= 2.3.0'
21
21
 
22
- spec.add_runtime_dependency 'tilt', '~> 2.0', '>= 2.0.1'
23
- spec.add_runtime_dependency 'hanami-utils', '~> 1.3'
22
+ spec.metadata['allowed_push_host'] = 'https://rubygems.org'
23
+ spec.metadata['changelog_uri'] = 'https://github.com/hanami/view/blob/master/CHANGELOG.md'
24
+ spec.metadata['source_code_uri'] = 'https://github.com/hanami/view'
25
+ spec.metadata['bug_tracker_uri'] = 'https://github.com/hanami/view/issues'
26
+
27
+ spec.required_ruby_version = ">= 2.4.0"
28
+
29
+ spec.add_runtime_dependency "concurrent-ruby", "~> 1.0"
30
+ spec.add_runtime_dependency "dry-configurable", "~> 0.1"
31
+ spec.add_runtime_dependency "dry-core", "~> 0.5", ">= 0.5"
32
+ spec.add_runtime_dependency "dry-inflector", "~> 0.1"
33
+ spec.add_runtime_dependency "tilt", "~> 2.0", ">= 2.0.6"
24
34
 
25
- spec.add_development_dependency 'bundler', '>= 1.6', '< 3'
26
- spec.add_development_dependency 'rspec', '~> 3.7'
27
- spec.add_development_dependency 'rake', '~> 13'
35
+ spec.add_development_dependency "bundler"
36
+ spec.add_development_dependency "rake"
37
+ spec.add_development_dependency "rspec"
28
38
  end
data/lib/hanami-view.rb CHANGED
@@ -1 +1,3 @@
1
- require 'hanami/view'
1
+ # frozen_string_literal: true
2
+
3
+ require "hanami/view"
data/lib/hanami/view.rb CHANGED
@@ -1,267 +1,252 @@
1
- require 'set'
2
- require 'pathname'
3
- require 'hanami/utils/class_attribute'
4
- require 'hanami/view/version'
5
- require 'hanami/view/configuration'
6
- require 'hanami/view/inheritable'
7
- require 'hanami/view/rendering'
8
- require 'hanami/view/escape'
9
- require 'hanami/view/dsl'
10
- require 'hanami/view/errors'
11
- require 'hanami/layout'
12
- require 'hanami/presenter'
1
+ # frozen_string_literal: true
2
+
3
+ require "dry/configurable"
4
+ require "dry/core/cache"
5
+ require "dry/core/equalizer"
6
+ require "dry/inflector"
7
+
8
+ require_relative "view/application_view"
9
+ require_relative "view/context"
10
+ require_relative "view/exposures"
11
+ require_relative "view/errors"
12
+ require_relative "view/part_builder"
13
+ require_relative "view/path"
14
+ require_relative "view/render_environment"
15
+ require_relative "view/rendered"
16
+ require_relative "view/renderer"
17
+ require_relative "view/scope_builder"
18
+ require_relative "view/standalone_view"
13
19
 
14
- # Hanami
15
- #
16
- # @since 0.1.0
17
20
  module Hanami
18
- # View
21
+ # A standalone, template-based view rendering system that offers everything
22
+ # you need to write well-factored view code.
19
23
  #
20
- # @since 0.1.0
21
- module View
22
- include Utils::ClassAttribute
23
- # Framework configuration
24
- #
25
- # @since 0.2.0
24
+ # This represents a single view, holding the configuration and exposures
25
+ # necessary for rendering its template.
26
+ #
27
+ # @abstract Subclass this and provide your own configuration and exposures to
28
+ # define your own view (along with a custom `#initialize` if you wish to
29
+ # inject dependencies into your subclass)
30
+ #
31
+ # @see https://dry-rb.org/gems/dry-view/
32
+ #
33
+ # @api public
34
+ class View
26
35
  # @api private
27
- class_attribute :configuration
28
- self.configuration = Configuration.new
36
+ DEFAULT_RENDERER_OPTIONS = {default_encoding: "utf-8"}.freeze
29
37
 
30
- # Configure the framework.
31
- # It yields the given block in the context of the configuration
32
- #
33
- # @param blk [Proc] the configuration block
38
+ include Dry::Equalizer(:config, :exposures)
39
+
40
+ extend Dry::Core::Cache
41
+
42
+ extend Dry::Configurable
43
+
44
+ # @!group Configuration
45
+
46
+ # @overload config.paths=(paths)
47
+ # Set an array of directories that will be searched for all templates
48
+ # (templates, partials, and layouts).
34
49
  #
35
- # @since 0.2.0
50
+ # These will be converted into Path objects and used for template lookup
51
+ # when rendering.
36
52
  #
37
- # @see Hanami::View::Configuration
53
+ # This is a **required setting**.
38
54
  #
39
- # @example
40
- # require 'hanami/view'
55
+ # @param paths [String, Path, Array<String, Path>] the paths
41
56
  #
42
- # Hanami::View.configure do
43
- # root '/path/to/root'
44
- # end
45
- def self.configure(&blk)
46
- configuration.instance_eval(&blk)
57
+ # @api public
58
+ # @!scope class
59
+ setting :paths do |paths|
60
+ Array(paths).map { |path| Path[path] }
47
61
  end
48
62
 
49
- # Duplicate Hanami::View in order to create a new separated instance
50
- # of the framework.
63
+ # @overload config.template=(name)
64
+ # Set the name of the template for rendering this view. Template name
65
+ # should be relative to the configured `paths`.
51
66
  #
52
- # The new instance of the framework will be completely decoupled from the
53
- # original. It will inherit the configuration, but all the changes that
54
- # happen after the duplication, won't be reflected on the other copies.
67
+ # This is a **required setting**.
55
68
  #
56
- # @return [Module] a copy of Hanami::View
69
+ # @param name [String] template name
70
+ # @api public
71
+ # @!scope class
72
+ setting :template
73
+
74
+ # @overload config.template_inference_base=(base_path)
75
+ # Set the base path to strip away when when inferring a view's template
76
+ # names from its class name.
57
77
  #
58
- # @since 0.2.0
59
- # @api private
78
+ # **This setting only applies for views within an Hanami application.**
60
79
  #
61
- # @example Basic usage
62
- # require 'hanami/view'
80
+ # For example, given a view `Main::Views::Articles::Index`, in the `Main`
81
+ # slice, and a template_inference_base of "views", the inferred template
82
+ # name will be "articles/index".
63
83
  #
64
- # module MyApp
65
- # View = Hanami::View.dupe
66
- # end
84
+ # @param base_path [String, nil] base templates path
85
+ # @api public
86
+ # @!scope class
87
+ setting :template_inference_base
88
+
89
+ # @overload config.layout=(name)
90
+ # Set the name of the layout to render templates within. Layouts will be
91
+ # looked up within the configured `layouts_dir`, within the configured
92
+ # `paths`.
67
93
  #
68
- # MyApp::View == Hanami::View # => false
94
+ # A false or nil value will use no layout. Defaults to `nil`.
69
95
  #
70
- # MyApp::View.configuration ==
71
- # Hanami::View.configuration # => false
96
+ # @param name [String, FalseClass, nil] layout name, or false to indicate no layout
97
+ # @api public
98
+ # @!scope class
99
+ setting :layout, false
100
+
101
+ # @overload config.layouts_dir=(dir)
102
+ # Set the name of the directory (within the configured `paths`) holding
103
+ # the layouts. Defaults to `"layouts"`
104
+ #
105
+ # @param dir [String] directory name
106
+ # @api public
107
+ # @!scope class
108
+ setting :layouts_dir, "layouts"
109
+
110
+ # @overload config.scope=(scope_class)
111
+ # Set the scope class to use when rendering the view's template.
72
112
  #
73
- # @example Inheriting configuration
74
- # require 'hanami/view'
113
+ # Configuring a custom scope class allows you to provide extra behaviour
114
+ # (alongside exposures) to the template.
75
115
  #
76
- # Hanami::View.configure do
77
- # root '/path/to/root'
78
- # end
116
+ # @see https://dry-rb.org/gems/dry-view/scopes/
79
117
  #
80
- # module MyApp
81
- # View = Hanami::View.dupe
82
- # end
118
+ # @param scope_class [Class] scope class (inheriting from `Hanami::View::Scope`)
119
+ # @api public
120
+ # @!scope class
121
+ setting :scope
122
+
123
+ # @overload config.default_context=(context)
124
+ # Set the default context object to use when rendering. This will be used
125
+ # unless another context object is applied at render-time to `View#call`
83
126
  #
84
- # module MyApi
85
- # View = Hanami::View.dupe
86
- # View.configure do
87
- # root '/another/root'
88
- # end
89
- # end
127
+ # Defaults to a frozen instance of `Hanami::View::Context`.
90
128
  #
91
- # Hanami::View.configuration.root # => #<Pathname:/path/to/root>
92
- # MyApp::View.configuration.root # => #<Pathname:/path/to/root>
93
- # MyApi::View.configuration.root # => #<Pathname:/another/root>
94
- def self.dupe
95
- dup.tap do |duplicated|
96
- duplicated.configuration = configuration.duplicate
97
- end
98
- end
129
+ # @see View#call
130
+ #
131
+ # @param context [Hanami::View::Context] context object
132
+ # @api public
133
+ # @!scope class
134
+ setting :default_context, Context.new.freeze
99
135
 
100
- # Duplicate the framework and generate modules for the target application
136
+ # @overload config.default_format=(format)
137
+ # Set the default format to use when rendering.
101
138
  #
102
- # @param mod [Module] the Ruby namespace of the application
103
- # @param views [String] the optional namespace where the application's
104
- # views will live
105
- # @param blk [Proc] an optional block to configure the framework
139
+ # Defaults to `:html`.
106
140
  #
107
- # @return [Module] a copy of Hanami::View
108
- #
109
- # @since 0.2.0
110
- #
111
- # @see Hanami::View#dupe
112
- # @see Hanami::View::Configuration
113
- # @see Hanami::View::Configuration#namespace
114
- #
115
- # @example Basic usage
116
- # require 'hanami/view'
117
- #
118
- # module MyApp
119
- # View = Hanami::View.duplicate(self)
120
- # end
121
- #
122
- # # It will:
123
- # #
124
- # # 1. Generate MyApp::View
125
- # # 2. Generate MyApp::Layout
126
- # # 3. Generate MyApp::Presenter
127
- # # 4. Generate MyApp::Views
128
- # # 5. Configure MyApp::Views as the default namespace for views
129
- #
130
- # module MyApp::Views::Dashboard
131
- # class Index
132
- # include MyApp::View
133
- # end
134
- # end
135
- #
136
- # @example Compare code
137
- # require 'hanami/view'
138
- #
139
- # module MyApp
140
- # View = Hanami::View.duplicate(self) do
141
- # # ...
142
- # end
143
- # end
144
- #
145
- # # it's equivalent to:
146
- #
147
- # module MyApp
148
- # View = Hanami::View.dupe
149
- # Layout = Hanami::Layout.dup
150
- #
151
- # module Views
152
- # end
153
- #
154
- # View.configure do
155
- # namespace 'MyApp::Views'
156
- # end
157
- #
158
- # View.configure do
159
- # # ...
160
- # end
161
- # end
162
- #
163
- # @example Custom views module
164
- # require 'hanami/view
165
- #
166
- # module MyApp
167
- # View = Hanami::View.duplicate(self, 'Vs')
168
- # end
169
- #
170
- # defined?(MyApp::Views) # => nil
171
- # defined?(MyApp::Vs) # => "constant"
172
- #
173
- # # Developers can namespace views under Vs
174
- # module MyApp::Vs::Dashboard
175
- # # ...
176
- # end
177
- #
178
- # @example Nil views module
179
- # require 'hanami/view'
180
- #
181
- # module MyApp
182
- # View = Hanami::View.duplicate(self, nil)
183
- # end
184
- #
185
- # defined?(MyApp::Views) # => nil
186
- #
187
- # # Developers can namespace views under MyApp
188
- # module MyApp
189
- # # ...
190
- # end
191
- #
192
- # @example Block usage
193
- # require 'hanami/view'
194
- #
195
- # module MyApp
196
- # View = Hanami::View.duplicate(self) do
197
- # root '/path/to/root'
198
- # end
199
- # end
200
- #
201
- # Hanami::View.configuration.root # => #<Pathname:.>
202
- # MyApp::View.configuration.root # => #<Pathname:/path/to/root>
203
- def self.duplicate(mod, views = 'Views', &blk)
204
- dupe.tap do |duplicated|
205
- mod.module_eval %{ module #{ views }; end } if views
206
- mod.module_eval %{
207
- Layout = Hanami::Layout.dup
208
- Presenter = Hanami::Presenter.dup
209
- }
141
+ # @param format [Symbol]
142
+ # @api public
143
+ # @!scope class
144
+ setting :default_format, :html
210
145
 
211
- duplicated.configure do
212
- namespace [mod, views].compact.join '::'
213
- end
146
+ # @overload config.scope_namespace=(namespace)
147
+ # Set a namespace that will be searched when building scope classes.
148
+ #
149
+ # @param namespace [Module, Class]
150
+ #
151
+ # @see Scope
152
+ #
153
+ # @api public
154
+ # @!scope class
155
+ setting :part_namespace
214
156
 
215
- duplicated.configure(&blk) if block_given?
216
- end
217
- end
157
+ # @overload config.part_builder=(part_builder)
158
+ # Set a custom part builder class
159
+ #
160
+ # @see https://dry-rb.org/gems/dry-view/parts/
161
+ #
162
+ # @param part_builder [Class]
163
+ # @api public
164
+ # @!scope class
165
+ setting :part_builder, PartBuilder
218
166
 
219
- # Override Ruby's hook for modules.
220
- # It includes basic Hanami::View modules to the given Class.
221
- # It sets a copy of the framework configuration
167
+ # @overload config.scope_namespace=(namespace)
168
+ # Set a namespace that will be searched when building scope classes.
222
169
  #
223
- # @param base [Class] the target view
170
+ # @param namespace [Module, Class]
224
171
  #
225
- # @since 0.1.0
226
- # @api private
172
+ # @see Scope
173
+ #
174
+ # @api public
175
+ # @!scope class
176
+ setting :scope_namespace
177
+
178
+ # @overload config.scope_builder=(scope_builder)
179
+ # Set a custom scope builder class
180
+ #
181
+ # @see https://dry-rb.org/gems/dry-view/scopes/
182
+ #
183
+ # @param scope_builder [Class]
184
+ # @api public
185
+ # @!scope class
186
+ setting :scope_builder, ScopeBuilder
187
+
188
+ # @overload config.inflector=(inflector)
189
+ # Set an inflector to provide to the part_builder and scope_builder.
190
+ #
191
+ # Defaults to `Dry::Inflector.new`.
227
192
  #
228
- # @see http://www.ruby-doc.org/core-2.1.2/Module.html#method-i-included
193
+ # @param inflector
194
+ # @api public
195
+ # @!scope class
196
+ setting :inflector, Dry::Inflector.new
197
+
198
+ # @overload config.renderer_options=(options)
199
+ # A hash of options to pass to the template engine. Template engines are
200
+ # provided by Tilt; see Tilt's documentation for what options your
201
+ # template engine may support.
229
202
  #
230
- # @see Hanami::View::Dsl
231
- # @see Hanami::View::Inheritable
232
- # @see Hanami::View::Rendering
203
+ # Defaults to `{default_encoding: "utf-8"}`. Any options passed will be
204
+ # merged onto the defaults.
233
205
  #
234
- # @example
235
- # require 'hanami/view'
206
+ # @see https://github.com/rtomayko/tilt
236
207
  #
237
- # class IndexView
238
- # include Hanami::View
239
- # end
240
- def self.included(base)
241
- conf = self.configuration
242
- conf.add_view(base)
208
+ # @param options [Hash] renderer options
209
+ # @api public
210
+ # @!scope class
211
+ setting :renderer_options, DEFAULT_RENDERER_OPTIONS do |options|
212
+ DEFAULT_RENDERER_OPTIONS.merge(options.to_h).freeze
213
+ end
243
214
 
244
- base.class_eval do
245
- extend Inheritable
246
- extend Dsl
247
- extend Rendering
248
- extend Escape
215
+ # @overload config.renderer_engine_mapping=(mapping)
216
+ # A hash specifying the (Tilt-compatible) template engine class to use
217
+ # for a given format. Template engine detection is automatic based on
218
+ # format; use this setting only if you want to force a non-preferred
219
+ # engine.
220
+ #
221
+ # @example
222
+ # config.renderer_engine_mapping = {erb: Tilt::ErubiTemplate}
223
+ #
224
+ # @see https://github.com/rtomayko/tilt
225
+ #
226
+ # @param mapping [Hash<Symbol, Class>] engine mapping
227
+ # @api public
228
+ # @!scope class
229
+ setting :renderer_engine_mapping
249
230
 
250
- include Utils::ClassAttribute
251
- class_attribute :configuration
231
+ # @!endgroup
252
232
 
253
- self.configuration = conf.duplicate
254
- end
233
+ include StandaloneView
255
234
 
256
- conf.copy!(base)
235
+ def self.inherited(subclass)
236
+ super
237
+
238
+ # If inheriting directly from Hanami::View within an Hanami app, configure
239
+ # the view for the application
240
+ if subclass.superclass == View && (provider = application_provider(subclass))
241
+ subclass.include ApplicationView.new(provider)
242
+ end
257
243
  end
258
244
 
259
- # Load the framework
260
- #
261
- # @since 0.1.0
262
- # @api private
263
- def self.load!
264
- configuration.load!
245
+ def self.application_provider(subclass)
246
+ if Hanami.respond_to?(:application?) && Hanami.application?
247
+ Hanami.application.component_provider(subclass)
248
+ end
265
249
  end
250
+ private_class_method :application_provider
266
251
  end
267
252
  end