phlex-hanami 0.2.0.pre.alpha.2 → 0.2.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.
Files changed (48) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +61 -1
  3. data/lib/phlex/hanami/component.rb +41 -0
  4. data/lib/phlex/hanami/context.rb +21 -1
  5. data/lib/phlex/hanami/contextual.rb +294 -0
  6. data/lib/phlex/hanami/errors/mailer_view_error.rb +1 -0
  7. data/lib/phlex/hanami/errors/missing_context_error.rb +1 -0
  8. data/lib/phlex/hanami/errors/relative_path_error.rb +1 -0
  9. data/lib/phlex/hanami/extensions/mailer.rb +6 -0
  10. data/lib/phlex/hanami/extensions/slice.rb +12 -3
  11. data/lib/phlex/hanami/helpers.rb +3 -2
  12. data/lib/phlex/hanami/mailer/renderable.rb +6 -0
  13. data/lib/phlex/hanami/mailer/text.rb +19 -6
  14. data/lib/phlex/hanami/renderable.rb +14 -237
  15. data/lib/phlex/hanami/rspec.rb +14 -0
  16. data/lib/phlex/hanami/slice_configured.rb +5 -1
  17. data/lib/phlex/hanami/slice_configured_context.rb +9 -1
  18. data/lib/phlex/hanami/testing.rb +156 -0
  19. data/lib/phlex/hanami.rb +3 -1
  20. data/sig/external/dry.rbs +9 -0
  21. data/sig/external/hanami.rbs +77 -0
  22. data/sig/external/hanami_view.rbs +9 -0
  23. data/sig/external/phlex.rbs +16 -0
  24. data/sig/phlex/hanami/component.rbs +9 -0
  25. data/sig/phlex/hanami/context.rbs +39 -0
  26. data/sig/phlex/hanami/contextual.rbs +57 -0
  27. data/sig/phlex/hanami/errors/error.rbs +8 -0
  28. data/sig/phlex/hanami/errors/mailer_view_error.rbs +9 -0
  29. data/sig/phlex/hanami/errors/missing_context_error.rbs +9 -0
  30. data/sig/phlex/hanami/errors/relative_path_error.rbs +9 -0
  31. data/sig/phlex/hanami/extensions/mailer.rbs +25 -0
  32. data/sig/phlex/hanami/extensions/slice.rbs +21 -0
  33. data/sig/phlex/hanami/helpers.rbs +13 -0
  34. data/sig/phlex/hanami/layout.rbs +9 -0
  35. data/sig/phlex/hanami/mailer/layout.rbs +11 -0
  36. data/sig/phlex/hanami/mailer/renderable.rbs +27 -0
  37. data/sig/phlex/hanami/mailer/text.rbs +47 -0
  38. data/sig/phlex/hanami/mailer/view.rbs +11 -0
  39. data/sig/phlex/hanami/mailer.rbs +8 -0
  40. data/sig/phlex/hanami/renderable.rbs +27 -0
  41. data/sig/phlex/hanami/rspec.rbs +1 -0
  42. data/sig/phlex/hanami/slice_configured.rbs +19 -0
  43. data/sig/phlex/hanami/slice_configured_context.rbs +27 -0
  44. data/sig/phlex/hanami/testing.rbs +19 -0
  45. data/sig/phlex/hanami/view.rbs +9 -0
  46. data/sig/phlex/hanami.rbs +7 -0
  47. data/sig/phlex-hanami.rbs +1 -2
  48. metadata +32 -1
@@ -46,7 +46,7 @@ module Phlex
46
46
  OVERRIDDEN_METHODS = {
47
47
  raw: Phlex::SGML.instance_method(:raw),
48
48
  tag: Phlex::HTML.instance_method(:tag),
49
- }.freeze
49
+ }.freeze #: Hash[Symbol, UnboundMethod]
50
50
 
51
51
  # Phlex's implementations, mixed back in over Hanami's.
52
52
  #
@@ -56,10 +56,11 @@ module Phlex
56
56
  OVERRIDDEN_METHODS.each do |name, method|
57
57
  define_method(name) { |*args, **options, &block| method.bind_call(self, *args, **options, &block) }
58
58
  end
59
- end
59
+ end #: Module
60
60
 
61
61
  # @api private
62
62
  # @since 0.2.0
63
+ #: (singleton(::Phlex::SGML)) -> void
63
64
  def self.included(view_class)
64
65
  view_class.include(::Hanami::Extensions::View::StandardHelpers)
65
66
  view_class.include(PhlexMethods)
@@ -22,6 +22,7 @@ module Phlex
22
22
  module Renderable
23
23
  # @api private
24
24
  # @since 0.2.0
25
+ #: (singleton(::Phlex::SGML)) -> void
25
26
  def self.included(view_class)
26
27
  view_class.include(::Phlex::Hanami::Renderable)
27
28
 
@@ -48,6 +49,7 @@ module Phlex
48
49
  #
49
50
  # @api public
50
51
  # @since 0.2.0
52
+ #: (?context: (::Hanami::View::Context | Context)?, ?format: Symbol | String, **untyped) -> String
51
53
  def call(context: nil, format: :html, **input)
52
54
  html = super(context: context || mail_context, **input)
53
55
  return html unless format.to_s == "text"
@@ -62,6 +64,7 @@ module Phlex
62
64
  # The web layout is the wrong one for an email — it carries the stylesheets, scripts and
63
65
  # page chrome no mail client wants — so mail looks for its own, and renders without a
64
66
  # layout when the slice has none.
67
+ #: () -> singleton(::Phlex::SGML)?
65
68
  def default_layout
66
69
  return nil unless slice
67
70
  return nil unless slice.namespace.const_defined?(:Views, false)
@@ -78,6 +81,7 @@ module Phlex
78
81
  # The same class an action would be given, so a view reads the same in both places. What
79
82
  # a request would have filled in — `request`, `session`, `flash`, `csrf_token` — is
80
83
  # missing, and the context says so when a view reaches for it.
84
+ #: () -> (::Hanami::View::Context | Context)?
81
85
  def mail_context
82
86
  return nil unless slice
83
87
 
@@ -99,6 +103,7 @@ module Phlex
99
103
  #
100
104
  # @api public
101
105
  # @since 0.2.0
106
+ #: (*untyped, **untyped) -> bot
102
107
  def path(*, **)
103
108
  raise RelativePathError, self.class
104
109
  end
@@ -119,6 +124,7 @@ module Phlex
119
124
  #
120
125
  # @api public
121
126
  # @since 0.2.0
127
+ #: (String) -> String
122
128
  def text_body(html)
123
129
  Text.call(html)
124
130
  end
@@ -21,11 +21,11 @@ module Phlex
21
21
  # @since 0.2.0
22
22
  BLOCK_ELEMENTS = %w[
23
23
  article blockquote div footer h1 h2 h3 h4 h5 h6 header hr li ol p section table td tr ul
24
- ].freeze
24
+ ].freeze #: Array[String]
25
25
 
26
26
  # @api private
27
27
  # @since 0.2.0
28
- BLOCK_PATTERN = %r{</?(?:#{BLOCK_ELEMENTS.join('|')})\b[^>]*>}i
28
+ BLOCK_PATTERN = %r{</?(?:#{BLOCK_ELEMENTS.join('|')})\b[^>]*>}i #: Regexp
29
29
 
30
30
  # The entities Phlex escapes, and the characters they stand for.
31
31
  #
@@ -39,22 +39,22 @@ module Phlex
39
39
  "&lt;" => "<",
40
40
  "&nbsp;" => " ",
41
41
  "&quot;" => '"',
42
- }.freeze
42
+ }.freeze #: Hash[String, String]
43
43
 
44
44
  # @api private
45
45
  # @since 0.2.0
46
- ENTITY_PATTERN = /&(?:amp|apos|gt|lt|nbsp|quot|#39);/
46
+ ENTITY_PATTERN = /&(?:amp|apos|gt|lt|nbsp|quot|#39);/ #: Regexp
47
47
 
48
48
  # Stand in for the angle brackets around a URL until the tags are gone, so that stripping
49
49
  # tags does not eat the link the converter has just written.
50
50
  #
51
51
  # @api private
52
52
  # @since 0.2.0
53
- LINK_CLOSE = "\u0011"
53
+ LINK_CLOSE = "\u0011" #: String
54
54
 
55
55
  # @api private
56
56
  # @since 0.2.0
57
- LINK_OPEN = "\u0010"
57
+ LINK_OPEN = "\u0010" #: String
58
58
 
59
59
  class << self
60
60
  # Converts rendered HTML to plain text.
@@ -65,6 +65,7 @@ module Phlex
65
65
  #
66
66
  # @api public
67
67
  # @since 0.2.0
68
+ #: (_ToS) -> String
68
69
  def call(html)
69
70
  text = body_of(html.to_s)
70
71
  text = strip_hidden_elements(text)
@@ -80,6 +81,7 @@ module Phlex
80
81
  private
81
82
 
82
83
  # `Label <https://example.com/path>`, or one of the two when the other adds nothing.
84
+ #: (String, String) -> String
83
85
  def anchor_text(attributes, inner)
84
86
  url = href_in(attributes)
85
87
  label = strip_tags(inner).gsub(/\s+/, " ").strip
@@ -89,18 +91,24 @@ module Phlex
89
91
  "#{label} #{LINK_OPEN}#{url}#{LINK_CLOSE}"
90
92
  end
91
93
 
94
+ #: (String) -> String
92
95
  def body_of(html) = html[%r{<body\b[^>]*>(.*?)</body>}mi, 1] || html
93
96
 
97
+ #: (String) -> String
94
98
  def expand_anchors(html)
95
99
  html.gsub(%r{<a\b([^>]*)>(.*?)</a>}mi) { anchor_text(Regexp.last_match(1), Regexp.last_match(2)) }
96
100
  end
97
101
 
102
+ #: (String) -> String
98
103
  def expand_blocks(html) = html.gsub(BLOCK_PATTERN, "\n\n")
99
104
 
105
+ #: (String) -> String
100
106
  def expand_breaks(html) = html.gsub(%r{<br\b[^>]*/?>}i, "\n")
101
107
 
108
+ #: (String) -> String
102
109
  def expand_list_items(html) = html.gsub(%r{</li\s*>}i, "").gsub(/<li\b[^>]*>/i, "\n- ")
103
110
 
111
+ #: (String) -> String
104
112
  def href_in(attributes)
105
113
  match = attributes.match(/\bhref\s*=\s*(?:"([^"]*)"|'([^']*)'|([^\s"'>]+))/i)
106
114
  return "" unless match
@@ -108,14 +116,19 @@ module Phlex
108
116
  (match[1] || match[2] || match[3]).to_s.strip
109
117
  end
110
118
 
119
+ #: (String) -> String
111
120
  def restore_links(text) = text.gsub(LINK_OPEN, "<").gsub(LINK_CLOSE, ">")
112
121
 
122
+ #: (String) -> String
113
123
  def strip_hidden_elements(html) = html.gsub(%r{<(head|style|script)\b[^>]*>.*?</\1\s*>}mi, "")
114
124
 
125
+ #: (String) -> String
115
126
  def strip_tags(html) = html.gsub(/<!--.*?-->/m, "").gsub(/<[^>]*>/, "")
116
127
 
128
+ #: (String) -> String
117
129
  def tidy(text) = text.gsub(/[ \t]+/, " ").gsub(/[ \t]+$/, "").gsub(/\n{3,}/, "\n\n").strip
118
130
 
131
+ #: (String) -> String
119
132
  def unescape(text) = text.gsub(ENTITY_PATTERN) { ENTITIES.fetch(Regexp.last_match(0)) }
120
133
  end
121
134
  end
@@ -5,8 +5,11 @@ module Phlex
5
5
  # Makes a Phlex class renderable by Hanami.
6
6
  #
7
7
  # Include this into an existing `Phlex::SGML` subclass, or subclass {View}, which includes it
8
- # already. It gives the class the `call` contract Hanami's `Response#render` expects, per-slice
9
- # configuration, and access to the request's view context.
8
+ # already. It gives the class the `call` contract Hanami's `Response#render` expects, a layout,
9
+ # and everything in {Contextual}: per-slice configuration and the request's view context.
10
+ #
11
+ # A component is not called by Hanami and wants no layout, so it includes {Contextual} on its
12
+ # own. {Component} does that already.
10
13
  #
11
14
  # @example
12
15
  # module MyApp
@@ -22,70 +25,21 @@ module Phlex
22
25
  #
23
26
  # @api private
24
27
  # @since 0.2.0
25
- KEYWORD_TYPES = %i[key keyreq].freeze
28
+ KEYWORD_TYPES = %i[key keyreq].freeze #: Array[Symbol]
26
29
 
27
30
  # Distinguishes "no layout was configured" from "a layout of `nil` was configured", which is
28
31
  # how a view opts out.
29
32
  #
30
33
  # @api private
31
34
  # @since 0.2.0
32
- UNSET = ::Object.new.freeze
35
+ UNSET = ::Object.new.freeze #: Object
33
36
 
34
37
  # @api private
35
38
  # @since 0.2.0
39
+ #: (singleton(::Phlex::SGML)) -> void
36
40
  def self.included(view_class)
37
- view_class.extend(::Hanami::SliceConfigurable)
41
+ view_class.include(Contextual)
38
42
  view_class.extend(ClassMethods)
39
- return unless defined?(::Hanami::Helpers::I18nHelper)
40
-
41
- # Order matters: a module included later sits higher in the lookup chain, so the overrides
42
- # have to go in after Hanami's helper, not alongside it in this module.
43
- view_class.include(::Hanami::Helpers::I18nHelper)
44
- view_class.include(I18nOverrides)
45
- end
46
-
47
- # Relative-key resolution for Hanami's i18n helper.
48
- #
49
- # A Phlex view has no template, so Hanami's implementation — which reads
50
- # `_context.current_template_name` — cannot work. Included after `Hanami::Helpers::I18nHelper`
51
- # so that it wins: a module included later sits higher in the lookup chain.
52
- #
53
- # @api private
54
- # @since 0.2.0
55
- module I18nOverrides
56
- private
57
-
58
- # Resolves a relative translation key (`t(".title")`) against the view's own container key, so
59
- # `MyApp::Views::Posts::Index` looks up `posts.index.title`.
60
- #
61
- # Hanami's own implementation uses `_context.current_template_name`, which is hanami-view's
62
- # template stack and has no meaning for a Phlex view.
63
- def _resolve_i18n_key(key)
64
- return key unless key.to_s.start_with?(".")
65
-
66
- base = i18n_key_base
67
- unless base
68
- raise ::I18n::ArgumentError,
69
- "Cannot resolve the relative translation key #{key.inspect} for #{self.class}. " \
70
- "Give the view a name inside a slice, or use an absolute key (without a leading dot)."
71
- end
72
-
73
- "#{base}#{key}"
74
- end
75
-
76
- # The view's container key, dotted: `MyApp::Views::Posts::Index` in the app slice becomes
77
- # `posts.index`. Nil for an anonymous view, or one outside any slice.
78
- def i18n_key_base
79
- view_slice = self.class.slice
80
- name = self.class.name
81
- return nil unless view_slice && name
82
-
83
- view_slice.inflector
84
- .underscore(name)
85
- .sub(%r{^#{view_slice.slice_name.path}/}, "")
86
- .sub(%r{^views/}, "")
87
- .tr("/", ".")
88
- end
89
43
  end
90
44
 
91
45
  # @api public
@@ -111,6 +65,7 @@ module Phlex
111
65
  #
112
66
  # @api public
113
67
  # @since 0.2.0
68
+ #: (?context: (::Hanami::View::Context | Context)?, **untyped) -> String
114
69
  def call(context: nil, **input)
115
70
  phlex_context = { CONTEXT_KEY => context }
116
71
  body = new(**accepted_input(input)).call(context: phlex_context)
@@ -121,17 +76,12 @@ module Phlex
121
76
  layout_class.new.call(context: phlex_context) { |layout| layout.raw(layout.safe(body)) }
122
77
  end
123
78
 
124
- # @api private
125
- # @since 0.2.0
126
- def configure_for_slice(slice)
127
- extend SliceConfigured.new(slice)
128
- end
129
-
130
79
  # The layout configured on this class, or inherited from a superclass. {UNSET} when none has
131
80
  # been configured anywhere in the chain.
132
81
  #
133
82
  # @api private
134
83
  # @since 0.2.0
84
+ #: () -> (singleton(::Phlex::SGML) | Object | nil)
135
85
  def configured_layout
136
86
  return @layout if defined?(@layout)
137
87
  return superclass.configured_layout if superclass.respond_to?(:configured_layout)
@@ -165,6 +115,7 @@ module Phlex
165
115
  #
166
116
  # @api public
167
117
  # @since 0.2.0
118
+ #: (?singleton(::Phlex::SGML)?) -> singleton(::Phlex::SGML)?
168
119
  def layout(layout_class = UNSET)
169
120
  unless UNSET.equal?(layout_class)
170
121
  @layout = layout_class
@@ -175,22 +126,13 @@ module Phlex
175
126
  UNSET.equal?(configured) ? default_layout : configured
176
127
  end
177
128
 
178
- # The slice this view belongs to, or nil when it is defined outside a slice namespace.
179
- #
180
- # @return [Hanami::Slice, nil]
181
- #
182
- # @api public
183
- # @since 0.2.0
184
- def slice
185
- nil
186
- end
187
-
188
129
  private
189
130
 
190
131
  # Keeps only the keywords `initialize` declares.
191
132
  #
192
133
  # A view whose initializer takes `**` opts out and receives everything, request params
193
134
  # included.
135
+ #: (Hash[Symbol, untyped]) -> Hash[Symbol, untyped]
194
136
  def accepted_input(input)
195
137
  parameters = instance_method(:initialize).parameters
196
138
  return input if parameters.any? { |type, _| type == :keyrest }
@@ -200,6 +142,7 @@ module Phlex
200
142
 
201
143
  # The slice's conventional layout: `Views::Layout` in the slice's namespace, if it defines
202
144
  # one. Resolved on each call rather than memoized, so code reloading is not defeated.
145
+ #: () -> singleton(::Phlex::SGML)?
203
146
  def default_layout
204
147
  return nil unless slice
205
148
  return nil unless slice.namespace.const_defined?(:Views, false)
@@ -208,172 +151,6 @@ module Phlex
208
151
  views.const_get(:Layout, false) if views.const_defined?(:Layout, false)
209
152
  end
210
153
  end
211
-
212
- # The URL for an asset.
213
- #
214
- # @example
215
- # script(src: asset_url("app.js"))
216
- #
217
- # @param source [String] the asset's source path
218
- #
219
- # @return [String] the URL
220
- #
221
- # @api public
222
- # @since 0.2.0
223
- def asset_url(source)
224
- assets[source].url
225
- end
226
-
227
- # The slice's assets.
228
- #
229
- # @api public
230
- # @since 0.2.0
231
- def assets
232
- hanami_context.assets
233
- end
234
-
235
- # Stores a string of markup for later use, or reads back what was stored.
236
- #
237
- # @api public
238
- # @since 0.2.0
239
- def content_for(...)
240
- hanami_context.content_for(...)
241
- end
242
-
243
- # The current request's CSRF token.
244
- #
245
- # @return [String]
246
- #
247
- # @api public
248
- # @since 0.2.0
249
- def csrf_token
250
- hanami_context.csrf_token
251
- end
252
-
253
- # The flash hash for the current request.
254
- #
255
- # @api public
256
- # @since 0.2.0
257
- def flash
258
- hanami_context.flash
259
- end
260
-
261
- # The Hanami view context for this render.
262
- #
263
- # Read from Phlex's user context, which is shared with every component in the render tree, so
264
- # a component nested any number of levels deep sees the same context.
265
- #
266
- # @return [Object] a `Hanami::View::Context` when hanami-view is bundled, otherwise a {Context}
267
- #
268
- # @raise [MissingContextError] when the view was rendered without a context
269
- #
270
- # @api public
271
- # @since 0.2.0
272
- def hanami_context
273
- context[CONTEXT_KEY] || raise(MissingContextError, self.class)
274
- end
275
-
276
- # Whether a Hanami view context is available.
277
- #
278
- # @return [Boolean]
279
- #
280
- # @api public
281
- # @since 0.2.0
282
- def hanami_context?
283
- !context[CONTEXT_KEY].nil?
284
- end
285
-
286
- # The slice's i18n backend.
287
- #
288
- # @api public
289
- # @since 0.2.0
290
- def i18n
291
- hanami_context.i18n
292
- end
293
-
294
- # The path for a named route.
295
- #
296
- # @example
297
- # a(href: path(:posts)) { "Posts" }
298
- #
299
- # @return [String]
300
- #
301
- # @api public
302
- # @since 0.2.0
303
- def path(...)
304
- routes.path(...)
305
- end
306
-
307
- # The current request.
308
- #
309
- # @return [Hanami::Action::Request]
310
- #
311
- # @api public
312
- # @since 0.2.0
313
- def request
314
- hanami_context.request
315
- end
316
-
317
- # Whether the view is being rendered from within a request.
318
- #
319
- # @return [Boolean]
320
- #
321
- # @api public
322
- # @since 0.2.0
323
- def request?
324
- hanami_context? && hanami_context.request?
325
- end
326
-
327
- # The app's routes helper.
328
- #
329
- # @return [Hanami::Slice::RoutesHelper]
330
- #
331
- # @api public
332
- # @since 0.2.0
333
- def routes
334
- hanami_context.routes
335
- end
336
-
337
- # The session for the current request.
338
- #
339
- # @api public
340
- # @since 0.2.0
341
- def session
342
- hanami_context.session
343
- end
344
-
345
- # The slice this view belongs to.
346
- #
347
- # @return [Hanami::Slice, nil]
348
- #
349
- # @api public
350
- # @since 0.2.0
351
- def slice
352
- self.class.slice
353
- end
354
-
355
- # The full URL for a named route.
356
- #
357
- # Hanami's routes helper returns a `URI`; Phlex writes strings, and rejects anything else as
358
- # an attribute value, so this hands back the string.
359
- #
360
- # @example
361
- # a(href: url(:posts)) { "Posts" }
362
- #
363
- # @return [String]
364
- #
365
- # @api public
366
- # @since 0.2.0
367
- def url(...)
368
- routes.url(...).to_s
369
- end
370
-
371
- private
372
-
373
- # Hanami's helper modules reach for the view context under this name.
374
- def _context
375
- hanami_context
376
- end
377
154
  end
378
155
  end
379
156
  end
@@ -0,0 +1,14 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "testing"
4
+
5
+ # Wires {Phlex::Hanami::Testing::ViewHelpers} into RSpec. Require it from `spec/spec_helper.rb`, or
6
+ # from a file under `spec/support`, which the spec helper hanami-rspec generates already loads:
7
+ #
8
+ # require "phlex/hanami/rspec"
9
+ #
10
+ # The helpers go into every example group tagged `type: :view`. Tag component specs the same way; a
11
+ # component is a view as far as this is concerned.
12
+ RSpec.configure do |config|
13
+ config.include Phlex::Hanami::Testing::ViewHelpers, type: :view
14
+ end
@@ -17,10 +17,11 @@ module Phlex
17
17
  #
18
18
  # @api private
19
19
  # @since 0.2.0
20
- attr_reader :slice
20
+ attr_reader :slice #: singleton(::Hanami::Slice)
21
21
 
22
22
  # @api private
23
23
  # @since 0.2.0
24
+ #: (singleton(::Hanami::Slice)) -> void
24
25
  def initialize(slice)
25
26
  super()
26
27
  @slice = slice
@@ -28,18 +29,21 @@ module Phlex
28
29
 
29
30
  # @api private
30
31
  # @since 0.2.0
32
+ #: (singleton(::Phlex::SGML) | singleton(::Hanami::Mailer)) -> void
31
33
  def extended(_klass)
32
34
  define_slice
33
35
  end
34
36
 
35
37
  # @api private
36
38
  # @since 0.2.0
39
+ #: () -> String
37
40
  def inspect
38
41
  "#<#{self.class.name}[#{slice.name}]>"
39
42
  end
40
43
 
41
44
  private
42
45
 
46
+ #: () -> void
43
47
  def define_slice
44
48
  slice = @slice
45
49
 
@@ -16,10 +16,11 @@ module Phlex
16
16
  #
17
17
  # @api private
18
18
  # @since 0.2.0
19
- attr_reader :slice
19
+ attr_reader :slice #: singleton(::Hanami::Slice)
20
20
 
21
21
  # @api private
22
22
  # @since 0.2.0
23
+ #: (singleton(::Hanami::Slice)) -> void
23
24
  def initialize(slice)
24
25
  super()
25
26
  @slice = slice
@@ -27,12 +28,14 @@ module Phlex
27
28
 
28
29
  # @api private
29
30
  # @since 0.2.0
31
+ #: (singleton(Context)) -> void
30
32
  def extended(_context_class)
31
33
  define_new
32
34
  end
33
35
 
34
36
  # @api private
35
37
  # @since 0.2.0
38
+ #: () -> String
36
39
  def inspect
37
40
  "#<#{self.class.name}[#{slice.name}]>"
38
41
  end
@@ -40,6 +43,7 @@ module Phlex
40
43
  private
41
44
 
42
45
  # Defines a `.new` that resolves the slice's components and passes them to `#initialize`.
46
+ #: () -> void
43
47
  def define_new
44
48
  dependencies = method(:dependencies)
45
49
 
@@ -50,18 +54,22 @@ module Phlex
50
54
 
51
55
  # The slice components a context is injected with, resolved fresh for each context so that
52
56
  # a container which does not memoize still hands out working objects.
57
+ #: () -> Hash[Symbol, untyped]
53
58
  def dependencies
54
59
  { assets: resolve_assets, i18n: resolve_i18n, inflector: slice.inflector, routes: resolve_routes }
55
60
  end
56
61
 
62
+ #: () -> ::Hanami::Assets?
57
63
  def resolve_assets
58
64
  slice["assets"] if slice.key?("assets")
59
65
  end
60
66
 
67
+ #: () -> ::Hanami::Providers::I18n::Backend?
61
68
  def resolve_i18n
62
69
  slice["i18n"] if slice.key?("i18n")
63
70
  end
64
71
 
72
+ #: () -> ::Hanami::Slice::RoutesHelper?
65
73
  def resolve_routes
66
74
  slice["routes"] if slice.key?("routes")
67
75
  end