pakyow-presenter 0.11.3 → 1.0.0.rc1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (100) hide show
  1. checksums.yaml +5 -5
  2. data/{pakyow-presenter/CHANGELOG.md → CHANGELOG.md} +0 -5
  3. data/LICENSE +4 -0
  4. data/{pakyow-presenter/README.md → README.md} +1 -2
  5. data/lib/pakyow/generators/presenter/presenter_generator.rb +28 -0
  6. data/lib/pakyow/generators/presenter/templates/presenter.rb.tt +2 -0
  7. data/lib/pakyow/plugin/helpers/rendering.rb +19 -0
  8. data/lib/pakyow/presenter/actions/auto_render.rb +15 -0
  9. data/lib/pakyow/presenter/attributes/attribute.rb +22 -0
  10. data/lib/pakyow/presenter/attributes/boolean.rb +24 -0
  11. data/lib/pakyow/presenter/attributes/hash.rb +89 -0
  12. data/lib/pakyow/presenter/attributes/set.rb +61 -0
  13. data/lib/pakyow/presenter/attributes/string.rb +38 -0
  14. data/lib/pakyow/presenter/attributes.rb +126 -0
  15. data/lib/pakyow/presenter/behavior/config.rb +29 -0
  16. data/lib/pakyow/presenter/behavior/error_rendering.rb +124 -0
  17. data/lib/pakyow/presenter/behavior/exposures.rb +29 -0
  18. data/lib/pakyow/presenter/behavior/implicit_rendering.rb +21 -0
  19. data/lib/pakyow/presenter/behavior/initializing.rb +37 -0
  20. data/lib/pakyow/presenter/behavior/modes.rb +117 -0
  21. data/lib/pakyow/presenter/behavior/versions.rb +71 -0
  22. data/lib/pakyow/presenter/behavior/watching.rb +21 -0
  23. data/lib/pakyow/presenter/binder.rb +115 -0
  24. data/lib/pakyow/presenter/binding_parts.rb +64 -0
  25. data/lib/pakyow/presenter/component.rb +74 -0
  26. data/lib/pakyow/presenter/composers/component.rb +78 -0
  27. data/lib/pakyow/presenter/composers/view.rb +73 -0
  28. data/lib/pakyow/presenter/errors.rb +63 -0
  29. data/lib/pakyow/presenter/framework.rb +203 -0
  30. data/lib/pakyow/presenter/front_matter_parser.rb +42 -0
  31. data/lib/pakyow/presenter/helpers/rendering.rb +18 -0
  32. data/lib/pakyow/presenter/presentable.rb +28 -0
  33. data/lib/pakyow/presenter/presentable_error.rb +19 -0
  34. data/lib/pakyow/presenter/presenter.rb +766 -0
  35. data/lib/pakyow/presenter/presenters/endpoint.rb +158 -0
  36. data/lib/pakyow/presenter/presenters/form.rb +522 -0
  37. data/lib/pakyow/presenter/processor.rb +61 -0
  38. data/lib/pakyow/presenter/renderable.rb +28 -0
  39. data/lib/pakyow/presenter/renderer.rb +269 -0
  40. data/lib/pakyow/presenter/rendering/actions/cleanup_prototype_nodes.rb +21 -0
  41. data/lib/pakyow/presenter/rendering/actions/cleanup_unbound_bindings.rb +35 -0
  42. data/lib/pakyow/presenter/rendering/actions/create_template_nodes.rb +27 -0
  43. data/lib/pakyow/presenter/rendering/actions/insert_prototype_bar.rb +101 -0
  44. data/lib/pakyow/presenter/rendering/actions/install_authenticity.rb +42 -0
  45. data/lib/pakyow/presenter/rendering/actions/place_in_mode.rb +56 -0
  46. data/lib/pakyow/presenter/rendering/actions/render_components.rb +279 -0
  47. data/lib/pakyow/presenter/rendering/actions/set_page_title.rb +35 -0
  48. data/lib/pakyow/presenter/rendering/actions/setup_endpoints.rb +62 -0
  49. data/lib/pakyow/presenter/rendering/actions/setup_forms.rb +174 -0
  50. data/lib/pakyow/presenter/significant_nodes.rb +309 -0
  51. data/lib/pakyow/presenter/templates.rb +229 -0
  52. data/lib/pakyow/presenter/versioned_view.rb +209 -0
  53. data/lib/pakyow/presenter/view.rb +586 -0
  54. data/lib/pakyow/presenter/views/form.rb +58 -0
  55. data/lib/pakyow/presenter/views/layout.rb +32 -0
  56. data/lib/pakyow/presenter/views/page.rb +72 -0
  57. data/lib/pakyow/presenter/views/partial.rb +28 -0
  58. data/lib/pakyow/presenter.rb +33 -0
  59. data/lib/pakyow/views/errors/layouts/development_error.html +102 -0
  60. data/lib/pakyow/views/errors/layouts/production_error.html +27 -0
  61. data/lib/pakyow/views/errors/pages/404.html +12 -0
  62. data/lib/pakyow/views/errors/pages/500.html +12 -0
  63. data/lib/pakyow/views/errors/pages/development/500.html +39 -0
  64. data/lib/string_doc/attributes.rb +109 -0
  65. data/lib/string_doc/meta_attributes.rb +48 -0
  66. data/lib/string_doc/meta_node.rb +328 -0
  67. data/lib/string_doc/node.rb +377 -0
  68. data/lib/string_doc.rb +642 -0
  69. metadata +95 -75
  70. data/pakyow-presenter/LICENSE +0 -20
  71. data/pakyow-presenter/lib/pakyow/presenter/attributes.rb +0 -228
  72. data/pakyow-presenter/lib/pakyow/presenter/base.rb +0 -38
  73. data/pakyow-presenter/lib/pakyow/presenter/binder.rb +0 -116
  74. data/pakyow-presenter/lib/pakyow/presenter/binder_set.rb +0 -94
  75. data/pakyow-presenter/lib/pakyow/presenter/binding_eval.rb +0 -37
  76. data/pakyow-presenter/lib/pakyow/presenter/config/presenter.rb +0 -42
  77. data/pakyow-presenter/lib/pakyow/presenter/container.rb +0 -6
  78. data/pakyow-presenter/lib/pakyow/presenter/doc_helpers.rb +0 -17
  79. data/pakyow-presenter/lib/pakyow/presenter/exceptions.rb +0 -11
  80. data/pakyow-presenter/lib/pakyow/presenter/ext/app.rb +0 -33
  81. data/pakyow-presenter/lib/pakyow/presenter/ext/call_context.rb +0 -28
  82. data/pakyow-presenter/lib/pakyow/presenter/helpers.rb +0 -46
  83. data/pakyow-presenter/lib/pakyow/presenter/page.rb +0 -110
  84. data/pakyow-presenter/lib/pakyow/presenter/partial.rb +0 -6
  85. data/pakyow-presenter/lib/pakyow/presenter/presenter.rb +0 -232
  86. data/pakyow-presenter/lib/pakyow/presenter/string_doc.rb +0 -380
  87. data/pakyow-presenter/lib/pakyow/presenter/string_doc_parser.rb +0 -144
  88. data/pakyow-presenter/lib/pakyow/presenter/string_doc_renderer.rb +0 -18
  89. data/pakyow-presenter/lib/pakyow/presenter/template.rb +0 -51
  90. data/pakyow-presenter/lib/pakyow/presenter/view.rb +0 -541
  91. data/pakyow-presenter/lib/pakyow/presenter/view_collection.rb +0 -330
  92. data/pakyow-presenter/lib/pakyow/presenter/view_composer.rb +0 -237
  93. data/pakyow-presenter/lib/pakyow/presenter/view_context.rb +0 -111
  94. data/pakyow-presenter/lib/pakyow/presenter/view_store.rb +0 -262
  95. data/pakyow-presenter/lib/pakyow/presenter/view_store_loader.rb +0 -43
  96. data/pakyow-presenter/lib/pakyow/presenter/view_version.rb +0 -113
  97. data/pakyow-presenter/lib/pakyow/presenter.rb +0 -8
  98. data/pakyow-presenter/lib/pakyow/views/errors/404.erb +0 -26
  99. data/pakyow-presenter/lib/pakyow/views/errors/500.erb +0 -23
  100. data/pakyow-presenter/lib/pakyow-presenter.rb +0 -1
@@ -0,0 +1,37 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "pakyow/support/extension"
4
+
5
+ require "pakyow/plugin"
6
+
7
+ module Pakyow
8
+ module Presenter
9
+ module Behavior
10
+ module Initializing
11
+ extend Support::Extension
12
+
13
+ apply_extension do
14
+ unless ancestors.include?(Plugin)
15
+ after "load", "load.presenter" do
16
+ state(:templates) << Templates.new(
17
+ :default,
18
+ config.presenter.path,
19
+ processor: ProcessorCaller.new(
20
+ state(:processor)
21
+ )
22
+ )
23
+
24
+ state(:templates) << Templates.new(:errors, File.join(File.expand_path("../../../", __FILE__), "views", "errors"))
25
+ end
26
+
27
+ after "load.plugins" do
28
+ # Load plugin frontend after the app so that app has priority.
29
+ #
30
+ @plugs.each(&:load_frontend)
31
+ end
32
+ end
33
+ end
34
+ end
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,117 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "pakyow/support/extension"
4
+
5
+ module Pakyow
6
+ module Presenter
7
+ module Behavior
8
+ module Modes
9
+ class ModeCallContext
10
+ def initialize(connection)
11
+ @connection = connection
12
+ end
13
+ end
14
+
15
+ extend Support::Extension
16
+
17
+ attr_accessor :__ui_modes
18
+
19
+ def mode(name, &block)
20
+ @__ui_modes[name.to_sym] = wrap_mode_block(block)
21
+ end
22
+
23
+ private def wrap_mode_block(block)
24
+ if is_a?(Plugin)
25
+ wrap_mode_block_for_plug(self, block)
26
+ else
27
+ Proc.new do |connection|
28
+ isolated(:ModeCallContext).new(connection).instance_eval(&block)
29
+ end
30
+ end
31
+ end
32
+
33
+ private def wrap_mode_block_for_plug(plug, block)
34
+ Proc.new do |connection|
35
+ plug.helper_caller(:passive, connection, plug).instance_eval(&block)
36
+ end
37
+ end
38
+
39
+ prepend_methods do
40
+ def initialize(*)
41
+ @__ui_modes = Hash[
42
+ self.class.__ui_modes.map { |name, block|
43
+ [name, wrap_mode_block(block)]
44
+ }
45
+ ]
46
+
47
+ super
48
+ end
49
+ end
50
+
51
+ apply_extension do
52
+ class_state :__ui_modes, default: {}, inheritable: true
53
+
54
+ after "load" do
55
+ ([:html] + state(:processor).map(&:extensions).flatten).uniq.each do |extension|
56
+ config.process.watched_paths << File.join(config.presenter.path, "**/*.#{extension}")
57
+ end
58
+ end
59
+
60
+ isolate ModeCallContext
61
+
62
+ on "load" do
63
+ self.class.include_helpers :passive, isolated(:ModeCallContext)
64
+ end
65
+
66
+ if ancestors.include?(Plugin)
67
+ # Copy ui modes from other plugins to this plugin.
68
+ #
69
+ after "load" do
70
+ parent.plugs.each do |plug|
71
+ plug.__ui_modes.each do |mode, block|
72
+ unless @__ui_modes.key?(mode)
73
+ plug_namespace = plug.class.__object_name.namespace.parts.last
74
+
75
+ full_mode = if plug_namespace == :default
76
+ :"@#{plug.class.plugin_name}.#{mode}"
77
+ else
78
+ :"@#{plug.class.plugin_name}(#{plug_namespace}).#{mode}"
79
+ end
80
+
81
+ @__ui_modes[full_mode] = block
82
+ end
83
+ end
84
+ end
85
+ end
86
+ else
87
+ # Copy ui modes from plugins to the app.
88
+ #
89
+ after "load.plugins" do
90
+ plugs.each do |plug|
91
+ plug.__ui_modes.each do |mode, block|
92
+ unless @__ui_modes.key?(mode)
93
+ plug_namespace = plug.class.__object_name.namespace.parts.last
94
+
95
+ full_mode = if plug_namespace == :default
96
+ :"@#{plug.class.plugin_name}.#{mode}"
97
+ else
98
+ :"@#{plug.class.plugin_name}(#{plug_namespace}).#{mode}"
99
+ end
100
+
101
+ @__ui_modes[full_mode] = block
102
+ end
103
+ end
104
+ end
105
+ end
106
+ end
107
+ end
108
+
109
+ class_methods do
110
+ def mode(name, &block)
111
+ @__ui_modes[name.to_sym] = block
112
+ end
113
+ end
114
+ end
115
+ end
116
+ end
117
+ end
@@ -0,0 +1,71 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "pakyow/support/extension"
4
+
5
+ module Pakyow
6
+ module Presenter
7
+ module Behavior
8
+ module Versions
9
+ extend Support::Extension
10
+
11
+ apply_extension do
12
+ if ancestors.include?(Plugin)
13
+ # Copy global versioning logic from other plugins to this plugin.
14
+ #
15
+ after "load" do
16
+ presenters = [isolated(:Presenter)].concat(state(:presenter))
17
+
18
+ parent.plugs.each do |plug|
19
+ plug.isolated(:Presenter).__versioning_logic.each_pair do |version, logic_arr|
20
+ logic_arr.each do |logic|
21
+ presenters.each do |presenter|
22
+ unless presenter.__versioning_logic.include?(version)
23
+ plug_namespace = plug.class.__object_name.namespace.parts.last
24
+
25
+ prefix = if plug_namespace == :default
26
+ plug.class.plugin_name
27
+ else
28
+ "#{plug.class.plugin_name}(#{plug_namespace})"
29
+ end
30
+
31
+ presenter.version :"@#{prefix}.#{version}" do |object|
32
+ instance_exec(object, plug, &logic[:block])
33
+ end
34
+ end
35
+ end
36
+ end
37
+ end
38
+ end
39
+ end
40
+ else
41
+ # Copy global versioning logic from plugin presenters.
42
+ #
43
+ after "load.plugins" do
44
+ presenters = [isolated(:Presenter)].concat(state(:presenter))
45
+
46
+ plugs.each do |plug|
47
+ plug.isolated(:Presenter).__versioning_logic.each_pair do |version, logic_arr|
48
+ logic_arr.each do |logic|
49
+ presenters.each do |presenter|
50
+ plug_namespace = plug.class.__object_name.namespace.parts.last
51
+
52
+ prefix = if plug_namespace == :default
53
+ plug.class.plugin_name
54
+ else
55
+ "#{plug.class.plugin_name}(#{plug_namespace})"
56
+ end
57
+
58
+ presenter.version :"@#{prefix}.#{version}" do |object|
59
+ instance_exec(object, plug, &logic[:block])
60
+ end
61
+ end
62
+ end
63
+ end
64
+ end
65
+ end
66
+ end
67
+ end
68
+ end
69
+ end
70
+ end
71
+ end
@@ -0,0 +1,21 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "pakyow/support/extension"
4
+
5
+ module Pakyow
6
+ module Presenter
7
+ module Behavior
8
+ module Watching
9
+ extend Support::Extension
10
+
11
+ apply_extension do
12
+ after "load" do
13
+ ([:html] + state(:processor).map(&:extensions).flatten).uniq.each do |extension|
14
+ config.process.watched_paths << File.join(config.presenter.path, "**/*.#{extension}")
15
+ end
16
+ end
17
+ end
18
+ end
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,115 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "pakyow/support/makeable"
4
+
5
+ module Pakyow
6
+ module Presenter
7
+ # Decorates an object being bound to the view.
8
+ #
9
+ class Binder
10
+ extend Support::Makeable
11
+
12
+ # The object being bound.
13
+ #
14
+ attr_reader :object
15
+
16
+ def initialize(object, app:)
17
+ @object, @app = object, app
18
+ @parts = {}
19
+ @binding = false
20
+ @memoized = {}
21
+ end
22
+
23
+ # Defines a binding part, which binds to an aspect of the view.
24
+ #
25
+ # @example
26
+ # binder :post do
27
+ # def title(value)
28
+ # part :class do
29
+ # [:first_classname, :second_classname]
30
+ # end
31
+ #
32
+ # part :content do
33
+ # value.to_s.reverse
34
+ # end
35
+ # end
36
+ # end
37
+ #
38
+ def part(name, &block)
39
+ parts_for(caller_locations(1, 1)[0].label.to_sym).define_part(name, block)
40
+ end
41
+
42
+ # Returns the value for a key (including parts).
43
+ #
44
+ def __value(key)
45
+ if @memoized.include?(key)
46
+ @memoized[key]
47
+ else
48
+ @memoized[key] = if respond_to?(key)
49
+ value = public_send(key)
50
+
51
+ if parts?(key)
52
+ parts_for(key)
53
+ else
54
+ value
55
+ end
56
+ else
57
+ @object[key]
58
+ end
59
+ end
60
+ end
61
+
62
+ # Returns the underlying object value for a key.
63
+ #
64
+ def [](key)
65
+ @object[key]
66
+ end
67
+
68
+ # Returns only the content value for a key.
69
+ #
70
+ def __content(key, view)
71
+ return_value = __value(key)
72
+ if return_value.is_a?(BindingParts)
73
+ if return_value.content?
74
+ return_value.content(view)
75
+ else
76
+ @object[key]
77
+ end
78
+ else
79
+ return_value
80
+ end
81
+ end
82
+
83
+ # Returns +true+ if the binder might return a value for +key+.
84
+ #
85
+ def include?(key)
86
+ return false if @binding && !@object.include?(key) && (parts?(key) && !parts_for(key).content?)
87
+ respond_to?(key) || @object.include?(key)
88
+ end
89
+
90
+ # Returns +true+ if the a value is present for +key+.
91
+ #
92
+ def present?(key)
93
+ !__value(key).nil?
94
+ end
95
+
96
+ # Flips a switch, telling the binder that we now only care about content, not other parts.
97
+ # This is so that we can transform based on parts, but bind only based on content.
98
+ #
99
+ # @api private
100
+ def binding!
101
+ @binding = true
102
+ end
103
+
104
+ private
105
+
106
+ def parts?(name)
107
+ @parts.include?(name.to_sym)
108
+ end
109
+
110
+ def parts_for(name)
111
+ @parts[name] ||= BindingParts.new
112
+ end
113
+ end
114
+ end
115
+ end
@@ -0,0 +1,64 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Pakyow
4
+ module Presenter
5
+ class BindingParts
6
+ def initialize
7
+ @parts = {}
8
+ end
9
+
10
+ def define_part(name, block)
11
+ @parts[name] = block
12
+ end
13
+
14
+ def content?
15
+ @parts.include?(:content)
16
+ end
17
+
18
+ def content(view)
19
+ @parts[:content].call(view.text)
20
+ end
21
+
22
+ def values(view)
23
+ values_for_parts(@parts, view)
24
+ end
25
+
26
+ def non_content_values(view)
27
+ values_for_parts(@parts.reject { |name, _|
28
+ name == :content
29
+ }, view)
30
+ end
31
+
32
+ def reject(*parts)
33
+ parts = parts.map(&:to_sym)
34
+ @parts.delete_if { |key, _| parts.include? key }
35
+ end
36
+
37
+ def accept(*parts)
38
+ return if parts.empty?
39
+ parts = parts.map(&:to_sym)
40
+ @parts.keep_if { |key, _| parts.include? key }
41
+ end
42
+
43
+ def to_json(*)
44
+ @parts.to_json
45
+ end
46
+
47
+ private
48
+
49
+ def values_for_parts(parts, view)
50
+ Hash[parts.map { |name, block|
51
+ value = if block.arity == 0
52
+ block.call
53
+ else
54
+ view.attrs[name].tap do |current_value|
55
+ block.call(current_value)
56
+ end
57
+ end
58
+
59
+ [name, value]
60
+ }]
61
+ end
62
+ end
63
+ end
64
+ end
@@ -0,0 +1,74 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "pakyow/support/class_state"
4
+ require "pakyow/support/hookable"
5
+ require "pakyow/support/makeable"
6
+ require "pakyow/support/makeable/object_maker"
7
+
8
+ module Pakyow
9
+ module Presenter
10
+ # Reusable functionality for a component of your presentation.
11
+ #
12
+ class Component
13
+ extend Support::Makeable
14
+
15
+ extend Support::ClassState
16
+ class_state :__presenter_class, default: Presenter, inheritable: true
17
+
18
+ include Support::Hookable
19
+ events :render
20
+
21
+ # @api private
22
+ attr_reader :connection
23
+
24
+ def initialize(connection:, config: {})
25
+ @connection, @config = connection, config
26
+ end
27
+
28
+ def perform
29
+ # intentionally empty
30
+ end
31
+
32
+ private
33
+
34
+ class << self
35
+ def presenter(&block)
36
+ @__presenter_class = Class.new(@__presenter_class) do
37
+ class_eval(&block)
38
+ end
39
+
40
+ Support::ObjectMaker.define_object_on_target_with_constant_name(
41
+ @__presenter_class, self, :Presenter
42
+ )
43
+ end
44
+
45
+ def parse(string)
46
+ component, config_string = string.split("(")
47
+
48
+ {
49
+ name: component.strip.to_sym,
50
+ config: parse_config(config_string)
51
+ }
52
+ end
53
+
54
+ def parse_config(string)
55
+ if string
56
+ string.strip[0..-2].split(",").each_with_object({}) { |config_string_part, values|
57
+ key, value = config_string_part.split(":")
58
+
59
+ value = if value
60
+ value.strip
61
+ else
62
+ true
63
+ end
64
+
65
+ values[key.strip.to_sym] = value
66
+ }
67
+ else
68
+ {}
69
+ end
70
+ end
71
+ end
72
+ end
73
+ end
74
+ end
@@ -0,0 +1,78 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "string_doc"
4
+
5
+ require "pakyow/support/deep_dup"
6
+
7
+ require "pakyow/presenter/errors"
8
+ require "pakyow/presenter/view"
9
+
10
+ require "pakyow/presenter/composers/view"
11
+
12
+ module Pakyow
13
+ module Presenter
14
+ module Composers
15
+ class Component < View
16
+ using Support::DeepDup
17
+
18
+ attr_reader :component_path
19
+
20
+ def initialize(view_path, component_path, app:, labels: {})
21
+ super(view_path, app: app)
22
+ @component_path = component_path
23
+ @labels = labels
24
+ end
25
+
26
+ def key
27
+ @view_path + "::" + @component_path.join("::")
28
+ end
29
+
30
+ def post_process(view)
31
+ self.class.follow_path(@component_path, view)
32
+ end
33
+
34
+ def finalize(view)
35
+ if @labels.any?
36
+ if view.frozen?
37
+ view = view.soft_copy
38
+ end
39
+
40
+ @labels.each_pair do |key, value|
41
+ view.object.set_label(key, value)
42
+ end
43
+ end
44
+
45
+ view
46
+ end
47
+
48
+ def marshal_dump
49
+ {
50
+ app: @app,
51
+ view_path: @view_path,
52
+ component_path: @component_path
53
+ }
54
+ end
55
+
56
+ def marshal_load(state)
57
+ @labels = {}
58
+ state.each do |key, value|
59
+ instance_variable_set(:"@#{key}", value)
60
+ end
61
+ end
62
+
63
+ class << self
64
+ # Follow the path to find the correct component.
65
+ #
66
+ def follow_path(path, view)
67
+ path = path.dup
68
+ while step = path.shift
69
+ view = view.components[step]
70
+ end
71
+
72
+ view
73
+ end
74
+ end
75
+ end
76
+ end
77
+ end
78
+ end
@@ -0,0 +1,73 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "string_doc"
4
+
5
+ require "pakyow/support/class_state"
6
+ require "pakyow/support/deep_dup"
7
+ require "pakyow/support/core_refinements/string/normalization"
8
+
9
+ require "pakyow/presenter/errors"
10
+
11
+ module Pakyow
12
+ module Presenter
13
+ module Composers
14
+ class View
15
+ extend Support::ClassState
16
+ class_state :__cache, default: {}
17
+
18
+ using Support::DeepDup
19
+ using Support::Refinements::String::Normalization
20
+
21
+ attr_reader :view_path
22
+
23
+ UNRETAINED_SIGNIFICANCE = %i(container partial template).freeze
24
+
25
+ def initialize(view_path, app:)
26
+ @view_path = String.normalize_path(view_path)
27
+ @app = app
28
+ end
29
+
30
+ def key
31
+ @view_path
32
+ end
33
+
34
+ def view(return_cached: false)
35
+ cache_key = :"#{@app.config.name}__#{@view_path}"
36
+
37
+ unless view = View.__cache[cache_key]
38
+ unless info = @app.view_info_for_path(@view_path)
39
+ error = UnknownPage.new("No view at path `#{@view_path}'")
40
+ error.context = @view_path
41
+ raise error
42
+ end
43
+
44
+ info = info.deep_dup
45
+ view = info[:layout].build(info[:page]).tap { |view_without_partials|
46
+ view_without_partials.mixin(info[:partials])
47
+ }
48
+
49
+ # We collapse built views down to significance that is considered "renderable". This is
50
+ # mostly an optimization, since it lets us collapse some nodes into single strings and
51
+ # reduce the number of operations needed for a render.
52
+ #
53
+ view.object.collapse(
54
+ *(StringDoc.significant_types.keys - UNRETAINED_SIGNIFICANCE)
55
+ )
56
+
57
+ # Empty nodes are removed as another render-time optimization leading to fewer operations.
58
+ #
59
+ view.object.remove_empty_nodes
60
+
61
+ View.__cache[cache_key] = view
62
+ end
63
+
64
+ if return_cached
65
+ view
66
+ else
67
+ view.dup
68
+ end
69
+ end
70
+ end
71
+ end
72
+ end
73
+ end