Class: Rails::Hyperdrive::GemManifest
- Inherits:
-
Object
- Object
- Rails::Hyperdrive::GemManifest
- Defined in:
- lib/rails/hyperdrive/gem_manifest.rb
Overview
Everything the installer reads from a companion gem: where each artifact kind lives, and which artifacts a given bundle gets. Fail-open at every level — malformed input warns and resolves to an ungated install at the default roots; an artifact is never skipped for unreadable config, and nothing raises.
Defined Under Namespace
Classes: Gate, GemKey, TargetSpec
Constant Summary collapse
- FILE_NAME =
"hyperdrive.yml"- METADATA_KEY =
"hyperdrive_manifest"- SKILL_TEMPLATES_DIR_KEY =
"skill_templates_dir".freeze
- DIR_KEYS =
(InstallLayout.dir_keys + [SKILL_TEMPLATES_DIR_KEY]).freeze
- UNGATED =
["*"].freeze
- MATCH_MODES =
%w[any all].freeze
- GEM_FORMS =
"a gem, a comma-separated list, a YAML list of names and name: requirement " \ "pairs, or an any:/all: map (gems: is an alias)".freeze
- VERSIONS_REMOVED =
"versions: is no longer supported; version constraints ignored — " \ "put the requirement on the gem: member, e.g. `- name: \">= 1.0\"`".freeze
Class Method Summary collapse
-
.gem_key(map) ⇒ Object
gems: and gem: are exact aliases.
- .load(spec, warnings: []) ⇒ Object
-
.malformed_fence?(value) ⇒ Boolean
The fence names one requirement against one version, so a per-gem requirement map is meaningless here.
-
.malformed_requirements?(versions) ⇒ Boolean
A version requirement is optional everywhere; nil means unconstrained.
-
.manifest_relpath(spec) ⇒ Object
".." segments are rejected (silent fallback to the conventional path) to prevent escaping the gem root.
-
.opt_in?(spec) ⇒ Boolean
The metadata key counts even when its value is unusable — declaring it at all signals companion intent.
-
.parse_targets(raw) ⇒ Object
nil signals malformed; the caller decides how to fail open.
-
.read_dir(root, key) ⇒ Object
Returns [relative directory or nil, failure reason or nil].
-
.read_root(path) ⇒ Object
Returns [root map or nil, failure reason or nil].
- .requirement_parts(req) ⇒ Object
Instance Method Summary collapse
-
#dir(key) ⇒ Object
nil means the caller's default root.
- #gate(type, key) ⇒ Object
-
#initialize(spec, warnings:) ⇒ GemManifest
constructor
A new instance of GemManifest.
-
#name_prefix(type) ⇒ Object
The section-level scalar a kind prepends to every artifact name it discovers; nil when absent or unusable.
- #section_keys(type) ⇒ Object
- #skill_templates_dir ⇒ Object
- #skills_dir ⇒ Object
Constructor Details
#initialize(spec, warnings:) ⇒ GemManifest
Returns a new instance of GemManifest.
218 219 220 221 222 223 224 225 226 227 228 229 230 231 |
# File 'lib/rails/hyperdrive/gem_manifest.rb', line 218 def initialize(spec, warnings:) @spec = spec @warnings = warnings root = load_root @sections = {} @prefixes = {} InstallLayout.content_kinds.each do |kind| entries, settings = section(root, kind) @sections[kind.type] = entries @prefixes[kind.type] = read_name_prefix(kind, settings) end @dirs = DIR_KEYS.each_with_object({}) { |key, h| h[key] = resolve_dir(root, key) } @default_spec, @default_fence = defaults(root) end |
Class Method Details
.gem_key(map) ⇒ Object
gems: and gem: are exact aliases. A map carrying both is a stylistic slip, not malformed input: picking a winner keeps the gate intact where failing open would silently widen it.
42 43 44 45 46 47 48 49 50 |
# File 'lib/rails/hyperdrive/gem_manifest.rb', line 42 def gem_key(map) both = map.key?("gem") && map.key?("gems") GemKey.new( value: map.key?("gems") ? map["gems"] : map["gem"], present: map.key?("gem") || map.key?("gems"), conflict: both, warning: ("gem: and gems: are aliases; reading gems: and ignoring gem:" if both) ) end |
.load(spec, warnings: []) ⇒ Object
35 36 37 |
# File 'lib/rails/hyperdrive/gem_manifest.rb', line 35 def load(spec, warnings: []) new(spec, warnings: warnings) end |
.malformed_fence?(value) ⇒ Boolean
The fence names one requirement against one version, so a per-gem requirement map is meaningless here.
93 94 95 96 |
# File 'lib/rails/hyperdrive/gem_manifest.rb', line 93 def malformed_fence?(value) return true if value.is_a?(Hash) malformed_requirements?(value) end |
.malformed_requirements?(versions) ⇒ Boolean
A version requirement is optional everywhere; nil means unconstrained.
79 80 81 82 83 84 85 86 87 88 89 |
# File 'lib/rails/hyperdrive/gem_manifest.rb', line 79 def malformed_requirements?(versions) requirements = versions.is_a?(Hash) ? versions.values : [versions] requirements.compact.any? do |req| begin Gem::Requirement.new(*requirement_parts(req)) false rescue ArgumentError true end end end |
.manifest_relpath(spec) ⇒ Object
".." segments are rejected (silent fallback to the conventional path) to prevent escaping the gem root.
54 55 56 57 58 59 60 |
# File 'lib/rails/hyperdrive/gem_manifest.rb', line 54 def manifest_relpath(spec) return FILE_NAME unless spec.respond_to?(:metadata) raw = spec. && spec.[METADATA_KEY] return FILE_NAME if raw.nil? || raw.to_s.strip.empty? return FILE_NAME if raw.to_s.split(%r{[/\\]}).include?("..") raw.to_s end |
.opt_in?(spec) ⇒ Boolean
The metadata key counts even when its value is unusable — declaring it at all signals companion intent.
64 65 66 67 68 69 70 |
# File 'lib/rails/hyperdrive/gem_manifest.rb', line 64 def opt_in?(spec) return true if File.file?(File.join(spec.full_gem_path, FILE_NAME)) return false unless spec.respond_to?(:metadata) raw = spec. && spec.[METADATA_KEY] !raw.to_s.strip.empty? end |
.parse_targets(raw) ⇒ Object
nil signals malformed; the caller decides how to fail open.
73 74 75 76 |
# File 'lib/rails/hyperdrive/gem_manifest.rb', line 73 def parse_targets(raw) return parse_target_map(raw) if raw.is_a?(Hash) parse_target_names(raw) end |
.read_dir(root, key) ⇒ Object
Returns [relative directory or nil, failure reason or nil]. Absent or blank is no failure — the caller uses its own default root. ".." segments are rejected to prevent escaping the gem root.
105 106 107 108 109 110 111 112 113 |
# File 'lib/rails/hyperdrive/gem_manifest.rb', line 105 def read_dir(root, key) raw = root[key] return [nil, nil] if raw.nil? return [nil, "#{key}: must be a directory path relative to the gem root"] unless raw.is_a?(String) return [nil, nil] if raw.strip.empty? return [nil, "#{key}: must not contain '..' segments"] if raw.split(%r{[/\\]}).include?("..") [raw, nil] end |
.read_root(path) ⇒ Object
Returns [root map or nil, failure reason or nil]. An absent or empty file is a valid empty manifest; the caller phrases the failure and decides whether to fall open on it.
118 119 120 121 122 123 124 125 126 127 128 129 130 |
# File 'lib/rails/hyperdrive/gem_manifest.rb', line 118 def read_root(path) return [{}, nil] unless File.file?(path) data = YAML.safe_load(File.read(path), permitted_classes: [Symbol]) return [{}, nil] if data.nil? return [data.transform_keys(&:to_s), nil] if data.is_a?(Hash) [nil, "root must be a YAML map"] rescue Psych::SyntaxError => e [nil, "malformed YAML (#{e.})"] rescue StandardError => e [nil, "unreadable (#{e.})"] end |
.requirement_parts(req) ⇒ Object
98 99 100 |
# File 'lib/rails/hyperdrive/gem_manifest.rb', line 98 def requirement_parts(req) Array(req).flat_map { |s| s.is_a?(String) ? s.split(",").map(&:strip) : s } end |
Instance Method Details
#dir(key) ⇒ Object
nil means the caller's default root.
250 251 252 |
# File 'lib/rails/hyperdrive/gem_manifest.rb', line 250 def dir(key) @dirs[key.to_s] end |
#gate(type, key) ⇒ Object
233 234 235 236 237 |
# File 'lib/rails/hyperdrive/gem_manifest.rb', line 233 def gate(type, key) entries = @sections[type] || {} return default_gate unless entries.key?(key) entry_gate(entries[key], key, with_conditional: InstallLayout.kind(type).dir_shaped?) end |
#name_prefix(type) ⇒ Object
The section-level scalar a kind prepends to every artifact name it discovers; nil when absent or unusable.
245 246 247 |
# File 'lib/rails/hyperdrive/gem_manifest.rb', line 245 def name_prefix(type) @prefixes[type] end |
#section_keys(type) ⇒ Object
239 240 241 |
# File 'lib/rails/hyperdrive/gem_manifest.rb', line 239 def section_keys(type) (@sections[type] || {}).keys end |
#skill_templates_dir ⇒ Object
258 259 260 |
# File 'lib/rails/hyperdrive/gem_manifest.rb', line 258 def skill_templates_dir dir(SKILL_TEMPLATES_DIR_KEY) end |
#skills_dir ⇒ Object
254 255 256 |
# File 'lib/rails/hyperdrive/gem_manifest.rb', line 254 def skills_dir dir(InstallLayout.kind(:skill).dir_key) end |