Class: Coradoc::AsciiDoc::Model::AttributeList

Inherits:
Base
  • Object
show all
Includes:
Matchers
Defined in:
lib/coradoc/asciidoc/model/attribute_list.rb,
lib/coradoc/asciidoc/model/attribute_list/matchers.rb

Overview

Attribute list for AsciiDoc elements.

Attribute lists represent the square-bracket attribute syntax in AsciiDoc:

positional1, pos2, name1=value1, name2=value2

This class manages both positional and named attributes, with support for validation and rejection of invalid values.

Examples:

Create an attribute list

attrs = Coradoc::AsciiDoc::Model::Coradoc::AsciiDoc::Model::AttributeList.new
attrs.add_positional("value1")
attrs.add_named("option", "value")
attrs.to_adoc # => "[value1,option=value]\n"

Empty attribute list

attrs = Coradoc::AsciiDoc::Model::Coradoc::AsciiDoc::Model::AttributeList.new
attrs.to_adoc # => "[]"

Hide empty attribute list

attrs = Coradoc::AsciiDoc::Model::Coradoc::AsciiDoc::Model::AttributeList.new
attrs.to_adoc(show_empty: false) # => ""

Defined Under Namespace

Modules: Matchers

Instance Attribute Summary collapse

Attributes inherited from Base

#id

Instance Method Summary collapse

Methods included from Matchers

#many, #one

Methods inherited from Base

#block_level?, #inline?, #serialize_content, #simplify_block_content, #to_h, visit, #visit

Instance Attribute Details

#namedArray<Coradoc::AsciiDoc::Model::NamedAttribute> (readonly)

Returns Named attributes (key-value pairs).

Returns:



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
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
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
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
# File 'lib/coradoc/asciidoc/model/attribute_list.rb', line 40

class AttributeList < Base
  # Autoload Matchers module
  autoload :Matchers, 'coradoc/asciidoc/model/attribute_list/matchers'

  # Include Matchers module for validation methods
  include Matchers

  attribute :positional,
            Coradoc::AsciiDoc::Model::AttributeListAttribute,
            collection: true,
            initialize_empty: true
  attribute :named, Coradoc::AsciiDoc::Model::NamedAttribute, collection: true, initialize_empty: true
  attribute :rejected_positional,
            Coradoc::AsciiDoc::Model::RejectedPositionalAttribute,
            collection: true,
            initialize_empty: true
  attribute :rejected_named,
            Coradoc::AsciiDoc::Model::NamedAttribute,
            collection: true,
            initialize_empty: true

  # Add positional attributes to this list
  #
  # @param attr [Array<Object>] Values to add as positional attributes
  #
  # @example Adding positional attributes
  #   attrs.add_positional("value1", "value2")
  #
  def add_positional(*attr)
    attr.each do |a|
      @positional << AttributeListAttribute.new(value: a)
    end
  end

  # Add a named attribute to this list
  #
  # @param name [String, Symbol] The attribute name
  # @param value [Object] The attribute value (will be converted to array)
  #
  # @example Adding named attributes
  #   attrs.add_named("title", "My Title")
  #   attrs.add_named("cols", "3,2,1")
  #
  def add_named(name, value)
    @named << NamedAttribute.new(
      name:,
      value: value.is_a?(Array) ? value : [value]
    )
  end

  # Validate named attributes against validators
  #
  # @param validators [Hash] Hash of name => matcher pairs
  # @yield [name, value] Block called for each invalid attribute
  #
  # @example Validate with custom validator
  #   attrs.validate_named(title: /./) do |name, value|
  #     puts "Invalid #{name}: #{value}"
  #   end
  #
  def validate_named(validators: {})
    named.each_with_index do |named_attribute, _i|
      name = named_attribute.name.to_sym
      value = named_attribute.value

      matcher = validators[name]

      next if matcher && matcher === value

      # Previous implementation would remove the value from the list
      # named.delete(name)
      rejected_named << named_attribute.dup
      yield(name, value) if block_given?
    end
  end

  # Validate positional attributes against validators
  #
  # @param validators [Array] Array of [position, matcher] pairs
  # @yield [position, value] Block called for each invalid attribute
  #
  # @example Validate positional attributes
  #   attrs.validate_positional([[0, /./], [1, Integer]])
  #
  def validate_positional(validators: [])
    positional.each_with_index do |positional_attribute, i|
      matcher = validators[i][1]
      value = positional_attribute.value

      next unless matcher && !(matcher === value)

      warn "#{value} does not match #{matcher}"
      # Previous implementation would remove the value from the list
      # positional[i] = nil
      rejected_positional << RejectedPositionalAttribute.new(
        position: i, value:
      )
      yield(i, value) if block_given?
    end
  end

  # To be overridden in subclasses.
  # @return [Array] Array of positional validators
  def positional_validators
    []
  end

  # To be overridden in subclasses.
  # @return [Hash] Hash of named validators
  def named_validators
    {}
  end

  # Validate this attribute list
  #
  # @return [Array<Lutaml::Model::Error>] Validation errors (empty if valid)
  def validate
    errors = super

    validate_positional(positional_validators) do |i, value|
      errors << Lutaml::Model::Error.new(
        "Positional attribute at position #{i} with value '#{value}' is not valid"
      )
    end

    validate_named(named_validators) do |name, value|
      errors << Lutaml::Model::Error.new(
        "Named attribute #{name} with value '#{value}' is not valid"
      )
    end

    errors
  end

  # Serialize this attribute list to AsciiDoc
  #
  # Generates the square-bracket syntax with valid attributes only.
  #
  # @param show_empty [Boolean] If true, show "[]" for empty lists (default: true)
  # @return [String] AsciiDoc representation of this attribute list
  #
  # @example Serialize with options
  #   attrs.to_adoc(show_empty: true)  # => "[value1,name=val]"
  #   attrs.to_adoc(show_empty: false) # => "[value1,name=val]"
  #   empty.to_adoc                     # => "[]"
  #   empty.to_adoc(show_empty: false) # => ""
  #
  def to_adoc(show_empty: true)
    valid_positional = positional.reject.with_index do |_p, i|
      rejected_positional.any? { |r| r.position == i }
    end

    valid_named = named.reject do |n|
      rejected_named.any? { |r| r.name == n.name }
    end

    adoc = [valid_positional,
            valid_named].flatten.map(&:to_adoc).join(',')

    if adoc.empty? && show_empty
      '[]'
    elsif adoc.empty?
      ''
    else
      "[#{adoc}]"
    end
  end

  def empty?
    positional.empty? && named.empty?
  end

  # Get a named attribute value by name
  # @param name [String, Symbol] The attribute name
  # @return [Object, nil] The attribute value or nil if not found
  def [](name)
    name_str = name.to_s
    named.find { |n| n.name.to_s == name_str }&.value
  end

  # Get a named attribute value with default
  # @param name [String, Symbol] The attribute name
  # @param default [Object] The default value if not found
  # @return [Object] The attribute value or default
  def fetch(name, default = nil)
    self[name] || default
  end
end

#positionalArray<Coradoc::AsciiDoc::Model::AttributeListAttribute> (readonly)

Returns Positional attributes (by position).

Returns:



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
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
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
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
# File 'lib/coradoc/asciidoc/model/attribute_list.rb', line 40

class AttributeList < Base
  # Autoload Matchers module
  autoload :Matchers, 'coradoc/asciidoc/model/attribute_list/matchers'

  # Include Matchers module for validation methods
  include Matchers

  attribute :positional,
            Coradoc::AsciiDoc::Model::AttributeListAttribute,
            collection: true,
            initialize_empty: true
  attribute :named, Coradoc::AsciiDoc::Model::NamedAttribute, collection: true, initialize_empty: true
  attribute :rejected_positional,
            Coradoc::AsciiDoc::Model::RejectedPositionalAttribute,
            collection: true,
            initialize_empty: true
  attribute :rejected_named,
            Coradoc::AsciiDoc::Model::NamedAttribute,
            collection: true,
            initialize_empty: true

  # Add positional attributes to this list
  #
  # @param attr [Array<Object>] Values to add as positional attributes
  #
  # @example Adding positional attributes
  #   attrs.add_positional("value1", "value2")
  #
  def add_positional(*attr)
    attr.each do |a|
      @positional << AttributeListAttribute.new(value: a)
    end
  end

  # Add a named attribute to this list
  #
  # @param name [String, Symbol] The attribute name
  # @param value [Object] The attribute value (will be converted to array)
  #
  # @example Adding named attributes
  #   attrs.add_named("title", "My Title")
  #   attrs.add_named("cols", "3,2,1")
  #
  def add_named(name, value)
    @named << NamedAttribute.new(
      name:,
      value: value.is_a?(Array) ? value : [value]
    )
  end

  # Validate named attributes against validators
  #
  # @param validators [Hash] Hash of name => matcher pairs
  # @yield [name, value] Block called for each invalid attribute
  #
  # @example Validate with custom validator
  #   attrs.validate_named(title: /./) do |name, value|
  #     puts "Invalid #{name}: #{value}"
  #   end
  #
  def validate_named(validators: {})
    named.each_with_index do |named_attribute, _i|
      name = named_attribute.name.to_sym
      value = named_attribute.value

      matcher = validators[name]

      next if matcher && matcher === value

      # Previous implementation would remove the value from the list
      # named.delete(name)
      rejected_named << named_attribute.dup
      yield(name, value) if block_given?
    end
  end

  # Validate positional attributes against validators
  #
  # @param validators [Array] Array of [position, matcher] pairs
  # @yield [position, value] Block called for each invalid attribute
  #
  # @example Validate positional attributes
  #   attrs.validate_positional([[0, /./], [1, Integer]])
  #
  def validate_positional(validators: [])
    positional.each_with_index do |positional_attribute, i|
      matcher = validators[i][1]
      value = positional_attribute.value

      next unless matcher && !(matcher === value)

      warn "#{value} does not match #{matcher}"
      # Previous implementation would remove the value from the list
      # positional[i] = nil
      rejected_positional << RejectedPositionalAttribute.new(
        position: i, value:
      )
      yield(i, value) if block_given?
    end
  end

  # To be overridden in subclasses.
  # @return [Array] Array of positional validators
  def positional_validators
    []
  end

  # To be overridden in subclasses.
  # @return [Hash] Hash of named validators
  def named_validators
    {}
  end

  # Validate this attribute list
  #
  # @return [Array<Lutaml::Model::Error>] Validation errors (empty if valid)
  def validate
    errors = super

    validate_positional(positional_validators) do |i, value|
      errors << Lutaml::Model::Error.new(
        "Positional attribute at position #{i} with value '#{value}' is not valid"
      )
    end

    validate_named(named_validators) do |name, value|
      errors << Lutaml::Model::Error.new(
        "Named attribute #{name} with value '#{value}' is not valid"
      )
    end

    errors
  end

  # Serialize this attribute list to AsciiDoc
  #
  # Generates the square-bracket syntax with valid attributes only.
  #
  # @param show_empty [Boolean] If true, show "[]" for empty lists (default: true)
  # @return [String] AsciiDoc representation of this attribute list
  #
  # @example Serialize with options
  #   attrs.to_adoc(show_empty: true)  # => "[value1,name=val]"
  #   attrs.to_adoc(show_empty: false) # => "[value1,name=val]"
  #   empty.to_adoc                     # => "[]"
  #   empty.to_adoc(show_empty: false) # => ""
  #
  def to_adoc(show_empty: true)
    valid_positional = positional.reject.with_index do |_p, i|
      rejected_positional.any? { |r| r.position == i }
    end

    valid_named = named.reject do |n|
      rejected_named.any? { |r| r.name == n.name }
    end

    adoc = [valid_positional,
            valid_named].flatten.map(&:to_adoc).join(',')

    if adoc.empty? && show_empty
      '[]'
    elsif adoc.empty?
      ''
    else
      "[#{adoc}]"
    end
  end

  def empty?
    positional.empty? && named.empty?
  end

  # Get a named attribute value by name
  # @param name [String, Symbol] The attribute name
  # @return [Object, nil] The attribute value or nil if not found
  def [](name)
    name_str = name.to_s
    named.find { |n| n.name.to_s == name_str }&.value
  end

  # Get a named attribute value with default
  # @param name [String, Symbol] The attribute name
  # @param default [Object] The default value if not found
  # @return [Object] The attribute value or default
  def fetch(name, default = nil)
    self[name] || default
  end
end

#rejected_namedArray<Coradoc::AsciiDoc::Model::NamedAttribute> (readonly)

Returns Rejected named attributes.

Returns:



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
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
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
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
# File 'lib/coradoc/asciidoc/model/attribute_list.rb', line 40

class AttributeList < Base
  # Autoload Matchers module
  autoload :Matchers, 'coradoc/asciidoc/model/attribute_list/matchers'

  # Include Matchers module for validation methods
  include Matchers

  attribute :positional,
            Coradoc::AsciiDoc::Model::AttributeListAttribute,
            collection: true,
            initialize_empty: true
  attribute :named, Coradoc::AsciiDoc::Model::NamedAttribute, collection: true, initialize_empty: true
  attribute :rejected_positional,
            Coradoc::AsciiDoc::Model::RejectedPositionalAttribute,
            collection: true,
            initialize_empty: true
  attribute :rejected_named,
            Coradoc::AsciiDoc::Model::NamedAttribute,
            collection: true,
            initialize_empty: true

  # Add positional attributes to this list
  #
  # @param attr [Array<Object>] Values to add as positional attributes
  #
  # @example Adding positional attributes
  #   attrs.add_positional("value1", "value2")
  #
  def add_positional(*attr)
    attr.each do |a|
      @positional << AttributeListAttribute.new(value: a)
    end
  end

  # Add a named attribute to this list
  #
  # @param name [String, Symbol] The attribute name
  # @param value [Object] The attribute value (will be converted to array)
  #
  # @example Adding named attributes
  #   attrs.add_named("title", "My Title")
  #   attrs.add_named("cols", "3,2,1")
  #
  def add_named(name, value)
    @named << NamedAttribute.new(
      name:,
      value: value.is_a?(Array) ? value : [value]
    )
  end

  # Validate named attributes against validators
  #
  # @param validators [Hash] Hash of name => matcher pairs
  # @yield [name, value] Block called for each invalid attribute
  #
  # @example Validate with custom validator
  #   attrs.validate_named(title: /./) do |name, value|
  #     puts "Invalid #{name}: #{value}"
  #   end
  #
  def validate_named(validators: {})
    named.each_with_index do |named_attribute, _i|
      name = named_attribute.name.to_sym
      value = named_attribute.value

      matcher = validators[name]

      next if matcher && matcher === value

      # Previous implementation would remove the value from the list
      # named.delete(name)
      rejected_named << named_attribute.dup
      yield(name, value) if block_given?
    end
  end

  # Validate positional attributes against validators
  #
  # @param validators [Array] Array of [position, matcher] pairs
  # @yield [position, value] Block called for each invalid attribute
  #
  # @example Validate positional attributes
  #   attrs.validate_positional([[0, /./], [1, Integer]])
  #
  def validate_positional(validators: [])
    positional.each_with_index do |positional_attribute, i|
      matcher = validators[i][1]
      value = positional_attribute.value

      next unless matcher && !(matcher === value)

      warn "#{value} does not match #{matcher}"
      # Previous implementation would remove the value from the list
      # positional[i] = nil
      rejected_positional << RejectedPositionalAttribute.new(
        position: i, value:
      )
      yield(i, value) if block_given?
    end
  end

  # To be overridden in subclasses.
  # @return [Array] Array of positional validators
  def positional_validators
    []
  end

  # To be overridden in subclasses.
  # @return [Hash] Hash of named validators
  def named_validators
    {}
  end

  # Validate this attribute list
  #
  # @return [Array<Lutaml::Model::Error>] Validation errors (empty if valid)
  def validate
    errors = super

    validate_positional(positional_validators) do |i, value|
      errors << Lutaml::Model::Error.new(
        "Positional attribute at position #{i} with value '#{value}' is not valid"
      )
    end

    validate_named(named_validators) do |name, value|
      errors << Lutaml::Model::Error.new(
        "Named attribute #{name} with value '#{value}' is not valid"
      )
    end

    errors
  end

  # Serialize this attribute list to AsciiDoc
  #
  # Generates the square-bracket syntax with valid attributes only.
  #
  # @param show_empty [Boolean] If true, show "[]" for empty lists (default: true)
  # @return [String] AsciiDoc representation of this attribute list
  #
  # @example Serialize with options
  #   attrs.to_adoc(show_empty: true)  # => "[value1,name=val]"
  #   attrs.to_adoc(show_empty: false) # => "[value1,name=val]"
  #   empty.to_adoc                     # => "[]"
  #   empty.to_adoc(show_empty: false) # => ""
  #
  def to_adoc(show_empty: true)
    valid_positional = positional.reject.with_index do |_p, i|
      rejected_positional.any? { |r| r.position == i }
    end

    valid_named = named.reject do |n|
      rejected_named.any? { |r| r.name == n.name }
    end

    adoc = [valid_positional,
            valid_named].flatten.map(&:to_adoc).join(',')

    if adoc.empty? && show_empty
      '[]'
    elsif adoc.empty?
      ''
    else
      "[#{adoc}]"
    end
  end

  def empty?
    positional.empty? && named.empty?
  end

  # Get a named attribute value by name
  # @param name [String, Symbol] The attribute name
  # @return [Object, nil] The attribute value or nil if not found
  def [](name)
    name_str = name.to_s
    named.find { |n| n.name.to_s == name_str }&.value
  end

  # Get a named attribute value with default
  # @param name [String, Symbol] The attribute name
  # @param default [Object] The default value if not found
  # @return [Object] The attribute value or default
  def fetch(name, default = nil)
    self[name] || default
  end
end

#rejected_positionalArray<Coradoc::AsciiDoc::Model::RejectedPositionalAttribute> (readonly)

Returns Rejected positional attributes.

Returns:



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
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
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
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
# File 'lib/coradoc/asciidoc/model/attribute_list.rb', line 40

class AttributeList < Base
  # Autoload Matchers module
  autoload :Matchers, 'coradoc/asciidoc/model/attribute_list/matchers'

  # Include Matchers module for validation methods
  include Matchers

  attribute :positional,
            Coradoc::AsciiDoc::Model::AttributeListAttribute,
            collection: true,
            initialize_empty: true
  attribute :named, Coradoc::AsciiDoc::Model::NamedAttribute, collection: true, initialize_empty: true
  attribute :rejected_positional,
            Coradoc::AsciiDoc::Model::RejectedPositionalAttribute,
            collection: true,
            initialize_empty: true
  attribute :rejected_named,
            Coradoc::AsciiDoc::Model::NamedAttribute,
            collection: true,
            initialize_empty: true

  # Add positional attributes to this list
  #
  # @param attr [Array<Object>] Values to add as positional attributes
  #
  # @example Adding positional attributes
  #   attrs.add_positional("value1", "value2")
  #
  def add_positional(*attr)
    attr.each do |a|
      @positional << AttributeListAttribute.new(value: a)
    end
  end

  # Add a named attribute to this list
  #
  # @param name [String, Symbol] The attribute name
  # @param value [Object] The attribute value (will be converted to array)
  #
  # @example Adding named attributes
  #   attrs.add_named("title", "My Title")
  #   attrs.add_named("cols", "3,2,1")
  #
  def add_named(name, value)
    @named << NamedAttribute.new(
      name:,
      value: value.is_a?(Array) ? value : [value]
    )
  end

  # Validate named attributes against validators
  #
  # @param validators [Hash] Hash of name => matcher pairs
  # @yield [name, value] Block called for each invalid attribute
  #
  # @example Validate with custom validator
  #   attrs.validate_named(title: /./) do |name, value|
  #     puts "Invalid #{name}: #{value}"
  #   end
  #
  def validate_named(validators: {})
    named.each_with_index do |named_attribute, _i|
      name = named_attribute.name.to_sym
      value = named_attribute.value

      matcher = validators[name]

      next if matcher && matcher === value

      # Previous implementation would remove the value from the list
      # named.delete(name)
      rejected_named << named_attribute.dup
      yield(name, value) if block_given?
    end
  end

  # Validate positional attributes against validators
  #
  # @param validators [Array] Array of [position, matcher] pairs
  # @yield [position, value] Block called for each invalid attribute
  #
  # @example Validate positional attributes
  #   attrs.validate_positional([[0, /./], [1, Integer]])
  #
  def validate_positional(validators: [])
    positional.each_with_index do |positional_attribute, i|
      matcher = validators[i][1]
      value = positional_attribute.value

      next unless matcher && !(matcher === value)

      warn "#{value} does not match #{matcher}"
      # Previous implementation would remove the value from the list
      # positional[i] = nil
      rejected_positional << RejectedPositionalAttribute.new(
        position: i, value:
      )
      yield(i, value) if block_given?
    end
  end

  # To be overridden in subclasses.
  # @return [Array] Array of positional validators
  def positional_validators
    []
  end

  # To be overridden in subclasses.
  # @return [Hash] Hash of named validators
  def named_validators
    {}
  end

  # Validate this attribute list
  #
  # @return [Array<Lutaml::Model::Error>] Validation errors (empty if valid)
  def validate
    errors = super

    validate_positional(positional_validators) do |i, value|
      errors << Lutaml::Model::Error.new(
        "Positional attribute at position #{i} with value '#{value}' is not valid"
      )
    end

    validate_named(named_validators) do |name, value|
      errors << Lutaml::Model::Error.new(
        "Named attribute #{name} with value '#{value}' is not valid"
      )
    end

    errors
  end

  # Serialize this attribute list to AsciiDoc
  #
  # Generates the square-bracket syntax with valid attributes only.
  #
  # @param show_empty [Boolean] If true, show "[]" for empty lists (default: true)
  # @return [String] AsciiDoc representation of this attribute list
  #
  # @example Serialize with options
  #   attrs.to_adoc(show_empty: true)  # => "[value1,name=val]"
  #   attrs.to_adoc(show_empty: false) # => "[value1,name=val]"
  #   empty.to_adoc                     # => "[]"
  #   empty.to_adoc(show_empty: false) # => ""
  #
  def to_adoc(show_empty: true)
    valid_positional = positional.reject.with_index do |_p, i|
      rejected_positional.any? { |r| r.position == i }
    end

    valid_named = named.reject do |n|
      rejected_named.any? { |r| r.name == n.name }
    end

    adoc = [valid_positional,
            valid_named].flatten.map(&:to_adoc).join(',')

    if adoc.empty? && show_empty
      '[]'
    elsif adoc.empty?
      ''
    else
      "[#{adoc}]"
    end
  end

  def empty?
    positional.empty? && named.empty?
  end

  # Get a named attribute value by name
  # @param name [String, Symbol] The attribute name
  # @return [Object, nil] The attribute value or nil if not found
  def [](name)
    name_str = name.to_s
    named.find { |n| n.name.to_s == name_str }&.value
  end

  # Get a named attribute value with default
  # @param name [String, Symbol] The attribute name
  # @param default [Object] The default value if not found
  # @return [Object] The attribute value or default
  def fetch(name, default = nil)
    self[name] || default
  end
end

Instance Method Details

#[](name) ⇒ Object?

Get a named attribute value by name

Parameters:

  • name (String, Symbol)

    The attribute name

Returns:

  • (Object, nil)

    The attribute value or nil if not found



215
216
217
218
# File 'lib/coradoc/asciidoc/model/attribute_list.rb', line 215

def [](name)
  name_str = name.to_s
  named.find { |n| n.name.to_s == name_str }&.value
end

#add_named(name, value) ⇒ Object

Add a named attribute to this list

Examples:

Adding named attributes

attrs.add_named("title", "My Title")
attrs.add_named("cols", "3,2,1")

Parameters:

  • name (String, Symbol)

    The attribute name

  • value (Object)

    The attribute value (will be converted to array)



83
84
85
86
87
88
# File 'lib/coradoc/asciidoc/model/attribute_list.rb', line 83

def add_named(name, value)
  @named << NamedAttribute.new(
    name:,
    value: value.is_a?(Array) ? value : [value]
  )
end

#add_positional(*attr) ⇒ Object

Add positional attributes to this list

Examples:

Adding positional attributes

attrs.add_positional("value1", "value2")

Parameters:

  • attr (Array<Object>)

    Values to add as positional attributes



68
69
70
71
72
# File 'lib/coradoc/asciidoc/model/attribute_list.rb', line 68

def add_positional(*attr)
  attr.each do |a|
    @positional << AttributeListAttribute.new(value: a)
  end
end

#empty?Boolean

Returns:

  • (Boolean)


208
209
210
# File 'lib/coradoc/asciidoc/model/attribute_list.rb', line 208

def empty?
  positional.empty? && named.empty?
end

#fetch(name, default = nil) ⇒ Object

Get a named attribute value with default

Parameters:

  • name (String, Symbol)

    The attribute name

  • default (Object) (defaults to: nil)

    The default value if not found

Returns:

  • (Object)

    The attribute value or default



224
225
226
# File 'lib/coradoc/asciidoc/model/attribute_list.rb', line 224

def fetch(name, default = nil)
  self[name] || default
end

#named_validatorsHash

To be overridden in subclasses.

Returns:

  • (Hash)

    Hash of named validators



149
150
151
# File 'lib/coradoc/asciidoc/model/attribute_list.rb', line 149

def named_validators
  {}
end

#positional_validatorsArray

To be overridden in subclasses.

Returns:

  • (Array)

    Array of positional validators



143
144
145
# File 'lib/coradoc/asciidoc/model/attribute_list.rb', line 143

def positional_validators
  []
end

#to_adoc(show_empty: true) ⇒ String

Serialize this attribute list to AsciiDoc

Generates the square-bracket syntax with valid attributes only.

Examples:

Serialize with options

attrs.to_adoc(show_empty: true)  # => "[value1,name=val]"
attrs.to_adoc(show_empty: false) # => "[value1,name=val]"
empty.to_adoc                     # => "[]"
empty.to_adoc(show_empty: false) # => ""

Parameters:

  • show_empty (Boolean) (defaults to: true)

    If true, show “[]” for empty lists (default: true)

Returns:

  • (String)

    AsciiDoc representation of this attribute list



187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
# File 'lib/coradoc/asciidoc/model/attribute_list.rb', line 187

def to_adoc(show_empty: true)
  valid_positional = positional.reject.with_index do |_p, i|
    rejected_positional.any? { |r| r.position == i }
  end

  valid_named = named.reject do |n|
    rejected_named.any? { |r| r.name == n.name }
  end

  adoc = [valid_positional,
          valid_named].flatten.map(&:to_adoc).join(',')

  if adoc.empty? && show_empty
    '[]'
  elsif adoc.empty?
    ''
  else
    "[#{adoc}]"
  end
end

#validateArray<Lutaml::Model::Error>

Validate this attribute list

Returns:

  • (Array<Lutaml::Model::Error>)

    Validation errors (empty if valid)



156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
# File 'lib/coradoc/asciidoc/model/attribute_list.rb', line 156

def validate
  errors = super

  validate_positional(positional_validators) do |i, value|
    errors << Lutaml::Model::Error.new(
      "Positional attribute at position #{i} with value '#{value}' is not valid"
    )
  end

  validate_named(named_validators) do |name, value|
    errors << Lutaml::Model::Error.new(
      "Named attribute #{name} with value '#{value}' is not valid"
    )
  end

  errors
end

#validate_named(validators: {}) {|name, value| ... } ⇒ Object

Validate named attributes against validators

Examples:

Validate with custom validator

attrs.validate_named(title: /./) do |name, value|
  puts "Invalid #{name}: #{value}"
end

Parameters:

  • validators (Hash) (defaults to: {})

    Hash of name => matcher pairs

Yields:

  • (name, value)

    Block called for each invalid attribute



100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
# File 'lib/coradoc/asciidoc/model/attribute_list.rb', line 100

def validate_named(validators: {})
  named.each_with_index do |named_attribute, _i|
    name = named_attribute.name.to_sym
    value = named_attribute.value

    matcher = validators[name]

    next if matcher && matcher === value

    # Previous implementation would remove the value from the list
    # named.delete(name)
    rejected_named << named_attribute.dup
    yield(name, value) if block_given?
  end
end

#validate_positional(validators: []) {|position, value| ... } ⇒ Object

Validate positional attributes against validators

Examples:

Validate positional attributes

attrs.validate_positional([[0, /./], [1, Integer]])

Parameters:

  • validators (Array) (defaults to: [])

    Array of [position, matcher] pairs

Yields:

  • (position, value)

    Block called for each invalid attribute



124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
# File 'lib/coradoc/asciidoc/model/attribute_list.rb', line 124

def validate_positional(validators: [])
  positional.each_with_index do |positional_attribute, i|
    matcher = validators[i][1]
    value = positional_attribute.value

    next unless matcher && !(matcher === value)

    warn "#{value} does not match #{matcher}"
    # Previous implementation would remove the value from the list
    # positional[i] = nil
    rejected_positional << RejectedPositionalAttribute.new(
      position: i, value:
    )
    yield(i, value) if block_given?
  end
end