Module: Textus::Schema::Tools

Defined in:
lib/textus/schema/tools.rb

Class Method Summary collapse

Class Method Details

.diff(store, name:) ⇒ Object

textus schema diff NAME → list keys whose frontmatter violates the schema



24
25
26
27
28
29
30
31
32
33
34
35
36
# File 'lib/textus/schema/tools.rb', line 24

def self.diff(store, name:)
  schema = load_schema(store, name)
  drift = []
  store.manifest.enumerate.each do |row|
    env = store.get(row[:key])
    begin
      schema.validate!(env["_meta"])
    rescue SchemaViolation => e
      drift << { "key" => row[:key], "details" => e.details }
    end
  end
  { "protocol" => PROTOCOL, "schema_name" => name, "drift" => drift }
end

.infer_type(value) ⇒ Object



71
72
73
74
75
76
77
78
79
80
# File 'lib/textus/schema/tools.rb', line 71

def self.infer_type(value)
  case value
  when String  then "string"
  when Numeric then "number"
  when true, false then "boolean"
  when Array   then "array"
  when Hash    then "object"
  else "string"
  end
end

.init(store, name:, from:) ⇒ Object

textus schema init NAME –from=KEY → infer YAML schema from an entry’s frontmatter



8
9
10
11
12
13
14
15
16
17
18
19
20
21
# File 'lib/textus/schema/tools.rb', line 8

def self.init(store, name:, from:)
  env = store.get(from)
  meta = env["_meta"]
  schema = {
    "name" => name,
    "required" => meta.keys,
    "optional" => [],
    "fields" => meta.each_with_object({}) { |(k, v), h| h[k] = { "type" => infer_type(v) } },
  }
  FileUtils.mkdir_p(File.join(store.root, "schemas"))
  target = File.join(store.root, "schemas", "#{name}.yaml")
  File.write(target, YAML.dump(schema))
  { "protocol" => PROTOCOL, "schema_name" => name, "path" => target }
end

.load_schema(store, name) ⇒ Object



82
83
84
85
86
# File 'lib/textus/schema/tools.rb', line 82

def self.load_schema(store, name)
  store.schema_for(name)
rescue IoError
  raise UsageError.new("schema not found: #{name}")
end

.migrate(store, name:, rename: nil) ⇒ Object

textus schema migrate NAME –rename=OLD:NEW → rewrites frontmatter across affected entries If –rename is omitted, falls back to schema.evolution.migrate_from.

Raises:



40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
# File 'lib/textus/schema/tools.rb', line 40

def self.migrate(store, name:, rename: nil)
  renames =
    if rename
      old_field, new_field = rename.split(":", 2)
      raise UsageError.new("--rename=OLD:NEW") unless old_field && new_field && !new_field.empty?

      { old_field => new_field }
    else
      load_schema(store, name).evolution["migrate_from"] || {}
    end
  raise UsageError.new("schema migrate needs --rename=OLD:NEW or schema.evolution.migrate_from") if renames.empty?

  touched = []
  store.manifest.enumerate.each do |row|
    env = store.get(row[:key])
    meta = env["_meta"]
    changed = false
    renames.each do |old, new|
      if meta.key?(old)
        meta[new] = meta.delete(old)
        changed = true
      end
    end
    next unless changed

    store.put(row[:key], meta: meta, body: env["body"], as: "human")
    touched << row[:key]
  end
  { "protocol" => PROTOCOL, "migrated" => touched, "renames" => renames }
end