lutaml-lml 0.1.3 → 0.1.4

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: 848b5d75872b444720cee8fb6cdc2ae93db738657cb8ad13633d0317d8bd9808
4
- data.tar.gz: f9718ba7428f4948d06bf8e489d55d78584c95086ebc4ff6ea6fb2bd0c0e41a9
3
+ metadata.gz: f72188a69457d2cf82445867f2b726dd621d63e82d79c7e56b01241e0d15ec84
4
+ data.tar.gz: 402135ddb2bf31953c21f6166c859c6b5f67d24d1a9e18e8369eb3646a413381
5
5
  SHA512:
6
- metadata.gz: adae7746cdcf26e0709537239e7a4cc234c9287f49615eca76d44add023d11d317b30754ac9fef8d1dd6fbbc0899d19a77c284aa3726b6f8809b9f49ea440b4a
7
- data.tar.gz: ac7189d5cef1e567485b38b56e23998961280600d157c037e8e21def73ee41e22f632ff82c0ab298c286319c00ab74d9a71e6d3fe0f4e9669ea8644cdd7b03ba
6
+ metadata.gz: d07e5279b6c0d983d1ba1384ef3579aa3d493fba0520aedadfd97ac11a42bfca1fd2e96e621e6551e8dd22bef52e30136779cdf88e8bbe81e611b762e1efa3cd
7
+ data.tar.gz: 20e402c37d1506fd86c4ded5fac0bb010dc58d3cdda8efcf3e92cd287ec8025331375310b7e40093741af136aaaf4f61d78a44456fe59b2d75dffe5e1d57568b
@@ -14,6 +14,7 @@ module Lutaml
14
14
  end
15
15
 
16
16
  def process_attributes_array(obj)
17
+ return [] if obj.empty?
17
18
  return obj.map { |item| process_attributes(item) } unless single_key_hashes?(obj)
18
19
 
19
20
  obj.each_with_object({}) do |item, hash|
@@ -26,8 +26,7 @@ module Lutaml
26
26
  key = INSTANCE_KEY_HANDLERS.keys.find { |k| instance.key?(k) }
27
27
  next unless key
28
28
 
29
- result = public_send(INSTANCE_KEY_HANDLERS[key], instance[key])
30
- key == :instance ? (acc[:instances] << result) : (acc[key] = result)
29
+ append_result(acc, key, public_send(INSTANCE_KEY_HANDLERS[key], instance[key]))
31
30
  end
32
31
  end
33
32
 
@@ -57,6 +56,16 @@ module Lutaml
57
56
  def handle_instance_template(value, result)
58
57
  result[:template] = process_attributes(value[:attributes])
59
58
  end
59
+
60
+ private
61
+
62
+ def append_result(acc, key, result)
63
+ case key
64
+ when :instance then acc[:instances] << result
65
+ when :collections then (acc[:collections] ||= []) << result
66
+ else (acc[key] ||= []).concat(result)
67
+ end
68
+ end
60
69
  end
61
70
  end
62
71
  end
@@ -30,8 +30,8 @@ module Lutaml
30
30
  # Evaluate all validation conditions against a collection of instances.
31
31
  # Returns an array of error strings (empty if all pass).
32
32
  def self.evaluate(collection, instances)
33
- return [] unless collection.validations&.any?
34
33
  return [] unless collection.is_a?(Collection)
34
+ return [] unless collection.validations&.any?
35
35
 
36
36
  new(instances).evaluate_all(collection.validations)
37
37
  end
@@ -73,10 +73,9 @@ module Lutaml
73
73
  def validate_collections(doc, instances)
74
74
  return [] unless doc.instances&.collections
75
75
 
76
- collection = doc.instances.collections
77
- return [] unless collection.is_a?(Collection)
78
-
79
- ConditionEvaluator.evaluate(collection, instances)
76
+ Array(doc.instances.collections).flat_map do |collection|
77
+ ConditionEvaluator.evaluate(collection, instances)
78
+ end
80
79
  end
81
80
 
82
81
  # --- Export ---
@@ -19,12 +19,18 @@ module Lutaml
19
19
  rule(:whitespace?) { whitespace.maybe }
20
20
  rule(:newline) { match('[\r\n]') }
21
21
 
22
- rule(:quoted_string_content) do
23
- (str('"').absent? >> any).repeat
22
+ # Body of a string delimited by `char`, captured as `label`.
23
+ def quoted(char, label)
24
+ str(char) >> (str(char).absent? >> any).repeat.as(label) >> str(char)
24
25
  end
25
- rule(:quoted_string) do
26
- str('"') >> quoted_string_content.as(:string) >> str('"')
26
+
27
+ # A string in either quote style. Delimiters must match, so `"a'`
28
+ # stays a parse error.
29
+ def any_quoted(label)
30
+ quoted('"', label) | quoted("'", label)
27
31
  end
32
+
33
+ rule(:quoted_string) { any_quoted(:string) }
28
34
  rule(:boolean) { (str("true") | str("false")).as(:boolean) }
29
35
  rule(:number) { (match("[0-9]").repeat(1) >> str(".") >> match("[0-9]").repeat(1)).as(:float) | match("[0-9]").repeat(1).as(:number) }
30
36
  rule(:variable) { (quoted_string | match("[a-zA-Z0-9_]").repeat(1)) }
@@ -22,9 +22,7 @@ module Lutaml
22
22
  end
23
23
 
24
24
  rule(:view_import) do
25
- kw_view_import >>
26
- str('"') >> quoted_string_content.as(:path) >> str('"') >>
27
- whitespace?
25
+ kw_view_import >> any_quoted(:path) >> whitespace?
28
26
  end
29
27
 
30
28
  # Entity names in show/hide lists must be space-free: general
@@ -0,0 +1,32 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "lutaml/model"
4
+
5
+ module Lutaml
6
+ module Lml
7
+ # The type of an LML attribute literal: a scalar, a list, or a map.
8
+ #
9
+ # This exists as a subclass rather than using Lutaml::Model::Type::Value
10
+ # directly because lutaml-model's key-value serializer tests the declared
11
+ # attribute type with a strict `attribute_type < Type::Value`. Type::Value
12
+ # is not a strict subclass of itself, so declaring it hands the serializer
13
+ # the raw wrapper object: JSON calls to_json(state) on it and raises
14
+ # ArgumentError, and Psych emits a !ruby/object tag that from_yaml then
15
+ # refuses to load. A subclass puts the attribute on the working path, where
16
+ # the serializer asks the wrapper for its value instead.
17
+ #
18
+ # Deleting this class needs two upstream changes, not one: that `<` widened
19
+ # to `<=`, and the serializer no longer re-wrapping a value it already
20
+ # wrapped. Widening `<` alone still leaves the nested wrapper below.
21
+ class LiteralValue < Lutaml::Model::Type::Value
22
+ # The serializer re-wraps an already-wrapped value before asking it for a
23
+ # format, so #value has to see through that extra layer. Every to_<format>
24
+ # method lutaml-model generates reads through here, which is why this is
25
+ # one override rather than one per format.
26
+ def value
27
+ wrapped = super
28
+ wrapped.is_a?(LiteralValue) ? wrapped.value : wrapped
29
+ end
30
+ end
31
+ end
32
+ end
@@ -6,7 +6,7 @@ module Lutaml
6
6
  attribute :instances, "Lutaml::Lml::Instance", collection: true, default: []
7
7
  attribute :imports, "Lutaml::Lml::InstancesImport", collection: true, default: []
8
8
  attribute :exports, "Lutaml::Lml::InstancesExport", collection: true, default: []
9
- attribute :collections, "Lutaml::Lml::Collection", default: []
9
+ attribute :collections, "Lutaml::Lml::Collection", collection: true, default: []
10
10
  end
11
11
  end
12
12
  end
@@ -22,10 +22,73 @@ module Lutaml
22
22
 
23
23
  # LML-specific attributes
24
24
  attribute :properties, "Lutaml::Lml::TopElementAttribute", collection: true, default: -> { [] }
25
- attribute :value, "Lutaml::Lml::TopElementAttribute", collection: true, default: -> { [] }
25
+ attribute :value, LiteralValue
26
26
  attribute :attributes, "Lutaml::Lml::TopElementAttribute", collection: true, default: -> { [] }
27
27
  attribute :extended, :boolean
28
28
  attribute :instances, "Lutaml::Lml::Instance", collection: true, default: -> { [] }
29
+
30
+ # Declared in full because a mapping block replaces the defaults. The
31
+ # rules below are the defaults, 1:1, except for `value`.
32
+ #
33
+ # `value` needs custom methods because a list literal reaches the model
34
+ # as a bare Array, and lutaml-model refuses one on a non-collection
35
+ # attribute: Attribute#cast splits it, then valid_collection! raises
36
+ # CollectionTrueMissingError. A custom `from` returns before that check,
37
+ # so a non-empty list survives a round trip without changing the
38
+ # serialized form. An empty list still does not: the transform skips a
39
+ # blank value before reaching the custom method, so `[]` reloads as nil.
40
+ key_value do
41
+ map "name", to: :name
42
+ map "visibility", to: :visibility
43
+ map "type", to: :type
44
+ map "id", to: :id
45
+ map "contain", to: :contain
46
+ map "static", to: :static
47
+ map "cardinality", to: :cardinality
48
+ map "keyword", to: :keyword
49
+ map "is_derived", to: :is_derived
50
+ map "is_static", to: :is_static
51
+ map "is_read_only", to: :is_read_only
52
+ map "stereotype", to: :stereotype
53
+ map "definition", to: :definition
54
+ map "association", to: :association
55
+ map "default", to: :default
56
+ map "properties", to: :properties
57
+ map "value", to: :value, with: { to: :value_to, from: :value_from }
58
+ map "attributes", to: :attributes
59
+ map "extended", to: :extended
60
+ map "instances", to: :instances
61
+ end
62
+
63
+ # Public because lutaml-model invokes mapping methods via public_send.
64
+ #
65
+ # Skipping nil keeps the key out of the output, which is what the default
66
+ # mapping does. `false` is a real literal, so the guard tests for nil
67
+ # rather than truthiness.
68
+ def value_to(model, doc)
69
+ literal = model.value
70
+ return if literal.nil?
71
+
72
+ doc["value"] = unwrap_literal(literal)
73
+ end
74
+
75
+ # lutaml-model runs this on a throwaway mapper instance, so `self` is not
76
+ # the object being deserialized - `model` is. Writing to `self.value`
77
+ # here would update an object that is discarded a moment later.
78
+ def value_from(model, value)
79
+ model.value = value
80
+ end
81
+
82
+ private
83
+
84
+ # A LiteralValue sits at the top level for a map literal, and inside the
85
+ # array for a list of references. Writing one through unwrapped raises
86
+ # ArgumentError in the adapter.
87
+ def unwrap_literal(value)
88
+ return value.map { |item| unwrap_literal(item) } if value.is_a?(::Array)
89
+
90
+ value.is_a?(LiteralValue) ? value.value : value
91
+ end
29
92
  end
30
93
  end
31
94
  end
@@ -2,6 +2,6 @@
2
2
 
3
3
  module Lutaml
4
4
  module Lml
5
- VERSION = "0.1.3"
5
+ VERSION = "0.1.4"
6
6
  end
7
7
  end
data/lib/lutaml/lml.rb CHANGED
@@ -52,6 +52,9 @@ module Lutaml
52
52
  autoload :HasAttributes, "lutaml/lml/has_attributes"
53
53
  autoload :VERSION, "lutaml/lml/version"
54
54
 
55
+ # Type classes (Lutaml::Model::Type::Value subclasses, not models)
56
+ autoload :LiteralValue, "lutaml/lml/literal_value"
57
+
55
58
  # Model classes (in Lutaml::Lml namespace, files in models/ directory)
56
59
  autoload :Action, "lutaml/lml/models/action"
57
60
  autoload :Association, "lutaml/lml/models/association"
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: lutaml-lml
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.3
4
+ version: 0.1.4
5
5
  platform: ruby
6
6
  authors:
7
7
  - Ribose Inc.
@@ -123,6 +123,7 @@ files:
123
123
  - lib/lutaml/lml/layout.rb
124
124
  - lib/lutaml/lml/layout/engine.rb
125
125
  - lib/lutaml/lml/layout/graph_viz_engine.rb
126
+ - lib/lutaml/lml/literal_value.rb
126
127
  - lib/lutaml/lml/model_compiler.rb
127
128
  - lib/lutaml/lml/models/action.rb
128
129
  - lib/lutaml/lml/models/association.rb