pakyow-reflection 1.0.2

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 4453e5a8eb26d536324c377715c14a8759f96c0f5d931636c3aaeb1eece23f50
4
+ data.tar.gz: f8d6d5b0c543135bc22bf168c69dfebf40ee5af0ee1c6ef947a0cd674435e50e
5
+ SHA512:
6
+ metadata.gz: 83c3f37af6e1758e7f9483bd68bb7eb573e797c457e253777a0da7e3d959fdde19a07f29fbb0aabcd84688592e7e7fc0f38575d61699c841ab28871a4e798d84
7
+ data.tar.gz: c1f361a2dd6c9c5ad85af447537c363de74d7ba8286f7b70e482fcc0d9c352fb7c61c0b5dd77e329703ee07c3c503283acc8f66bc1bf5b2e41be0f9ee243f8db
@@ -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