Class: Textus::Manifest
- Inherits:
-
Object
- Object
- Textus::Manifest
- Defined in:
- lib/textus/manifest.rb,
lib/textus/manifest/entry.rb,
lib/textus/manifest/policies.rb
Defined Under Namespace
Constant Summary collapse
- EXT_TO_FORMAT =
{ ".md" => "markdown", ".json" => "json", ".yaml" => "yaml", ".yml" => "yaml", ".txt" => "text", }.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
- #policies ⇒ Object
- #policies_for(key) ⇒ Object
-
#resolve(key) ⇒ Object
Returns [Manifest::Entry, resolved_path, remaining_segments].
-
#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_writers(zone_name) ⇒ Object
- #zones ⇒ Object
Constructor Details
#initialize(root, raw) ⇒ Manifest
Returns a new instance of Manifest.
43 44 45 46 47 48 49 50 |
# File 'lib/textus/manifest.rb', line 43 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? @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.
13 14 15 |
# File 'lib/textus/manifest.rb', line 13 def entries @entries end |
#raw ⇒ Object (readonly)
Returns the value of attribute raw.
13 14 15 |
# File 'lib/textus/manifest.rb', line 13 def raw @raw end |
#root ⇒ Object (readonly)
Returns the value of attribute root.
13 14 15 |
# File 'lib/textus/manifest.rb', line 13 def root @root end |
Class Method Details
.load(root) ⇒ Object
31 32 33 34 35 36 37 38 39 40 41 |
# File 'lib/textus/manifest.rb', line 31 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}") 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: }, …
103 104 105 106 107 |
# File 'lib/textus/manifest.rb', line 103 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
23 24 25 26 27 28 29 |
# File 'lib/textus/manifest.rb', line 23 def (zone_name) Textus::Domain::Permission.new( zone: zone_name, writable_by: zone_writers(zone_name), readable_by: :all, ) end |
#policies ⇒ Object
52 53 54 |
# File 'lib/textus/manifest.rb', line 52 def policies @policies ||= Textus::Manifest::Policies.parse(@raw["policies"] || []) end |
#policies_for(key) ⇒ Object
56 57 58 |
# File 'lib/textus/manifest.rb', line 56 def policies_for(key) policies.for(key) end |
#resolve(key) ⇒ Object
Returns [Manifest::Entry, resolved_path, remaining_segments]
61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 |
# File 'lib/textus/manifest.rb', line 61 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 |
#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.
91 92 93 94 95 96 97 98 99 |
# File 'lib/textus/manifest.rb', line 91 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
109 110 111 112 113 |
# File 'lib/textus/manifest.rb', line 109 def validate_key!(key) raise UsageError.new("empty key") if key.nil? || key.empty? Key::Grammar.validate!(key) end |
#zone_writers(zone_name) ⇒ Object
19 20 21 |
# File 'lib/textus/manifest.rb', line 19 def zone_writers(zone_name) zones[zone_name] or raise UsageError.new("undeclared zone '#{zone_name}'") end |
#zones ⇒ Object
15 16 17 |
# File 'lib/textus/manifest.rb', line 15 def zones @zones ||= Array(@raw["zones"]).to_h { |z| [z["name"], Array(z["writable_by"])] } end |