spec_forge 0.7.1 → 1.0.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/CHANGELOG.md +75 -1
- data/README.md +124 -202
- data/bin/spec_forge +1 -1
- data/flake.lock +76 -4
- data/flake.nix +5 -4
- data/lib/spec_forge/attribute/chainable.rb +6 -6
- data/lib/spec_forge/attribute/environment.rb +45 -0
- data/lib/spec_forge/attribute/factory.rb +26 -17
- data/lib/spec_forge/attribute/faker.rb +6 -1
- data/lib/spec_forge/attribute/generate.rb +114 -0
- data/lib/spec_forge/attribute/literal.rb +1 -14
- data/lib/spec_forge/attribute/matcher.rb +6 -2
- data/lib/spec_forge/attribute/parameterized.rb +20 -22
- data/lib/spec_forge/attribute/resolvable_array.rb +16 -16
- data/lib/spec_forge/attribute/resolvable_hash.rb +17 -16
- data/lib/spec_forge/attribute/resolvable_struct.rb +67 -0
- data/lib/spec_forge/attribute/template.rb +118 -0
- data/lib/spec_forge/attribute/transform.rb +14 -19
- data/lib/spec_forge/attribute/variable.rb +31 -31
- data/lib/spec_forge/attribute.rb +54 -100
- data/lib/spec_forge/blueprint.rb +27 -0
- data/lib/spec_forge/cli/docs/generate.rb +28 -8
- data/lib/spec_forge/cli/docs.rb +5 -2
- data/lib/spec_forge/cli/init.rb +4 -4
- data/lib/spec_forge/cli/new.rb +78 -27
- data/lib/spec_forge/cli/run.rb +84 -52
- data/lib/spec_forge/cli/serve.rb +5 -0
- data/lib/spec_forge/cli.rb +6 -14
- data/lib/spec_forge/configuration.rb +209 -79
- data/lib/spec_forge/documentation/{loader → builder}/cache.rb +26 -23
- data/lib/spec_forge/documentation/builder/compiler.rb +373 -0
- data/lib/spec_forge/documentation/builder/extractor.rb +75 -0
- data/lib/spec_forge/documentation/builder.rb +77 -329
- data/lib/spec_forge/documentation/document/operation.rb +4 -4
- data/lib/spec_forge/documentation/document.rb +0 -6
- data/lib/spec_forge/documentation/generator.rb +88 -0
- data/lib/spec_forge/documentation/{generators/openapi → openapi/v3_0}/error_formatter.rb +2 -2
- data/lib/spec_forge/documentation/openapi/v3_0/example.rb +1 -1
- data/lib/spec_forge/documentation/openapi/v3_0/media_type.rb +1 -1
- data/lib/spec_forge/documentation/openapi/v3_0/operation.rb +21 -5
- data/lib/spec_forge/documentation/openapi/v3_0/response.rb +28 -6
- data/lib/spec_forge/documentation/openapi/v3_0/schema.rb +20 -2
- data/lib/spec_forge/documentation/openapi/v3_0/tag.rb +1 -1
- data/lib/spec_forge/documentation/openapi/v3_0.rb +116 -0
- data/lib/spec_forge/documentation/openapi.rb +40 -12
- data/lib/spec_forge/documentation.rb +1 -7
- data/lib/spec_forge/error.rb +215 -41
- data/lib/spec_forge/factory.rb +38 -18
- data/lib/spec_forge/forge/action.rb +41 -0
- data/lib/spec_forge/forge/actions/call.rb +33 -0
- data/lib/spec_forge/forge/actions/debug.rb +47 -0
- data/lib/spec_forge/forge/actions/expect.rb +44 -0
- data/lib/spec_forge/forge/actions/request.rb +65 -0
- data/lib/spec_forge/forge/actions/store.rb +31 -0
- data/lib/spec_forge/forge/callbacks.rb +80 -0
- data/lib/spec_forge/forge/context.rb +41 -0
- data/lib/spec_forge/forge/display.rb +503 -0
- data/lib/spec_forge/forge/hooks.rb +131 -0
- data/lib/spec_forge/forge/runner/array_io.rb +81 -0
- data/lib/spec_forge/forge/runner/content_validator.rb +92 -0
- data/lib/spec_forge/forge/runner/header_validator.rb +66 -0
- data/lib/spec_forge/forge/runner/reporter.rb +56 -0
- data/lib/spec_forge/forge/runner/schema_validator.rb +113 -0
- data/lib/spec_forge/forge/runner.rb +118 -0
- data/lib/spec_forge/forge/timer.rb +94 -0
- data/lib/spec_forge/forge/variables.rb +38 -0
- data/lib/spec_forge/forge.rb +207 -133
- data/lib/spec_forge/http/backend.rb +49 -146
- data/lib/spec_forge/http/client.rb +14 -17
- data/lib/spec_forge/http/request.rb +37 -84
- data/lib/spec_forge/http/verb.rb +4 -0
- data/lib/spec_forge/http.rb +0 -5
- data/lib/spec_forge/loader/filter.rb +85 -0
- data/lib/spec_forge/loader/step_processor.rb +282 -0
- data/lib/spec_forge/loader.rb +105 -220
- data/lib/spec_forge/normalizer/default.rb +1 -1
- data/lib/spec_forge/normalizer/structure.rb +140 -0
- data/lib/spec_forge/normalizer/transformers.rb +168 -0
- data/lib/spec_forge/normalizer/validators.rb +50 -8
- data/lib/spec_forge/normalizer.rb +76 -119
- data/lib/spec_forge/normalizers/callback.yml +38 -0
- data/lib/spec_forge/normalizers/configuration.yml +59 -9
- data/lib/spec_forge/normalizers/factory.yml +53 -2
- data/lib/spec_forge/normalizers/factory_reference.yml +63 -2
- data/lib/spec_forge/normalizers/json_schema.yml +79 -0
- data/lib/spec_forge/normalizers/step.yml +506 -0
- data/lib/spec_forge/step/call.rb +36 -0
- data/lib/spec_forge/step/expect.rb +110 -0
- data/lib/spec_forge/step/source.rb +22 -0
- data/lib/spec_forge/step.rb +129 -0
- data/lib/spec_forge/type.rb +115 -66
- data/lib/spec_forge/version.rb +1 -1
- data/lib/spec_forge.rb +44 -106
- data/lib/templates/forge_helper.rb.tt +43 -22
- data/lib/templates/new_blueprint.yml.tt +54 -0
- metadata +75 -44
- data/lib/spec_forge/attribute/global.rb +0 -96
- data/lib/spec_forge/attribute/store.rb +0 -65
- data/lib/spec_forge/backtrace_formatter.rb +0 -50
- data/lib/spec_forge/callbacks.rb +0 -88
- data/lib/spec_forge/context/callbacks.rb +0 -91
- data/lib/spec_forge/context/global.rb +0 -72
- data/lib/spec_forge/context/store.rb +0 -131
- data/lib/spec_forge/context/variables.rb +0 -91
- data/lib/spec_forge/context.rb +0 -36
- data/lib/spec_forge/core_ext/rspec.rb +0 -55
- data/lib/spec_forge/core_ext.rb +0 -5
- data/lib/spec_forge/documentation/generators/base.rb +0 -81
- data/lib/spec_forge/documentation/generators/openapi/base.rb +0 -100
- data/lib/spec_forge/documentation/generators/openapi/v3_0.rb +0 -65
- data/lib/spec_forge/documentation/generators/openapi.rb +0 -59
- data/lib/spec_forge/documentation/generators.rb +0 -17
- data/lib/spec_forge/documentation/loader.rb +0 -159
- data/lib/spec_forge/documentation/openapi/base.rb +0 -33
- data/lib/spec_forge/filter.rb +0 -86
- data/lib/spec_forge/normalizer/definition.rb +0 -248
- data/lib/spec_forge/normalizers/_shared.yml +0 -76
- data/lib/spec_forge/normalizers/constraint.yml +0 -8
- data/lib/spec_forge/normalizers/expectation.yml +0 -47
- data/lib/spec_forge/normalizers/global_context.yml +0 -28
- data/lib/spec_forge/normalizers/spec.yml +0 -50
- data/lib/spec_forge/runner/adapter.rb +0 -181
- data/lib/spec_forge/runner/callbacks.rb +0 -246
- data/lib/spec_forge/runner/debug_proxy.rb +0 -215
- data/lib/spec_forge/runner/listener.rb +0 -54
- data/lib/spec_forge/runner/metadata.rb +0 -58
- data/lib/spec_forge/runner/state.rb +0 -98
- data/lib/spec_forge/runner.rb +0 -75
- data/lib/spec_forge/spec/expectation/constraint.rb +0 -127
- data/lib/spec_forge/spec/expectation.rb +0 -68
- data/lib/spec_forge/spec.rb +0 -68
- data/lib/templates/new_spec.yml.tt +0 -43
|
@@ -2,133 +2,263 @@
|
|
|
2
2
|
|
|
3
3
|
module SpecForge
|
|
4
4
|
#
|
|
5
|
-
#
|
|
6
|
-
# Defines default values and validation for all configuration options
|
|
5
|
+
# Holds configuration options for SpecForge
|
|
7
6
|
#
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
7
|
+
# Configuration is typically set in forge_helper.rb using the configure block.
|
|
8
|
+
# It controls base URL, global variables, factory settings, callbacks, and more.
|
|
9
|
+
#
|
|
10
|
+
# @example Basic configuration
|
|
11
|
+
# SpecForge.configure do |config|
|
|
12
|
+
# config.base_url = "http://localhost:3000"
|
|
13
|
+
# config.global_variables = {api_version: "v1"}
|
|
14
|
+
# end
|
|
15
|
+
#
|
|
16
|
+
class Configuration
|
|
12
17
|
#
|
|
13
|
-
#
|
|
14
|
-
# config.factories.auto_discover = false
|
|
15
|
-
# config.factories.paths += ["lib/factories"]
|
|
18
|
+
# Configuration for FactoryBot factory loading
|
|
16
19
|
#
|
|
17
20
|
class Factories < Struct.new(:auto_discover, :paths)
|
|
18
|
-
#
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
#
|
|
22
|
-
attr_predicate :
|
|
23
|
-
|
|
24
|
-
#
|
|
25
|
-
# Initializes a new Factories configuration
|
|
26
|
-
# Sets default values for auto-discovery and paths
|
|
27
|
-
#
|
|
28
|
-
# @param auto_discover [Boolean] Whether to auto-discover factories (default: true)
|
|
29
|
-
# @param paths [Array<String>] Additional paths to look for factories (default: [])
|
|
30
|
-
#
|
|
31
|
-
# @return [Factories] A new factories configuration instance
|
|
32
|
-
#
|
|
21
|
+
# @return [Boolean] Whether auto-discovery is enabled
|
|
22
|
+
attr_predicate :auto_discover
|
|
23
|
+
|
|
24
|
+
# @return [Array<String>] Factory file paths
|
|
25
|
+
attr_predicate :paths
|
|
26
|
+
|
|
33
27
|
def initialize(auto_discover: true, paths: []) = super
|
|
34
28
|
end
|
|
35
29
|
|
|
30
|
+
# @return [String] Base URL for HTTP requests
|
|
31
|
+
attr_accessor :base_url
|
|
32
|
+
|
|
33
|
+
# @return [Hash] Global variables available to all blueprints
|
|
34
|
+
attr_accessor :global_variables
|
|
35
|
+
|
|
36
|
+
# @return [Factories] Factory configuration
|
|
37
|
+
attr_reader :factories
|
|
38
|
+
|
|
39
|
+
# @return [Proc, nil] Debug handler proc
|
|
40
|
+
attr_reader :on_debug_proc
|
|
41
|
+
|
|
42
|
+
# @return [Hash{Symbol => Proc}] Registered callbacks
|
|
43
|
+
attr_reader :callbacks
|
|
44
|
+
|
|
45
|
+
# @return [Hash{Symbol => Array}] Global lifecycle hooks for forge, blueprint, and step events
|
|
46
|
+
attr_reader :hooks
|
|
47
|
+
|
|
36
48
|
#
|
|
37
|
-
#
|
|
38
|
-
# Sets up the configuration structure including factory settings and debug proxy
|
|
49
|
+
# Creates a new Configuration with default values
|
|
39
50
|
#
|
|
40
|
-
# @return [Configuration] A new configuration instance
|
|
51
|
+
# @return [Configuration] A new configuration instance
|
|
41
52
|
#
|
|
42
53
|
def initialize
|
|
43
|
-
|
|
54
|
+
# Validated
|
|
55
|
+
@base_url = "http://localhost:3000"
|
|
56
|
+
@factories = Factories.new
|
|
57
|
+
@global_variables = {}
|
|
44
58
|
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
59
|
+
# Internal
|
|
60
|
+
@on_debug_proc = nil
|
|
61
|
+
@callbacks = {}
|
|
62
|
+
@hooks = {
|
|
63
|
+
before_forge: [],
|
|
64
|
+
before_blueprint: [],
|
|
65
|
+
before_step: [],
|
|
66
|
+
after_step: [],
|
|
67
|
+
after_blueprint: [],
|
|
68
|
+
after_forge: []
|
|
69
|
+
}
|
|
50
70
|
end
|
|
51
71
|
|
|
52
72
|
#
|
|
53
|
-
# Validates the configuration and
|
|
54
|
-
# Ensures all required fields have values and applies defaults when needed
|
|
73
|
+
# Validates the configuration and normalizes values
|
|
55
74
|
#
|
|
56
|
-
# @return [
|
|
75
|
+
# @return [Configuration] self
|
|
57
76
|
#
|
|
58
|
-
# @
|
|
77
|
+
# @raise [Error::InvalidStructureError] If configuration is invalid
|
|
59
78
|
#
|
|
60
79
|
def validate
|
|
61
|
-
output = Normalizer.normalize!(
|
|
80
|
+
output = Normalizer.normalize!(
|
|
81
|
+
{
|
|
82
|
+
base_url: @base_url,
|
|
83
|
+
factories: @factories.to_h,
|
|
84
|
+
global_variables: @global_variables
|
|
85
|
+
},
|
|
86
|
+
using: :configuration
|
|
87
|
+
)
|
|
62
88
|
|
|
63
89
|
# In case any value was set to `nil`
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
self.headers = output[:headers] if headers.blank?
|
|
90
|
+
@global_variables = output[:global_variables] if @global_variables.blank?
|
|
91
|
+
@global_variables.symbolize_keys!
|
|
67
92
|
|
|
68
93
|
self
|
|
69
94
|
end
|
|
70
95
|
|
|
96
|
+
#
|
|
97
|
+
# Sets a debug handler block to be called when a step has debug: true
|
|
98
|
+
#
|
|
99
|
+
# @yield [context] Block called when debug is triggered
|
|
100
|
+
# @yieldparam context [Forge::Context] The current execution context
|
|
101
|
+
#
|
|
102
|
+
# @example
|
|
103
|
+
# config.on_debug { binding.pry }
|
|
104
|
+
#
|
|
71
105
|
def on_debug(&block)
|
|
72
|
-
|
|
106
|
+
@on_debug_proc = block
|
|
73
107
|
end
|
|
74
108
|
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
109
|
+
#
|
|
110
|
+
# Returns RSpec's configuration for customization
|
|
111
|
+
#
|
|
112
|
+
# @return [RSpec::Core::Configuration] RSpec configuration
|
|
113
|
+
#
|
|
114
|
+
def rspec
|
|
115
|
+
RSpec.configuration
|
|
78
116
|
end
|
|
79
117
|
|
|
80
118
|
#
|
|
81
|
-
#
|
|
119
|
+
# Registers a callback that can be invoked from blueprints using call:
|
|
82
120
|
#
|
|
83
|
-
# @
|
|
121
|
+
# @param name [String, Symbol] The callback name to register
|
|
84
122
|
#
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
123
|
+
# @yield [context, *args] Block to execute when callback is called
|
|
124
|
+
# @yieldparam context [Forge::Context] The current execution context
|
|
125
|
+
#
|
|
126
|
+
# @example Simple callback
|
|
127
|
+
# config.register_callback("seed_data") do |context|
|
|
128
|
+
# User.create!(name: "Test")
|
|
129
|
+
# end
|
|
130
|
+
#
|
|
131
|
+
# @example Callback with arguments
|
|
132
|
+
# config.register_callback("create_users") do |context, count:|
|
|
133
|
+
# count.times { User.create! }
|
|
134
|
+
# end
|
|
135
|
+
#
|
|
136
|
+
def register_callback(name, &block)
|
|
137
|
+
@callbacks[name.to_sym] = block
|
|
89
138
|
end
|
|
90
139
|
|
|
91
140
|
#
|
|
92
|
-
#
|
|
93
|
-
# Provides access to RSpec's internal configuration for test customization
|
|
141
|
+
# Removes a registered callback and detaches it from all lifecycle hooks
|
|
94
142
|
#
|
|
95
|
-
# @
|
|
143
|
+
# @param name [String, Symbol] The callback name to remove
|
|
96
144
|
#
|
|
97
|
-
# @
|
|
98
|
-
# SpecForge.configure do |config|
|
|
99
|
-
# config.specs.formatter = :documentation
|
|
100
|
-
# end
|
|
145
|
+
# @return [Proc, nil] The removed callback proc, or nil if not found
|
|
101
146
|
#
|
|
102
|
-
|
|
103
|
-
|
|
147
|
+
# @example Remove a callback
|
|
148
|
+
# config.register_callback(:my_hook) { |context| puts "hook" }
|
|
149
|
+
# config.before(:step, :my_hook)
|
|
150
|
+
# config.deregister_callback(:my_hook) # Removes from callbacks and hooks
|
|
151
|
+
#
|
|
152
|
+
def deregister_callback(name)
|
|
153
|
+
name = name.to_sym
|
|
154
|
+
|
|
155
|
+
callback = @callbacks.delete(name)
|
|
156
|
+
@hooks.each_value { |a| a.delete(name) }
|
|
157
|
+
|
|
158
|
+
callback
|
|
104
159
|
end
|
|
105
160
|
|
|
106
|
-
|
|
161
|
+
#
|
|
162
|
+
# Attaches a callback to a before lifecycle event
|
|
163
|
+
#
|
|
164
|
+
# Global hooks run for all blueprints and execute in registration order.
|
|
165
|
+
# Can either reference a pre-registered callback by name, or accept a block
|
|
166
|
+
# to register and attach a callback in one step (like RSpec's before hooks).
|
|
167
|
+
#
|
|
168
|
+
# @param event [Symbol] The lifecycle event (:forge, :blueprint, or :step)
|
|
169
|
+
# @param callback_name [String, Symbol, nil] The name of a registered callback
|
|
170
|
+
# (optional if block is provided)
|
|
171
|
+
#
|
|
172
|
+
# @yield [context] Block to execute (registers callback automatically)
|
|
173
|
+
# @yieldparam context [Forge::Context] The current execution context
|
|
174
|
+
#
|
|
175
|
+
# @return [String, Symbol] The callback name (auto-generated if block provided)
|
|
176
|
+
#
|
|
177
|
+
# @raise [ArgumentError] If the event is invalid
|
|
178
|
+
# @raise [ArgumentError] If the callback is not registered (when using name)
|
|
179
|
+
#
|
|
180
|
+
# @example Attach a pre-registered callback
|
|
181
|
+
# config.register_callback(:setup) { |context| Database.seed }
|
|
182
|
+
# config.before(:forge, :setup)
|
|
183
|
+
#
|
|
184
|
+
# @example Register and attach with a block (like RSpec)
|
|
185
|
+
# config.before(:step) { |context| Logger.info("Starting step") }
|
|
186
|
+
# config.before(:blueprint) { |context| Database.clean }
|
|
187
|
+
#
|
|
188
|
+
# @example Store the callback name for later deregistration
|
|
189
|
+
# callback_name = config.before(:step) { |context| puts "hook" }
|
|
190
|
+
# config.deregister_callback(callback_name)
|
|
191
|
+
#
|
|
192
|
+
def before(event, callback_name = nil, &block)
|
|
193
|
+
if block
|
|
194
|
+
callback_name = "__sf_cb_#{SecureRandom.uuid.tr("-", "")}"
|
|
195
|
+
register_callback(callback_name, &block)
|
|
196
|
+
end
|
|
197
|
+
|
|
198
|
+
register_hook("before", event, callback_name)
|
|
199
|
+
|
|
200
|
+
callback_name
|
|
201
|
+
end
|
|
107
202
|
|
|
108
203
|
#
|
|
109
|
-
#
|
|
110
|
-
# Allows custom code execution at specific points during test execution
|
|
204
|
+
# Attaches a callback to an after lifecycle event
|
|
111
205
|
#
|
|
112
|
-
#
|
|
113
|
-
#
|
|
114
|
-
#
|
|
115
|
-
# @yieldparam context [Object] An object containing context-specific state data, depending
|
|
116
|
-
# on which hook the callback is triggered from.
|
|
206
|
+
# Global hooks run for all blueprints and execute in registration order.
|
|
207
|
+
# Can either reference a pre-registered callback by name, or accept a block
|
|
208
|
+
# to register and attach a callback in one step (like RSpec's after hooks).
|
|
117
209
|
#
|
|
118
|
-
# @
|
|
210
|
+
# @param event [Symbol] The lifecycle event (:forge, :blueprint, or :step)
|
|
211
|
+
# @param callback_name [String, Symbol, nil] The name of a registered callback
|
|
212
|
+
# (optional if block is provided)
|
|
119
213
|
#
|
|
120
|
-
# @
|
|
121
|
-
#
|
|
122
|
-
#
|
|
123
|
-
#
|
|
124
|
-
#
|
|
125
|
-
#
|
|
214
|
+
# @yield [context] Block to execute (registers callback automatically)
|
|
215
|
+
# @yieldparam context [Forge::Context] The current execution context
|
|
216
|
+
#
|
|
217
|
+
# @return [String, Symbol] The callback name (auto-generated if block provided)
|
|
218
|
+
#
|
|
219
|
+
# @raise [ArgumentError] If the event is invalid
|
|
220
|
+
# @raise [ArgumentError] If the callback is not registered (when using name)
|
|
221
|
+
#
|
|
222
|
+
# @example Attach a pre-registered callback
|
|
223
|
+
# config.register_callback(:cleanup) { |context| Database.clean }
|
|
224
|
+
# config.after(:forge, :cleanup)
|
|
225
|
+
#
|
|
226
|
+
# @example Register and attach with a block (like RSpec)
|
|
227
|
+
# config.after(:step) { |context| Logger.info("Step complete") }
|
|
228
|
+
# config.after(:blueprint) { |context| Database.rollback }
|
|
229
|
+
#
|
|
230
|
+
# @example Store the callback name for later deregistration
|
|
231
|
+
# callback_name = config.after(:step) { |context| puts "done" }
|
|
232
|
+
# config.deregister_callback(callback_name)
|
|
126
233
|
#
|
|
127
|
-
def
|
|
128
|
-
|
|
234
|
+
def after(event, callback_name = nil, &block)
|
|
235
|
+
if block
|
|
236
|
+
callback_name = "__sf_cb_#{SecureRandom.uuid.tr("-", "")}"
|
|
237
|
+
register_callback(callback_name, &block)
|
|
238
|
+
end
|
|
239
|
+
|
|
240
|
+
register_hook("after", event, callback_name)
|
|
241
|
+
|
|
242
|
+
callback_name
|
|
129
243
|
end
|
|
130
244
|
|
|
131
|
-
|
|
132
|
-
|
|
245
|
+
private
|
|
246
|
+
|
|
247
|
+
def register_hook(timing, event, callback_name)
|
|
248
|
+
hook = :"#{timing}_#{event}"
|
|
249
|
+
callback_name = callback_name.to_sym
|
|
250
|
+
|
|
251
|
+
if !@hooks.key?(hook)
|
|
252
|
+
keys = @hooks.keys.select { |k| k.to_s.start_with?(timing) }.map(&:in_quotes)
|
|
253
|
+
raise ArgumentError, "Invalid event #{hook.in_quotes}. Expected one of #{keys.to_or_sentence}"
|
|
254
|
+
end
|
|
255
|
+
|
|
256
|
+
if !@callbacks.key?(callback_name)
|
|
257
|
+
keys = @callbacks.keys.map(&:in_quotes)
|
|
258
|
+
raise ArgumentError, "Invalid callback #{callback_name.in_quotes}. Expected one of #{keys.to_or_sentence}"
|
|
259
|
+
end
|
|
260
|
+
|
|
261
|
+
@hooks[hook] << callback_name
|
|
262
|
+
end
|
|
133
263
|
end
|
|
134
264
|
end
|
|
@@ -2,20 +2,23 @@
|
|
|
2
2
|
|
|
3
3
|
module SpecForge
|
|
4
4
|
module Documentation
|
|
5
|
-
class
|
|
5
|
+
class Builder
|
|
6
6
|
#
|
|
7
|
-
# Manages caching of
|
|
7
|
+
# Manages caching of endpoint data to avoid re-running blueprints
|
|
8
8
|
#
|
|
9
|
-
#
|
|
10
|
-
#
|
|
11
|
-
#
|
|
9
|
+
# The Cache stores extracted endpoint data and tracks blueprint file
|
|
10
|
+
# modification times. When blueprints haven't changed, cached data
|
|
11
|
+
# can be reused to speed up documentation generation.
|
|
12
|
+
#
|
|
13
|
+
# Cache files are stored in the OpenAPI generated directory under
|
|
14
|
+
# a .cache subdirectory.
|
|
12
15
|
#
|
|
13
16
|
# @example Using the cache
|
|
14
17
|
# cache = Cache.new
|
|
15
18
|
# if cache.valid?
|
|
16
19
|
# endpoints = cache.read
|
|
17
20
|
# else
|
|
18
|
-
# endpoints =
|
|
21
|
+
# endpoints = extract_from_blueprints
|
|
19
22
|
# cache.create(endpoints)
|
|
20
23
|
# end
|
|
21
24
|
#
|
|
@@ -23,41 +26,41 @@ module SpecForge
|
|
|
23
26
|
#
|
|
24
27
|
# Creates a new cache manager
|
|
25
28
|
#
|
|
26
|
-
# Sets up file paths for endpoint and
|
|
29
|
+
# Sets up file paths for endpoint and blueprint caches in the OpenAPI
|
|
27
30
|
# generated directory structure.
|
|
28
31
|
#
|
|
29
32
|
# @return [Cache] A new cache instance
|
|
30
33
|
#
|
|
31
34
|
def initialize
|
|
32
35
|
@endpoint_cache = SpecForge.openapi_path.join("generated", ".cache", "endpoints.yml")
|
|
33
|
-
@
|
|
36
|
+
@blueprint_cache = SpecForge.openapi_path.join("generated", ".cache", "blueprints.yml")
|
|
34
37
|
end
|
|
35
38
|
|
|
36
39
|
#
|
|
37
40
|
# Checks if the cache is valid and can be used
|
|
38
41
|
#
|
|
39
42
|
# Determines cache validity by checking if endpoint cache exists
|
|
40
|
-
# and whether any
|
|
43
|
+
# and whether any blueprint files have been modified since the cache
|
|
41
44
|
# was created.
|
|
42
45
|
#
|
|
43
46
|
# @return [Boolean] true if cache is valid and can be used
|
|
44
47
|
#
|
|
45
48
|
def valid?
|
|
46
|
-
endpoint_cache? && !
|
|
49
|
+
endpoint_cache? && !blueprints_updated?
|
|
47
50
|
end
|
|
48
51
|
|
|
49
52
|
#
|
|
50
|
-
# Creates a cache entry with endpoint data and
|
|
53
|
+
# Creates a cache entry with endpoint data and blueprint file metadata
|
|
51
54
|
#
|
|
52
|
-
# Writes both the endpoint data and current
|
|
53
|
-
# to enable cache invalidation when
|
|
55
|
+
# Writes both the endpoint data and current blueprint file modification times
|
|
56
|
+
# to enable cache invalidation when blueprints change.
|
|
54
57
|
#
|
|
55
58
|
# @param endpoints [Array<Hash>] Endpoint data to cache
|
|
56
59
|
#
|
|
57
60
|
# @return [void]
|
|
58
61
|
#
|
|
59
62
|
def create(endpoints)
|
|
60
|
-
|
|
63
|
+
write_blueprint_cache
|
|
61
64
|
write(endpoints)
|
|
62
65
|
end
|
|
63
66
|
|
|
@@ -93,11 +96,11 @@ module SpecForge
|
|
|
93
96
|
YAML.safe_load_file(path, symbolize_names: true, permitted_classes: [Symbol, Time])
|
|
94
97
|
end
|
|
95
98
|
|
|
96
|
-
def
|
|
97
|
-
return true if !File.exist?(@
|
|
99
|
+
def blueprints_updated?
|
|
100
|
+
return true if !File.exist?(@blueprint_cache)
|
|
98
101
|
|
|
99
|
-
cache = read_from_file(@
|
|
100
|
-
new_cache =
|
|
102
|
+
cache = read_from_file(@blueprint_cache)
|
|
103
|
+
new_cache = generate_blueprint_cache
|
|
101
104
|
|
|
102
105
|
different?(cache, new_cache)
|
|
103
106
|
end
|
|
@@ -106,17 +109,17 @@ module SpecForge
|
|
|
106
109
|
File.exist?(@endpoint_cache)
|
|
107
110
|
end
|
|
108
111
|
|
|
109
|
-
def
|
|
110
|
-
paths = SpecForge.forge_path.join("
|
|
112
|
+
def generate_blueprint_cache
|
|
113
|
+
paths = SpecForge.forge_path.join("blueprints", "**", "*.{yml,yaml}")
|
|
111
114
|
|
|
112
115
|
Dir[paths].each_with_object({}) do |path, hash|
|
|
113
116
|
hash[path.to_sym] = File.mtime(path)
|
|
114
117
|
end
|
|
115
118
|
end
|
|
116
119
|
|
|
117
|
-
def
|
|
118
|
-
data =
|
|
119
|
-
write_to_file(data, @
|
|
120
|
+
def write_blueprint_cache
|
|
121
|
+
data = generate_blueprint_cache
|
|
122
|
+
write_to_file(data, @blueprint_cache)
|
|
120
123
|
end
|
|
121
124
|
|
|
122
125
|
def different?(cache_left, cache_right)
|