lutaml-model 0.3.1 → 0.3.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (50) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop.yml +0 -5
  3. data/.rubocop_todo.yml +20 -101
  4. data/Gemfile +3 -18
  5. data/README.adoc +109 -6
  6. data/lib/lutaml/model/attribute.rb +15 -2
  7. data/lib/lutaml/model/config.rb +0 -1
  8. data/lib/lutaml/model/json_adapter/json_document.rb +20 -0
  9. data/lib/lutaml/model/json_adapter/json_object.rb +28 -0
  10. data/lib/lutaml/model/json_adapter/{multi_json.rb → multi_json_adapter.rb} +2 -3
  11. data/lib/lutaml/model/json_adapter/{standard.rb → standard_json_adapter.rb} +2 -3
  12. data/lib/lutaml/model/json_adapter.rb +1 -31
  13. data/lib/lutaml/model/key_value_mapping.rb +0 -1
  14. data/lib/lutaml/model/key_value_mapping_rule.rb +0 -1
  15. data/lib/lutaml/model/mapping_hash.rb +0 -2
  16. data/lib/lutaml/model/mapping_rule.rb +0 -1
  17. data/lib/lutaml/model/schema/json_schema.rb +0 -1
  18. data/lib/lutaml/model/schema/relaxng_schema.rb +0 -1
  19. data/lib/lutaml/model/schema/xsd_schema.rb +0 -1
  20. data/lib/lutaml/model/schema/yaml_schema.rb +0 -1
  21. data/lib/lutaml/model/schema.rb +0 -1
  22. data/lib/lutaml/model/serializable.rb +0 -1
  23. data/lib/lutaml/model/serialize.rb +22 -4
  24. data/lib/lutaml/model/toml_adapter/toml_document.rb +20 -0
  25. data/lib/lutaml/model/toml_adapter/toml_object.rb +28 -0
  26. data/lib/lutaml/model/toml_adapter/toml_rb_adapter.rb +2 -3
  27. data/lib/lutaml/model/toml_adapter/tomlib_adapter.rb +2 -3
  28. data/lib/lutaml/model/toml_adapter.rb +0 -31
  29. data/lib/lutaml/model/type/date_time.rb +20 -0
  30. data/lib/lutaml/model/type/json.rb +34 -0
  31. data/lib/lutaml/model/type/time_without_date.rb +4 -3
  32. data/lib/lutaml/model/type.rb +61 -124
  33. data/lib/lutaml/model/version.rb +1 -1
  34. data/lib/lutaml/model/xml_adapter/nokogiri_adapter.rb +5 -6
  35. data/lib/lutaml/model/xml_adapter/oga_adapter.rb +4 -5
  36. data/lib/lutaml/model/xml_adapter/ox_adapter.rb +8 -9
  37. data/lib/lutaml/model/xml_adapter/xml_attribute.rb +27 -0
  38. data/lib/lutaml/model/xml_adapter/xml_document.rb +184 -0
  39. data/lib/lutaml/model/xml_adapter/xml_element.rb +94 -0
  40. data/lib/lutaml/model/xml_adapter/xml_namespace.rb +49 -0
  41. data/lib/lutaml/model/xml_adapter.rb +0 -285
  42. data/lib/lutaml/model/xml_mapping.rb +1 -1
  43. data/lib/lutaml/model/xml_mapping_rule.rb +3 -4
  44. data/lib/lutaml/model/yaml_adapter/standard_yaml_adapter.rb +34 -0
  45. data/lib/lutaml/model/yaml_adapter/yaml_document.rb +20 -0
  46. data/lib/lutaml/model/yaml_adapter/yaml_object.rb +28 -0
  47. data/lib/lutaml/model/yaml_adapter.rb +1 -27
  48. data/lib/lutaml/model.rb +0 -5
  49. metadata +17 -5
  50. data/lib/lutaml/model/xml_namespace.rb +0 -47
@@ -0,0 +1,184 @@
1
+ require_relative "../mapping_hash"
2
+ require_relative "xml_element"
3
+ require_relative "xml_attribute"
4
+ require_relative "xml_namespace"
5
+
6
+ module Lutaml
7
+ module Model
8
+ module XmlAdapter
9
+ class XmlDocument
10
+ attr_reader :root
11
+
12
+ def initialize(root)
13
+ @root = root
14
+ end
15
+
16
+ def self.parse(xml)
17
+ raise NotImplementedError, "Subclasses must implement `parse`."
18
+ end
19
+
20
+ def children
21
+ @root.children
22
+ end
23
+
24
+ def declaration(options)
25
+ version = "1.0"
26
+ version = options[:declaration] if options[:declaration].is_a?(String)
27
+
28
+ encoding = options[:encoding] ? "UTF-8" : nil
29
+ encoding = options[:encoding] if options[:encoding].is_a?(String)
30
+
31
+ declaration = "<?xml version=\"#{version}\""
32
+ declaration += " encoding=\"#{encoding}\"" if encoding
33
+ declaration += "?>\n"
34
+ declaration
35
+ end
36
+
37
+ def to_h
38
+ parse_element(@root)
39
+ end
40
+
41
+ def order
42
+ @root.order
43
+ end
44
+
45
+ def handle_nested_elements(builder, value, rule: nil, attribute: nil)
46
+ options = build_options_for_nested_elements(attribute, rule)
47
+
48
+ case value
49
+ when Array
50
+ value.each { |val| build_element(builder, val, options) }
51
+ else
52
+ build_element(builder, value, options)
53
+ end
54
+ end
55
+
56
+ def build_options_for_nested_elements(attribute, rule)
57
+ return {} unless rule
58
+
59
+ options = {}
60
+
61
+ options[:namespace_prefix] = rule.prefix if rule&.namespace_set?
62
+ options[:mixed_content] = rule.mixed_content
63
+
64
+ options[:mapper_class] = attribute&.type if attribute
65
+
66
+ options
67
+ end
68
+
69
+ def parse_element(element)
70
+ result = Lutaml::Model::MappingHash.new
71
+ result.item_order = element.order
72
+
73
+ element.children.each_with_object(result) do |child, hash|
74
+ value = child.text? ? child.text : parse_element(child)
75
+
76
+ if hash[child.unprefixed_name]
77
+ hash[child.unprefixed_name] =
78
+ [hash[child.unprefixed_name], value].flatten
79
+ else
80
+ hash[child.unprefixed_name] = value
81
+ end
82
+ end
83
+
84
+ element.attributes.each_value do |attr|
85
+ result[attr.unprefixed_name] = attr.value
86
+ end
87
+
88
+ result
89
+ end
90
+
91
+ def build_element(xml, element, options = {})
92
+ if ordered?(element, options)
93
+ build_ordered_element(xml, element, options)
94
+ else
95
+ build_unordered_element(xml, element, options)
96
+ end
97
+ end
98
+
99
+ def ordered?(element, options = {})
100
+ return false unless element.respond_to?(:element_order)
101
+ return element.ordered? if element.respond_to?(:ordered?)
102
+ return options[:mixed_content] if options.key?(:mixed_content)
103
+
104
+ mapper_class = options[:mapper_class]
105
+ mapper_class ? mapper_class.mappings_for(:xml).mixed_content? : false
106
+ end
107
+
108
+ def build_namespace_attributes(klass, processed = {})
109
+ xml_mappings = klass.mappings_for(:xml)
110
+ attributes = klass.attributes
111
+
112
+ attrs = {}
113
+
114
+ if xml_mappings.namespace_prefix
115
+ attrs["xmlns:#{xml_mappings.namespace_prefix}"] =
116
+ xml_mappings.namespace_uri
117
+ end
118
+
119
+ xml_mappings.mappings.each do |mapping_rule|
120
+ processed[klass] ||= {}
121
+
122
+ next if processed[klass][mapping_rule.name]
123
+
124
+ processed[klass][mapping_rule.name] = true
125
+
126
+ type = if mapping_rule.delegate
127
+ attributes[mapping_rule.delegate].type.attributes[mapping_rule.to].type
128
+ else
129
+ attributes[mapping_rule.to].type
130
+ end
131
+
132
+ if type <= Lutaml::Model::Serialize
133
+ attrs = attrs.merge(build_namespace_attributes(type, processed))
134
+ end
135
+
136
+ if mapping_rule.namespace
137
+ attrs["xmlns:#{mapping_rule.prefix}"] = mapping_rule.namespace
138
+ end
139
+ end
140
+
141
+ attrs
142
+ end
143
+
144
+ def build_attributes(element, xml_mapping)
145
+ attrs = namespace_attributes(xml_mapping)
146
+
147
+ xml_mapping.attributes.each_with_object(attrs) do |mapping_rule, hash|
148
+ if mapping_rule.namespace
149
+ hash["xmlns:#{mapping_rule.prefix}"] = mapping_rule.namespace
150
+ end
151
+
152
+ hash[mapping_rule.prefixed_name] = element.send(mapping_rule.to)
153
+ end
154
+
155
+ xml_mapping.elements.each_with_object(attrs) do |mapping_rule, hash|
156
+ if mapping_rule.namespace
157
+ hash["xmlns:#{mapping_rule.prefix}"] = mapping_rule.namespace
158
+ end
159
+ end
160
+ end
161
+
162
+ def attribute_definition_for(element, rule, mapper_class: nil)
163
+ klass = mapper_class || element.class
164
+ return klass.attributes[rule.to] unless rule.delegate
165
+
166
+ element.send(rule.delegate).class.attributes[rule.to]
167
+ end
168
+
169
+ def attribute_value_for(element, rule)
170
+ return element.send(rule.to) unless rule.delegate
171
+
172
+ element.send(rule.delegate).send(rule.to)
173
+ end
174
+
175
+ def namespace_attributes(xml_mapping)
176
+ return {} unless xml_mapping.namespace_uri
177
+
178
+ key = ["xmlns", xml_mapping.namespace_prefix].compact.join(":")
179
+ { key => xml_mapping.namespace_uri }
180
+ end
181
+ end
182
+ end
183
+ end
184
+ end
@@ -0,0 +1,94 @@
1
+ require_relative "xml_attribute"
2
+
3
+ module Lutaml
4
+ module Model
5
+ module XmlAdapter
6
+ class XmlElement
7
+ attr_reader :attributes,
8
+ :children,
9
+ :text,
10
+ :namespace_prefix,
11
+ :parent_document
12
+
13
+ def initialize(
14
+ name,
15
+ attributes = {},
16
+ children = [],
17
+ text = nil,
18
+ parent_document: nil,
19
+ namespace_prefix: nil
20
+ )
21
+ @name = extract_name(name)
22
+ @namespace_prefix = namespace_prefix || extract_namespace_prefix(name)
23
+ @attributes = attributes # .map { |k, v| XmlAttribute.new(k, v) }
24
+ @children = children
25
+ @text = text
26
+ @parent_document = parent_document
27
+ end
28
+
29
+ def name
30
+ if namespace_prefix
31
+ "#{namespace_prefix}:#{@name}"
32
+ else
33
+ @name
34
+ end
35
+ end
36
+
37
+ def unprefixed_name
38
+ @name
39
+ end
40
+
41
+ def document
42
+ XmlDocument.new(self)
43
+ end
44
+
45
+ def namespaces
46
+ @namespaces || @parent_document&.namespaces || {}
47
+ end
48
+
49
+ def own_namespaces
50
+ @namespaces || {}
51
+ end
52
+
53
+ def namespace
54
+ return default_namespace unless namespace_prefix
55
+
56
+ namespaces[namespace_prefix]
57
+ end
58
+
59
+ def attribute_is_namespace?(name)
60
+ name.to_s.start_with?("xmlns")
61
+ end
62
+
63
+ def add_namespace(namespace)
64
+ @namespaces ||= {}
65
+ @namespaces[namespace.prefix] = namespace
66
+ end
67
+
68
+ def default_namespace
69
+ namespaces[nil] || @parent_document&.namespaces&.dig(nil)
70
+ end
71
+
72
+ def extract_name(name)
73
+ n = name.to_s.split(":")
74
+ return name if n.length <= 1
75
+
76
+ n[1..].join(":")
77
+ end
78
+
79
+ def extract_namespace_prefix(name)
80
+ n = name.to_s.split(":")
81
+ return if n.length <= 1
82
+
83
+ n.first
84
+ end
85
+
86
+ def order
87
+ children.each_with_object([]) do |child, arr|
88
+ arr << child.unprefixed_name
89
+ end
90
+ end
91
+ end
92
+ end
93
+ end
94
+ end
@@ -0,0 +1,49 @@
1
+ # frozen_striing_literal: true
2
+
3
+ module Lutaml
4
+ module Model
5
+ module XmlAdapter
6
+ class XmlNamespace
7
+ # Return name
8
+ #
9
+ # @return [String]
10
+ #
11
+ # @api private
12
+ attr_accessor :uri
13
+
14
+ # Return prefix
15
+ #
16
+ # @return [String]
17
+ #
18
+ # @api private
19
+ attr_accessor :prefix
20
+
21
+ # Initialize instance
22
+ #
23
+ # @param [String, nil] name
24
+ # @param [String, nil] prefix
25
+ #
26
+ # @api private
27
+ def initialize(uri = nil, prefix = nil)
28
+ @uri = uri
29
+ @prefix = normalize_prefix(prefix)
30
+ end
31
+
32
+ def normalize_prefix(prefix)
33
+ normalized_prefix = prefix.to_s.gsub(/xmlns:?/, "")
34
+ return if normalized_prefix.empty?
35
+
36
+ normalized_prefix
37
+ end
38
+
39
+ def attr_name
40
+ if prefix && !prefix.empty?
41
+ "xmlns:#{prefix}"
42
+ else
43
+ "xmlns"
44
+ end
45
+ end
46
+ end
47
+ end
48
+ end
49
+ end
@@ -1,292 +1,7 @@
1
- # lib/lutaml/model/xml_adapter.rb
2
-
3
- require_relative "xml_namespace"
4
- require_relative "mapping_hash"
5
-
6
1
  module Lutaml
7
2
  module Model
8
3
  module XmlAdapter
9
4
  XML_NAMESPACE_URI = "http://www.w3.org/XML/1998/namespace".freeze
10
-
11
- class Document
12
- attr_reader :root
13
-
14
- def initialize(root)
15
- @root = root
16
- end
17
-
18
- def self.parse(xml)
19
- raise NotImplementedError, "Subclasses must implement `parse`."
20
- end
21
-
22
- def children
23
- @root.children
24
- end
25
-
26
- def declaration(options)
27
- version = "1.0"
28
- version = options[:declaration] if options[:declaration].is_a?(String)
29
-
30
- encoding = options[:encoding] ? "UTF-8" : nil
31
- encoding = options[:encoding] if options[:encoding].is_a?(String)
32
-
33
- declaration = "<?xml version=\"#{version}\""
34
- declaration += " encoding=\"#{encoding}\"" if encoding
35
- declaration += "?>\n"
36
- declaration
37
- end
38
-
39
- def to_h
40
- parse_element(@root)
41
- end
42
-
43
- def order
44
- @root.order
45
- end
46
-
47
- def handle_nested_elements(builder, value, rule: nil, attribute: nil)
48
- options = build_options_for_nested_elements(attribute, rule)
49
-
50
- case value
51
- when Array
52
- value.each { |val| build_element(builder, val, options) }
53
- else
54
- build_element(builder, value, options)
55
- end
56
- end
57
-
58
- def build_options_for_nested_elements(attribute, rule)
59
- return {} unless rule
60
-
61
- options = {}
62
-
63
- options[:namespace_prefix] = rule.prefix if rule&.namespace_set?
64
- options[:mixed_content] = rule.mixed_content
65
-
66
- options[:mapper_class] = attribute&.type if attribute
67
-
68
- options
69
- end
70
-
71
- def parse_element(element)
72
- result = Lutaml::Model::MappingHash.new
73
- result.item_order = element.order
74
-
75
- element.children.each_with_object(result) do |child, hash|
76
- value = child.text? ? child.text : parse_element(child)
77
-
78
- if hash[child.unprefixed_name]
79
- hash[child.unprefixed_name] =
80
- [hash[child.unprefixed_name], value].flatten
81
- else
82
- hash[child.unprefixed_name] = value
83
- end
84
- end
85
-
86
- element.attributes.each_value do |attr|
87
- result[attr.unprefixed_name] = attr.value
88
- end
89
-
90
- result
91
- end
92
-
93
- def build_element(xml, element, options = {})
94
- if ordered?(element, options)
95
- build_ordered_element(xml, element, options)
96
- else
97
- build_unordered_element(xml, element, options)
98
- end
99
- end
100
-
101
- def ordered?(element, options = {})
102
- return false unless element.respond_to?(:element_order)
103
- return element.ordered? if element.respond_to?(:ordered?)
104
- return options[:mixed_content] if options.key?(:mixed_content)
105
-
106
- mapper_class = options[:mapper_class]
107
- mapper_class ? mapper_class.mappings_for(:xml).mixed_content? : false
108
- end
109
-
110
- def build_namespace_attributes(klass, processed = {})
111
- xml_mappings = klass.mappings_for(:xml)
112
- attributes = klass.attributes
113
-
114
- attrs = {}
115
-
116
- if xml_mappings.namespace_prefix
117
- attrs["xmlns:#{xml_mappings.namespace_prefix}"] =
118
- xml_mappings.namespace_uri
119
- end
120
-
121
- xml_mappings.mappings.each do |mapping_rule|
122
- processed[klass] ||= {}
123
-
124
- next if processed[klass][mapping_rule.name]
125
-
126
- processed[klass][mapping_rule.name] = true
127
-
128
- type = if mapping_rule.delegate
129
- attributes[mapping_rule.delegate].type.attributes[mapping_rule.to].type
130
- else
131
- attributes[mapping_rule.to].type
132
- end
133
-
134
- if type <= Lutaml::Model::Serialize
135
- attrs = attrs.merge(build_namespace_attributes(type, processed))
136
- end
137
-
138
- if mapping_rule.namespace
139
- attrs["xmlns:#{mapping_rule.prefix}"] = mapping_rule.namespace
140
- end
141
- end
142
-
143
- attrs
144
- end
145
-
146
- def build_attributes(element, xml_mapping)
147
- attrs = namespace_attributes(xml_mapping)
148
-
149
- xml_mapping.attributes.each_with_object(attrs) do |mapping_rule, hash|
150
- if mapping_rule.namespace
151
- hash["xmlns:#{mapping_rule.prefix}"] = mapping_rule.namespace
152
- end
153
-
154
- hash[mapping_rule.prefixed_name] = element.send(mapping_rule.to)
155
- end
156
-
157
- xml_mapping.elements.each_with_object(attrs) do |mapping_rule, hash|
158
- if mapping_rule.namespace
159
- hash["xmlns:#{mapping_rule.prefix}"] = mapping_rule.namespace
160
- end
161
- end
162
- end
163
-
164
- def attribute_definition_for(element, rule, mapper_class: nil)
165
- klass = mapper_class || element.class
166
- return klass.attributes[rule.to] unless rule.delegate
167
-
168
- element.send(rule.delegate).class.attributes[rule.to]
169
- end
170
-
171
- def attribute_value_for(element, rule)
172
- return element.send(rule.to) unless rule.delegate
173
-
174
- element.send(rule.delegate).send(rule.to)
175
- end
176
-
177
- def namespace_attributes(xml_mapping)
178
- return {} unless xml_mapping.namespace_uri
179
-
180
- key = ["xmlns", xml_mapping.namespace_prefix].compact.join(":")
181
- { key => xml_mapping.namespace_uri }
182
- end
183
- end
184
-
185
- class Element
186
- attr_reader :attributes,
187
- :children,
188
- :text,
189
- :namespace_prefix,
190
- :parent_document
191
-
192
- def initialize(
193
- name,
194
- attributes = {},
195
- children = [],
196
- text = nil,
197
- parent_document: nil,
198
- namespace_prefix: nil
199
- )
200
- @name = extract_name(name)
201
- @namespace_prefix = namespace_prefix || extract_namespace_prefix(name)
202
- @attributes = attributes # .map { |k, v| Attribute.new(k, v) }
203
- @children = children
204
- @text = text
205
- @parent_document = parent_document
206
- end
207
-
208
- def name
209
- if namespace_prefix
210
- "#{namespace_prefix}:#{@name}"
211
- else
212
- @name
213
- end
214
- end
215
-
216
- def unprefixed_name
217
- @name
218
- end
219
-
220
- def document
221
- Document.new(self)
222
- end
223
-
224
- def namespaces
225
- @namespaces || @parent_document&.namespaces || {}
226
- end
227
-
228
- def own_namespaces
229
- @namespaces || {}
230
- end
231
-
232
- def namespace
233
- return default_namespace unless namespace_prefix
234
-
235
- namespaces[namespace_prefix]
236
- end
237
-
238
- def attribute_is_namespace?(name)
239
- name.to_s.start_with?("xmlns")
240
- end
241
-
242
- def add_namespace(namespace)
243
- @namespaces ||= {}
244
- @namespaces[namespace.prefix] = namespace
245
- end
246
-
247
- def default_namespace
248
- namespaces[nil] || @parent_document&.namespaces&.dig(nil)
249
- end
250
-
251
- def extract_name(name)
252
- n = name.to_s.split(":")
253
- return name if n.length <= 1
254
-
255
- n[1..].join(":")
256
- end
257
-
258
- def extract_namespace_prefix(name)
259
- n = name.to_s.split(":")
260
- return if n.length <= 1
261
-
262
- n.first
263
- end
264
-
265
- def order
266
- children.each_with_object([]) do |child, arr|
267
- arr << child.unprefixed_name
268
- end
269
- end
270
- end
271
-
272
- class Attribute
273
- attr_reader :name, :value, :namespace, :namespace_prefix
274
-
275
- def initialize(name, value, namespace: nil, namespace_prefix: nil)
276
- @name = name
277
- @value = value
278
- @namespace = namespace
279
- @namespace_prefix = namespace_prefix
280
- end
281
-
282
- def unprefixed_name
283
- if namespace_prefix
284
- name.split(":").last
285
- else
286
- name
287
- end
288
- end
289
- end
290
5
  end
291
6
  end
292
7
  end
@@ -1,4 +1,3 @@
1
- # lib/lutaml/model/xml_mapping.rb
2
1
  require_relative "xml_mapping_rule"
3
2
 
4
3
  module Lutaml
@@ -82,6 +81,7 @@ module Lutaml
82
81
  namespace_set: namespace_set != false,
83
82
  )
84
83
  end
84
+
85
85
  # rubocop:enable Metrics/ParameterLists
86
86
 
87
87
  def map_content(
@@ -1,4 +1,3 @@
1
- # lib/lutaml/model/xml_mapping_rule.rb
2
1
  require_relative "mapping_rule"
3
2
 
4
3
  module Lutaml
@@ -24,12 +23,12 @@ module Lutaml
24
23
  with: with,
25
24
  delegate: delegate,
26
25
  mixed_content: mixed_content,
27
- namespace_set: namespace_set
26
+ namespace_set: namespace_set,
28
27
  )
29
28
 
30
29
  @namespace = if namespace.to_s == "inherit"
31
- # we are using inherit_namespace in xml builder by
32
- # default so no need to do anything here.
30
+ # we are using inherit_namespace in xml builder by
31
+ # default so no need to do anything here.
33
32
  else
34
33
  namespace
35
34
  end
@@ -0,0 +1,34 @@
1
+ require "yaml"
2
+ require_relative "yaml_document"
3
+
4
+ module Lutaml
5
+ module Model
6
+ module YamlAdapter
7
+ class StandardYamlAdapter < YamlDocument
8
+ def self.parse(yaml)
9
+ YAML.safe_load(
10
+ yaml,
11
+ permitted_classes: [Date, Time, DateTime, Symbol,
12
+ BigDecimal, Hash, Array],
13
+ )
14
+ end
15
+
16
+ def to_yaml(options = {})
17
+ YAML.dump(@attributes, options)
18
+ end
19
+
20
+ # TODO: Is this really needed?
21
+ def self.to_yaml(attributes, *args)
22
+ new(attributes).to_yaml(*args)
23
+ end
24
+
25
+ # TODO: Is this really needed?
26
+ def self.from_yaml(yaml, klass)
27
+ data = parse(yaml)
28
+ mapped_attrs = klass.send(:apply_mappings, data, :yaml)
29
+ klass.new(mapped_attrs)
30
+ end
31
+ end
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,20 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "yaml_object"
4
+
5
+ module Lutaml
6
+ module Model
7
+ module YamlAdapter
8
+ # Base class for YAML documents
9
+ class YamlDocument < YamlObject
10
+ def self.parse(yaml)
11
+ raise NotImplementedError, "Subclasses must implement `parse`."
12
+ end
13
+
14
+ def to_yaml(*args)
15
+ raise NotImplementedError, "Subclasses must implement `to_yaml`."
16
+ end
17
+ end
18
+ end
19
+ end
20
+ end