lutaml-model 0.1.0 → 0.2.1

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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 223c53c673e167e5bf85d7eec79a95011e37db1e07849a6a005d935bd5586c0f
4
- data.tar.gz: 4adc0dc4bc3d30b15c7901b2b997c3e4ff24ff9d92896020d038e443dda99925
3
+ metadata.gz: c28f7695801483c724c6d8b03e37b1bc269eb82a506b4e363f65078142bed860
4
+ data.tar.gz: feb57e7df6c86c2cc3cdaa03012045e017c557a2f957b74c639d80feb53a36fc
5
5
  SHA512:
6
- metadata.gz: 34a4d857e96d2955581a6df37c0aff10466edb1eb57293d6a818c62b998ba2244609ac9963d676ff789c0c2e08593296bd96bf7d8c6bd628ff867718e9f8e31a
7
- data.tar.gz: a0e0c005f90ce0d029f119f1d2013da1b2ea7d61e8a8f149c46fce3c6bbfea79237d90cb9b8fc4a5eed7ef40939f2b4a35d121ab2be034b04a897103e78b7c0e
6
+ metadata.gz: 4a9641bec82c8c12099bc5c332931aa1f52b0dd8594ebc6e43722ace015c838522e7ebc873b834e324167db151b5055bbb495461ee585f6fdcd1c400ddbe7513
7
+ data.tar.gz: 88090ad9c5dc0be10531d1fc5c053db353fd5a30412279f19868b4786d046ae1f9b9f03237709c1928afd039f2b94809e137a6ad430e3fe2d04e200416cc8949
@@ -12,4 +12,4 @@ jobs:
12
12
  rake:
13
13
  uses: metanorma/ci/.github/workflows/generic-rake.yml@main
14
14
  secrets:
15
- pat_token: ${{ secrets.METANORMA_CI_PAT_TOKEN }}
15
+ pat_token: ${{ secrets.LUTAML_CI_PAT_TOKEN }}
@@ -19,5 +19,5 @@ jobs:
19
19
  with:
20
20
  next_version: ${{ github.event.inputs.next_version }}
21
21
  secrets:
22
- rubygems-api-key: ${{ secrets.METANORMA_CI_RUBYGEMS_API_KEY }}
23
- pat_token: ${{ secrets.METANORMA_CI_PAT_TOKEN }}
22
+ rubygems-api-key: ${{ secrets.LUTAML_CI_RUBYGEMS_API_KEY }}
23
+ pat_token: ${{ secrets.LUTAML_CI_PAT_TOKEN }}
data/.rubocop.yml CHANGED
@@ -5,7 +5,7 @@ inherit_from:
5
5
  # ...
6
6
 
7
7
  AllCops:
8
- TargetRubyVersion: 2.6
8
+ TargetRubyVersion: 3.0
9
9
  NewCops: enable
10
10
  Exclude:
11
11
  - 'lib/sts/mapper.rb'
data/Gemfile CHANGED
@@ -10,3 +10,23 @@ gem "rake", "~> 13.0"
10
10
  gem "rspec", "~> 3.0"
11
11
 
12
12
  gem "rubocop", "~> 1.21"
13
+
14
+ gem "equivalent-xml"
15
+
16
+ gem "multi_json"
17
+
18
+ gem "nokogiri"
19
+
20
+ gem "oga"
21
+
22
+ gem "ox"
23
+
24
+ gem "pry"
25
+
26
+ gem "rubocop-performance"
27
+
28
+ gem "rubocop-rails"
29
+
30
+ gem "tomlib"
31
+
32
+ gem "toml-rb"
@@ -15,7 +15,9 @@ module Lutaml
15
15
  end
16
16
 
17
17
  def default
18
- options[:default].is_a?(Proc) ? options[:default].call : options[:default]
18
+ return options[:default].call if options[:default].is_a?(Proc)
19
+
20
+ options[:default]
19
21
  end
20
22
 
21
23
  def render_nil?
@@ -11,7 +11,13 @@ module Lutaml
11
11
  end
12
12
 
13
13
  def map(name, to:, render_nil: false, with: {}, delegate: nil)
14
- @mappings << KeyValueMappingRule.new(name, to: to, render_nil: render_nil, with: with, delegate: delegate)
14
+ @mappings << KeyValueMappingRule.new(
15
+ name,
16
+ to: to,
17
+ render_nil: render_nil,
18
+ with: with,
19
+ delegate: delegate,
20
+ )
15
21
  end
16
22
 
17
23
  alias map_element map
@@ -13,6 +13,15 @@ module Lutaml
13
13
  end
14
14
 
15
15
  alias from name
16
+ alias render_nil? render_nil
17
+
18
+ def prefixed_name
19
+ if prefix
20
+ "#{prefix}:#{name}"
21
+ else
22
+ name
23
+ end
24
+ end
16
25
 
17
26
  def serialize(model, value)
18
27
  if custom_methods[:to]
@@ -17,8 +17,6 @@ module Lutaml
17
17
  options[:pretty] ? JSON.pretty_generate(schema) : schema.to_json
18
18
  end
19
19
 
20
- private
21
-
22
20
  def self.generate_definitions(klass)
23
21
  {
24
22
  klass.name => {
@@ -30,8 +28,8 @@ module Lutaml
30
28
  end
31
29
 
32
30
  def self.generate_properties(klass)
33
- klass.attributes.each_with_object({}) do |(name, attr), properties|
34
- properties[name] = generate_property_schema(attr)
31
+ klass.attributes.transform_values do |attr|
32
+ generate_property_schema(attr)
35
33
  end
36
34
  end
37
35
 
@@ -40,22 +38,14 @@ module Lutaml
40
38
  end
41
39
 
42
40
  def self.get_json_type(type)
43
- case type
44
- when Lutaml::Model::Type::String
45
- "string"
46
- when Lutaml::Model::Type::Integer
47
- "integer"
48
- when Lutaml::Model::Type::Boolean
49
- "boolean"
50
- when Lutaml::Model::Type::Float
51
- "number"
52
- when Lutaml::Model::Type::Array
53
- "array"
54
- when Lutaml::Model::Type::Hash
55
- "object"
56
- else
57
- "string" # Default to string for unknown types
58
- end
41
+ {
42
+ Lutaml::Model::Type::String => "string",
43
+ Lutaml::Model::Type::Integer => "integer",
44
+ Lutaml::Model::Type::Boolean => "boolean",
45
+ Lutaml::Model::Type::Float => "number",
46
+ Lutaml::Model::Type::Array => "array",
47
+ Lutaml::Model::Type::Hash => "object",
48
+ }[type] || "string" # Default to string for unknown types
59
49
  end
60
50
  end
61
51
  end
@@ -5,7 +5,7 @@ module Lutaml
5
5
  module Model
6
6
  module Schema
7
7
  class RelaxngSchema
8
- def self.generate(klass, options = {})
8
+ def self.generate(klass, _options = {})
9
9
  schema = Nokogiri::XML::Builder.new do |xml|
10
10
  xml.element(name: klass.name) do
11
11
  xml.complexType do
@@ -18,8 +18,6 @@ module Lutaml
18
18
  schema.to_xml
19
19
  end
20
20
 
21
- private
22
-
23
21
  def self.generate_elements(klass, xml)
24
22
  klass.attributes.each do |name, attr|
25
23
  xml.element(name: name, type: get_relaxng_type(attr.type))
@@ -27,22 +25,14 @@ module Lutaml
27
25
  end
28
26
 
29
27
  def self.get_relaxng_type(type)
30
- case type
31
- when Lutaml::Model::Type::String
32
- "string"
33
- when Lutaml::Model::Type::Integer
34
- "integer"
35
- when Lutaml::Model::Type::Boolean
36
- "boolean"
37
- when Lutaml::Model::Type::Float
38
- "float"
39
- when Lutaml::Model::Type::Array
40
- "array"
41
- when Lutaml::Model::Type::Hash
42
- "object"
43
- else
44
- "string" # Default to string for unknown types
45
- end
28
+ {
29
+ Lutaml::Model::Type::String => "string",
30
+ Lutaml::Model::Type::Integer => "integer",
31
+ Lutaml::Model::Type::Boolean => "boolean",
32
+ Lutaml::Model::Type::Float => "float",
33
+ Lutaml::Model::Type::Array => "array",
34
+ Lutaml::Model::Type::Hash => "object",
35
+ }[type] || "string" # Default to string for unknown types
46
36
  end
47
37
  end
48
38
  end
@@ -5,14 +5,12 @@ module Lutaml
5
5
  module Model
6
6
  module Schema
7
7
  class XsdSchema
8
- def self.generate(klass, options = {})
8
+ def self.generate(klass, _options = {})
9
9
  schema = Nokogiri::XML::Builder.new do |xml|
10
10
  xml.schema(xmlns: "http://www.w3.org/2001/XMLSchema") do
11
11
  xml.element(name: klass.name) do
12
12
  xml.complexType do
13
- xml.sequence do
14
- generate_elements(klass, xml)
15
- end
13
+ xml.sequence { generate_elements(klass, xml) }
16
14
  end
17
15
  end
18
16
  end
@@ -20,8 +18,6 @@ module Lutaml
20
18
  schema.to_xml
21
19
  end
22
20
 
23
- private
24
-
25
21
  def self.generate_elements(klass, xml)
26
22
  klass.attributes.each do |name, attr|
27
23
  xml.element(name: name, type: get_xsd_type(attr.type))
@@ -29,24 +25,15 @@ module Lutaml
29
25
  end
30
26
 
31
27
  def self.get_xsd_type(type)
32
- case type
33
- when Lutaml::Model::Type::String
34
- "xs:string"
35
- when Lutaml::Model::Type::Integer
36
- "xs:integer"
37
- when Lutaml::Model::Type::Boolean
38
- "xs:boolean"
39
- when Lutaml::Model::Type::Float
40
- "xs:float"
41
- when Lutaml::Model::Type::Decimal
42
- "xs:decimal"
43
- when Lutaml::Model::Type::Array
44
- "xs:array"
45
- when Lutaml::Model::Type::Hash
46
- "xs:object"
47
- else
48
- "xs:string" # Default to string for unknown types
49
- end
28
+ {
29
+ Lutaml::Model::Type::String => "xs:string",
30
+ Lutaml::Model::Type::Integer => "xs:integer",
31
+ Lutaml::Model::Type::Boolean => "xs:boolean",
32
+ Lutaml::Model::Type::Float => "xs:float",
33
+ Lutaml::Model::Type::Decimal => "xs:decimal",
34
+ Lutaml::Model::Type::Array => "xs:array",
35
+ Lutaml::Model::Type::Hash => "xs:object",
36
+ }[type] || "xs:string" # Default to string for unknown types
50
37
  end
51
38
  end
52
39
  end
@@ -5,7 +5,7 @@ module Lutaml
5
5
  module Model
6
6
  module Schema
7
7
  class YamlSchema
8
- def self.generate(klass, options = {})
8
+ def self.generate(klass, _options = {})
9
9
  schema = {
10
10
  "type" => "map",
11
11
  "mapping" => generate_mapping(klass),
@@ -13,8 +13,6 @@ module Lutaml
13
13
  YAML.dump(schema)
14
14
  end
15
15
 
16
- private
17
-
18
16
  def self.generate_mapping(klass)
19
17
  klass.attributes.each_with_object({}) do |(name, attr), mapping|
20
18
  mapping[name.to_s] = { "type" => get_yaml_type(attr.type) }
@@ -22,24 +20,16 @@ module Lutaml
22
20
  end
23
21
 
24
22
  def self.get_yaml_type(type)
25
- case type
26
- when Lutaml::Model::Type::String
27
- "str"
28
- when Lutaml::Model::Type::Integer
29
- "int"
30
- when Lutaml::Model::Type::Boolean
31
- "bool"
32
- when Lutaml::Model::Type::Float
33
- "float"
34
- when Lutaml::Model::Type::Decimal
35
- "float" # YAML does not have a separate decimal type, so we use float
36
- when Lutaml::Model::Type::Array
37
- "seq"
38
- when Lutaml::Model::Type::Hash
39
- "map"
40
- else
41
- "str" # Default to string for unknown types
42
- end
23
+ {
24
+ Lutaml::Model::Type::String => "str",
25
+ Lutaml::Model::Type::Integer => "int",
26
+ Lutaml::Model::Type::Boolean => "bool",
27
+ Lutaml::Model::Type::Float => "float",
28
+ # YAML does not have a separate decimal type, so we use float
29
+ Lutaml::Model::Type::Decimal => "float",
30
+ Lutaml::Model::Type::Array => "seq",
31
+ Lutaml::Model::Type::Hash => "map",
32
+ }[type] || "str" # Default to string for unknown types
43
33
  end
44
34
  end
45
35
  end
@@ -22,6 +22,11 @@ module Lutaml
22
22
  base.extend(ClassMethods)
23
23
  end
24
24
 
25
+ # rubocop:disable Metrics/MethodLength
26
+ # rubocop:disable Metrics/BlockLength
27
+ # rubocop:disable Metrics/AbcSize
28
+ # rubocop:disable Metrics/CyclomaticComplexity
29
+ # rubocop:disable Metrics/PerceivedComplexity
25
30
  module ClassMethods
26
31
  attr_accessor :attributes, :mappings
27
32
 
@@ -31,11 +36,11 @@ module Lutaml
31
36
  attributes[name] = attr
32
37
 
33
38
  define_method(name) do
34
- instance_variable_get("@#{name}")
39
+ instance_variable_get(:"@#{name}")
35
40
  end
36
41
 
37
- define_method("#{name}=") do |value|
38
- instance_variable_set("@#{name}", value)
42
+ define_method(:"#{name}=") do |value|
43
+ instance_variable_set(:"@#{name}", value)
39
44
  end
40
45
  end
41
46
 
@@ -47,8 +52,8 @@ module Lutaml
47
52
  self.mappings[format].instance_eval(&block)
48
53
  end
49
54
 
50
- define_method("from_#{format}") do |data|
51
- adapter = Lutaml::Model::Config.send("#{format}_adapter")
55
+ define_method(:"from_#{format}") do |data|
56
+ adapter = Lutaml::Model::Config.send(:"#{format}_adapter")
52
57
  doc = adapter.parse(data)
53
58
  mapped_attrs = apply_mappings(doc.to_h, format)
54
59
  apply_content_mapping(doc, mapped_attrs) if format == :xml
@@ -64,22 +69,98 @@ module Lutaml
64
69
  klass = format == :xml ? XmlMapping : KeyValueMapping
65
70
  klass.new.tap do |mapping|
66
71
  attributes&.each do |name, attr|
67
- mapping.map_element(name.to_s, to: name, render_nil: attr.render_nil?)
72
+ mapping.map_element(name.to_s, to: name,
73
+ render_nil: attr.render_nil?)
68
74
  end
69
75
  end
70
76
  end
71
77
 
72
78
  def apply_mappings(doc, format)
79
+ return apply_xml_mapping(doc) if format == :xml
80
+
73
81
  mappings = mappings_for(format).mappings
82
+ mappings.each_with_object({}) do |rule, hash|
83
+ attr = if rule.delegate
84
+ attributes[rule.delegate].type.attributes[rule.to]
85
+ else
86
+ attributes[rule.to]
87
+ end
88
+
89
+ raise "Attribute '#{rule.to}' not found in #{self}" unless attr
90
+
91
+ value = if rule.custom_methods[:from]
92
+ new.send(rule.custom_methods[:from], hash, doc)
93
+ elsif doc.key?(rule.name) || doc.key?(rule.name.to_sym)
94
+ doc[rule.name] || doc[rule.name.to_sym]
95
+ else
96
+ attr.default
97
+ end
98
+ # if attr.collection?
99
+ # value = (value || []).map do |v|
100
+ # attr.type <= Serialize ? attr.type.new(v) : v
101
+ # end
102
+ # elsif value.is_a?(Hash) && attr.type <= Serialize
103
+ # value = attr.type.new(value)
104
+ # else
105
+ # value = attr.type.cast(value)
106
+ # end
107
+
108
+ if attr.collection?
109
+ value = (value || []).map do |v|
110
+ attr.type <= Serialize ? attr.type.apply_mappings(v, format) : v
111
+ end
112
+ elsif value.is_a?(Hash) && attr.type != Lutaml::Model::Type::Hash
113
+ value = attr.type.apply_mappings(value, format)
114
+ end
115
+
116
+ if rule.delegate
117
+ hash[rule.delegate] ||= {}
118
+ hash[rule.delegate][rule.to] = value
119
+ else
120
+ hash[rule.to] = value
121
+ end
122
+ end
123
+ end
124
+
125
+ def apply_xml_mapping(doc)
126
+ mappings = mappings_for(:xml).mappings
127
+
74
128
  mappings.each_with_object({}) do |rule, hash|
75
129
  attr = attributes[rule.to]
76
130
  raise "Attribute '#{rule.to}' not found in #{self}" unless attr
77
131
 
78
- value = doc[rule.name]
132
+ value = if rule.name
133
+ doc[rule.name.to_s] || doc[rule.name.to_sym]
134
+ else
135
+ doc["text"]
136
+ end
137
+
138
+ # if attr.collection?
139
+ # value = (value || []).map do |v|
140
+ # attr.type <= Serialize ? attr.type.from_hash(v) : v
141
+ # end
142
+ # elsif value.is_a?(Hash) && attr.type <= Serialize
143
+ # value = attr.type.cast(value)
144
+ # elsif value.is_a?(Array)
145
+ # value = attr.type.cast(value.first["text"]&.first)
146
+ # end
147
+
79
148
  if attr.collection?
80
- value = (value || []).map { |v| attr.type <= Serialize ? attr.type.new(v) : v }
81
- elsif value.is_a?(Hash) && attr.type <= Serialize
82
- value = attr.type.new(value)
149
+ value = (value || []).map do |v|
150
+ if attr.type <= Serialize
151
+ attr.type.apply_xml_mapping(v)
152
+ else
153
+ v["text"]
154
+ end
155
+ end
156
+ elsif attr.type <= Serialize
157
+ value = attr.type.apply_xml_mapping(value) if value
158
+ else
159
+ if value.is_a?(Hash) && attr.type != Lutaml::Model::Type::Hash
160
+ value = value["text"]
161
+ end
162
+
163
+ value = attr.type.cast(value)
83
164
  end
84
165
  hash[rule.to] = value
85
166
  end
@@ -94,25 +175,50 @@ module Lutaml
94
175
  end
95
176
  end
96
177
 
178
+ # rubocop:disable Layout/LineLength
97
179
  def initialize(attrs = {})
98
- return self unless self.class.attributes
180
+ return unless self.class.attributes
99
181
 
100
182
  self.class.attributes.each do |name, attr|
101
- value = attrs.key?(name) ? attrs[name] : attr.default
183
+ value = if attrs.key?(name)
184
+ attrs[name]
185
+ elsif attrs.key?(name.to_sym)
186
+ attrs[name.to_sym]
187
+ elsif attrs.key?(name.to_s)
188
+ attrs[name.to_s]
189
+ else
190
+ attr.default
191
+ end
192
+
102
193
  value = if attr.collection?
103
- (value || []).map { |v| Lutaml::Model::Type.cast(v, attr.type) }
104
- else
105
- Lutaml::Model::Type.cast(value, attr.type)
106
- end
107
- send("#{name}=", ensure_utf8(value))
194
+ (value || []).map do |v|
195
+ if v.is_a?(Hash)
196
+ attr.type.new(v)
197
+ else
198
+ Lutaml::Model::Type.cast(
199
+ v, attr.type
200
+ )
201
+ end
202
+ end
203
+ elsif value.is_a?(Hash) && attr.type != Lutaml::Model::Type::Hash
204
+ attr.type.new(value)
205
+ else
206
+ Lutaml::Model::Type.cast(value, attr.type)
207
+ end
208
+ send(:"#{name}=", ensure_utf8(value))
108
209
  end
109
210
  end
211
+ # rubocop:enable Layout/LineLength
110
212
 
111
213
  # TODO: Make this work
112
214
  # FORMATS.each do |format|
113
215
  # define_method("to_#{format}") do |options = {}|
114
216
  # adapter = Lutaml::Model::Config.send("#{format}_adapter")
115
- # representation = format == :yaml ? self : hash_representation(format, options)
217
+ # representation = if format == :yaml
218
+ # self
219
+ # else
220
+ # hash_representation(format, options)
221
+ # end
116
222
  # adapter.new(representation).send("to_#{format}", options)
117
223
  # end
118
224
  # end
@@ -148,20 +254,63 @@ module Lutaml
148
254
  name = rule.to
149
255
  next if except&.include?(name) || (only && !only.include?(name))
150
256
 
151
- value = send(name)
257
+ next handle_delegate(self, rule, hash) if rule.delegate
258
+
259
+ value = if rule.custom_methods[:to]
260
+ send(rule.custom_methods[:to], self, send(name))
261
+ else
262
+ send(name)
263
+ end
264
+
152
265
  next if value.nil? && !rule.render_nil
153
266
 
267
+ attribute = self.class.attributes[name]
268
+
154
269
  hash[rule.from] = case value
155
- when Array
156
- value.map { |v| v.is_a?(Serialize) ? v.hash_representation(format, options) : v }
157
- else
158
- value.is_a?(Serialize) ? value.hash_representation(format, options) : value
159
- end
270
+ when Array
271
+ value.map do |v|
272
+ if v.is_a?(Serialize)
273
+ v.hash_representation(format, options)
274
+ else
275
+ attribute.type.serialize(v)
276
+ end
277
+ end
278
+ else
279
+ if value.is_a?(Serialize)
280
+ value.hash_representation(format, options)
281
+ else
282
+ attribute.type.serialize(value)
283
+ end
284
+ end
160
285
  end
161
286
  end
162
287
 
163
288
  private
164
289
 
290
+ def handle_delegate(_obj, rule, hash)
291
+ name = rule.to
292
+ value = send(rule.delegate).send(name)
293
+ return if value.nil? && !rule.render_nil
294
+
295
+ attribute = send(rule.delegate).class.attributes[name]
296
+ hash[rule.from] = case value
297
+ when Array
298
+ value.map do |v|
299
+ if v.is_a?(Serialize)
300
+ v.hash_representation(format, options)
301
+ else
302
+ attribute.type.serialize(v)
303
+ end
304
+ end
305
+ else
306
+ if value.is_a?(Serialize)
307
+ value.hash_representation(format, options)
308
+ else
309
+ attribute.type.serialize(value)
310
+ end
311
+ end
312
+ end
313
+
165
314
  def ensure_utf8(value)
166
315
  case value
167
316
  when String
@@ -169,11 +318,18 @@ module Lutaml
169
318
  when Array
170
319
  value.map { |v| ensure_utf8(v) }
171
320
  when Hash
172
- value.transform_keys { |k| ensure_utf8(k) }.transform_values { |v| ensure_utf8(v) }
321
+ value.transform_keys do |k|
322
+ ensure_utf8(k)
323
+ end.transform_values { |v| ensure_utf8(v) }
173
324
  else
174
325
  value
175
326
  end
176
327
  end
328
+ # rubocop:enable Metrics/MethodLength
329
+ # rubocop:enable Metrics/BlockLength
330
+ # rubocop:enable Metrics/AbcSize
331
+ # rubocop:enable Metrics/CyclomaticComplexity
332
+ # rubocop:enable Metrics/PerceivedComplexity
177
333
  end
178
334
  end
179
335
  end
@@ -7,12 +7,13 @@ module Lutaml
7
7
  module TomlAdapter
8
8
  class TomlibDocument < Document
9
9
  def self.parse(toml)
10
- data = Tomlib::Parser.new(toml).parsed
10
+ data = Tomlib.load(toml)
11
11
  new(data)
12
12
  end
13
13
 
14
14
  def to_toml(*args)
15
- Tomlib::Generator.new(to_h).toml_str
15
+ Tomlib.dump(to_h, *args)
16
+ # Tomlib::Generator.new(to_h).toml_str
16
17
  end
17
18
  end
18
19
  end