textus 0.15.0 → 0.20.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/ARCHITECTURE.md +50 -55
- data/CHANGELOG.md +486 -0
- data/README.md +13 -9
- data/SPEC.md +13 -10
- data/docs/conventions.md +2 -2
- data/lib/textus/application/context.rb +20 -34
- data/lib/textus/application/policy/predicates/human_accept.rb +30 -0
- data/lib/textus/{domain → application}/policy/predicates/schema_valid.rb +5 -5
- data/lib/textus/{domain → application}/policy/promotion.rb +20 -3
- data/lib/textus/application/projection.rb +91 -0
- data/lib/textus/application/reads/audit.rb +4 -4
- data/lib/textus/application/reads/blame.rb +11 -8
- data/lib/textus/application/reads/deps.rb +14 -3
- data/lib/textus/application/reads/freshness.rb +17 -6
- data/lib/textus/application/reads/get.rb +37 -11
- data/lib/textus/application/reads/get_or_refresh.rb +8 -8
- data/lib/textus/application/reads/list.rb +5 -3
- data/lib/textus/application/reads/policy_explain.rb +3 -3
- data/lib/textus/application/reads/published.rb +5 -3
- data/lib/textus/application/reads/rdeps.rb +15 -3
- data/lib/textus/application/reads/schema_envelope.rb +6 -3
- data/lib/textus/application/reads/stale.rb +3 -3
- data/lib/textus/application/reads/uid.rb +11 -3
- data/lib/textus/application/reads/validate_all.rb +12 -3
- data/lib/textus/application/reads/validator.rb +84 -0
- data/lib/textus/application/reads/where.rb +6 -3
- data/lib/textus/application/refresh/all.rb +16 -5
- data/lib/textus/application/refresh/orchestrator.rb +9 -9
- data/lib/textus/application/refresh/worker.rb +59 -32
- data/lib/textus/application/tools/migrate_keys.rb +191 -0
- data/lib/textus/application/tools/migrate_manifest_to_kinds.rb +31 -0
- data/lib/textus/application/writes/accept.rb +36 -13
- data/lib/textus/application/writes/delete.rb +13 -15
- data/lib/textus/application/writes/envelope_io.rb +166 -0
- data/lib/textus/application/writes/materializer.rb +50 -0
- data/lib/textus/application/writes/mv.rb +56 -95
- data/lib/textus/application/writes/publish.rb +132 -27
- data/lib/textus/application/writes/put.rb +17 -20
- data/lib/textus/application/writes/reject.rb +18 -9
- data/lib/textus/builder/pipeline.rb +21 -15
- data/lib/textus/builder/renderer/json.rb +4 -1
- data/lib/textus/builder/renderer/markdown.rb +7 -1
- data/lib/textus/builder/renderer/yaml.rb +4 -1
- data/lib/textus/cli/group/hook.rb +1 -3
- data/lib/textus/cli/group/key.rb +1 -4
- data/lib/textus/cli/group/refresh.rb +1 -2
- data/lib/textus/cli/group/rule.rb +1 -3
- data/lib/textus/cli/group/schema.rb +1 -5
- data/lib/textus/cli/group.rb +12 -16
- data/lib/textus/cli/verb/accept.rb +3 -1
- data/lib/textus/cli/verb/audit.rb +3 -1
- data/lib/textus/cli/verb/blame.rb +3 -1
- data/lib/textus/cli/verb/build.rb +4 -5
- data/lib/textus/cli/verb/delete.rb +3 -1
- data/lib/textus/cli/verb/deps.rb +3 -1
- data/lib/textus/cli/verb/doctor.rb +2 -0
- data/lib/textus/cli/verb/freshness.rb +3 -1
- data/lib/textus/cli/verb/get.rb +4 -2
- data/lib/textus/cli/verb/hook_run.rb +6 -4
- data/lib/textus/cli/verb/hooks.rb +8 -5
- data/lib/textus/cli/verb/init.rb +2 -0
- data/lib/textus/cli/verb/intro.rb +2 -0
- data/lib/textus/cli/verb/key_normalize.rb +35 -3
- data/lib/textus/cli/verb/list.rb +3 -1
- data/lib/textus/cli/verb/mv.rb +4 -1
- data/lib/textus/cli/verb/published.rb +3 -1
- data/lib/textus/cli/verb/put.rb +5 -4
- data/lib/textus/cli/verb/rdeps.rb +3 -1
- data/lib/textus/cli/verb/refresh.rb +1 -1
- data/lib/textus/cli/verb/refresh_stale.rb +4 -2
- data/lib/textus/cli/verb/reject.rb +3 -1
- data/lib/textus/cli/verb/rule_explain.rb +4 -1
- data/lib/textus/cli/verb/rule_list.rb +3 -0
- data/lib/textus/cli/verb/schema.rb +4 -1
- data/lib/textus/cli/verb/schema_diff.rb +3 -0
- data/lib/textus/cli/verb/schema_init.rb +3 -0
- data/lib/textus/cli/verb/schema_migrate.rb +3 -0
- data/lib/textus/cli/verb/uid.rb +4 -1
- data/lib/textus/cli/verb/where.rb +3 -1
- data/lib/textus/cli/verb.rb +30 -0
- data/lib/textus/cli.rb +18 -27
- data/lib/textus/doctor/check/audit_log.rb +1 -1
- data/lib/textus/doctor/check/handler_allowlist.rb +3 -2
- data/lib/textus/doctor/check/hooks.rb +4 -2
- data/lib/textus/doctor/check/illegal_keys.rb +6 -5
- data/lib/textus/doctor/check/intake_registration.rb +5 -5
- data/lib/textus/doctor/check/manifest_files.rb +1 -1
- data/lib/textus/doctor/check/protocol_version.rb +2 -2
- data/lib/textus/doctor/check/schema_violations.rb +1 -1
- data/lib/textus/doctor/check/sentinels.rb +2 -2
- data/lib/textus/doctor/check/templates.rb +4 -3
- data/lib/textus/doctor.rb +3 -4
- data/lib/textus/domain/authorizer.rb +37 -0
- data/lib/textus/domain/freshness/evaluator.rb +1 -1
- data/lib/textus/domain/freshness/policy.rb +1 -1
- data/lib/textus/domain/freshness/verdict.rb +1 -1
- data/lib/textus/domain/freshness.rb +40 -0
- data/lib/textus/{store → domain}/sentinel.rb +1 -1
- data/lib/textus/{store → domain}/staleness/generator_check.rb +9 -8
- data/lib/textus/{store → domain}/staleness/intake_check.rb +3 -3
- data/lib/textus/{store → domain}/staleness.rb +1 -1
- data/lib/textus/entry/json.rb +1 -1
- data/lib/textus/entry/markdown.rb +1 -1
- data/lib/textus/entry/yaml.rb +1 -1
- data/lib/textus/envelope.rb +7 -3
- data/lib/textus/errors.rb +19 -0
- data/lib/textus/hooks/builtin.rb +6 -6
- data/lib/textus/hooks/bus.rb +155 -0
- data/lib/textus/hooks/context.rb +38 -0
- data/lib/textus/hooks/fire_report.rb +23 -0
- data/lib/textus/hooks/loader.rb +20 -17
- data/lib/textus/{store → infra}/audit_log.rb +1 -1
- data/lib/textus/infra/audit_subscriber.rb +43 -0
- data/lib/textus/infra/event_bus.rb +3 -3
- data/lib/textus/infra/publisher.rb +3 -3
- data/lib/textus/infra/refresh/detached.rb +1 -1
- data/lib/textus/infra/storage/file_store.rb +26 -0
- data/lib/textus/init.rb +14 -11
- data/lib/textus/intro.rb +7 -7
- data/lib/textus/manifest/entry/base.rb +38 -0
- data/lib/textus/manifest/entry/derived.rb +25 -0
- data/lib/textus/manifest/entry/intake.rb +19 -0
- data/lib/textus/manifest/entry/leaf.rb +16 -0
- data/lib/textus/manifest/entry/nested.rb +39 -0
- data/lib/textus/manifest/entry/parser.rb +64 -31
- data/lib/textus/manifest/entry/validators/events.rb +3 -2
- data/lib/textus/manifest/entry/validators/format_matrix.rb +5 -3
- data/lib/textus/manifest/entry/validators/index_filename.rb +11 -10
- data/lib/textus/manifest/entry/validators/inject_intro.rb +5 -2
- data/lib/textus/manifest/entry/validators/publish_each.rb +9 -6
- data/lib/textus/manifest/entry.rb +0 -72
- data/lib/textus/manifest/resolution.rb +5 -0
- data/lib/textus/manifest/resolver.rb +109 -0
- data/lib/textus/manifest/schema.rb +1 -1
- data/lib/textus/manifest.rb +4 -100
- data/lib/textus/operations.rb +147 -23
- data/lib/textus/schema/tools.rb +7 -7
- data/lib/textus/schemas.rb +46 -0
- data/lib/textus/store.rb +12 -49
- data/lib/textus/uid.rb +18 -0
- data/lib/textus/version.rb +1 -1
- data/lib/textus.rb +17 -1
- metadata +31 -23
- data/lib/textus/application/writes/build.rb +0 -79
- data/lib/textus/dependencies.rb +0 -23
- data/lib/textus/domain/policy/predicates/human_accept.rb +0 -31
- data/lib/textus/hooks/dispatcher.rb +0 -63
- data/lib/textus/hooks/dsl.rb +0 -11
- data/lib/textus/hooks/registry.rb +0 -81
- data/lib/textus/migrate_keys.rb +0 -187
- data/lib/textus/operations/reads.rb +0 -56
- data/lib/textus/operations/refresh.rb +0 -27
- data/lib/textus/operations/writes.rb +0 -21
- data/lib/textus/projection.rb +0 -89
- data/lib/textus/refresh.rb +0 -39
- data/lib/textus/store/reader.rb +0 -69
- data/lib/textus/store/validator.rb +0 -82
- data/lib/textus/store/writer.rb +0 -102
data/lib/textus/store.rb
CHANGED
|
@@ -1,16 +1,8 @@
|
|
|
1
1
|
require "fileutils"
|
|
2
|
-
require "securerandom"
|
|
3
2
|
|
|
4
3
|
module Textus
|
|
5
4
|
class Store
|
|
6
|
-
attr_reader :root, :manifest, :
|
|
7
|
-
|
|
8
|
-
# A Textus UID: 16 lowercase hex chars (SecureRandom.hex(8)). Not a UUID —
|
|
9
|
-
# short on purpose. Random enough for collision-never-in-practice within a
|
|
10
|
-
# single store.
|
|
11
|
-
def self.mint_uid
|
|
12
|
-
SecureRandom.hex(8)
|
|
13
|
-
end
|
|
5
|
+
attr_reader :root, :manifest, :schemas, :file_store, :audit_log, :bus
|
|
14
6
|
|
|
15
7
|
def self.discover(start_dir = Dir.pwd, root: nil)
|
|
16
8
|
explicit = root || ENV.fetch("TEXTUS_ROOT", nil)
|
|
@@ -37,46 +29,17 @@ module Textus
|
|
|
37
29
|
end
|
|
38
30
|
|
|
39
31
|
def initialize(root)
|
|
40
|
-
@root
|
|
41
|
-
@manifest
|
|
42
|
-
@
|
|
43
|
-
@
|
|
44
|
-
@
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
@bus.
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
def load_hooks
|
|
52
|
-
Textus.with_registry(@registry) do
|
|
53
|
-
Hooks::Builtin.register_all
|
|
54
|
-
dir = File.join(@root, "hooks")
|
|
55
|
-
return unless File.directory?(dir)
|
|
56
|
-
|
|
57
|
-
Dir.glob(File.join(dir, "**/*.rb")).sort.each do |f| # rubocop:disable Lint/RedundantDirGlobSort
|
|
58
|
-
begin
|
|
59
|
-
load(f)
|
|
60
|
-
rescue StandardError, ScriptError => e
|
|
61
|
-
raise UsageError.new("failed loading hook #{File.basename(f)}: #{e.class}: #{e.message}")
|
|
62
|
-
end
|
|
63
|
-
end
|
|
64
|
-
end
|
|
65
|
-
end
|
|
66
|
-
|
|
67
|
-
def schema_for(name)
|
|
68
|
-
return nil if name.nil?
|
|
69
|
-
|
|
70
|
-
@schemas[name] ||= begin
|
|
71
|
-
sp = File.join(@root, "schemas", "#{name}.yaml")
|
|
72
|
-
raise IoError.new("schema not found: #{sp}") unless File.exist?(sp)
|
|
73
|
-
|
|
74
|
-
Schema.load(sp)
|
|
75
|
-
end
|
|
76
|
-
end
|
|
77
|
-
|
|
78
|
-
def audit_log
|
|
79
|
-
@audit_log ||= Store::AuditLog.new(@root)
|
|
32
|
+
@root = File.expand_path(root)
|
|
33
|
+
@manifest = Manifest.load(@root)
|
|
34
|
+
@schemas = Schemas.new(File.join(@root, "schemas"))
|
|
35
|
+
@file_store = Infra::Storage::FileStore.new
|
|
36
|
+
@audit_log = Infra::AuditLog.new(@root)
|
|
37
|
+
@bus = Hooks::Bus.new
|
|
38
|
+
Infra::AuditSubscriber.new(@audit_log).attach(@bus)
|
|
39
|
+
Hooks::Builtin.register_all(@bus)
|
|
40
|
+
Hooks::Loader.new(bus: @bus).load_dir(File.join(@root, "hooks"))
|
|
41
|
+
ops = Operations.for(self, role: Role::DEFAULT)
|
|
42
|
+
@bus.publish(:store_loaded, ctx: ops.hook_context)
|
|
80
43
|
end
|
|
81
44
|
end
|
|
82
45
|
end
|
data/lib/textus/uid.rb
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
require "securerandom"
|
|
2
|
+
|
|
3
|
+
module Textus
|
|
4
|
+
# A Textus UID: 16 lowercase hex chars (SecureRandom.hex(8)). Not a UUID —
|
|
5
|
+
# short on purpose. Random enough for collision-never-in-practice within a
|
|
6
|
+
# single store.
|
|
7
|
+
module Uid
|
|
8
|
+
module_function
|
|
9
|
+
|
|
10
|
+
def mint
|
|
11
|
+
SecureRandom.hex(8)
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def valid?(str)
|
|
15
|
+
str.is_a?(String) && str.match?(/\A[0-9a-f]{16}\z/)
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
end
|
data/lib/textus/version.rb
CHANGED
data/lib/textus.rb
CHANGED
|
@@ -8,11 +8,27 @@ loader.inflector.inflect(
|
|
|
8
8
|
"json" => "Json",
|
|
9
9
|
"yaml" => "Yaml",
|
|
10
10
|
"hook_dsl_scanner" => "HookDSLScanner",
|
|
11
|
+
"envelope_io" => "EnvelopeIO",
|
|
11
12
|
)
|
|
12
13
|
loader.ignore(File.expand_path("textus/errors.rb", __dir__))
|
|
13
14
|
loader.setup
|
|
14
15
|
loader.eager_load
|
|
15
16
|
|
|
16
17
|
module Textus
|
|
17
|
-
|
|
18
|
+
@hook_mutex = Mutex.new
|
|
19
|
+
@hook_blocks = []
|
|
20
|
+
|
|
21
|
+
def self.hook(&blk)
|
|
22
|
+
raise UsageError.new("hook block required") unless blk
|
|
23
|
+
|
|
24
|
+
@hook_mutex.synchronize { @hook_blocks << blk }
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def self.drain_hook_blocks
|
|
28
|
+
@hook_mutex.synchronize do
|
|
29
|
+
blocks = @hook_blocks
|
|
30
|
+
@hook_blocks = []
|
|
31
|
+
blocks
|
|
32
|
+
end
|
|
33
|
+
end
|
|
18
34
|
end
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: textus
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.20.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Patrick
|
|
@@ -110,6 +110,10 @@ files:
|
|
|
110
110
|
- exe/textus
|
|
111
111
|
- lib/textus.rb
|
|
112
112
|
- lib/textus/application/context.rb
|
|
113
|
+
- lib/textus/application/policy/predicates/human_accept.rb
|
|
114
|
+
- lib/textus/application/policy/predicates/schema_valid.rb
|
|
115
|
+
- lib/textus/application/policy/promotion.rb
|
|
116
|
+
- lib/textus/application/projection.rb
|
|
113
117
|
- lib/textus/application/reads/audit.rb
|
|
114
118
|
- lib/textus/application/reads/blame.rb
|
|
115
119
|
- lib/textus/application/reads/deps.rb
|
|
@@ -124,13 +128,17 @@ files:
|
|
|
124
128
|
- lib/textus/application/reads/stale.rb
|
|
125
129
|
- lib/textus/application/reads/uid.rb
|
|
126
130
|
- lib/textus/application/reads/validate_all.rb
|
|
131
|
+
- lib/textus/application/reads/validator.rb
|
|
127
132
|
- lib/textus/application/reads/where.rb
|
|
128
133
|
- lib/textus/application/refresh/all.rb
|
|
129
134
|
- lib/textus/application/refresh/orchestrator.rb
|
|
130
135
|
- lib/textus/application/refresh/worker.rb
|
|
136
|
+
- lib/textus/application/tools/migrate_keys.rb
|
|
137
|
+
- lib/textus/application/tools/migrate_manifest_to_kinds.rb
|
|
131
138
|
- lib/textus/application/writes/accept.rb
|
|
132
|
-
- lib/textus/application/writes/build.rb
|
|
133
139
|
- lib/textus/application/writes/delete.rb
|
|
140
|
+
- lib/textus/application/writes/envelope_io.rb
|
|
141
|
+
- lib/textus/application/writes/materializer.rb
|
|
134
142
|
- lib/textus/application/writes/mv.rb
|
|
135
143
|
- lib/textus/application/writes/publish.rb
|
|
136
144
|
- lib/textus/application/writes/put.rb
|
|
@@ -179,7 +187,6 @@ files:
|
|
|
179
187
|
- lib/textus/cli/verb/schema_migrate.rb
|
|
180
188
|
- lib/textus/cli/verb/uid.rb
|
|
181
189
|
- lib/textus/cli/verb/where.rb
|
|
182
|
-
- lib/textus/dependencies.rb
|
|
183
190
|
- lib/textus/doctor.rb
|
|
184
191
|
- lib/textus/doctor/check.rb
|
|
185
192
|
- lib/textus/doctor/check/audit_log.rb
|
|
@@ -198,6 +205,8 @@ files:
|
|
|
198
205
|
- lib/textus/doctor/check/templates.rb
|
|
199
206
|
- lib/textus/doctor/check/unowned_schema_fields.rb
|
|
200
207
|
- lib/textus/domain/action.rb
|
|
208
|
+
- lib/textus/domain/authorizer.rb
|
|
209
|
+
- lib/textus/domain/freshness.rb
|
|
201
210
|
- lib/textus/domain/freshness/evaluator.rb
|
|
202
211
|
- lib/textus/domain/freshness/policy.rb
|
|
203
212
|
- lib/textus/domain/freshness/verdict.rb
|
|
@@ -206,11 +215,12 @@ files:
|
|
|
206
215
|
- lib/textus/domain/policy.rb
|
|
207
216
|
- lib/textus/domain/policy/handler_allowlist.rb
|
|
208
217
|
- lib/textus/domain/policy/matcher.rb
|
|
209
|
-
- lib/textus/domain/policy/predicates/human_accept.rb
|
|
210
|
-
- lib/textus/domain/policy/predicates/schema_valid.rb
|
|
211
218
|
- lib/textus/domain/policy/promote.rb
|
|
212
|
-
- lib/textus/domain/policy/promotion.rb
|
|
213
219
|
- lib/textus/domain/policy/refresh.rb
|
|
220
|
+
- lib/textus/domain/sentinel.rb
|
|
221
|
+
- lib/textus/domain/staleness.rb
|
|
222
|
+
- lib/textus/domain/staleness/generator_check.rb
|
|
223
|
+
- lib/textus/domain/staleness/intake_check.rb
|
|
214
224
|
- lib/textus/entry.rb
|
|
215
225
|
- lib/textus/entry/base.rb
|
|
216
226
|
- lib/textus/entry/json.rb
|
|
@@ -221,16 +231,19 @@ files:
|
|
|
221
231
|
- lib/textus/errors.rb
|
|
222
232
|
- lib/textus/etag.rb
|
|
223
233
|
- lib/textus/hooks/builtin.rb
|
|
224
|
-
- lib/textus/hooks/
|
|
225
|
-
- lib/textus/hooks/
|
|
234
|
+
- lib/textus/hooks/bus.rb
|
|
235
|
+
- lib/textus/hooks/context.rb
|
|
236
|
+
- lib/textus/hooks/fire_report.rb
|
|
226
237
|
- lib/textus/hooks/loader.rb
|
|
227
|
-
- lib/textus/
|
|
238
|
+
- lib/textus/infra/audit_log.rb
|
|
239
|
+
- lib/textus/infra/audit_subscriber.rb
|
|
228
240
|
- lib/textus/infra/build_lock.rb
|
|
229
241
|
- lib/textus/infra/clock.rb
|
|
230
242
|
- lib/textus/infra/event_bus.rb
|
|
231
243
|
- lib/textus/infra/publisher.rb
|
|
232
244
|
- lib/textus/infra/refresh/detached.rb
|
|
233
245
|
- lib/textus/infra/refresh/lock.rb
|
|
246
|
+
- lib/textus/infra/storage/file_store.rb
|
|
234
247
|
- lib/textus/init.rb
|
|
235
248
|
- lib/textus/intro.rb
|
|
236
249
|
- lib/textus/key/distance.rb
|
|
@@ -238,6 +251,11 @@ files:
|
|
|
238
251
|
- lib/textus/key/path.rb
|
|
239
252
|
- lib/textus/manifest.rb
|
|
240
253
|
- lib/textus/manifest/entry.rb
|
|
254
|
+
- lib/textus/manifest/entry/base.rb
|
|
255
|
+
- lib/textus/manifest/entry/derived.rb
|
|
256
|
+
- lib/textus/manifest/entry/intake.rb
|
|
257
|
+
- lib/textus/manifest/entry/leaf.rb
|
|
258
|
+
- lib/textus/manifest/entry/nested.rb
|
|
241
259
|
- lib/textus/manifest/entry/parser.rb
|
|
242
260
|
- lib/textus/manifest/entry/validators.rb
|
|
243
261
|
- lib/textus/manifest/entry/validators/events.rb
|
|
@@ -245,28 +263,18 @@ files:
|
|
|
245
263
|
- lib/textus/manifest/entry/validators/index_filename.rb
|
|
246
264
|
- lib/textus/manifest/entry/validators/inject_intro.rb
|
|
247
265
|
- lib/textus/manifest/entry/validators/publish_each.rb
|
|
266
|
+
- lib/textus/manifest/resolution.rb
|
|
267
|
+
- lib/textus/manifest/resolver.rb
|
|
248
268
|
- lib/textus/manifest/rules.rb
|
|
249
269
|
- lib/textus/manifest/schema.rb
|
|
250
|
-
- lib/textus/migrate_keys.rb
|
|
251
270
|
- lib/textus/mustache.rb
|
|
252
271
|
- lib/textus/operations.rb
|
|
253
|
-
- lib/textus/operations/reads.rb
|
|
254
|
-
- lib/textus/operations/refresh.rb
|
|
255
|
-
- lib/textus/operations/writes.rb
|
|
256
|
-
- lib/textus/projection.rb
|
|
257
|
-
- lib/textus/refresh.rb
|
|
258
272
|
- lib/textus/role.rb
|
|
259
273
|
- lib/textus/schema.rb
|
|
260
274
|
- lib/textus/schema/tools.rb
|
|
275
|
+
- lib/textus/schemas.rb
|
|
261
276
|
- lib/textus/store.rb
|
|
262
|
-
- lib/textus/
|
|
263
|
-
- lib/textus/store/reader.rb
|
|
264
|
-
- lib/textus/store/sentinel.rb
|
|
265
|
-
- lib/textus/store/staleness.rb
|
|
266
|
-
- lib/textus/store/staleness/generator_check.rb
|
|
267
|
-
- lib/textus/store/staleness/intake_check.rb
|
|
268
|
-
- lib/textus/store/validator.rb
|
|
269
|
-
- lib/textus/store/writer.rb
|
|
277
|
+
- lib/textus/uid.rb
|
|
270
278
|
- lib/textus/version.rb
|
|
271
279
|
homepage: https://github.com/patrick204nqh/textus
|
|
272
280
|
licenses:
|
|
@@ -1,79 +0,0 @@
|
|
|
1
|
-
require "fileutils"
|
|
2
|
-
|
|
3
|
-
module Textus
|
|
4
|
-
module Application
|
|
5
|
-
module Writes
|
|
6
|
-
# Materializes generator-zone entries (template + projection) onto disk
|
|
7
|
-
# and copies the result to any configured `publish_to:` targets. Fires
|
|
8
|
-
# `:build_completed` and `:file_published` events.
|
|
9
|
-
#
|
|
10
|
-
# For `publish_each:` (per-leaf publishing of nested entries), see
|
|
11
|
-
# `Application::Writes::Publish`. The CLI verb `textus build` calls
|
|
12
|
-
# both classes and merges the results.
|
|
13
|
-
class Build
|
|
14
|
-
def initialize(ctx:, bus:)
|
|
15
|
-
@ctx = ctx
|
|
16
|
-
@bus = bus
|
|
17
|
-
end
|
|
18
|
-
|
|
19
|
-
def call(prefix: nil)
|
|
20
|
-
built = manifest.entries.filter_map do |mentry|
|
|
21
|
-
next unless mentry.in_generator_zone?
|
|
22
|
-
next unless mentry.projection || mentry.template
|
|
23
|
-
next if prefix && !mentry.key.start_with?(prefix)
|
|
24
|
-
|
|
25
|
-
materialize(mentry)
|
|
26
|
-
end
|
|
27
|
-
{ "protocol" => Textus::PROTOCOL, "built" => built }
|
|
28
|
-
end
|
|
29
|
-
|
|
30
|
-
private
|
|
31
|
-
|
|
32
|
-
def store = @ctx.store
|
|
33
|
-
def manifest = store.manifest
|
|
34
|
-
def root = store.root
|
|
35
|
-
|
|
36
|
-
def materialize(mentry)
|
|
37
|
-
target_path = Builder::Pipeline.run(
|
|
38
|
-
store: store,
|
|
39
|
-
mentry: mentry,
|
|
40
|
-
template_loader: ->(name) { read_template(name) },
|
|
41
|
-
)
|
|
42
|
-
publish_and_fire(mentry, target_path)
|
|
43
|
-
{ "key" => mentry.key, "path" => target_path, "published_to" => mentry.publish_to }
|
|
44
|
-
end
|
|
45
|
-
|
|
46
|
-
def read_template(name)
|
|
47
|
-
tpl_path = File.join(root, "templates", name)
|
|
48
|
-
raise TemplateError.new("template not found: #{tpl_path}", template_name: name) unless File.exist?(tpl_path)
|
|
49
|
-
|
|
50
|
-
File.read(tpl_path)
|
|
51
|
-
end
|
|
52
|
-
|
|
53
|
-
def publish_and_fire(mentry, target_path)
|
|
54
|
-
envelope = store.reader.get(mentry.key)
|
|
55
|
-
repo_root = File.dirname(root)
|
|
56
|
-
|
|
57
|
-
mentry.publish_to.each do |rel|
|
|
58
|
-
target_abs = File.join(repo_root, rel)
|
|
59
|
-
Textus::Infra::Publisher.publish(source: target_path, target: target_abs, store_root: root)
|
|
60
|
-
publish_event(:file_published,
|
|
61
|
-
key: mentry.key,
|
|
62
|
-
envelope: envelope,
|
|
63
|
-
source: target_path,
|
|
64
|
-
target: target_abs)
|
|
65
|
-
end
|
|
66
|
-
|
|
67
|
-
publish_event(:build_completed,
|
|
68
|
-
key: mentry.key,
|
|
69
|
-
envelope: envelope,
|
|
70
|
-
sources: Array(mentry.projection&.fetch("select", nil)).compact)
|
|
71
|
-
end
|
|
72
|
-
|
|
73
|
-
def publish_event(event, **payload)
|
|
74
|
-
@bus.publish(event, store: @ctx.with_role(@ctx.role), correlation_id: @ctx.correlation_id, **payload)
|
|
75
|
-
end
|
|
76
|
-
end
|
|
77
|
-
end
|
|
78
|
-
end
|
|
79
|
-
end
|
data/lib/textus/dependencies.rb
DELETED
|
@@ -1,23 +0,0 @@
|
|
|
1
|
-
module Textus
|
|
2
|
-
module Dependencies
|
|
3
|
-
def self.deps_of(manifest, key)
|
|
4
|
-
entry = manifest.entries.find { |e| e.key == key } or return []
|
|
5
|
-
result = Array(entry.projection&.fetch("select", nil)).map { |s| s }
|
|
6
|
-
Array(entry.generator&.fetch("sources", nil)).each { |s| result << s }
|
|
7
|
-
result.uniq
|
|
8
|
-
end
|
|
9
|
-
|
|
10
|
-
def self.rdeps_of(manifest, key)
|
|
11
|
-
manifest.entries.each_with_object([]) do |e, acc|
|
|
12
|
-
sources = Array(e.projection&.fetch("select", nil)) + Array(e.generator&.fetch("sources", nil))
|
|
13
|
-
acc << e.key if sources.any? { |s| s == key || key.start_with?("#{s}.") }
|
|
14
|
-
end
|
|
15
|
-
end
|
|
16
|
-
|
|
17
|
-
def self.published_of(manifest)
|
|
18
|
-
manifest.entries.reject { |e| e.publish_to.empty? }.map do |e|
|
|
19
|
-
{ "key" => e.key, "publish_to" => e.publish_to }
|
|
20
|
-
end
|
|
21
|
-
end
|
|
22
|
-
end
|
|
23
|
-
end
|
|
@@ -1,31 +0,0 @@
|
|
|
1
|
-
module Textus
|
|
2
|
-
module Domain
|
|
3
|
-
module Policy
|
|
4
|
-
module Predicates
|
|
5
|
-
class HumanAccept
|
|
6
|
-
attr_reader :reason
|
|
7
|
-
|
|
8
|
-
def name
|
|
9
|
-
"human_accept"
|
|
10
|
-
end
|
|
11
|
-
|
|
12
|
-
# The role is passed via `store` (an Application::Context-like object
|
|
13
|
-
# with a `role` reader) or through the entry metadata. In practice,
|
|
14
|
-
# Accept already enforces role == "human" before reaching the
|
|
15
|
-
# promotion gate, so this predicate trivially passes. It documents
|
|
16
|
-
# intent and future-proofs multi-actor accept flows.
|
|
17
|
-
def call(store:, entry: nil) # rubocop:disable Lint/UnusedMethodArgument
|
|
18
|
-
role = store.respond_to?(:role) ? store.role.to_s : nil
|
|
19
|
-
# If we cannot determine the role (e.g. store doesn't expose it),
|
|
20
|
-
# we trust that Accept has already checked — allow through.
|
|
21
|
-
return true if role.nil?
|
|
22
|
-
|
|
23
|
-
ok = (role == "human")
|
|
24
|
-
@reason = "current role is '#{role}', expected 'human'" unless ok
|
|
25
|
-
ok
|
|
26
|
-
end
|
|
27
|
-
end
|
|
28
|
-
end
|
|
29
|
-
end
|
|
30
|
-
end
|
|
31
|
-
end
|
|
@@ -1,63 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
require "timeout"
|
|
4
|
-
|
|
5
|
-
module Textus
|
|
6
|
-
module Hooks
|
|
7
|
-
class Dispatcher
|
|
8
|
-
HOOK_TIMEOUT_SECONDS = 2
|
|
9
|
-
|
|
10
|
-
def initialize(audit_log:)
|
|
11
|
-
@audit_log = audit_log
|
|
12
|
-
@subscribers = Hash.new { |h, k| h[k] = [] }
|
|
13
|
-
end
|
|
14
|
-
|
|
15
|
-
def subscribe(event, name, keys: nil, &block)
|
|
16
|
-
@subscribers[event.to_sym] << { name: name.to_sym, callable: block, keys: keys }
|
|
17
|
-
end
|
|
18
|
-
|
|
19
|
-
def publish(event, **kwargs)
|
|
20
|
-
key = kwargs[:key] || "-"
|
|
21
|
-
@subscribers[event.to_sym].each do |sub|
|
|
22
|
-
next unless match?(sub[:keys], key)
|
|
23
|
-
|
|
24
|
-
invoke(event, sub, key, kwargs)
|
|
25
|
-
end
|
|
26
|
-
end
|
|
27
|
-
|
|
28
|
-
private
|
|
29
|
-
|
|
30
|
-
def invoke(event, sub, key, kwargs)
|
|
31
|
-
accepted = filter_kwargs(sub[:callable], kwargs)
|
|
32
|
-
Timeout.timeout(HOOK_TIMEOUT_SECONDS) { sub[:callable].call(**accepted) }
|
|
33
|
-
rescue StandardError => e
|
|
34
|
-
extras = { "event" => event.to_s, "hook" => sub[:name].to_s, "error" => "#{e.class}: #{e.message}" }
|
|
35
|
-
extras["target_key"] = kwargs[:target_key] if kwargs.key?(:target_key)
|
|
36
|
-
extras["pending_key"] = kwargs[:pending_key] if kwargs.key?(:pending_key)
|
|
37
|
-
@audit_log.append(
|
|
38
|
-
role: "runner", verb: "event_error", key: key,
|
|
39
|
-
etag_before: nil, etag_after: nil, extras: extras
|
|
40
|
-
)
|
|
41
|
-
end
|
|
42
|
-
|
|
43
|
-
# Passes only the kwargs a hook block declares. Lets us extend event
|
|
44
|
-
# payloads (e.g., correlation_id) without breaking hooks written against
|
|
45
|
-
# the old signature.
|
|
46
|
-
def filter_kwargs(callable, kwargs)
|
|
47
|
-
params = callable.parameters
|
|
48
|
-
return kwargs if params.any? { |type, _| type == :keyrest }
|
|
49
|
-
|
|
50
|
-
accepted = params.each_with_object([]) do |(type, name), acc|
|
|
51
|
-
acc << name if %i[key keyreq].include?(type)
|
|
52
|
-
end
|
|
53
|
-
kwargs.slice(*accepted)
|
|
54
|
-
end
|
|
55
|
-
|
|
56
|
-
def match?(globs, key)
|
|
57
|
-
return true if globs.nil?
|
|
58
|
-
|
|
59
|
-
Array(globs).any? { |g| File.fnmatch?(g, key.to_s, File::FNM_PATHNAME) }
|
|
60
|
-
end
|
|
61
|
-
end
|
|
62
|
-
end
|
|
63
|
-
end
|
data/lib/textus/hooks/dsl.rb
DELETED
|
@@ -1,81 +0,0 @@
|
|
|
1
|
-
module Textus
|
|
2
|
-
module Hooks
|
|
3
|
-
class Registry
|
|
4
|
-
EVENTS = {
|
|
5
|
-
# RPC: exactly 1 handler per name; return value flows into store; failure aborts.
|
|
6
|
-
resolve_intake: { mode: :rpc, args: %i[store config args] },
|
|
7
|
-
transform_rows: { mode: :rpc, args: %i[store rows config] },
|
|
8
|
-
validate: { mode: :rpc, args: %i[store] },
|
|
9
|
-
|
|
10
|
-
# Pub-sub: 0..N handlers per event; return discarded; failure logged to audit.
|
|
11
|
-
entry_put: { mode: :pubsub, args: %i[store key envelope] },
|
|
12
|
-
entry_deleted: { mode: :pubsub, args: %i[store key] },
|
|
13
|
-
entry_refreshed: { mode: :pubsub, args: %i[store key envelope change] },
|
|
14
|
-
entry_renamed: { mode: :pubsub, args: %i[store key from_key to_key envelope] },
|
|
15
|
-
build_completed: { mode: :pubsub, args: %i[store key envelope sources] },
|
|
16
|
-
proposal_accepted: { mode: :pubsub, args: %i[store key target_key] },
|
|
17
|
-
proposal_rejected: { mode: :pubsub, args: %i[store key target_key] },
|
|
18
|
-
file_published: { mode: :pubsub, args: %i[store key envelope source target] },
|
|
19
|
-
store_loaded: { mode: :pubsub, args: %i[store] },
|
|
20
|
-
refresh_started: { mode: :pubsub, args: %i[store key mode] },
|
|
21
|
-
refresh_failed: { mode: :pubsub, args: %i[store key error_class error_message] },
|
|
22
|
-
refresh_backgrounded: { mode: :pubsub, args: %i[store key started_at budget_ms] },
|
|
23
|
-
}.freeze
|
|
24
|
-
|
|
25
|
-
def initialize(dispatcher: nil)
|
|
26
|
-
@rpc = Hash.new { |h, k| h[k] = {} } # event => { name => callable }
|
|
27
|
-
@pubsub = Hash.new { |h, k| h[k] = [] } # event => [{name:, callable:, keys:}]
|
|
28
|
-
@dispatcher = dispatcher
|
|
29
|
-
end
|
|
30
|
-
|
|
31
|
-
def register(event, name, keys: nil, &blk)
|
|
32
|
-
event_sym = event.to_sym
|
|
33
|
-
spec = EVENTS[event_sym] or raise UsageError.new("unknown event: #{event}")
|
|
34
|
-
shape_check!(event_sym, spec, blk)
|
|
35
|
-
name = name.to_sym
|
|
36
|
-
|
|
37
|
-
case spec[:mode]
|
|
38
|
-
when :rpc
|
|
39
|
-
raise UsageError.new("#{event_sym} '#{name}' already registered") if @rpc[event_sym].key?(name)
|
|
40
|
-
|
|
41
|
-
@rpc[event_sym][name] = blk
|
|
42
|
-
when :pubsub
|
|
43
|
-
raise UsageError.new("#{event_sym} hook '#{name}' already registered") if @pubsub[event_sym].any? { |h| h[:name] == name }
|
|
44
|
-
|
|
45
|
-
@pubsub[event_sym] << { name: name, callable: blk, keys: keys }
|
|
46
|
-
@dispatcher&.subscribe(event_sym, name, keys: keys, &blk)
|
|
47
|
-
end
|
|
48
|
-
end
|
|
49
|
-
|
|
50
|
-
def rpc_callable(event, name)
|
|
51
|
-
@rpc[event.to_sym][name.to_sym] or
|
|
52
|
-
raise UsageError.new("unknown #{event}: #{name}")
|
|
53
|
-
end
|
|
54
|
-
|
|
55
|
-
def listeners(event, key:)
|
|
56
|
-
@pubsub[event.to_sym].select { |h| h[:keys].nil? || matches_any?(h[:keys], key) }
|
|
57
|
-
end
|
|
58
|
-
|
|
59
|
-
def rpc_names(event) = @rpc[event.to_sym].keys
|
|
60
|
-
def pubsub_handlers(event) = @pubsub[event.to_sym]
|
|
61
|
-
|
|
62
|
-
private
|
|
63
|
-
|
|
64
|
-
def shape_check!(event, spec, blk)
|
|
65
|
-
required = spec[:args]
|
|
66
|
-
provided = blk.parameters.select { |t, _| %i[keyreq key keyrest].include?(t) } # rubocop:disable Style/HashSlice
|
|
67
|
-
keyrest = provided.any? { |t, _| t == :keyrest }
|
|
68
|
-
missing = required - provided.map { |_, n| n }
|
|
69
|
-
return if keyrest || missing.empty?
|
|
70
|
-
|
|
71
|
-
raise UsageError.new(
|
|
72
|
-
"#{event} hooks must accept kwargs: #{required.join(", ")} (missing: #{missing.join(", ")})",
|
|
73
|
-
)
|
|
74
|
-
end
|
|
75
|
-
|
|
76
|
-
def matches_any?(globs, key)
|
|
77
|
-
Array(globs).any? { |g| File.fnmatch?(g, key.to_s, File::FNM_PATHNAME) }
|
|
78
|
-
end
|
|
79
|
-
end
|
|
80
|
-
end
|
|
81
|
-
end
|