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
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
module Textus
|
|
2
|
+
class CLI
|
|
3
|
+
class Verb
|
|
4
|
+
class MigrateKeys < Verb
|
|
5
|
+
option :write, "--write"
|
|
6
|
+
option :dry_run, "--dry-run"
|
|
7
|
+
|
|
8
|
+
def call(store)
|
|
9
|
+
effective_write = write && !dry_run
|
|
10
|
+
res = Textus::MigrateKeys.run(store, write: effective_write || false)
|
|
11
|
+
emit(res, exit_code: res["ok"] ? 0 : 1)
|
|
12
|
+
end
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
end
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
module Textus
|
|
2
|
+
class CLI
|
|
3
|
+
class Verb
|
|
4
|
+
class Mv < Verb
|
|
5
|
+
option :as_flag, "--as=ROLE"
|
|
6
|
+
option :dry_run, "--dry-run"
|
|
7
|
+
|
|
8
|
+
def call(store)
|
|
9
|
+
old_key = positional.shift or raise UsageError.new("mv requires <old-key> <new-key>")
|
|
10
|
+
new_key = positional.shift or raise UsageError.new("mv requires <old-key> <new-key>")
|
|
11
|
+
role = Role.resolve(flag: as_flag, env: ENV, root: store.root)
|
|
12
|
+
emit(store.mv(old_key, new_key, as: role, dry_run: dry_run || false))
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
end
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
module Textus
|
|
2
|
+
class CLI
|
|
3
|
+
class Verb
|
|
4
|
+
class Put < Verb
|
|
5
|
+
option :as_flag, "--as=ROLE"
|
|
6
|
+
option :use_stdin, "--stdin"
|
|
7
|
+
option :fetch_name, "--fetch=NAME"
|
|
8
|
+
|
|
9
|
+
def call(store) # rubocop:disable Metrics/AbcSize
|
|
10
|
+
key = positional.shift or raise UsageError.new("put requires a key")
|
|
11
|
+
raise UsageError.new("put requires --stdin in v1") unless use_stdin
|
|
12
|
+
|
|
13
|
+
role = Role.resolve(flag: as_flag, env: ENV, root: store.root)
|
|
14
|
+
|
|
15
|
+
raw = @stdin.read
|
|
16
|
+
payload =
|
|
17
|
+
if fetch_name
|
|
18
|
+
callable = store.registry.rpc_callable(:fetch, fetch_name)
|
|
19
|
+
result =
|
|
20
|
+
begin
|
|
21
|
+
Timeout.timeout(Textus::Refresh::FETCH_TIMEOUT_SECONDS) do
|
|
22
|
+
callable.call(config: { "bytes" => raw }, store: Textus::Store::View.new(store), args: {})
|
|
23
|
+
end
|
|
24
|
+
rescue Timeout::Error
|
|
25
|
+
raise UsageError.new(
|
|
26
|
+
"fetch '#{fetch_name}' exceeded #{Textus::Refresh::FETCH_TIMEOUT_SECONDS}s timeout",
|
|
27
|
+
)
|
|
28
|
+
end
|
|
29
|
+
basename = key.split(".").last
|
|
30
|
+
{
|
|
31
|
+
"_meta" => {
|
|
32
|
+
"name" => basename,
|
|
33
|
+
"last_refreshed_at" => Time.now.utc.iso8601,
|
|
34
|
+
"fetched_with" => fetch_name,
|
|
35
|
+
}.merge(result[:_meta] || result["_meta"] || result[:frontmatter] || result["frontmatter"] || {}),
|
|
36
|
+
"body" => result[:body] || result["body"] || "",
|
|
37
|
+
}
|
|
38
|
+
else
|
|
39
|
+
JSON.parse(raw)
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
meta = payload["_meta"] || payload["frontmatter"] || {}
|
|
43
|
+
body = payload["body"] || ""
|
|
44
|
+
if_etag = payload["if_etag"]
|
|
45
|
+
emit(store.put(key, meta: meta, body: body, if_etag: if_etag, as: role))
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
end
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
module Textus
|
|
2
|
+
class CLI
|
|
3
|
+
class Verb
|
|
4
|
+
class Refresh < Verb
|
|
5
|
+
option :as_flag, "--as=ROLE"
|
|
6
|
+
|
|
7
|
+
def call(store)
|
|
8
|
+
key = positional.shift or raise UsageError.new("refresh requires a key")
|
|
9
|
+
role = Role.resolve(flag: as_flag, env: ENV, root: store.root)
|
|
10
|
+
emit(Textus::Refresh.call(store, key, as: role))
|
|
11
|
+
end
|
|
12
|
+
end
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
end
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
module Textus
|
|
2
|
+
class CLI
|
|
3
|
+
class Verb
|
|
4
|
+
class SchemaInit < Verb
|
|
5
|
+
option :from_key, "--from=KEY"
|
|
6
|
+
|
|
7
|
+
def call(store)
|
|
8
|
+
name = positional.shift or raise UsageError.new("schema init NAME")
|
|
9
|
+
raise UsageError.new("schema init requires --from=KEY") unless from_key
|
|
10
|
+
|
|
11
|
+
emit(Textus::Schema::Tools.init(store, name: name, from: from_key))
|
|
12
|
+
end
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
end
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
module Textus
|
|
2
|
+
class CLI
|
|
3
|
+
class Verb
|
|
4
|
+
class SchemaMigrate < Verb
|
|
5
|
+
option :rename, "--rename=O:N"
|
|
6
|
+
|
|
7
|
+
def call(store)
|
|
8
|
+
name = positional.shift or raise UsageError.new("schema migrate NAME")
|
|
9
|
+
raise UsageError.new("schema migrate requires --rename=OLD:NEW") unless rename
|
|
10
|
+
|
|
11
|
+
emit(Textus::Schema::Tools.migrate(store, name: name, rename: rename))
|
|
12
|
+
end
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
end
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
require "json"
|
|
2
|
+
require "optparse"
|
|
3
|
+
|
|
4
|
+
module Textus
|
|
5
|
+
class CLI
|
|
6
|
+
# Subclasses must implement #call(store) and return an integer exit code.
|
|
7
|
+
# Use #emit(obj) for normal JSON output (returns 0).
|
|
8
|
+
# Subclasses that don't need a Textus store (e.g. Init) override
|
|
9
|
+
# `.needs_store?` to return false; dispatch will pass nil instead.
|
|
10
|
+
class Verb
|
|
11
|
+
class << self
|
|
12
|
+
def option(name, optspec)
|
|
13
|
+
options << [name, optspec]
|
|
14
|
+
attr_accessor(name)
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def options
|
|
18
|
+
@options ||= []
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def needs_store?
|
|
22
|
+
true
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def inherited(subclass)
|
|
26
|
+
super
|
|
27
|
+
subclass.instance_variable_set(:@options, [])
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def initialize(stdin:, stdout:, stderr:, cwd: nil)
|
|
32
|
+
@stdin = stdin
|
|
33
|
+
@stdout = stdout
|
|
34
|
+
@stderr = stderr
|
|
35
|
+
@cwd = cwd
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def parse(argv)
|
|
39
|
+
fmt = "json"
|
|
40
|
+
OptionParser.new do |o|
|
|
41
|
+
self.class.options.each do |name, optspec|
|
|
42
|
+
o.on(optspec) { |v| public_send(:"#{name}=", v) }
|
|
43
|
+
end
|
|
44
|
+
o.on("--format=FMT") { |v| fmt = v }
|
|
45
|
+
end.permute!(argv)
|
|
46
|
+
raise UsageError.new("only --format=json is supported in v1") unless fmt == "json"
|
|
47
|
+
|
|
48
|
+
@positional = argv.dup
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
attr_reader :positional
|
|
52
|
+
|
|
53
|
+
# Hashes get "protocol" => PROTOCOL prepended unless they already
|
|
54
|
+
# carry one (Store envelopes do). Caller's value wins on collision.
|
|
55
|
+
def emit(obj, exit_code: 0)
|
|
56
|
+
payload = obj.is_a?(Hash) ? { "protocol" => PROTOCOL }.merge(obj) : obj
|
|
57
|
+
@stdout.puts(JSON.generate(payload))
|
|
58
|
+
exit_code
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
end
|