stroma 0.3.0 → 0.4.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.
- checksums.yaml +4 -4
- data/README.md +9 -14
- data/lib/stroma/dsl/generator.rb +116 -0
- data/lib/stroma/hooks/applier.rb +27 -21
- data/lib/stroma/hooks/factory.rb +17 -18
- data/lib/stroma/matrix.rb +114 -0
- data/lib/stroma/registry.rb +58 -33
- data/lib/stroma/version.rb +1 -1
- metadata +4 -3
- data/lib/stroma/dsl.rb +0 -124
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 73bb516679b54a0f43b33efffc0719d76211dda900024976ef6398e0b0911564
|
|
4
|
+
data.tar.gz: 34301bc64ab57e7f5f3b0fbf9bd9d71d3a23887d83dd652706b5184d4d6237a4
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 2ee5cfa5a9f5f54aa8cb2b713a29cf1802abb6b98cd70f2e89eb3697652ce631e4f77017c0c8207309877e7e2289275caeabcda123d48cebd1527a0e397185d8
|
|
7
|
+
data.tar.gz: d8f0362ad411eea72508423e3a931e360924c996d88c011924a6ca0d483d45ddbdbd83bd590fc90cc5b466785bcac232c853a9dcc40fc8eefbfb154b90444180
|
data/README.md
CHANGED
|
@@ -39,32 +39,27 @@ Building modular DSLs shouldn't require reinventing the wheel. Stroma provides a
|
|
|
39
39
|
Stroma is a foundation for library authors building DSL-driven frameworks (service objects, form objects, decorators, etc.).
|
|
40
40
|
|
|
41
41
|
**Core lifecycle:**
|
|
42
|
-
1. **
|
|
43
|
-
2. **
|
|
44
|
-
3. **Extend** (optional) -
|
|
42
|
+
1. **Define** - Create a Matrix with DSL modules at boot time
|
|
43
|
+
2. **Include** - Classes include the matrix's DSL to gain all modules
|
|
44
|
+
3. **Extend** (optional) - Add cross-cutting logic via `before`/`after` hooks
|
|
45
45
|
|
|
46
46
|
## 🚀 Quick Start
|
|
47
47
|
|
|
48
48
|
### Installation
|
|
49
49
|
|
|
50
50
|
```ruby
|
|
51
|
-
spec.add_dependency "stroma", ">= 0.
|
|
51
|
+
spec.add_dependency "stroma", ">= 0.4"
|
|
52
52
|
```
|
|
53
53
|
|
|
54
54
|
### Define your library's DSL
|
|
55
55
|
|
|
56
56
|
```ruby
|
|
57
57
|
module MyLib
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
Stroma::Registry.register(:actions, MyLib::Actions::DSL)
|
|
62
|
-
Stroma::Registry.finalize!
|
|
63
|
-
|
|
64
|
-
def self.included(base)
|
|
65
|
-
base.include(Stroma::DSL)
|
|
66
|
-
end
|
|
58
|
+
STROMA = Stroma::Matrix.define(:my_lib) do
|
|
59
|
+
register :inputs, MyLib::Inputs::DSL
|
|
60
|
+
register :actions, MyLib::Actions::DSL
|
|
67
61
|
end
|
|
62
|
+
private_constant :STROMA
|
|
68
63
|
end
|
|
69
64
|
```
|
|
70
65
|
|
|
@@ -73,7 +68,7 @@ end
|
|
|
73
68
|
```ruby
|
|
74
69
|
module MyLib
|
|
75
70
|
class Base
|
|
76
|
-
include
|
|
71
|
+
include STROMA.dsl
|
|
77
72
|
end
|
|
78
73
|
end
|
|
79
74
|
```
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Stroma
|
|
4
|
+
module DSL
|
|
5
|
+
# Generates a DSL module scoped to a specific Matrix.
|
|
6
|
+
#
|
|
7
|
+
# ## Purpose
|
|
8
|
+
#
|
|
9
|
+
# Creates a module that:
|
|
10
|
+
# - Stores matrix reference on the module itself
|
|
11
|
+
# - Defines ClassMethods for service classes
|
|
12
|
+
# - Handles inheritance with state duplication
|
|
13
|
+
#
|
|
14
|
+
# Memory model:
|
|
15
|
+
# - Matrix owns @dsl_module (generated once, cached)
|
|
16
|
+
# - ServiceClass gets @stroma_matrix (same reference)
|
|
17
|
+
# - ServiceClass gets @stroma (unique State per class)
|
|
18
|
+
#
|
|
19
|
+
# ## Usage
|
|
20
|
+
#
|
|
21
|
+
# ```ruby
|
|
22
|
+
# # Called internally by Matrix#dsl
|
|
23
|
+
# dsl_module = Stroma::DSL::Generator.call(matrix)
|
|
24
|
+
#
|
|
25
|
+
# # The generated module is included in base classes
|
|
26
|
+
# class MyLib::Base
|
|
27
|
+
# include dsl_module
|
|
28
|
+
# end
|
|
29
|
+
# ```
|
|
30
|
+
#
|
|
31
|
+
# ## Integration
|
|
32
|
+
#
|
|
33
|
+
# Called by Matrix#dsl to generate the DSL module.
|
|
34
|
+
# Generated module includes all registered extensions.
|
|
35
|
+
class Generator
|
|
36
|
+
class << self
|
|
37
|
+
# Generates a DSL module for the given matrix.
|
|
38
|
+
#
|
|
39
|
+
# @param matrix [Matrix] The matrix to generate DSL for
|
|
40
|
+
# @return [Module] The generated DSL module
|
|
41
|
+
def call(matrix)
|
|
42
|
+
new(matrix).generate
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
# Creates a new generator for the given matrix.
|
|
47
|
+
#
|
|
48
|
+
# @param matrix [Matrix] The matrix to generate DSL for
|
|
49
|
+
def initialize(matrix)
|
|
50
|
+
@matrix = matrix
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
# Generates the DSL module.
|
|
54
|
+
#
|
|
55
|
+
# Creates a module with ClassMethods that provides:
|
|
56
|
+
# - stroma_matrix accessor for matrix reference
|
|
57
|
+
# - stroma accessor for per-class state
|
|
58
|
+
# - inherited hook for state duplication
|
|
59
|
+
# - extensions DSL for registering hooks
|
|
60
|
+
#
|
|
61
|
+
# @return [Module] The generated DSL module
|
|
62
|
+
def generate # rubocop:disable Metrics/MethodLength
|
|
63
|
+
matrix = @matrix
|
|
64
|
+
class_methods = build_class_methods
|
|
65
|
+
|
|
66
|
+
Module.new do
|
|
67
|
+
@stroma_matrix = matrix
|
|
68
|
+
|
|
69
|
+
class << self
|
|
70
|
+
attr_reader :stroma_matrix
|
|
71
|
+
|
|
72
|
+
def included(base)
|
|
73
|
+
mtx = stroma_matrix
|
|
74
|
+
base.extend(self::ClassMethods)
|
|
75
|
+
base.instance_variable_set(:@stroma_matrix, mtx)
|
|
76
|
+
base.instance_variable_set(:@stroma, State.new)
|
|
77
|
+
|
|
78
|
+
mtx.entries.each { |entry| base.include(entry.extension) }
|
|
79
|
+
end
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
const_set(:ClassMethods, class_methods)
|
|
83
|
+
end
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
private
|
|
87
|
+
|
|
88
|
+
# Builds the ClassMethods module.
|
|
89
|
+
#
|
|
90
|
+
# @return [Module] The ClassMethods module
|
|
91
|
+
def build_class_methods # rubocop:disable Metrics/MethodLength, Metrics/AbcSize
|
|
92
|
+
Module.new do
|
|
93
|
+
attr_reader :stroma_matrix
|
|
94
|
+
|
|
95
|
+
def stroma
|
|
96
|
+
@stroma ||= State.new
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
def inherited(child)
|
|
100
|
+
super
|
|
101
|
+
child.instance_variable_set(:@stroma_matrix, stroma_matrix)
|
|
102
|
+
child.instance_variable_set(:@stroma, stroma.dup)
|
|
103
|
+
Hooks::Applier.apply!(child, child.stroma.hooks, stroma_matrix)
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
private
|
|
107
|
+
|
|
108
|
+
def extensions(&block)
|
|
109
|
+
@stroma_hooks_factory ||= Hooks::Factory.new(stroma.hooks, stroma_matrix)
|
|
110
|
+
@stroma_hooks_factory.instance_eval(&block)
|
|
111
|
+
end
|
|
112
|
+
end
|
|
113
|
+
end
|
|
114
|
+
end
|
|
115
|
+
end
|
|
116
|
+
end
|
data/lib/stroma/hooks/applier.rb
CHANGED
|
@@ -6,31 +6,46 @@ module Stroma
|
|
|
6
6
|
#
|
|
7
7
|
# ## Purpose
|
|
8
8
|
#
|
|
9
|
-
#
|
|
10
|
-
#
|
|
11
|
-
# before hooks
|
|
9
|
+
# Includes hook extension modules into target class.
|
|
10
|
+
# Maintains order based on matrix registry entries.
|
|
11
|
+
# For each entry: before hooks first, then after hooks.
|
|
12
12
|
#
|
|
13
13
|
# ## Usage
|
|
14
14
|
#
|
|
15
15
|
# ```ruby
|
|
16
|
-
#
|
|
16
|
+
# # Called internally during class inheritance
|
|
17
|
+
# applier = Stroma::Hooks::Applier.new(ChildService, hooks, matrix)
|
|
17
18
|
# applier.apply!
|
|
18
|
-
# # ChildService now includes all hook modules
|
|
19
19
|
# ```
|
|
20
20
|
#
|
|
21
21
|
# ## Integration
|
|
22
22
|
#
|
|
23
|
-
# Called by
|
|
24
|
-
#
|
|
25
|
-
# hook application order.
|
|
23
|
+
# Called by DSL::Generator's inherited hook.
|
|
24
|
+
# Creates a temporary instance that is garbage collected after apply!.
|
|
26
25
|
class Applier
|
|
26
|
+
class << self
|
|
27
|
+
# Applies all registered hooks to the target class.
|
|
28
|
+
#
|
|
29
|
+
# Convenience class method that creates an applier and applies hooks.
|
|
30
|
+
#
|
|
31
|
+
# @param target_class [Class] The class to apply hooks to
|
|
32
|
+
# @param hooks [Collection] The hooks collection to apply
|
|
33
|
+
# @param matrix [Matrix] The matrix providing registry entries
|
|
34
|
+
# @return [void]
|
|
35
|
+
def apply!(target_class, hooks, matrix)
|
|
36
|
+
new(target_class, hooks, matrix).apply!
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
|
|
27
40
|
# Creates a new applier for applying hooks to a class.
|
|
28
41
|
#
|
|
29
42
|
# @param target_class [Class] The class to apply hooks to
|
|
30
43
|
# @param hooks [Collection] The hooks collection to apply
|
|
31
|
-
|
|
44
|
+
# @param matrix [Matrix] The matrix providing registry entries
|
|
45
|
+
def initialize(target_class, hooks, matrix)
|
|
32
46
|
@target_class = target_class
|
|
33
47
|
@hooks = hooks
|
|
48
|
+
@matrix = matrix
|
|
34
49
|
end
|
|
35
50
|
|
|
36
51
|
# Applies all registered hooks to the target class.
|
|
@@ -39,21 +54,12 @@ module Stroma
|
|
|
39
54
|
# then after hooks. Does nothing if hooks collection is empty.
|
|
40
55
|
#
|
|
41
56
|
# @return [void]
|
|
42
|
-
#
|
|
43
|
-
# @example
|
|
44
|
-
# applier.apply!
|
|
45
|
-
# # Target class now includes all extension modules
|
|
46
57
|
def apply!
|
|
47
58
|
return if @hooks.empty?
|
|
48
59
|
|
|
49
|
-
|
|
50
|
-
@hooks.before(entry.key).each
|
|
51
|
-
|
|
52
|
-
end
|
|
53
|
-
|
|
54
|
-
@hooks.after(entry.key).each do |hook|
|
|
55
|
-
@target_class.include(hook.extension)
|
|
56
|
-
end
|
|
60
|
+
@matrix.entries.each do |entry|
|
|
61
|
+
@hooks.before(entry.key).each { |hook| @target_class.include(hook.extension) }
|
|
62
|
+
@hooks.after(entry.key).each { |hook| @target_class.include(hook.extension) }
|
|
57
63
|
end
|
|
58
64
|
end
|
|
59
65
|
end
|
data/lib/stroma/hooks/factory.rb
CHANGED
|
@@ -6,21 +6,16 @@ module Stroma
|
|
|
6
6
|
#
|
|
7
7
|
# ## Purpose
|
|
8
8
|
#
|
|
9
|
-
# Provides
|
|
10
|
-
#
|
|
11
|
-
#
|
|
9
|
+
# Provides before/after DSL methods for hook registration.
|
|
10
|
+
# Validates target keys against the matrix's registry.
|
|
11
|
+
# Delegates to Hooks::Collection for storage.
|
|
12
12
|
#
|
|
13
13
|
# ## Usage
|
|
14
14
|
#
|
|
15
|
-
# Used within `extensions` block in classes that include Stroma::DSL:
|
|
16
|
-
#
|
|
17
15
|
# ```ruby
|
|
18
|
-
#
|
|
19
|
-
# class MyLib::Base
|
|
20
|
-
# include MyLib::DSL # MyLib::DSL includes Stroma::DSL
|
|
21
|
-
#
|
|
16
|
+
# class MyService < MyLib::Base
|
|
22
17
|
# extensions do
|
|
23
|
-
# before :actions, ValidationModule
|
|
18
|
+
# before :actions, ValidationModule, AuthModule
|
|
24
19
|
# after :outputs, LoggingModule
|
|
25
20
|
# end
|
|
26
21
|
# end
|
|
@@ -28,15 +23,16 @@ module Stroma
|
|
|
28
23
|
#
|
|
29
24
|
# ## Integration
|
|
30
25
|
#
|
|
31
|
-
# Created by DSL
|
|
32
|
-
#
|
|
33
|
-
# for invalid keys.
|
|
26
|
+
# Created by DSL::Generator's extensions method.
|
|
27
|
+
# Cached as @stroma_hooks_factory on each service class.
|
|
34
28
|
class Factory
|
|
35
29
|
# Creates a new factory for registering hooks.
|
|
36
30
|
#
|
|
37
31
|
# @param hooks [Collection] The hooks collection to add to
|
|
38
|
-
|
|
32
|
+
# @param matrix [Matrix] The matrix providing valid keys
|
|
33
|
+
def initialize(hooks, matrix)
|
|
39
34
|
@hooks = hooks
|
|
35
|
+
@matrix = matrix
|
|
40
36
|
end
|
|
41
37
|
|
|
42
38
|
# Registers one or more before hooks for a target key.
|
|
@@ -44,6 +40,7 @@ module Stroma
|
|
|
44
40
|
# @param key [Symbol] The registry key to hook before
|
|
45
41
|
# @param extensions [Array<Module>] Extension modules to include
|
|
46
42
|
# @raise [Exceptions::UnknownHookTarget] If key is not registered
|
|
43
|
+
# @return [void]
|
|
47
44
|
#
|
|
48
45
|
# @example
|
|
49
46
|
# before :actions, ValidationModule, AuthorizationModule
|
|
@@ -57,6 +54,7 @@ module Stroma
|
|
|
57
54
|
# @param key [Symbol] The registry key to hook after
|
|
58
55
|
# @param extensions [Array<Module>] Extension modules to include
|
|
59
56
|
# @raise [Exceptions::UnknownHookTarget] If key is not registered
|
|
57
|
+
# @return [void]
|
|
60
58
|
#
|
|
61
59
|
# @example
|
|
62
60
|
# after :outputs, LoggingModule, AuditModule
|
|
@@ -67,16 +65,17 @@ module Stroma
|
|
|
67
65
|
|
|
68
66
|
private
|
|
69
67
|
|
|
70
|
-
# Validates that the key exists in the
|
|
68
|
+
# Validates that the key exists in the matrix's registry.
|
|
71
69
|
#
|
|
72
70
|
# @param key [Symbol] The key to validate
|
|
73
71
|
# @raise [Exceptions::UnknownHookTarget] If key is not registered
|
|
72
|
+
# @return [void]
|
|
74
73
|
def validate_key!(key)
|
|
75
|
-
return if
|
|
74
|
+
return if @matrix.key?(key)
|
|
76
75
|
|
|
77
76
|
raise Exceptions::UnknownHookTarget,
|
|
78
|
-
"Unknown hook target
|
|
79
|
-
"Valid
|
|
77
|
+
"Unknown hook target #{key.inspect} for #{@matrix.name.inspect}. " \
|
|
78
|
+
"Valid: #{@matrix.keys.map(&:inspect).join(', ')}"
|
|
80
79
|
end
|
|
81
80
|
end
|
|
82
81
|
end
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Stroma
|
|
4
|
+
# Main entry point for libraries using Stroma.
|
|
5
|
+
#
|
|
6
|
+
# ## Purpose
|
|
7
|
+
#
|
|
8
|
+
# Creates an isolated registry and generates a scoped DSL module.
|
|
9
|
+
# Each matrix has its own registry - no conflicts with other libraries.
|
|
10
|
+
#
|
|
11
|
+
# Lifecycle:
|
|
12
|
+
# - Boot time: Matrix.define creates Registry, registers extensions
|
|
13
|
+
# - Boot time: finalize! freezes registry, dsl generates Module
|
|
14
|
+
# - Boot time: freeze makes Matrix immutable
|
|
15
|
+
# - Runtime: All structures frozen, no allocations
|
|
16
|
+
#
|
|
17
|
+
# ## Usage
|
|
18
|
+
#
|
|
19
|
+
# ```ruby
|
|
20
|
+
# module MyLib
|
|
21
|
+
# STROMA = Stroma::Matrix.define(:my_lib) do
|
|
22
|
+
# register :inputs, Inputs::DSL
|
|
23
|
+
# register :outputs, Outputs::DSL
|
|
24
|
+
# end
|
|
25
|
+
# private_constant :STROMA
|
|
26
|
+
# end
|
|
27
|
+
#
|
|
28
|
+
# class MyLib::Base
|
|
29
|
+
# include MyLib::STROMA.dsl
|
|
30
|
+
# end
|
|
31
|
+
# ```
|
|
32
|
+
#
|
|
33
|
+
# ## Integration
|
|
34
|
+
#
|
|
35
|
+
# Stored as a constant in the library's namespace.
|
|
36
|
+
# Owns the Registry and generates DSL module via DSL::Generator.
|
|
37
|
+
class Matrix
|
|
38
|
+
class << self
|
|
39
|
+
# Defines a new Matrix with given name.
|
|
40
|
+
#
|
|
41
|
+
# Preferred way to create a Matrix. Semantically indicates
|
|
42
|
+
# that we are defining an immutable DSL scope.
|
|
43
|
+
#
|
|
44
|
+
# @param name [Symbol, String] The matrix identifier
|
|
45
|
+
# @yield Block for registering DSL modules
|
|
46
|
+
# @return [Matrix] The frozen matrix instance
|
|
47
|
+
#
|
|
48
|
+
# @example
|
|
49
|
+
# STROMA = Stroma::Matrix.define(:my_lib) do
|
|
50
|
+
# register :inputs, Inputs::DSL
|
|
51
|
+
# register :outputs, Outputs::DSL
|
|
52
|
+
# end
|
|
53
|
+
def define(name, &block)
|
|
54
|
+
new(name, &block)
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
# @!attribute [r] name
|
|
59
|
+
# @return [Symbol] The matrix identifier
|
|
60
|
+
# @!attribute [r] registry
|
|
61
|
+
# @return [Registry] The registry of DSL modules
|
|
62
|
+
# @!attribute [r] dsl
|
|
63
|
+
# @return [Module] The DSL module to include in base classes
|
|
64
|
+
attr_reader :name, :registry, :dsl
|
|
65
|
+
|
|
66
|
+
# Creates a new Matrix with given name.
|
|
67
|
+
#
|
|
68
|
+
# Evaluates the block to register DSL modules, then finalizes
|
|
69
|
+
# the registry and freezes the matrix.
|
|
70
|
+
#
|
|
71
|
+
# @param name [Symbol, String] The matrix identifier
|
|
72
|
+
# @yield Block for registering DSL modules
|
|
73
|
+
def initialize(name, &block)
|
|
74
|
+
@name = name.to_sym
|
|
75
|
+
@registry = Registry.new(@name)
|
|
76
|
+
|
|
77
|
+
instance_eval(&block) if block_given?
|
|
78
|
+
@registry.finalize!
|
|
79
|
+
@dsl = DSL::Generator.call(self)
|
|
80
|
+
freeze
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
# Registers a DSL module with the given key.
|
|
84
|
+
#
|
|
85
|
+
# @param key [Symbol] The registry key
|
|
86
|
+
# @param extension [Module] The DSL module to register
|
|
87
|
+
# @return [void]
|
|
88
|
+
def register(key, extension)
|
|
89
|
+
@registry.register(key, extension)
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
# Returns all registered entries.
|
|
93
|
+
#
|
|
94
|
+
# @return [Array<Entry>] The registry entries
|
|
95
|
+
def entries
|
|
96
|
+
registry.entries
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
# Returns all registered keys.
|
|
100
|
+
#
|
|
101
|
+
# @return [Array<Symbol>] The registry keys
|
|
102
|
+
def keys
|
|
103
|
+
registry.keys
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
# Checks if a key is registered.
|
|
107
|
+
#
|
|
108
|
+
# @param key [Symbol] The key to check
|
|
109
|
+
# @return [Boolean] true if the key is registered
|
|
110
|
+
def key?(key)
|
|
111
|
+
registry.key?(key)
|
|
112
|
+
end
|
|
113
|
+
end
|
|
114
|
+
end
|
data/lib/stroma/registry.rb
CHANGED
|
@@ -1,63 +1,71 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
module Stroma
|
|
4
|
-
# Manages
|
|
4
|
+
# Manages registration of DSL modules for a specific matrix.
|
|
5
5
|
#
|
|
6
6
|
# ## Purpose
|
|
7
7
|
#
|
|
8
|
-
#
|
|
9
|
-
#
|
|
10
|
-
#
|
|
8
|
+
# Stores DSL module entries with their keys.
|
|
9
|
+
# Implements two-phase lifecycle: registration → finalization.
|
|
10
|
+
# Each Matrix has its own Registry - no global state.
|
|
11
11
|
#
|
|
12
12
|
# ## Usage
|
|
13
13
|
#
|
|
14
14
|
# ```ruby
|
|
15
|
-
#
|
|
16
|
-
#
|
|
17
|
-
#
|
|
18
|
-
#
|
|
15
|
+
# registry = Stroma::Registry.new(:my_lib)
|
|
16
|
+
# registry.register(:inputs, Inputs::DSL)
|
|
17
|
+
# registry.register(:outputs, Outputs::DSL)
|
|
18
|
+
# registry.finalize!
|
|
19
19
|
#
|
|
20
|
-
# #
|
|
21
|
-
#
|
|
22
|
-
# Stroma::Registry.key?(:inputs) # => true
|
|
20
|
+
# registry.keys # => [:inputs, :outputs]
|
|
21
|
+
# registry.key?(:inputs) # => true
|
|
23
22
|
# ```
|
|
24
23
|
#
|
|
25
24
|
# ## Integration
|
|
26
25
|
#
|
|
27
|
-
#
|
|
28
|
-
#
|
|
29
|
-
#
|
|
30
|
-
# ## Thread Safety
|
|
31
|
-
#
|
|
32
|
-
# Registration must occur during single-threaded boot phase.
|
|
33
|
-
# After finalization, all read operations are thread-safe.
|
|
26
|
+
# Created and owned by Matrix.
|
|
27
|
+
# Entries are accessed via Matrix#entries and Matrix#keys.
|
|
34
28
|
class Registry
|
|
35
|
-
|
|
29
|
+
# @!attribute [r] matrix_name
|
|
30
|
+
# @return [Symbol] The name of the owning matrix
|
|
31
|
+
attr_reader :matrix_name
|
|
36
32
|
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
:key?,
|
|
43
|
-
to: :instance
|
|
44
|
-
end
|
|
45
|
-
|
|
46
|
-
def initialize
|
|
33
|
+
# Creates a new registry for the given matrix.
|
|
34
|
+
#
|
|
35
|
+
# @param matrix_name [Symbol, String] The matrix identifier
|
|
36
|
+
def initialize(matrix_name)
|
|
37
|
+
@matrix_name = matrix_name.to_sym
|
|
47
38
|
@entries = []
|
|
48
39
|
@finalized = false
|
|
49
40
|
end
|
|
50
41
|
|
|
42
|
+
# Registers a DSL module with the given key.
|
|
43
|
+
#
|
|
44
|
+
# @param key [Symbol, String] The registry key
|
|
45
|
+
# @param extension [Module] The DSL module to register
|
|
46
|
+
# @raise [Exceptions::RegistryFrozen] If registry is finalized
|
|
47
|
+
# @raise [Exceptions::KeyAlreadyRegistered] If key already exists
|
|
48
|
+
# @return [void]
|
|
51
49
|
def register(key, extension)
|
|
52
|
-
|
|
50
|
+
if @finalized
|
|
51
|
+
raise Exceptions::RegistryFrozen,
|
|
52
|
+
"Registry for #{@matrix_name.inspect} is finalized"
|
|
53
|
+
end
|
|
53
54
|
|
|
55
|
+
key = key.to_sym
|
|
54
56
|
if @entries.any? { |e| e.key == key }
|
|
55
|
-
raise Exceptions::KeyAlreadyRegistered,
|
|
57
|
+
raise Exceptions::KeyAlreadyRegistered,
|
|
58
|
+
"Key #{key.inspect} already registered in #{@matrix_name.inspect}"
|
|
56
59
|
end
|
|
57
60
|
|
|
58
61
|
@entries << Entry.new(key:, extension:)
|
|
59
62
|
end
|
|
60
63
|
|
|
64
|
+
# Finalizes the registry, preventing further registrations.
|
|
65
|
+
#
|
|
66
|
+
# Idempotent - can be called multiple times safely.
|
|
67
|
+
#
|
|
68
|
+
# @return [void]
|
|
61
69
|
def finalize!
|
|
62
70
|
return if @finalized
|
|
63
71
|
|
|
@@ -65,28 +73,45 @@ module Stroma
|
|
|
65
73
|
@finalized = true
|
|
66
74
|
end
|
|
67
75
|
|
|
76
|
+
# Returns all registered entries.
|
|
77
|
+
#
|
|
78
|
+
# @raise [Exceptions::RegistryNotFinalized] If not finalized
|
|
79
|
+
# @return [Array<Entry>] The registry entries
|
|
68
80
|
def entries
|
|
69
81
|
ensure_finalized!
|
|
70
82
|
@entries
|
|
71
83
|
end
|
|
72
84
|
|
|
85
|
+
# Returns all registered keys.
|
|
86
|
+
#
|
|
87
|
+
# @raise [Exceptions::RegistryNotFinalized] If not finalized
|
|
88
|
+
# @return [Array<Symbol>] The registry keys
|
|
73
89
|
def keys
|
|
74
90
|
ensure_finalized!
|
|
75
91
|
@entries.map(&:key)
|
|
76
92
|
end
|
|
77
93
|
|
|
94
|
+
# Checks if a key is registered.
|
|
95
|
+
#
|
|
96
|
+
# @param key [Symbol, String] The key to check
|
|
97
|
+
# @raise [Exceptions::RegistryNotFinalized] If not finalized
|
|
98
|
+
# @return [Boolean] true if the key is registered
|
|
78
99
|
def key?(key)
|
|
79
100
|
ensure_finalized!
|
|
80
|
-
@entries.any? { |e| e.key == key }
|
|
101
|
+
@entries.any? { |e| e.key == key.to_sym }
|
|
81
102
|
end
|
|
82
103
|
|
|
83
104
|
private
|
|
84
105
|
|
|
106
|
+
# Ensures the registry is finalized.
|
|
107
|
+
#
|
|
108
|
+
# @raise [Exceptions::RegistryNotFinalized] If not finalized
|
|
109
|
+
# @return [void]
|
|
85
110
|
def ensure_finalized!
|
|
86
111
|
return if @finalized
|
|
87
112
|
|
|
88
113
|
raise Exceptions::RegistryNotFinalized,
|
|
89
|
-
"Registry
|
|
114
|
+
"Registry for #{@matrix_name.inspect} not finalized"
|
|
90
115
|
end
|
|
91
116
|
end
|
|
92
117
|
end
|
data/lib/stroma/version.rb
CHANGED
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: stroma
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.4.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Anton Sokolov
|
|
@@ -132,7 +132,7 @@ files:
|
|
|
132
132
|
- README.md
|
|
133
133
|
- Rakefile
|
|
134
134
|
- lib/stroma.rb
|
|
135
|
-
- lib/stroma/dsl.rb
|
|
135
|
+
- lib/stroma/dsl/generator.rb
|
|
136
136
|
- lib/stroma/engine.rb
|
|
137
137
|
- lib/stroma/entry.rb
|
|
138
138
|
- lib/stroma/exceptions/base.rb
|
|
@@ -145,6 +145,7 @@ files:
|
|
|
145
145
|
- lib/stroma/hooks/collection.rb
|
|
146
146
|
- lib/stroma/hooks/factory.rb
|
|
147
147
|
- lib/stroma/hooks/hook.rb
|
|
148
|
+
- lib/stroma/matrix.rb
|
|
148
149
|
- lib/stroma/registry.rb
|
|
149
150
|
- lib/stroma/settings/collection.rb
|
|
150
151
|
- lib/stroma/settings/registry_settings.rb
|
|
@@ -173,7 +174,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
173
174
|
- !ruby/object:Gem::Version
|
|
174
175
|
version: '0'
|
|
175
176
|
requirements: []
|
|
176
|
-
rubygems_version: 3.
|
|
177
|
+
rubygems_version: 3.6.9
|
|
177
178
|
specification_version: 4
|
|
178
179
|
summary: Foundation for building modular, extensible DSLs
|
|
179
180
|
test_files: []
|
data/lib/stroma/dsl.rb
DELETED
|
@@ -1,124 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
module Stroma
|
|
4
|
-
# Main integration point between Stroma and service classes.
|
|
5
|
-
#
|
|
6
|
-
# ## Purpose
|
|
7
|
-
#
|
|
8
|
-
# Module that provides the core Stroma functionality to service classes:
|
|
9
|
-
# - Includes all registered DSL modules
|
|
10
|
-
# - Provides extensions block for hook registration
|
|
11
|
-
# - Handles inheritance with proper state copying
|
|
12
|
-
#
|
|
13
|
-
# ## Usage
|
|
14
|
-
#
|
|
15
|
-
# Library authors create a DSL module that includes Stroma::DSL:
|
|
16
|
-
#
|
|
17
|
-
# ```ruby
|
|
18
|
-
# module MyLib::DSL
|
|
19
|
-
# def self.included(base)
|
|
20
|
-
# base.include(Stroma::DSL)
|
|
21
|
-
# end
|
|
22
|
-
# end
|
|
23
|
-
#
|
|
24
|
-
# class MyLib::Base
|
|
25
|
-
# include MyLib::DSL
|
|
26
|
-
#
|
|
27
|
-
# extensions do
|
|
28
|
-
# before :actions, MyExtension
|
|
29
|
-
# end
|
|
30
|
-
# end
|
|
31
|
-
# ```
|
|
32
|
-
#
|
|
33
|
-
# ## Extension Settings Access
|
|
34
|
-
#
|
|
35
|
-
# Extensions access their settings through the stroma.settings hierarchy:
|
|
36
|
-
#
|
|
37
|
-
# ```ruby
|
|
38
|
-
# # In ClassMethods:
|
|
39
|
-
# stroma.settings[:actions][:authorization][:method_name] = :authorize
|
|
40
|
-
#
|
|
41
|
-
# # In InstanceMethods:
|
|
42
|
-
# self.class.stroma.settings[:actions][:authorization][:method_name]
|
|
43
|
-
# ```
|
|
44
|
-
#
|
|
45
|
-
# ## Integration
|
|
46
|
-
#
|
|
47
|
-
# Included by service classes that want Stroma hook functionality.
|
|
48
|
-
# Provides ClassMethods with: stroma, inherited, extensions.
|
|
49
|
-
module DSL
|
|
50
|
-
def self.included(base)
|
|
51
|
-
base.extend(ClassMethods)
|
|
52
|
-
|
|
53
|
-
Registry.entries.each do |entry|
|
|
54
|
-
base.include(entry.extension)
|
|
55
|
-
end
|
|
56
|
-
end
|
|
57
|
-
|
|
58
|
-
# Class-level methods for Stroma integration.
|
|
59
|
-
#
|
|
60
|
-
# ## Purpose
|
|
61
|
-
#
|
|
62
|
-
# Provides access to Stroma state and hooks DSL at the class level.
|
|
63
|
-
# Handles proper duplication during inheritance.
|
|
64
|
-
#
|
|
65
|
-
# ## Key Methods
|
|
66
|
-
#
|
|
67
|
-
# - `stroma` - Access the State container
|
|
68
|
-
# - `inherited` - Copy state to child classes
|
|
69
|
-
# - `extensions` - DSL block for hook registration
|
|
70
|
-
module ClassMethods
|
|
71
|
-
def self.extended(base)
|
|
72
|
-
base.instance_variable_set(:@stroma, State.new)
|
|
73
|
-
end
|
|
74
|
-
|
|
75
|
-
# Handles inheritance by duplicating Stroma state.
|
|
76
|
-
#
|
|
77
|
-
# Creates an independent copy of hooks and settings for the child class,
|
|
78
|
-
# then applies all registered hooks to the child.
|
|
79
|
-
#
|
|
80
|
-
# @param child [Class] The child class being created
|
|
81
|
-
# @return [void]
|
|
82
|
-
def inherited(child)
|
|
83
|
-
super
|
|
84
|
-
|
|
85
|
-
child.instance_variable_set(:@stroma, stroma.dup)
|
|
86
|
-
|
|
87
|
-
Hooks::Applier.new(child, child.stroma.hooks).apply!
|
|
88
|
-
end
|
|
89
|
-
|
|
90
|
-
# Returns the Stroma state for this service class.
|
|
91
|
-
#
|
|
92
|
-
# @return [State] The Stroma state container
|
|
93
|
-
#
|
|
94
|
-
# @example Accessing hooks
|
|
95
|
-
# stroma.hooks.before(:actions)
|
|
96
|
-
#
|
|
97
|
-
# @example Accessing settings
|
|
98
|
-
# stroma.settings[:actions][:authorization][:method_name]
|
|
99
|
-
def stroma
|
|
100
|
-
@stroma ||= State.new
|
|
101
|
-
end
|
|
102
|
-
|
|
103
|
-
private
|
|
104
|
-
|
|
105
|
-
# DSL block for registering hooks.
|
|
106
|
-
#
|
|
107
|
-
# Evaluates the block in the context of a Hooks::Factory,
|
|
108
|
-
# allowing before/after hook registration.
|
|
109
|
-
#
|
|
110
|
-
# @yield Block with before/after DSL calls
|
|
111
|
-
# @return [void]
|
|
112
|
-
#
|
|
113
|
-
# @example
|
|
114
|
-
# extensions do
|
|
115
|
-
# before :actions, AuthorizationExtension
|
|
116
|
-
# after :outputs, LoggingExtension
|
|
117
|
-
# end
|
|
118
|
-
def extensions(&block)
|
|
119
|
-
@stroma_hooks_factory ||= Hooks::Factory.new(stroma.hooks)
|
|
120
|
-
@stroma_hooks_factory.instance_eval(&block)
|
|
121
|
-
end
|
|
122
|
-
end
|
|
123
|
-
end
|
|
124
|
-
end
|