lutaml-model 0.1.0 → 0.2.1

Sign up to get free protection for your applications and to get access to all the features.
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