Class: Textus::Manifest
- Inherits:
-
Object
- Object
- Textus::Manifest
- Defined in:
- lib/textus/manifest.rb,
lib/textus/manifest/entry.rb,
lib/textus/manifest/rules.rb,
lib/textus/manifest/schema.rb
Defined Under Namespace
Modules: Schema Classes: Entry, Rules
Constant Summary collapse
- EXT_TO_FORMAT =
{ ".md" => "markdown", ".json" => "json", ".yaml" => "yaml", ".yml" => "yaml", ".txt" => "text", }.freeze
- TEXTUS_2_HINT =
"Install textus 0.11.x to run the migrator, then upgrade to this version. " \ "See https://github.com/patrick204nqh/textus/blob/main/CHANGELOG.md#0110".freeze
Instance Attribute Summary collapse
-
#entries ⇒ Object
readonly
Returns the value of attribute entries.
-
#raw ⇒ Object
readonly
Returns the value of attribute raw.
-
#root ⇒ Object
readonly
Returns the value of attribute root.
Class Method Summary collapse
Instance Method Summary collapse
-
#enumerate(prefix: nil) ⇒ Object
Enumerate all entry files reachable through the manifest.
-
#initialize(root, raw) ⇒ Manifest
constructor
A new instance of Manifest.
- #permission_for(zone_name) ⇒ Object
-
#resolve(key) ⇒ Object
Returns [Manifest::Entry, resolved_path, remaining_segments].
- #rules ⇒ Object
- #rules_for(key) ⇒ Object
-
#suggestions_for(key) ⇒ Object
Returns up to 5 dotted keys from the manifest that look similar to the requested key, ranked by shared-prefix length then Levenshtein distance.
- #validate_key!(key) ⇒ Object
- #zone_readers ⇒ Object
- #zone_writers(zone_name) ⇒ Object
- #zones ⇒ Object
Constructor Details
#initialize(root, raw) ⇒ Manifest
Returns a new instance of Manifest.
77 78 79 80 81 82 83 84 85 86 |
# File 'lib/textus/manifest.rb', line 77 def initialize(root, raw) @root = root @raw = raw raise BadFrontmatter.new(File.join(root, "manifest.yaml"), "manifest must declare zones:") if Array(raw["zones"]).empty? Schema.validate!(raw) @entries = Array(raw["entries"]).map { |e| Manifest::Entry.new(self, e) } validate_declared_keys! end |
Instance Attribute Details
#entries ⇒ Object (readonly)
Returns the value of attribute entries.
23 24 25 |
# File 'lib/textus/manifest.rb', line 23 def entries @entries end |
#raw ⇒ Object (readonly)
Returns the value of attribute raw.
23 24 25 |
# File 'lib/textus/manifest.rb', line 23 def raw @raw end |
#root ⇒ Object (readonly)
Returns the value of attribute root.
23 24 25 |
# File 'lib/textus/manifest.rb', line 23 def root @root end |
Class Method Details
.load(root) ⇒ Object
61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 |
# File 'lib/textus/manifest.rb', line 61 def self.load(root) manifest_path = File.join(root, "manifest.yaml") raise IoError.new("manifest not found: #{manifest_path}") unless File.exist?(manifest_path) raw = YAML.safe_load_file(manifest_path, aliases: false) unless raw["version"] == PROTOCOL raise BadFrontmatter.new( manifest_path, "unsupported manifest version #{raw["version"].inspect}; expected #{PROTOCOL.inspect}", hint: version_hint_for(raw["version"]), ) end new(root, raw) end |
.parse(yaml_text, root: ".") ⇒ Object
48 49 50 51 52 53 54 55 56 57 58 59 |
# File 'lib/textus/manifest.rb', line 48 def self.parse(yaml_text, root: ".") raw = YAML.safe_load(yaml_text, aliases: false) unless raw["version"] == PROTOCOL raise BadFrontmatter.new( "<string>", "unsupported manifest version #{raw["version"].inspect}; expected #{PROTOCOL.inspect}", hint: version_hint_for(raw["version"]), ) end new(root, raw) end |
Instance Method Details
#enumerate(prefix: nil) ⇒ Object
Enumerate all entry files reachable through the manifest. Returns
- { key:, path:, manifest_entry: }, …
139 140 141 142 143 |
# File 'lib/textus/manifest.rb', line 139 def enumerate(prefix: nil) out = @entries.flat_map { |entry| entry.nested ? enumerate_nested(entry) : enumerate_leaf(entry) } out.select! { |row| row[:key] == prefix || row[:key].start_with?("#{prefix}.") } if prefix out.sort_by { |row| row[:key] } end |
#permission_for(zone_name) ⇒ Object
40 41 42 43 44 45 46 |
# File 'lib/textus/manifest.rb', line 40 def (zone_name) Textus::Domain::Permission.new( zone: zone_name, write_policy: zone_writers(zone_name), read_policy: zone_readers[zone_name] || :all, ) end |
#resolve(key) ⇒ Object
Returns [Manifest::Entry, resolved_path, remaining_segments]
97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 |
# File 'lib/textus/manifest.rb', line 97 def resolve(key) validate_key!(key) segments = key.split(".") # longest-prefix match candidates = @entries .map { |e| [e, e.key.split(".")] } .select { |(_, esegs)| esegs == segments[0, esegs.length] } .sort_by { |(_, esegs)| -esegs.length } raise UnknownKey.new(key, suggestions: suggestions_for(key)) if candidates.empty? entry, esegs = candidates.first remaining = segments[esegs.length..] if remaining.empty? path = resolve_leaf_path(entry) [entry, path, []] else raise UnknownKey.new(key, suggestions: suggestions_for(key)) unless entry.nested path = if entry.index_filename File.join(@root, "zones", entry.path, *remaining, entry.index_filename) else primary_ext = Textus::Entry.for_format(entry.format).extensions.first File.join(@root, "zones", entry.path, *remaining) + primary_ext end [entry, path, remaining] end end |
#rules ⇒ Object
88 89 90 |
# File 'lib/textus/manifest.rb', line 88 def rules @rules ||= Textus::Manifest::Rules.parse(@raw["rules"] || []) end |
#rules_for(key) ⇒ Object
92 93 94 |
# File 'lib/textus/manifest.rb', line 92 def rules_for(key) rules.for(key) end |
#suggestions_for(key) ⇒ Object
Returns up to 5 dotted keys from the manifest that look similar to the requested key, ranked by shared-prefix length then Levenshtein distance.
127 128 129 130 131 132 133 134 135 |
# File 'lib/textus/manifest.rb', line 127 def suggestions_for(key) candidates = enumerate.map { |r| r[:key] } # Include declared (non-nested) entry keys even if file is missing. candidates.concat(@entries.reject(&:nested).map(&:key)) candidates.uniq! Key::Distance.suggest(key, candidates, limit: 5) rescue StandardError [] end |
#validate_key!(key) ⇒ Object
145 146 147 148 149 |
# File 'lib/textus/manifest.rb', line 145 def validate_key!(key) raise UsageError.new("empty key") if key.nil? || key.empty? Key::Grammar.validate!(key) end |
#zone_readers ⇒ Object
29 30 31 32 33 34 |
# File 'lib/textus/manifest.rb', line 29 def zone_readers @zone_readers ||= Array(@raw["zones"]).to_h do |z| rp = z["read_policy"] [z["name"], rp.nil? ? :all : Array(rp)] end end |
#zone_writers(zone_name) ⇒ Object
36 37 38 |
# File 'lib/textus/manifest.rb', line 36 def zone_writers(zone_name) zones[zone_name] or raise UsageError.new("undeclared zone '#{zone_name}'") end |
#zones ⇒ Object
25 26 27 |
# File 'lib/textus/manifest.rb', line 25 def zones @zones ||= Array(@raw["zones"]).to_h { |z| [z["name"], Array(z["write_policy"])] } end |