pakyow-reflection 1.0.3

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.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: d9e1be2fff54bd5e80f3768b5b5dd97807f1487b6d59fd7815cde828d9d17ed7
4
+ data.tar.gz: c7210f0d4c895b4f1148a7a78ed3f6ae13bac65608bcdfce2cca997babbb6d0a
5
+ SHA512:
6
+ metadata.gz: d48551b8e337febffedfa1415d11f0e695c9510e784ea8b802cb16e1868db51ca5470c6485a9081776cce0f6944cef8862cb57ee6e0868270dbddf2ebd063db8
7
+ data.tar.gz: 89f0632f6218b457a13e6401615a7df6d45faf58fc3c03080526ff3db2599492111eff260c7b87163f476a69e9502f44d3a1ce533e1f17b248c757b671a6b61d
@@ -0,0 +1,3 @@
1
+ # v1.0.0
2
+
3
+ * Hello, Web
data/LICENSE ADDED
@@ -0,0 +1,4 @@
1
+ Copyright (c) Metabahn, LLC
2
+
3
+ Pakyow is an open-source project licensed under the terms of the LGPLv3 license.
4
+ See <https://choosealicense.com/licenses/gpl-3.0/> for license text.
@@ -0,0 +1,29 @@
1
+ # pakyow-reflection
2
+
3
+ Reflected behavior for Pakyow.
4
+
5
+ # Download
6
+
7
+ The latest version of Pakyow Reflection can be installed with RubyGems:
8
+
9
+ ```
10
+ gem install pakyow-reflection
11
+ ```
12
+
13
+ Source code can be downloaded as part of the Pakyow project on Github:
14
+
15
+ - https://github.com/pakyow/pakyow/tree/master/pakyow-reflection
16
+
17
+ # License
18
+
19
+ Pakyow Reflection is free and open-source under the [LGPLv3 license](https://choosealicense.com/licenses/gpl-3.0/).
20
+
21
+ # Support
22
+
23
+ Found a bug? Tell us about it here:
24
+
25
+ - https://github.com/pakyow/pakyow/issues
26
+
27
+ We'd love to have you in the community:
28
+
29
+ - http://pakyow.org/get-involved
@@ -0,0 +1,54 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "pakyow/support/class_state"
4
+
5
+ require "pakyow/reflection/mirror"
6
+
7
+ module Pakyow
8
+ class Application
9
+ module Behavior
10
+ module Reflection
11
+ module Reflecting
12
+ extend Support::Extension
13
+
14
+ apply_extension do
15
+ attr_reader :mirror
16
+
17
+ after "initialize", priority: :high do
18
+ @mirror = Pakyow::Reflection::Mirror.new(self)
19
+
20
+ builders = Hash[
21
+ config.reflection.builders.map { |type, builder|
22
+ [type, builder.new(self, @mirror.scopes)]
23
+ }
24
+ ]
25
+
26
+ # Build the scopes.
27
+ #
28
+ @mirror.scopes.each do |scope|
29
+ builders[:source].build(scope)
30
+ end
31
+
32
+ # Build the actions.
33
+ #
34
+ @mirror.scopes.each do |scope|
35
+ builders[:actions].build(scope.actions)
36
+ end
37
+
38
+ # Build the endpoints.
39
+ #
40
+ builders[:endpoints].build(@mirror.endpoints)
41
+
42
+ # Cleanup.
43
+ #
44
+ unless Pakyow.env?(:test)
45
+ @mirror.scopes.each(&:cleanup)
46
+ @mirror.endpoints.each(&:cleanup)
47
+ end
48
+ end
49
+ end
50
+ end
51
+ end
52
+ end
53
+ end
54
+ end
@@ -0,0 +1,33 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "pakyow/support/class_state"
4
+
5
+ require "pakyow/reflection/builders/source"
6
+ require "pakyow/reflection/builders/endpoints"
7
+ require "pakyow/reflection/builders/actions"
8
+
9
+ module Pakyow
10
+ class Application
11
+ module Config
12
+ module Reflection
13
+ extend Support::Extension
14
+
15
+ apply_extension do
16
+ configurable :reflection do
17
+ setting :builders, {
18
+ source: Pakyow::Reflection::Builders::Source,
19
+ endpoints: Pakyow::Reflection::Builders::Endpoints,
20
+ actions: Pakyow::Reflection::Builders::Actions
21
+ }
22
+
23
+ setting :ignored_template_stores, [:errors]
24
+
25
+ configurable :data do
26
+ setting :connection, :default
27
+ end
28
+ end
29
+ end
30
+ end
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,30 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "pakyow/support/extension"
4
+
5
+ module Pakyow
6
+ module Presenter
7
+ class Renderer
8
+ module Behavior
9
+ module Reflection
10
+ module InstallFormMetadata
11
+ extend Support::Extension
12
+
13
+ apply_extension do
14
+ build do |view, composer:|
15
+ forms = view.forms
16
+ if !view.object.is_a?(StringDoc) && view.object.significant?(:form)
17
+ forms << view
18
+ end
19
+
20
+ forms.each do |form|
21
+ form.label(:form)[:view_path] = composer.view_path
22
+ end
23
+ end
24
+ end
25
+ end
26
+ end
27
+ end
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,12 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "pakyow/routing"
4
+ require "pakyow/presenter"
5
+
6
+ # Load data after presenter, so that containers are created with reflected attributes.
7
+ #
8
+ require "pakyow/data"
9
+
10
+ require "pakyow/form"
11
+
12
+ require "pakyow/reflection/framework"
@@ -0,0 +1,30 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "pakyow/support/inflector"
4
+
5
+ module Pakyow
6
+ module Reflection
7
+ # @api private
8
+ class Action
9
+ attr_reader :name, :scope, :node, :view_path, :binding, :attributes, :nested, :parents
10
+
11
+ def initialize(name:, scope:, node:, view_path:, binding: nil, attributes: [], nested: [], parents: [])
12
+ @name, @scope, @node, @view_path, @binding, @attributes, @nested, @parents = normalize(name), scope, node, view_path, binding, attributes, nested, parents
13
+ end
14
+
15
+ def named?(name)
16
+ @name == normalize(name)
17
+ end
18
+
19
+ def cleanup
20
+ @node = nil
21
+ end
22
+
23
+ private
24
+
25
+ def normalize(name)
26
+ Support.inflector.singularize(name.to_s).to_sym
27
+ end
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,30 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "pakyow/support/inflector"
4
+
5
+ module Pakyow
6
+ module Reflection
7
+ # @api private
8
+ class Attribute
9
+ attr_reader :name, :type
10
+
11
+ def initialize(name, type:, required: false)
12
+ @name, @type, @required = normalize(name), type, required
13
+ end
14
+
15
+ def named?(name)
16
+ @name == normalize(name)
17
+ end
18
+
19
+ def required?
20
+ @required == true
21
+ end
22
+
23
+ private
24
+
25
+ def normalize(name)
26
+ name.to_s.to_sym
27
+ end
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,81 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "pakyow/reflection/builders/base"
4
+ require "pakyow/reflection/builders/helpers/controller"
5
+
6
+ module Pakyow
7
+ module Reflection
8
+ module Builders
9
+ # @api private
10
+ class Actions < Base
11
+ include Helpers::Controller
12
+
13
+ def build(actions)
14
+ actions.each do |action|
15
+ define_action(action)
16
+ end
17
+ end
18
+
19
+ private
20
+
21
+ def define_action(action)
22
+ if action.parents.any?
23
+ parents = action.parents.dup
24
+
25
+ current_resource = ensure_controller_has_helpers(
26
+ find_or_define_resource_for_scope_at_path(parents.shift, action.view_path)
27
+ )
28
+
29
+ parents.each do |parent|
30
+ current_resource = ensure_controller_has_helpers(
31
+ find_or_define_resource_for_scope_in_resource(parent, current_resource)
32
+ )
33
+ end
34
+
35
+ resource = find_or_define_resource_for_scope_in_resource(action.scope, current_resource)
36
+ else
37
+ resource = find_or_define_resource_for_scope_at_path(action.scope, action.view_path)
38
+ end
39
+
40
+ ensure_controller_has_helpers(resource)
41
+
42
+ # Define the route unless it exists.
43
+ #
44
+ # Actions are easy since they always go in the resource controller for
45
+ # the scope. If a nested scope, the action is defined on the nested
46
+ # resource returned by `find_or_define_resource_for_scope`.
47
+ #
48
+ route = resource.routes.values.flatten.find { |possible_route|
49
+ possible_route.name == action.name
50
+ } || resource.send(action.name) do
51
+ reflect
52
+ end
53
+
54
+ # Install the reflect action if it hasn't been installed for this route.
55
+ #
56
+ if route.name
57
+ unless action.node.labeled?(:endpoint)
58
+ form_endpoint_name = [resource.name_of_self.to_s, route.name.to_s].join("_").to_sym
59
+ action.node.significance << :endpoint
60
+ action.node.set_label(:endpoint, form_endpoint_name)
61
+ action.node.attributes[:"data-e"] = form_endpoint_name
62
+ end
63
+
64
+ resource.action :set_reflected_action, only: [route.name] do
65
+ if connection.form
66
+ form_view_path = connection.form[:view_path]
67
+ form_binding = connection.form[:binding]&.to_sym
68
+
69
+ connection.set(:__reflected_action, action.scope.actions.find { |possible_action|
70
+ possible_action.view_path == form_view_path && possible_action.binding == form_binding
71
+ })
72
+ end
73
+ end
74
+ else
75
+ # TODO: warn the user that a reflection couldn't be installed for an unnamed route
76
+ end
77
+ end
78
+ end
79
+ end
80
+ end
81
+ end
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Pakyow
4
+ module Reflection
5
+ module Builders
6
+ class Base
7
+ def initialize(app, scopes)
8
+ @app, @scopes = app, scopes
9
+ end
10
+ end
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,87 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "pakyow/support/core_refinements/string/normalization"
4
+
5
+ require "pakyow/reflection/builders/base"
6
+ require "pakyow/reflection/builders/helpers/controller"
7
+ require "pakyow/reflection/extensions/controller"
8
+
9
+ module Pakyow
10
+ module Reflection
11
+ module Builders
12
+ # @api private
13
+ class Endpoints < Base
14
+ include Helpers::Controller
15
+
16
+ using Support::Refinements::String::Normalization
17
+
18
+ def build(endpoints)
19
+ endpoints.each do |endpoint|
20
+ define_endpoint(endpoint)
21
+ end
22
+ end
23
+
24
+ private
25
+
26
+ def define_endpoint(endpoint)
27
+ controller = if within_resource?(endpoint.view_path)
28
+ find_or_define_resource_for_scope_at_path(
29
+ resource_source_at_path(endpoint.view_path),
30
+ controller_path(endpoint.view_path),
31
+ endpoint.type
32
+ )
33
+ else
34
+ find_or_define_controller_at_path(controller_path(endpoint.view_path))
35
+ end
36
+
37
+ # TODO: Make this the responsibility of the helpers.
38
+ #
39
+ ensure_controller_has_helpers(controller)
40
+
41
+ if controller.expansions.include?(:resource)
42
+ endpoint_name = String.normalize_path(
43
+ endpoint.view_path.gsub(String.collapse_path(controller.path_to_self), "")
44
+ ).split("/", 2)[1]
45
+
46
+ endpoint_name = if endpoint_name.empty?
47
+ :list
48
+ else
49
+ endpoint_name.to_sym
50
+ end
51
+
52
+ case endpoint_name
53
+ when :new, :edit, :list, :show
54
+ # Find or define the route by resource endpoint name.
55
+ #
56
+ route = controller.routes.values.flatten.find { |possible_route|
57
+ possible_route.name == endpoint_name
58
+ } || controller.send(endpoint_name) do
59
+ reflect
60
+ end
61
+ end
62
+ end
63
+
64
+ unless route
65
+ # Find or define the route by path.
66
+ #
67
+ # TODO: This should look across all controllers, not just the current one. Look through endpoints?
68
+ #
69
+ route = controller.routes.values.flatten.find { |possible_route|
70
+ possible_route.path == route_path(endpoint.view_path)
71
+ } || controller.get(route_name(endpoint.view_path), route_path(endpoint.view_path)) do
72
+ operations.reflect(controller: self)
73
+ end
74
+ end
75
+
76
+ if route.name
77
+ controller.action :set_reflected_endpoint, only: [route.name] do
78
+ connection.set(:__reflected_endpoint, endpoint)
79
+ end
80
+ else
81
+ # TODO: warn the user that a reflection couldn't be installed for an unnamed route
82
+ end
83
+ end
84
+ end
85
+ end
86
+ end
87
+ end
@@ -0,0 +1,234 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "pakyow/support/core_refinements/string/normalization"
4
+
5
+ module Pakyow
6
+ module Reflection
7
+ module Builders
8
+ module Helpers
9
+ module Controller
10
+ using Support::Refinements::String::Normalization
11
+
12
+ def find_or_define_controller_at_path(path)
13
+ controller_at_path(path) || define_controller_at_path(path)
14
+ end
15
+
16
+ def controller_at_path(path, state = @app.state(:controller))
17
+ if state.any?
18
+ state.find { |controller|
19
+ String.normalize_path(String.collapse_path(controller.path_to_self)) == String.normalize_path(path)
20
+ } || controller_at_path(path, state.flat_map(&:children))
21
+ else
22
+ nil
23
+ end
24
+ end
25
+
26
+ def controller_closest_to_path(path, state = @app.state(:controller))
27
+ if state.any?
28
+ controller_closest_to_path(path, state.flat_map(&:children)) || state.find { |controller|
29
+ String.normalize_path(path).start_with?(String.normalize_path(String.collapse_path(controller.path_to_self)))
30
+ }
31
+ else
32
+ nil
33
+ end
34
+ end
35
+
36
+ def define_controller_at_path(path, within: nil)
37
+ nested_state = if within
38
+ within.children
39
+ else
40
+ @app.state(:controller)
41
+ end
42
+
43
+ path = String.normalize_path(path)
44
+
45
+ if controller = controller_closest_to_path(path, nested_state)
46
+ context = controller
47
+ path = path.gsub(
48
+ /^#{String.normalize_path(String.collapse_path(controller.path_to_self))}/, ""
49
+ )
50
+ else
51
+ context = within || @app
52
+ end
53
+
54
+ controller_name = if path == "/"
55
+ :root
56
+ else
57
+ String.normalize_path(path)[1..-1].gsub("/", "_").to_sym
58
+ end
59
+
60
+ method = if context.is_a?(Class) && context.ancestors.include?(Pakyow::Routing::Controller)
61
+ :namespace
62
+ else
63
+ :controller
64
+ end
65
+
66
+ context.send(method, controller_name, String.normalize_path(path)) do
67
+ # intentionally empty
68
+ end
69
+ end
70
+
71
+ def controller_path(view_path)
72
+ if view_path_directory?(view_path)
73
+ view_path
74
+ else
75
+ view_path.split("/")[0..-2].to_a.join("/")
76
+ end
77
+ end
78
+
79
+ def within_resource?(view_path)
80
+ view_path.split("/").any? { |view_path_part|
81
+ @app.state(:source).any? { |source|
82
+ source.plural_name == view_path_part.to_sym
83
+ }
84
+ }
85
+ end
86
+
87
+ def route_name(view_path)
88
+ if view_path_directory?(view_path)
89
+ :default
90
+ else
91
+ view_path.split("/").last.to_sym
92
+ end
93
+ end
94
+
95
+ def route_path(view_path)
96
+ if view_path_directory?(view_path)
97
+ "/"
98
+ else
99
+ "/#{view_path.split("/").last}"
100
+ end
101
+ end
102
+
103
+ def resource_source_at_path(view_path)
104
+ view_path.split("/").reverse.each do |view_path_part|
105
+ @app.state(:source).each do |source|
106
+ if source.plural_name == view_path_part.to_sym
107
+ return source
108
+ end
109
+ end
110
+ end
111
+ end
112
+
113
+ def view_path_directory?(view_path)
114
+ @app.state(:templates).any? { |templates|
115
+ File.directory?(File.join(templates.path, templates.config[:paths][:pages], view_path))
116
+ }
117
+ end
118
+
119
+ RESOURCE_ENDPOINTS = %i(new edit list show).freeze
120
+
121
+ def find_or_define_resource_for_scope_at_path(scope, path, endpoint_type = nil)
122
+ resource = resource_for_scope_at_path(scope, path) || define_resource_for_scope_at_path(scope, path)
123
+
124
+ if path.end_with?(resource_path_for_scope(scope)) || endpoint_type.nil? || RESOURCE_ENDPOINTS.include?(path.split("/").last.to_sym)
125
+ return resource
126
+ else
127
+ controller_for_endpoint_type = resource.send(endpoint_type)
128
+
129
+ nested_path = if view_path_directory?(path)
130
+ path
131
+ else
132
+ path.split("/")[0..-2].join("/")
133
+ end
134
+
135
+ nested_path = nested_path.gsub(
136
+ /^#{String.normalize_path(String.collapse_path(controller_for_endpoint_type.path_to_self))}/, ""
137
+ )
138
+
139
+ if current_controller = controller_at_path(nested_path, resource.children)
140
+ return current_controller
141
+ else
142
+ if nested_path.empty?
143
+ controller_for_endpoint_type
144
+ else
145
+ define_controller_at_path(nested_path, within: controller_for_endpoint_type)
146
+ end
147
+ end
148
+ end
149
+ end
150
+
151
+ def resource_for_scope_at_path(scope, path, state = @app.state(:controller))
152
+ if state.any?
153
+ state.select { |controller|
154
+ controller.expansions.include?(:resource)
155
+ }.find { |controller|
156
+ String.normalize_path(String.collapse_path(controller.path_to_self)) == full_resource_path_for_scope_at_path(scope, path)
157
+ } || resource_for_scope_at_path(scope, path, state.flat_map(&:children))
158
+ else
159
+ nil
160
+ end
161
+ end
162
+
163
+ def define_resource_for_scope_at_path(scope, path)
164
+ context = if resource_namespace_path = resource_namespace_path_for_scope_at_path(scope, path)
165
+ if within_resource?(resource_namespace_path)
166
+ ensure_controller_has_helpers(
167
+ find_or_define_resource_for_scope_at_path(
168
+ resource_source_at_path(resource_namespace_path), resource_namespace_path
169
+ )
170
+ )
171
+ else
172
+ ensure_controller_has_helpers(
173
+ find_or_define_controller_at_path(resource_namespace_path)
174
+ )
175
+ end
176
+ else
177
+ @app
178
+ end
179
+
180
+ context.resource resource_name_for_scope(scope), resource_path_for_scope(scope) do
181
+ # intentionally empty
182
+ end
183
+ end
184
+
185
+ def resource_namespace_path_for_scope_at_path(scope, path)
186
+ resource_path = resource_path_for_scope(scope)
187
+
188
+ if path.start_with?(resource_path)
189
+ nil
190
+ elsif path.include?(resource_path)
191
+ path.split(resource_path, 2)[0]
192
+ end
193
+ end
194
+
195
+ def resource_name_for_scope(scope)
196
+ scope.plural_name
197
+ end
198
+
199
+ def resource_path_for_scope(scope)
200
+ String.normalize_path(scope.plural_name)
201
+ end
202
+
203
+ def ensure_controller_has_helpers(controller)
204
+ unless controller.ancestors.include?(Extension::Controller)
205
+ controller.include Extension::Controller
206
+ end
207
+
208
+ controller
209
+ end
210
+
211
+ def find_or_define_resource_for_scope_in_resource(scope, resource)
212
+ resource.children.find { |child|
213
+ child.path == resource_path_for_scope(scope)
214
+ } || resource.resource(resource_name_for_scope(scope), resource_path_for_scope(scope)) do
215
+ # intentionally empty
216
+ end
217
+ end
218
+
219
+ def full_resource_path_for_scope_at_path(scope, path)
220
+ String.normalize_path(
221
+ File.join(
222
+ resource_namespace_path_for_scope_at_path(scope, path).to_s,
223
+ scope.plural_name.to_s
224
+ )
225
+ )
226
+ end
227
+
228
+
229
+
230
+ end
231
+ end
232
+ end
233
+ end
234
+ end