textus 0.4.0 → 0.8.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 +147 -2
- data/README.md +38 -28
- data/SPEC.md +84 -147
- data/docs/architecture.md +82 -28
- data/lib/textus/builder/pipeline.rb +56 -0
- data/lib/textus/builder/renderer/json.rb +42 -0
- data/lib/textus/builder/renderer/markdown.rb +22 -0
- data/lib/textus/builder/renderer/text.rb +14 -0
- data/lib/textus/builder/renderer/yaml.rb +42 -0
- data/lib/textus/builder/renderer.rb +17 -0
- data/lib/textus/builder.rb +9 -114
- data/lib/textus/cli/group/hook.rb +11 -0
- data/lib/textus/cli/group/key.rb +12 -0
- data/lib/textus/cli/group/schema.rb +13 -0
- data/lib/textus/cli/group.rb +51 -0
- data/lib/textus/cli/verb/accept.rb +15 -0
- data/lib/textus/cli/verb/build.rb +13 -0
- data/lib/textus/cli/verb/delete.rb +16 -0
- data/lib/textus/cli/verb/deps.rb +12 -0
- data/lib/textus/cli/verb/doctor.rb +15 -0
- data/lib/textus/cli/verb/get.rb +12 -0
- data/lib/textus/cli/verb/hook_run.rb +48 -0
- data/lib/textus/cli/verb/hooks.rb +50 -0
- data/lib/textus/cli/verb/init.rb +14 -0
- data/lib/textus/cli/verb/intro.rb +11 -0
- data/lib/textus/cli/verb/list.rb +14 -0
- data/lib/textus/cli/verb/migrate_keys.rb +16 -0
- data/lib/textus/cli/verb/mv.rb +17 -0
- data/lib/textus/cli/verb/published.rb +11 -0
- data/lib/textus/cli/verb/put.rb +50 -0
- data/lib/textus/cli/verb/rdeps.rb +12 -0
- data/lib/textus/cli/verb/refresh.rb +15 -0
- data/lib/textus/cli/verb/schema.rb +12 -0
- data/lib/textus/cli/verb/schema_diff.rb +12 -0
- data/lib/textus/cli/verb/schema_init.rb +16 -0
- data/lib/textus/cli/verb/schema_migrate.rb +16 -0
- data/lib/textus/cli/verb/stale.rb +14 -0
- data/lib/textus/cli/verb/uid.rb +12 -0
- data/lib/textus/cli/verb/where.rb +12 -0
- data/lib/textus/cli/verb.rb +62 -0
- data/lib/textus/cli.rb +44 -385
- data/lib/textus/doctor/check/audit_log.rb +50 -0
- data/lib/textus/doctor/check/hooks.rb +29 -0
- data/lib/textus/doctor/check/illegal_keys.rb +49 -0
- data/lib/textus/doctor/check/manifest_files.rb +38 -0
- data/lib/textus/doctor/check/schema_violations.rb +22 -0
- data/lib/textus/doctor/check/schemas.rb +26 -0
- data/lib/textus/doctor/check/sentinels.rb +57 -0
- data/lib/textus/doctor/check/templates.rb +26 -0
- data/lib/textus/doctor/check/unowned_schema_fields.rb +34 -0
- data/lib/textus/doctor/check.rb +30 -0
- data/lib/textus/doctor.rb +29 -264
- data/lib/textus/entry/base.rb +30 -0
- data/lib/textus/entry/json.rb +11 -5
- data/lib/textus/entry/markdown.rb +5 -5
- data/lib/textus/entry/text.rb +4 -4
- data/lib/textus/entry/yaml.rb +11 -5
- data/lib/textus/entry.rb +2 -7
- data/lib/textus/envelope.rb +30 -0
- data/lib/textus/errors.rb +2 -2
- data/lib/textus/hooks/builtin.rb +70 -0
- data/lib/textus/hooks/dispatcher.rb +49 -0
- data/lib/textus/hooks/loader.rb +26 -0
- data/lib/textus/hooks/registry.rb +73 -0
- data/lib/textus/init.rb +14 -11
- data/lib/textus/intro.rb +16 -18
- data/lib/textus/key/distance.rb +55 -0
- data/lib/textus/key/grammar.rb +33 -0
- data/lib/textus/key/path.rb +17 -0
- data/lib/textus/manifest/entry.rb +199 -0
- data/lib/textus/manifest.rb +20 -254
- data/lib/textus/migrate_keys.rb +1 -1
- data/lib/textus/projection.rb +6 -5
- data/lib/textus/proposal.rb +4 -4
- data/lib/textus/refresh.rb +17 -17
- data/lib/textus/schema/tools.rb +89 -0
- data/lib/textus/store/audit_log.rb +71 -0
- data/lib/textus/store/mover.rb +121 -0
- data/lib/textus/store/reader.rb +67 -0
- data/lib/textus/store/staleness.rb +133 -0
- data/lib/textus/store/validator.rb +56 -0
- data/lib/textus/store/view.rb +29 -0
- data/lib/textus/store/writer.rb +132 -0
- data/lib/textus/store.rb +26 -527
- data/lib/textus/version.rb +2 -2
- data/lib/textus.rb +14 -29
- metadata +78 -8
- data/lib/textus/audit_log.rb +0 -32
- data/lib/textus/builtin_actions.rb +0 -68
- data/lib/textus/extension_registry.rb +0 -61
- data/lib/textus/extensions.rb +0 -33
- data/lib/textus/key_distance.rb +0 -53
- data/lib/textus/schema_tools.rb +0 -87
- data/lib/textus/store_view.rb +0 -27
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.8.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Patrick
|
|
@@ -51,6 +51,20 @@ dependencies:
|
|
|
51
51
|
- - ">="
|
|
52
52
|
- !ruby/object:Gem::Version
|
|
53
53
|
version: '3.2'
|
|
54
|
+
- !ruby/object:Gem::Dependency
|
|
55
|
+
name: zeitwerk
|
|
56
|
+
requirement: !ruby/object:Gem::Requirement
|
|
57
|
+
requirements:
|
|
58
|
+
- - "~>"
|
|
59
|
+
- !ruby/object:Gem::Version
|
|
60
|
+
version: '2.6'
|
|
61
|
+
type: :runtime
|
|
62
|
+
prerelease: false
|
|
63
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
64
|
+
requirements:
|
|
65
|
+
- - "~>"
|
|
66
|
+
- !ruby/object:Gem::Version
|
|
67
|
+
version: '2.6'
|
|
54
68
|
- !ruby/object:Gem::Dependency
|
|
55
69
|
name: rake
|
|
56
70
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -95,25 +109,75 @@ files:
|
|
|
95
109
|
- docs/conventions.md
|
|
96
110
|
- exe/textus
|
|
97
111
|
- lib/textus.rb
|
|
98
|
-
- lib/textus/audit_log.rb
|
|
99
112
|
- lib/textus/builder.rb
|
|
100
|
-
- lib/textus/
|
|
113
|
+
- lib/textus/builder/pipeline.rb
|
|
114
|
+
- lib/textus/builder/renderer.rb
|
|
115
|
+
- lib/textus/builder/renderer/json.rb
|
|
116
|
+
- lib/textus/builder/renderer/markdown.rb
|
|
117
|
+
- lib/textus/builder/renderer/text.rb
|
|
118
|
+
- lib/textus/builder/renderer/yaml.rb
|
|
101
119
|
- lib/textus/cli.rb
|
|
120
|
+
- lib/textus/cli/group.rb
|
|
121
|
+
- lib/textus/cli/group/hook.rb
|
|
122
|
+
- lib/textus/cli/group/key.rb
|
|
123
|
+
- lib/textus/cli/group/schema.rb
|
|
124
|
+
- lib/textus/cli/verb.rb
|
|
125
|
+
- lib/textus/cli/verb/accept.rb
|
|
126
|
+
- lib/textus/cli/verb/build.rb
|
|
127
|
+
- lib/textus/cli/verb/delete.rb
|
|
128
|
+
- lib/textus/cli/verb/deps.rb
|
|
129
|
+
- lib/textus/cli/verb/doctor.rb
|
|
130
|
+
- lib/textus/cli/verb/get.rb
|
|
131
|
+
- lib/textus/cli/verb/hook_run.rb
|
|
132
|
+
- lib/textus/cli/verb/hooks.rb
|
|
133
|
+
- lib/textus/cli/verb/init.rb
|
|
134
|
+
- lib/textus/cli/verb/intro.rb
|
|
135
|
+
- lib/textus/cli/verb/list.rb
|
|
136
|
+
- lib/textus/cli/verb/migrate_keys.rb
|
|
137
|
+
- lib/textus/cli/verb/mv.rb
|
|
138
|
+
- lib/textus/cli/verb/published.rb
|
|
139
|
+
- lib/textus/cli/verb/put.rb
|
|
140
|
+
- lib/textus/cli/verb/rdeps.rb
|
|
141
|
+
- lib/textus/cli/verb/refresh.rb
|
|
142
|
+
- lib/textus/cli/verb/schema.rb
|
|
143
|
+
- lib/textus/cli/verb/schema_diff.rb
|
|
144
|
+
- lib/textus/cli/verb/schema_init.rb
|
|
145
|
+
- lib/textus/cli/verb/schema_migrate.rb
|
|
146
|
+
- lib/textus/cli/verb/stale.rb
|
|
147
|
+
- lib/textus/cli/verb/uid.rb
|
|
148
|
+
- lib/textus/cli/verb/where.rb
|
|
102
149
|
- lib/textus/dependencies.rb
|
|
103
150
|
- lib/textus/doctor.rb
|
|
151
|
+
- lib/textus/doctor/check.rb
|
|
152
|
+
- lib/textus/doctor/check/audit_log.rb
|
|
153
|
+
- lib/textus/doctor/check/hooks.rb
|
|
154
|
+
- lib/textus/doctor/check/illegal_keys.rb
|
|
155
|
+
- lib/textus/doctor/check/manifest_files.rb
|
|
156
|
+
- lib/textus/doctor/check/schema_violations.rb
|
|
157
|
+
- lib/textus/doctor/check/schemas.rb
|
|
158
|
+
- lib/textus/doctor/check/sentinels.rb
|
|
159
|
+
- lib/textus/doctor/check/templates.rb
|
|
160
|
+
- lib/textus/doctor/check/unowned_schema_fields.rb
|
|
104
161
|
- lib/textus/entry.rb
|
|
162
|
+
- lib/textus/entry/base.rb
|
|
105
163
|
- lib/textus/entry/json.rb
|
|
106
164
|
- lib/textus/entry/markdown.rb
|
|
107
165
|
- lib/textus/entry/text.rb
|
|
108
166
|
- lib/textus/entry/yaml.rb
|
|
167
|
+
- lib/textus/envelope.rb
|
|
109
168
|
- lib/textus/errors.rb
|
|
110
169
|
- lib/textus/etag.rb
|
|
111
|
-
- lib/textus/
|
|
112
|
-
- lib/textus/
|
|
170
|
+
- lib/textus/hooks/builtin.rb
|
|
171
|
+
- lib/textus/hooks/dispatcher.rb
|
|
172
|
+
- lib/textus/hooks/loader.rb
|
|
173
|
+
- lib/textus/hooks/registry.rb
|
|
113
174
|
- lib/textus/init.rb
|
|
114
175
|
- lib/textus/intro.rb
|
|
115
|
-
- lib/textus/
|
|
176
|
+
- lib/textus/key/distance.rb
|
|
177
|
+
- lib/textus/key/grammar.rb
|
|
178
|
+
- lib/textus/key/path.rb
|
|
116
179
|
- lib/textus/manifest.rb
|
|
180
|
+
- lib/textus/manifest/entry.rb
|
|
117
181
|
- lib/textus/migrate_keys.rb
|
|
118
182
|
- lib/textus/mustache.rb
|
|
119
183
|
- lib/textus/projection.rb
|
|
@@ -122,9 +186,15 @@ files:
|
|
|
122
186
|
- lib/textus/refresh.rb
|
|
123
187
|
- lib/textus/role.rb
|
|
124
188
|
- lib/textus/schema.rb
|
|
125
|
-
- lib/textus/
|
|
189
|
+
- lib/textus/schema/tools.rb
|
|
126
190
|
- lib/textus/store.rb
|
|
127
|
-
- lib/textus/
|
|
191
|
+
- lib/textus/store/audit_log.rb
|
|
192
|
+
- lib/textus/store/mover.rb
|
|
193
|
+
- lib/textus/store/reader.rb
|
|
194
|
+
- lib/textus/store/staleness.rb
|
|
195
|
+
- lib/textus/store/validator.rb
|
|
196
|
+
- lib/textus/store/view.rb
|
|
197
|
+
- lib/textus/store/writer.rb
|
|
128
198
|
- lib/textus/version.rb
|
|
129
199
|
homepage: https://github.com/patrick204nqh/textus
|
|
130
200
|
licenses:
|
data/lib/textus/audit_log.rb
DELETED
|
@@ -1,32 +0,0 @@
|
|
|
1
|
-
require "json"
|
|
2
|
-
require "time"
|
|
3
|
-
|
|
4
|
-
module Textus
|
|
5
|
-
class AuditLog
|
|
6
|
-
def initialize(root)
|
|
7
|
-
@path = File.join(root, "audit.log")
|
|
8
|
-
end
|
|
9
|
-
|
|
10
|
-
def last_writer_for(key)
|
|
11
|
-
return nil unless File.exist?(@path)
|
|
12
|
-
|
|
13
|
-
File.foreach(@path).map { |l| l.chomp.split("\t") }
|
|
14
|
-
.select { |row| row[3] == key && %w[put delete].include?(row[2]) }
|
|
15
|
-
.last&.fetch(1)
|
|
16
|
-
end
|
|
17
|
-
|
|
18
|
-
def append(role:, verb:, key:, etag_before:, etag_after:, extras: nil)
|
|
19
|
-
fields = [
|
|
20
|
-
Time.now.utc.iso8601, role, verb, key,
|
|
21
|
-
etag_before || "NULL",
|
|
22
|
-
etag_after || "NULL"
|
|
23
|
-
]
|
|
24
|
-
fields << JSON.generate(extras) if extras && !extras.empty?
|
|
25
|
-
line = fields.join("\t") + "\n"
|
|
26
|
-
File.open(@path, File::WRONLY | File::APPEND | File::CREAT, 0o644) do |f|
|
|
27
|
-
f.flock(File::LOCK_EX)
|
|
28
|
-
f.write(line)
|
|
29
|
-
end
|
|
30
|
-
end
|
|
31
|
-
end
|
|
32
|
-
end
|
|
@@ -1,68 +0,0 @@
|
|
|
1
|
-
require "json"
|
|
2
|
-
require "csv"
|
|
3
|
-
require "yaml"
|
|
4
|
-
require "rexml/document"
|
|
5
|
-
|
|
6
|
-
module Textus
|
|
7
|
-
module BuiltinActions
|
|
8
|
-
# rubocop:disable Metrics/AbcSize, Metrics/MethodLength
|
|
9
|
-
def self.register_all
|
|
10
|
-
Textus.action(:json) do |config:, store:, args:|
|
|
11
|
-
_ = store
|
|
12
|
-
_ = args
|
|
13
|
-
data = JSON.parse(config["bytes"].to_s)
|
|
14
|
-
{ frontmatter: {}, body: YAML.dump(data) }
|
|
15
|
-
end
|
|
16
|
-
|
|
17
|
-
Textus.action(:csv) do |config:, store:, args:|
|
|
18
|
-
_ = store
|
|
19
|
-
_ = args
|
|
20
|
-
rows = CSV.parse(config["bytes"].to_s, headers: true).map(&:to_h)
|
|
21
|
-
{ frontmatter: {}, body: YAML.dump(rows) }
|
|
22
|
-
end
|
|
23
|
-
|
|
24
|
-
Textus.action(:"markdown-links") do |config:, store:, args:|
|
|
25
|
-
_ = store
|
|
26
|
-
_ = args
|
|
27
|
-
links = config["bytes"].to_s.scan(%r{\[([^\]]+)\]\((https?://[^)\s]+)\)}).map do |text, href|
|
|
28
|
-
{ "text" => text, "href" => href }
|
|
29
|
-
end
|
|
30
|
-
{ frontmatter: {}, body: YAML.dump(links) }
|
|
31
|
-
end
|
|
32
|
-
|
|
33
|
-
Textus.action(:"ical-events") do |config:, store:, args:|
|
|
34
|
-
_ = store
|
|
35
|
-
_ = args
|
|
36
|
-
events = []
|
|
37
|
-
current = nil
|
|
38
|
-
config["bytes"].to_s.each_line do |line|
|
|
39
|
-
line = line.strip
|
|
40
|
-
case line
|
|
41
|
-
when "BEGIN:VEVENT" then current = {}
|
|
42
|
-
when "END:VEVENT"
|
|
43
|
-
events << current if current
|
|
44
|
-
current = nil
|
|
45
|
-
when /\A(SUMMARY|DTSTART|DTEND|UID|LOCATION|DESCRIPTION):(.*)\z/
|
|
46
|
-
current[Regexp.last_match(1).downcase] = Regexp.last_match(2) if current
|
|
47
|
-
end
|
|
48
|
-
end
|
|
49
|
-
{ frontmatter: {}, body: YAML.dump(events) }
|
|
50
|
-
end
|
|
51
|
-
|
|
52
|
-
Textus.action(:rss) do |config:, store:, args:|
|
|
53
|
-
_ = store
|
|
54
|
-
_ = args
|
|
55
|
-
doc = REXML::Document.new(config["bytes"].to_s)
|
|
56
|
-
items = doc.elements.to_a("//item").map do |item|
|
|
57
|
-
{
|
|
58
|
-
"title" => item.elements["title"]&.text,
|
|
59
|
-
"link" => item.elements["link"]&.text,
|
|
60
|
-
"pubDate" => item.elements["pubDate"]&.text,
|
|
61
|
-
}
|
|
62
|
-
end
|
|
63
|
-
{ frontmatter: {}, body: YAML.dump(items) }
|
|
64
|
-
end
|
|
65
|
-
end
|
|
66
|
-
# rubocop:enable Metrics/AbcSize, Metrics/MethodLength
|
|
67
|
-
end
|
|
68
|
-
end
|
|
@@ -1,61 +0,0 @@
|
|
|
1
|
-
module Textus
|
|
2
|
-
class ExtensionRegistry
|
|
3
|
-
EVENTS = %i[put delete refresh build accept].freeze
|
|
4
|
-
|
|
5
|
-
def initialize
|
|
6
|
-
@actions = {}
|
|
7
|
-
@reducers = {}
|
|
8
|
-
@hooks = {}
|
|
9
|
-
@doctor_checks = {}
|
|
10
|
-
end
|
|
11
|
-
|
|
12
|
-
def register_action(name, &blk)
|
|
13
|
-
name = name.to_sym
|
|
14
|
-
raise UsageError.new("action '#{name}' already registered") if @actions.key?(name)
|
|
15
|
-
|
|
16
|
-
@actions[name] = blk
|
|
17
|
-
end
|
|
18
|
-
|
|
19
|
-
def register_reducer(name, &blk)
|
|
20
|
-
name = name.to_sym
|
|
21
|
-
raise UsageError.new("reducer '#{name}' already registered") if @reducers.key?(name)
|
|
22
|
-
|
|
23
|
-
@reducers[name] = blk
|
|
24
|
-
end
|
|
25
|
-
|
|
26
|
-
def register_hook(event, name, &blk)
|
|
27
|
-
event = event.to_sym
|
|
28
|
-
raise UsageError.new("unknown event: #{event}") unless EVENTS.include?(event)
|
|
29
|
-
|
|
30
|
-
(@hooks[event] ||= []) << { name: name.to_sym, callable: blk }
|
|
31
|
-
end
|
|
32
|
-
|
|
33
|
-
def register_doctor_check(name, &blk)
|
|
34
|
-
name = name.to_sym
|
|
35
|
-
raise UsageError.new("doctor_check '#{name}' already registered") if @doctor_checks.key?(name)
|
|
36
|
-
|
|
37
|
-
@doctor_checks[name] = blk
|
|
38
|
-
end
|
|
39
|
-
|
|
40
|
-
def action(name)
|
|
41
|
-
@actions[name.to_sym] or raise UsageError.new("unknown action: #{name}")
|
|
42
|
-
end
|
|
43
|
-
|
|
44
|
-
def reducer(name)
|
|
45
|
-
@reducers[name.to_sym] or raise UsageError.new("unknown reducer: #{name}")
|
|
46
|
-
end
|
|
47
|
-
|
|
48
|
-
def hooks(event)
|
|
49
|
-
@hooks[event.to_sym] || []
|
|
50
|
-
end
|
|
51
|
-
|
|
52
|
-
def doctor_check(name)
|
|
53
|
-
@doctor_checks[name.to_sym] or raise UsageError.new("unknown doctor_check: #{name}")
|
|
54
|
-
end
|
|
55
|
-
|
|
56
|
-
def action_names = @actions.keys
|
|
57
|
-
def reducer_names = @reducers.keys
|
|
58
|
-
def hook_events = @hooks.keys
|
|
59
|
-
def doctor_check_names = @doctor_checks.keys
|
|
60
|
-
end
|
|
61
|
-
end
|
data/lib/textus/extensions.rb
DELETED
|
@@ -1,33 +0,0 @@
|
|
|
1
|
-
module Textus
|
|
2
|
-
THREAD_REGISTRY_KEY = :__textus_active_registry__
|
|
3
|
-
private_constant :THREAD_REGISTRY_KEY
|
|
4
|
-
|
|
5
|
-
def self.with_registry(registry)
|
|
6
|
-
prev = Thread.current[THREAD_REGISTRY_KEY]
|
|
7
|
-
Thread.current[THREAD_REGISTRY_KEY] = registry
|
|
8
|
-
yield
|
|
9
|
-
ensure
|
|
10
|
-
Thread.current[THREAD_REGISTRY_KEY] = prev
|
|
11
|
-
end
|
|
12
|
-
|
|
13
|
-
def self.current_registry
|
|
14
|
-
Thread.current[THREAD_REGISTRY_KEY] or
|
|
15
|
-
raise UsageError.new("no active registry; extension code must be loaded by a Store")
|
|
16
|
-
end
|
|
17
|
-
|
|
18
|
-
def self.action(name, &)
|
|
19
|
-
current_registry.register_action(name, &)
|
|
20
|
-
end
|
|
21
|
-
|
|
22
|
-
def self.reducer(name, &)
|
|
23
|
-
current_registry.register_reducer(name, &)
|
|
24
|
-
end
|
|
25
|
-
|
|
26
|
-
def self.hook(event, name, &)
|
|
27
|
-
current_registry.register_hook(event, name, &)
|
|
28
|
-
end
|
|
29
|
-
|
|
30
|
-
def self.doctor_check(name, &)
|
|
31
|
-
current_registry.register_doctor_check(name, &)
|
|
32
|
-
end
|
|
33
|
-
end
|
data/lib/textus/key_distance.rb
DELETED
|
@@ -1,53 +0,0 @@
|
|
|
1
|
-
module Textus
|
|
2
|
-
# Small utilities for ranking key suggestions. Bounded inputs only —
|
|
3
|
-
# Levenshtein is O(n*m) so we refuse to compute on long strings.
|
|
4
|
-
module KeyDistance
|
|
5
|
-
MAX_LEN = 200
|
|
6
|
-
|
|
7
|
-
# Length of the shared dot-separated prefix between two dotted keys.
|
|
8
|
-
def self.shared_prefix_segments(left, right)
|
|
9
|
-
asegs = left.split(".")
|
|
10
|
-
bsegs = right.split(".")
|
|
11
|
-
n = [asegs.length, bsegs.length].min
|
|
12
|
-
i = 0
|
|
13
|
-
i += 1 while i < n && asegs[i] == bsegs[i]
|
|
14
|
-
i
|
|
15
|
-
end
|
|
16
|
-
|
|
17
|
-
# Classic iterative Levenshtein with two rows. Bounded to MAX_LEN.
|
|
18
|
-
def self.levenshtein(left, right)
|
|
19
|
-
return nil if left.length > MAX_LEN || right.length > MAX_LEN
|
|
20
|
-
return right.length if left.empty?
|
|
21
|
-
return left.length if right.empty?
|
|
22
|
-
|
|
23
|
-
prev = (0..right.length).to_a
|
|
24
|
-
curr = Array.new(right.length + 1, 0)
|
|
25
|
-
(1..left.length).each do |i|
|
|
26
|
-
curr[0] = i
|
|
27
|
-
(1..right.length).each do |j|
|
|
28
|
-
cost = left[i - 1] == right[j - 1] ? 0 : 1
|
|
29
|
-
curr[j] = [
|
|
30
|
-
curr[j - 1] + 1, # insertion
|
|
31
|
-
prev[j] + 1, # deletion
|
|
32
|
-
prev[j - 1] + cost, # substitution
|
|
33
|
-
].min
|
|
34
|
-
end
|
|
35
|
-
prev, curr = curr, prev
|
|
36
|
-
end
|
|
37
|
-
prev[right.length]
|
|
38
|
-
end
|
|
39
|
-
|
|
40
|
-
# Rank candidate keys against requested. Returns up to `limit` keys.
|
|
41
|
-
# Sort: longer shared prefix first; then smaller Levenshtein distance.
|
|
42
|
-
def self.suggest(requested, candidates, limit: 5)
|
|
43
|
-
return [] if requested.nil? || requested.empty?
|
|
44
|
-
|
|
45
|
-
scored = candidates.first(200).map do |k|
|
|
46
|
-
prefix = shared_prefix_segments(requested, k)
|
|
47
|
-
dist = levenshtein(requested, k) || Float::INFINITY
|
|
48
|
-
[k, prefix, dist]
|
|
49
|
-
end
|
|
50
|
-
scored.sort_by { |(_, prefix, dist)| [-prefix, dist] }.first(limit).map(&:first)
|
|
51
|
-
end
|
|
52
|
-
end
|
|
53
|
-
end
|
data/lib/textus/schema_tools.rb
DELETED
|
@@ -1,87 +0,0 @@
|
|
|
1
|
-
require "yaml"
|
|
2
|
-
require "fileutils"
|
|
3
|
-
|
|
4
|
-
module Textus
|
|
5
|
-
module SchemaTools
|
|
6
|
-
# textus schema-init NAME --from=KEY → infer YAML schema from an entry's frontmatter
|
|
7
|
-
def self.init(store, name:, from:)
|
|
8
|
-
env = store.get(from)
|
|
9
|
-
fm = env["frontmatter"]
|
|
10
|
-
schema = {
|
|
11
|
-
"name" => name,
|
|
12
|
-
"required" => fm.keys,
|
|
13
|
-
"optional" => [],
|
|
14
|
-
"fields" => fm.each_with_object({}) { |(k, v), h| h[k] = { "type" => infer_type(v) } },
|
|
15
|
-
}
|
|
16
|
-
FileUtils.mkdir_p(File.join(store.root, "schemas"))
|
|
17
|
-
target = File.join(store.root, "schemas", "#{name}.yaml")
|
|
18
|
-
File.write(target, YAML.dump(schema))
|
|
19
|
-
{ "protocol" => PROTOCOL, "schema_name" => name, "path" => target }
|
|
20
|
-
end
|
|
21
|
-
|
|
22
|
-
# textus schema-diff NAME → list keys whose frontmatter violates the schema
|
|
23
|
-
def self.diff(store, name:)
|
|
24
|
-
schema = load_schema(store, name)
|
|
25
|
-
drift = []
|
|
26
|
-
store.manifest.enumerate.each do |row|
|
|
27
|
-
env = store.get(row[:key])
|
|
28
|
-
begin
|
|
29
|
-
schema.validate!(env["frontmatter"])
|
|
30
|
-
rescue SchemaViolation => e
|
|
31
|
-
drift << { "key" => row[:key], "details" => e.details }
|
|
32
|
-
end
|
|
33
|
-
end
|
|
34
|
-
{ "protocol" => PROTOCOL, "schema_name" => name, "drift" => drift }
|
|
35
|
-
end
|
|
36
|
-
|
|
37
|
-
# textus schema-migrate NAME --rename=OLD:NEW → rewrites frontmatter across affected entries
|
|
38
|
-
# If --rename is omitted, falls back to schema.evolution.migrate_from.
|
|
39
|
-
def self.migrate(store, name:, rename: nil)
|
|
40
|
-
renames =
|
|
41
|
-
if rename
|
|
42
|
-
old_field, new_field = rename.split(":", 2)
|
|
43
|
-
raise UsageError.new("--rename=OLD:NEW") unless old_field && new_field && !new_field.empty?
|
|
44
|
-
|
|
45
|
-
{ old_field => new_field }
|
|
46
|
-
else
|
|
47
|
-
load_schema(store, name).evolution["migrate_from"] || {}
|
|
48
|
-
end
|
|
49
|
-
raise UsageError.new("schema-migrate needs --rename=OLD:NEW or schema.evolution.migrate_from") if renames.empty?
|
|
50
|
-
|
|
51
|
-
touched = []
|
|
52
|
-
store.manifest.enumerate.each do |row|
|
|
53
|
-
env = store.get(row[:key])
|
|
54
|
-
fm = env["frontmatter"]
|
|
55
|
-
changed = false
|
|
56
|
-
renames.each do |old, new|
|
|
57
|
-
if fm.key?(old)
|
|
58
|
-
fm[new] = fm.delete(old)
|
|
59
|
-
changed = true
|
|
60
|
-
end
|
|
61
|
-
end
|
|
62
|
-
next unless changed
|
|
63
|
-
|
|
64
|
-
store.put(row[:key], frontmatter: fm, body: env["body"], as: "human")
|
|
65
|
-
touched << row[:key]
|
|
66
|
-
end
|
|
67
|
-
{ "protocol" => PROTOCOL, "migrated" => touched, "renames" => renames }
|
|
68
|
-
end
|
|
69
|
-
|
|
70
|
-
def self.infer_type(value)
|
|
71
|
-
case value
|
|
72
|
-
when String then "string"
|
|
73
|
-
when Numeric then "number"
|
|
74
|
-
when true, false then "boolean"
|
|
75
|
-
when Array then "array"
|
|
76
|
-
when Hash then "object"
|
|
77
|
-
else "string"
|
|
78
|
-
end
|
|
79
|
-
end
|
|
80
|
-
|
|
81
|
-
def self.load_schema(store, name)
|
|
82
|
-
store.schema_for(name)
|
|
83
|
-
rescue IoError
|
|
84
|
-
raise UsageError.new("schema not found: #{name}")
|
|
85
|
-
end
|
|
86
|
-
end
|
|
87
|
-
end
|
data/lib/textus/store_view.rb
DELETED
|
@@ -1,27 +0,0 @@
|
|
|
1
|
-
module Textus
|
|
2
|
-
class StoreView
|
|
3
|
-
READ_METHODS = %i[get list where schema_envelope deps rdeps published stale validate_all].freeze
|
|
4
|
-
WRITE_METHODS = %i[put delete accept].freeze
|
|
5
|
-
|
|
6
|
-
def initialize(store, writable: false, as: nil)
|
|
7
|
-
raise UsageError.new("writable StoreView requires an as: role") if writable && (as.nil? || as.to_s.empty?)
|
|
8
|
-
|
|
9
|
-
@store = store
|
|
10
|
-
@writable = writable
|
|
11
|
-
@as = as
|
|
12
|
-
end
|
|
13
|
-
|
|
14
|
-
READ_METHODS.each do |m|
|
|
15
|
-
define_method(m) { |*args, **kw| @store.public_send(m, *args, **kw) }
|
|
16
|
-
end
|
|
17
|
-
|
|
18
|
-
WRITE_METHODS.each do |m|
|
|
19
|
-
define_method(m) do |*args, **kw|
|
|
20
|
-
raise UsageError.new("StoreView is read-only") unless @writable
|
|
21
|
-
|
|
22
|
-
kw[:as] = @as unless kw.key?(:as)
|
|
23
|
-
@store.public_send(m, *args, **kw)
|
|
24
|
-
end
|
|
25
|
-
end
|
|
26
|
-
end
|
|
27
|
-
end
|