Module: Coradoc::AsciiDoc::Model::Image::AttributeExtractor
- Defined in:
- lib/coradoc/asciidoc/model/image/attribute_extractor.rb
Overview
Pure-function promoter: lifts semantically meaningful slots out of a generic AttributeList into the typed fields declared on an image class, and the inverse — composes a serialisable AttributeList from typed fields + residual.
The promotion map is owned by the target class itself, via
promoted_positional and promoted_named. This module is the
single place that consumes those declarations, so adding a new
promoted field is a one-line class-level change (OCP).
Positional slots are consumed by index; named slots are removed
from the residual list so they don't appear twice. The residual
list preserves order and identity for everything that wasn't
promoted (e.g. scaledwidth, pdfwidth, opts).
Class Method Summary collapse
- .append_residual(composed, residual) ⇒ Object
-
.call(attribute_list, target_class) ⇒ Array<(Hash{Symbol=>String}, Model::AttributeList)>
A tuple of promoted typed values and the residual list.
-
.compose(model) ⇒ Model::AttributeList
Inverse of AttributeExtractor.call: rebuild a serialisable AttributeList from a model's typed fields plus its residual list.
- .compose_named(composed, model, filled) ⇒ Object
- .compose_positional(composed, model) ⇒ Object
- .promote_named(extracted, residual, source, target_class) ⇒ Object
- .promote_one_named(extracted, residual, named_attr, promoted_names) ⇒ Object
- .promote_positional(extracted, residual, source, target_class) ⇒ Object
Class Method Details
.append_residual(composed, residual) ⇒ Object
132 133 134 135 136 137 |
# File 'lib/coradoc/asciidoc/model/image/attribute_extractor.rb', line 132 def append_residual(composed, residual) return unless residual.is_a?(Model::AttributeList) residual.positional.each { |p| composed.add_positional(p.value) } residual.named.each { |n| composed.add_named(n.name, n.value) } end |
.call(attribute_list, target_class) ⇒ Array<(Hash{Symbol=>String}, Model::AttributeList)>
Returns a tuple of promoted typed values and the residual list.
43 44 45 46 47 48 49 50 51 52 |
# File 'lib/coradoc/asciidoc/model/image/attribute_extractor.rb', line 43 def call(attribute_list, target_class) source = attribute_list || Model::AttributeList.new residual = Model::AttributeList.new extracted = {} promote_positional(extracted, residual, source, target_class) promote_named(extracted, residual, source, target_class) [extracted, residual] end |
.compose(model) ⇒ Model::AttributeList
Inverse of call: rebuild a serialisable AttributeList from a model's typed fields plus its residual list. Used by the AsciiDoc image serializer so round-trips reproduce the original syntax.
A field that's declared in both promoted_positional and
promoted_named (e.g. inline image role) is emitted only
once — via its positional slot when filled, otherwise via named.
63 64 65 66 67 68 69 |
# File 'lib/coradoc/asciidoc/model/image/attribute_extractor.rb', line 63 def compose(model) composed = Model::AttributeList.new filled = compose_positional(composed, model) compose_named(composed, model, filled) append_residual(composed, model.attributes) composed end |
.compose_named(composed, model, filled) ⇒ Object
83 84 85 86 87 88 89 90 91 92 |
# File 'lib/coradoc/asciidoc/model/image/attribute_extractor.rb', line 83 def compose_named(composed, model, filled) model.class.promoted_named.each do |attr_name| next if filled.include?(attr_name) value = model.public_send(attr_name) next if value.nil? || value.to_s.empty? composed.add_named(attr_name.to_s, value.to_s) end end |
.compose_positional(composed, model) ⇒ Object
71 72 73 74 75 76 77 78 79 80 81 |
# File 'lib/coradoc/asciidoc/model/image/attribute_extractor.rb', line 71 def compose_positional(composed, model) filled = [] model.class.promoted_positional.each do |attr_name| value = model.public_send(attr_name) next if value.nil? || value.to_s.empty? composed.add_positional(value.to_s) filled << attr_name end filled end |
.promote_named(extracted, residual, source, target_class) ⇒ Object
107 108 109 110 111 112 |
# File 'lib/coradoc/asciidoc/model/image/attribute_extractor.rb', line 107 def promote_named(extracted, residual, source, target_class) promoted_names = target_class.promoted_named.to_set(&:to_s) source.named.each do |named_attr| promote_one_named(extracted, residual, named_attr, promoted_names) end end |
.promote_one_named(extracted, residual, named_attr, promoted_names) ⇒ Object
114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 |
# File 'lib/coradoc/asciidoc/model/image/attribute_extractor.rb', line 114 def promote_one_named(extracted, residual, named_attr, promoted_names) name_str = named_attr.name.to_s unless promoted_names.include?(name_str) residual.add_named(named_attr.name, named_attr.value) return end # Positional promotion wins: a slot already filled positionally # is not overwritten by a same-named entry (rare, but possible # when both `[alt, role, role=X]` are supplied). key = name_str.to_sym return if extracted.key?(key) value = named_attr.value.first&.to_s return if value.nil? || value.empty? extracted[key] = value end |
.promote_positional(extracted, residual, source, target_class) ⇒ Object
94 95 96 97 98 99 100 101 102 103 104 105 |
# File 'lib/coradoc/asciidoc/model/image/attribute_extractor.rb', line 94 def promote_positional(extracted, residual, source, target_class) promoted = target_class.promoted_positional source.positional.each_with_index do |positional_attr, index| attr_name = promoted[index] value = positional_attr.value if attr_name && !value.to_s.empty? extracted[attr_name] = value.to_s else residual.add_positional(value) end end end |