lutaml-lml 0.1.7 → 0.2.0
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 +4 -4
- data/lib/lutaml/lml/cli.rb +12 -3
- data/lib/lutaml/lml/data_processor/attribute_processing.rb +49 -9
- data/lib/lutaml/lml/data_processor/instance_processing.rb +5 -0
- data/lib/lutaml/lml/data_processor/value_declaration_processing.rb +62 -0
- data/lib/lutaml/lml/data_processor.rb +3 -0
- data/lib/lutaml/lml/document_builder.rb +235 -9
- data/lib/lutaml/lml/grammar/lml.artifact.json +6473 -2425
- data/lib/lutaml/lml/grammar/lml.parg +82 -19
- data/lib/lutaml/lml/model_compiler.rb +392 -39
- data/lib/lutaml/lml/models/data_type.rb +3 -0
- data/lib/lutaml/lml/models/derived_field.rb +14 -0
- data/lib/lutaml/lml/models/document.rb +2 -0
- data/lib/lutaml/lml/models/enum.rb +3 -0
- data/lib/lutaml/lml/models/instance.rb +2 -0
- data/lib/lutaml/lml/models/instance_collection.rb +1 -0
- data/lib/lutaml/lml/models/mapping.rb +15 -0
- data/lib/lutaml/lml/models/mapping_rule.rb +16 -0
- data/lib/lutaml/lml/models/member_field.rb +12 -0
- data/lib/lutaml/lml/models/namespace.rb +14 -0
- data/lib/lutaml/lml/models/package.rb +1 -0
- data/lib/lutaml/lml/models/serialization_mapping.rb +17 -0
- data/lib/lutaml/lml/models/string_format.rb +16 -0
- data/lib/lutaml/lml/models/top_element_attribute.rb +4 -0
- data/lib/lutaml/lml/models/uml_class.rb +4 -0
- data/lib/lutaml/lml/preprocessor.rb +82 -6
- data/lib/lutaml/lml/validator.rb +276 -0
- data/lib/lutaml/lml/version.rb +1 -1
- data/lib/lutaml/lml.rb +8 -0
- metadata +11 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 8bdefb219dd99a2335669d57b9e7a79a21d822485ebc1a3748d915dee88d2901
|
|
4
|
+
data.tar.gz: a63770177d2c8ff0b85e0d30caa6c9033529c75eee9738fbe59b25dc7c1d30eb
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 3e12f920e7b614aa4902cd953e311fdde4dae2cd93a15bd9c5fe10b1c0ca234412a8228ca6cf2f0d22956ddb67cfd681127dedac417d43cd9b0c5b962e3e777a
|
|
7
|
+
data.tar.gz: c7a727c6d617731e160d1ad6bebbba053cd6216a10c93c2d177f391881f0405f0304b3bd7a28230c18cd85cddb3383ff7639a348b36489f142d1e6c6e052ec76
|
data/lib/lutaml/lml/cli.rb
CHANGED
|
@@ -186,13 +186,22 @@ module Lutaml
|
|
|
186
186
|
input_path = Pathname.new(path_string)
|
|
187
187
|
return report_file_error(input_path, 'File does not exist') unless input_path.exist?
|
|
188
188
|
|
|
189
|
-
parse_document(input_path)
|
|
190
|
-
|
|
191
|
-
|
|
189
|
+
document = parse_document(input_path)
|
|
190
|
+
violations = Lutaml::Lml::Validator.violations(document)
|
|
191
|
+
if violations.any?
|
|
192
|
+
report_file_error(input_path, violations_message(violations))
|
|
193
|
+
else
|
|
194
|
+
say "✓ #{input_path}", :green
|
|
195
|
+
nil
|
|
196
|
+
end
|
|
192
197
|
rescue StandardError => e
|
|
193
198
|
report_file_error(input_path, e.message)
|
|
194
199
|
end
|
|
195
200
|
|
|
201
|
+
def violations_message(violations)
|
|
202
|
+
violations.map { |v| "[#{v.rule}] #{v.message}" }.join('; ')
|
|
203
|
+
end
|
|
204
|
+
|
|
196
205
|
def report_file_error(input_path, message)
|
|
197
206
|
say "✗ #{input_path}: #{message}", :red
|
|
198
207
|
"#{input_path}: #{message}"
|
|
@@ -15,18 +15,52 @@ module Lutaml
|
|
|
15
15
|
|
|
16
16
|
def process_attributes_array(obj)
|
|
17
17
|
return [] if obj.empty?
|
|
18
|
-
return obj.map { |item| process_attributes(item) } unless
|
|
18
|
+
return obj.map { |item| process_attributes(item) } unless named_attribute_entries?(obj)
|
|
19
|
+
return merge_named_entries(obj) unless single_key_hashes?(obj)
|
|
19
20
|
|
|
21
|
+
obj.each_with_object({}) { |item, hash| append_uniform_entry(hash, item) }
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def append_uniform_entry(hash, item)
|
|
25
|
+
hash[:properties] ||= []
|
|
26
|
+
if item.key?(:properties)
|
|
27
|
+
hash[:properties] << process_attributes(item[:properties])
|
|
28
|
+
else
|
|
29
|
+
hash.merge!(process_attributes(item))
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
# Attribute-definition groups split across capture depths (e.g. the
|
|
34
|
+
# comma form: {name, type} beside a nested {value_set}) combine into
|
|
35
|
+
# one attribute entry. Key/value pair lists (import/export maps)
|
|
36
|
+
# carry :key and are never merged.
|
|
37
|
+
def named_attribute_entries?(obj)
|
|
38
|
+
obj.any? { |e| e.is_a?(Hash) && e.key?(:name) } &&
|
|
39
|
+
obj.all? { |e| e.is_a?(Hash) && !e.key?(:key) }
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
COLLECTION_KEYS = %i[properties attributes instances].freeze
|
|
43
|
+
|
|
44
|
+
def merge_named_entries(obj)
|
|
20
45
|
obj.each_with_object({}) do |item, hash|
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
hash[:properties] << process_attributes(item[:properties])
|
|
24
|
-
else
|
|
25
|
-
hash.merge!(process_attributes(item))
|
|
46
|
+
process_attributes(item).each do |key, value|
|
|
47
|
+
hash[key] = merge_attribute_value(hash[key], key, value)
|
|
26
48
|
end
|
|
27
49
|
end
|
|
28
50
|
end
|
|
29
51
|
|
|
52
|
+
def merge_attribute_value(existing, key, value)
|
|
53
|
+
return merge_collection_value(existing, value) if COLLECTION_KEYS.include?(key)
|
|
54
|
+
return existing + value if existing.is_a?(Array) && value.is_a?(Array)
|
|
55
|
+
|
|
56
|
+
value
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
def merge_collection_value(existing, value)
|
|
60
|
+
current = existing.is_a?(Array) ? existing : (existing ? [existing] : [])
|
|
61
|
+
current + (value.is_a?(Array) ? value : [value])
|
|
62
|
+
end
|
|
63
|
+
|
|
30
64
|
def process_attributes_hash(obj)
|
|
31
65
|
result = extract_name_and_value(obj)
|
|
32
66
|
convert_instance_type(result)
|
|
@@ -34,18 +68,17 @@ module Lutaml
|
|
|
34
68
|
result[:literal] = true if obj.key?(:type_literal)
|
|
35
69
|
apply_nested_attributes(result, obj)
|
|
36
70
|
apply_remaining_keys(result, obj)
|
|
71
|
+
translate_values_argument(result)
|
|
37
72
|
result
|
|
38
73
|
end
|
|
39
74
|
|
|
40
|
-
private
|
|
41
|
-
|
|
42
75
|
def single_key_hashes?(arr)
|
|
43
76
|
arr.all? { |e| e.is_a?(Hash) && e.keys.size == 1 }
|
|
44
77
|
end
|
|
45
78
|
|
|
46
79
|
def extract_name_and_value(obj)
|
|
47
80
|
result = {}
|
|
48
|
-
result[:name] = obj[:key] if obj.key?(:key)
|
|
81
|
+
result[:name] = strip_argument_colon(obj[:key]) if obj.key?(:key)
|
|
49
82
|
|
|
50
83
|
if obj.key?(:comments)
|
|
51
84
|
result[:name] = 'Comment'
|
|
@@ -74,9 +107,16 @@ module Lutaml
|
|
|
74
107
|
obj.each do |key, value|
|
|
75
108
|
next if EXCLUDED_PASS_THROUGH_KEYS.include?(key)
|
|
76
109
|
|
|
110
|
+
value = strip_argument_colon(value) if key == :name
|
|
77
111
|
result[key] = value unless result.key?(key)
|
|
78
112
|
end
|
|
79
113
|
end
|
|
114
|
+
|
|
115
|
+
# `default: "Clear"` uses the colon separator (RS 3001 §Attribute);
|
|
116
|
+
# the colon lands in the captured name and must not.
|
|
117
|
+
def strip_argument_colon(name)
|
|
118
|
+
name.to_s.sub(/:\z/, "")
|
|
119
|
+
end
|
|
80
120
|
end
|
|
81
121
|
end
|
|
82
122
|
end
|
|
@@ -13,6 +13,7 @@ module Lutaml
|
|
|
13
13
|
|
|
14
14
|
INSTANCE_FIELD_HANDLERS = {
|
|
15
15
|
instance_type: :handle_instance_type,
|
|
16
|
+
isa: :handle_instance_isa,
|
|
16
17
|
instance: :handle_instance_nested,
|
|
17
18
|
attributes: :handle_instance_attributes,
|
|
18
19
|
template: :handle_instance_template
|
|
@@ -45,6 +46,10 @@ module Lutaml
|
|
|
45
46
|
result[:type] = process_value(value).last
|
|
46
47
|
end
|
|
47
48
|
|
|
49
|
+
def handle_instance_isa(value, result)
|
|
50
|
+
result[:isa] = process_value(value).last
|
|
51
|
+
end
|
|
52
|
+
|
|
48
53
|
def handle_instance_nested(value, result)
|
|
49
54
|
result[:instance] = process_instance(value)
|
|
50
55
|
end
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Lutaml
|
|
4
|
+
module Lml
|
|
5
|
+
class DataProcessor
|
|
6
|
+
# RS 3001 §Value/§Attribute argument normalization: the canonical
|
|
7
|
+
# `value` construct and the attribute `values` argument.
|
|
8
|
+
module ValueDeclarationProcessing
|
|
9
|
+
# `value <name> { block }` is the canonical enum member; the bare
|
|
10
|
+
# identifier member is its documented shortcut. Normalize into the
|
|
11
|
+
# member shape the shortcut produces: the value block's `definition`
|
|
12
|
+
# lines become the member description and its attribute pairs become
|
|
13
|
+
# member payload properties.
|
|
14
|
+
def process_value_declaration(decl)
|
|
15
|
+
inner = { name: unwrap_string(decl[:value_name]) }
|
|
16
|
+
Array(decl[:members]).each do |member|
|
|
17
|
+
normalize_value_member(inner, member)
|
|
18
|
+
end
|
|
19
|
+
{ attributes: inner }
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
# `values <enum>` types the attribute with the named enum;
|
|
23
|
+
# `values { v1, v2 }` carries an inline value set that the compiler
|
|
24
|
+
# expands into an anonymous enum.
|
|
25
|
+
def translate_values_argument(result)
|
|
26
|
+
if result.key?(:value_set_ref)
|
|
27
|
+
ref = unwrap_string(result.delete(:value_set_ref))
|
|
28
|
+
result[:type] = ref unless result[:type]
|
|
29
|
+
end
|
|
30
|
+
return unless result.key?(:value_set)
|
|
31
|
+
|
|
32
|
+
result[:value_set] = Array(result[:value_set][:items]).map { |item| unwrap_data_value(item[:item]) }
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
private
|
|
36
|
+
|
|
37
|
+
def normalize_value_member(inner, member)
|
|
38
|
+
if member.key?(:definition)
|
|
39
|
+
(inner[:attributes] ||= []) << { name: 'description', type: unwrap_string(member[:definition]) }
|
|
40
|
+
elsif member.key?(:instance)
|
|
41
|
+
(inner[:instances] ||= []).concat(Array(member[:instance]))
|
|
42
|
+
elsif member.key?(:attributes)
|
|
43
|
+
(inner[:properties] ||= []) << member[:attributes]
|
|
44
|
+
elsif member.key?(:key)
|
|
45
|
+
(inner[:properties] ||= []) << extract_name_and_value(member)
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def unwrap_data_value(item)
|
|
50
|
+
return item unless item.is_a?(Hash)
|
|
51
|
+
|
|
52
|
+
item[:string] || item[:number] || item[:float] ||
|
|
53
|
+
item[:boolean] || item[:name] || item
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
def unwrap_string(value)
|
|
57
|
+
value.is_a?(Hash) && value.key?(:string) ? value[:string] : value
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
end
|
|
@@ -5,12 +5,14 @@ module Lutaml
|
|
|
5
5
|
class DataProcessor
|
|
6
6
|
autoload :ValueProcessing, "lutaml/lml/data_processor/value_processing"
|
|
7
7
|
autoload :AttributeProcessing, "lutaml/lml/data_processor/attribute_processing"
|
|
8
|
+
autoload :ValueDeclarationProcessing, "lutaml/lml/data_processor/value_declaration_processing"
|
|
8
9
|
autoload :InstanceProcessing, "lutaml/lml/data_processor/instance_processing"
|
|
9
10
|
autoload :CollectionProcessing, "lutaml/lml/data_processor/collection_processing"
|
|
10
11
|
autoload :ViewProcessing, "lutaml/lml/data_processor/view_processing"
|
|
11
12
|
|
|
12
13
|
include ValueProcessing
|
|
13
14
|
include AttributeProcessing
|
|
15
|
+
include ValueDeclarationProcessing
|
|
14
16
|
include InstanceProcessing
|
|
15
17
|
include CollectionProcessing
|
|
16
18
|
include ViewProcessing
|
|
@@ -20,6 +22,7 @@ module Lutaml
|
|
|
20
22
|
instances: :process_instances,
|
|
21
23
|
instance: :process_instance,
|
|
22
24
|
attributes: :process_attributes,
|
|
25
|
+
value_decl: :process_value_declaration,
|
|
23
26
|
show_list: :process_show_list,
|
|
24
27
|
hide_list: :process_hide_list
|
|
25
28
|
}.freeze
|
|
@@ -20,7 +20,13 @@ module Lutaml
|
|
|
20
20
|
constraint: ::Lutaml::Lml::Constraint,
|
|
21
21
|
value: ::Lutaml::Lml::Value,
|
|
22
22
|
view_import: ::Lutaml::Lml::ViewImport,
|
|
23
|
-
view_filter: ::Lutaml::Lml::ViewFilter
|
|
23
|
+
view_filter: ::Lutaml::Lml::ViewFilter,
|
|
24
|
+
mapping: ::Lutaml::Lml::Mapping,
|
|
25
|
+
string_format: ::Lutaml::Lml::StringFormat,
|
|
26
|
+
derived_field: ::Lutaml::Lml::DerivedField,
|
|
27
|
+
member_field: ::Lutaml::Lml::MemberField,
|
|
28
|
+
namespace: ::Lutaml::Lml::Namespace,
|
|
29
|
+
serialization_mapping: ::Lutaml::Lml::SerializationMapping
|
|
24
30
|
}.freeze
|
|
25
31
|
|
|
26
32
|
attr_reader :registry
|
|
@@ -31,7 +37,8 @@ module Lutaml
|
|
|
31
37
|
|
|
32
38
|
FACTORY_KEYS = %i[
|
|
33
39
|
document package class enum data_type diagram view_import view_filter
|
|
34
|
-
attribute association operation constraint value cardinality
|
|
40
|
+
attribute association operation constraint value cardinality mapping
|
|
41
|
+
string_format derived_field namespace serialization_mapping
|
|
35
42
|
].freeze
|
|
36
43
|
|
|
37
44
|
MEMBER_KEY_MAP = {
|
|
@@ -45,7 +52,11 @@ module Lutaml
|
|
|
45
52
|
associations: :association,
|
|
46
53
|
operations: :operation,
|
|
47
54
|
constraints: :constraint,
|
|
48
|
-
values: :value
|
|
55
|
+
values: :value,
|
|
56
|
+
mappings: :mapping,
|
|
57
|
+
member_fields: :member_field,
|
|
58
|
+
namespaces: :namespace,
|
|
59
|
+
serialization_mappings: :serialization_mapping
|
|
49
60
|
}.freeze
|
|
50
61
|
|
|
51
62
|
FACTORY_KEYS.each do |key|
|
|
@@ -95,6 +106,11 @@ module Lutaml
|
|
|
95
106
|
end
|
|
96
107
|
|
|
97
108
|
def add_members(model, hash)
|
|
109
|
+
expand_declared_members(model, hash)
|
|
110
|
+
normalize_namespace_declaration(hash)
|
|
111
|
+
normalize_serialization_mapping(hash)
|
|
112
|
+
expand_instances_collection_name(hash)
|
|
113
|
+
expand_nested_members(model, hash)
|
|
98
114
|
MEMBER_KEY_MAP.each do |plural_key, singular_key|
|
|
99
115
|
data = hash.delete(plural_key)
|
|
100
116
|
next if data.nil?
|
|
@@ -106,6 +122,218 @@ module Lutaml
|
|
|
106
122
|
remap_filter_keys(hash, model)
|
|
107
123
|
end
|
|
108
124
|
|
|
125
|
+
# RS 3001 §Enums: an enum-level `values { v1, v2 }` shorthand
|
|
126
|
+
# expands into plain members.
|
|
127
|
+
def expand_value_set_declaration(model, hash)
|
|
128
|
+
decl = hash.delete(:value_set_decl) or return
|
|
129
|
+
Array(decl[:items]).each do |item|
|
|
130
|
+
name = unquote(item[:item])
|
|
131
|
+
model.attributes << TopElementAttribute.new(name: name)
|
|
132
|
+
end
|
|
133
|
+
end
|
|
134
|
+
|
|
135
|
+
# RS 3001 §Value: the canonical `value` construct normalizes into
|
|
136
|
+
# the same member shape the bare-identifier shortcut produces.
|
|
137
|
+
def expand_value_declaration(model, hash)
|
|
138
|
+
decl = hash.delete(:value_decl) or return
|
|
139
|
+
apply_attribute(model, :attributes, decl.fetch(:attributes))
|
|
140
|
+
end
|
|
141
|
+
|
|
142
|
+
# Namespace declaration bodies arrive as keyword-line members
|
|
143
|
+
# ({uri: …}, {prefix: …}); flatten them onto the namespace hash.
|
|
144
|
+
def normalize_namespace_declaration(hash)
|
|
145
|
+
ns = hash[:namespaces] or return
|
|
146
|
+
Array(ns.delete(:members)).each do |pair|
|
|
147
|
+
pair.each { |key, value| ns[key] = unquote(value) }
|
|
148
|
+
end
|
|
149
|
+
end
|
|
150
|
+
|
|
151
|
+
# Serialization mapping rules arrive keyed by their line construct
|
|
152
|
+
# ({map_element: {wire:, field:}}); normalize into plain MappingRule
|
|
153
|
+
# hashes so the model cast lands typed rules.
|
|
154
|
+
def normalize_serialization_mapping(hash)
|
|
155
|
+
mapping = hash[:serialization_mappings] or return
|
|
156
|
+
rules = Array(mapping[:rules])
|
|
157
|
+
return if rules.any? && rules.all? { |e| e.is_a?(Hash) && e.key?(:kind) }
|
|
158
|
+
|
|
159
|
+
rules = rules.filter_map do |entry|
|
|
160
|
+
next nil unless entry.is_a?(Hash)
|
|
161
|
+
|
|
162
|
+
if entry[:wire] && entry[:to]
|
|
163
|
+
next { kind: 'map', wire: unquote(entry[:wire]), field: unquote(entry[:to]) }
|
|
164
|
+
end
|
|
165
|
+
|
|
166
|
+
first_key = entry.keys.first
|
|
167
|
+
kind = first_key.to_s
|
|
168
|
+
body = entry[first_key] || {}
|
|
169
|
+
unless body.is_a?(Hash)
|
|
170
|
+
kind_n = normalize_mapping_kind(kind)
|
|
171
|
+
next nil if body.to_s.empty?
|
|
172
|
+
|
|
173
|
+
term = unquote(body)
|
|
174
|
+
next { kind: kind_n, wire: term, field: term } if %w[element attribute map].include?(kind_n)
|
|
175
|
+
if kind_n == 'namespace'
|
|
176
|
+
mapping[:namespace_ref] = term
|
|
177
|
+
next nil
|
|
178
|
+
end
|
|
179
|
+
|
|
180
|
+
next { kind: kind_n, field: term }
|
|
181
|
+
end
|
|
182
|
+
if kind == 'element_name'
|
|
183
|
+
mapping[:element_name] = unquote(body[:element_name])
|
|
184
|
+
next nil
|
|
185
|
+
end
|
|
186
|
+
if kind == 'map_namespace'
|
|
187
|
+
mapping[:namespace_ref] = unquote(body[:namespace_ref])
|
|
188
|
+
next nil
|
|
189
|
+
end
|
|
190
|
+
normalized = { kind: normalize_mapping_kind(kind) }
|
|
191
|
+
wire = body[:wire] || body[:element_wire]
|
|
192
|
+
normalized[:wire] = unquote(wire) if wire
|
|
193
|
+
field = body[:field] || body[:to_field] || body[:element_field] ||
|
|
194
|
+
body[:attribute_field] || body[:to]
|
|
195
|
+
normalized[:field] = unquote(field) if field
|
|
196
|
+
normalized[:mode] = body[:mode] if body[:mode]
|
|
197
|
+
normalized[:namespace_ref] = unquote(body[:namespace_ref]) if body[:namespace_ref]
|
|
198
|
+
normalized
|
|
199
|
+
end
|
|
200
|
+
mapping[:rules] = rules
|
|
201
|
+
end
|
|
202
|
+
|
|
203
|
+
def normalize_mapping_kind(kind)
|
|
204
|
+
{
|
|
205
|
+
'map_element' => 'element', 'map_attribute' => 'attribute',
|
|
206
|
+
'map_content' => 'content', 'map_namespace' => 'namespace',
|
|
207
|
+
'map_key' => 'key', 'map_value' => 'value', 'map' => 'map',
|
|
208
|
+
'legacy_map' => 'map'
|
|
209
|
+
}.fetch(kind, kind)
|
|
210
|
+
end
|
|
211
|
+
|
|
212
|
+
# A named instances collection (`instances "Glazes" { ... }`) arrives
|
|
213
|
+
# with its name as a sibling of the collection body; move it inside
|
|
214
|
+
# so the InstanceCollection carries it.
|
|
215
|
+
def expand_instances_collection_name(hash)
|
|
216
|
+
return unless hash.key?(:instances) && hash.key?(:name)
|
|
217
|
+
|
|
218
|
+
name = unquote(hash.delete(:name))
|
|
219
|
+
body = hash[:instances].is_a?(Hash) ? hash[:instances] : {}
|
|
220
|
+
body[:name] = name unless body.key?(:name)
|
|
221
|
+
end
|
|
222
|
+
|
|
223
|
+
# The document root is a repetition of top-level constructs (RS 3001:
|
|
224
|
+
# documents interleave declarations), so models/packages blocks nest
|
|
225
|
+
# their members one level down; recurse into them.
|
|
226
|
+
def expand_nested_members(model, hash)
|
|
227
|
+
return unless hash.key?(:members)
|
|
228
|
+
|
|
229
|
+
members = hash.delete(:members)
|
|
230
|
+
members.to_a.each do |nested|
|
|
231
|
+
add_members(model, nested)
|
|
232
|
+
set_model_attributes(model, nested)
|
|
233
|
+
end
|
|
234
|
+
end
|
|
235
|
+
|
|
236
|
+
# Declaration-level member hooks: single-line definitions, canonical
|
|
237
|
+
# `value` members, mapping sections, class declarations, enum
|
|
238
|
+
# member_fields and from_table all arrive as bare member hashes.
|
|
239
|
+
def expand_declared_members(model, hash)
|
|
240
|
+
expand_definition_text(model, hash)
|
|
241
|
+
expand_value_declaration(model, hash)
|
|
242
|
+
expand_value_set_declaration(model, hash)
|
|
243
|
+
expand_mapping_section(model, hash)
|
|
244
|
+
expand_class_declarations(model, hash)
|
|
245
|
+
expand_enum_member_fields(model, hash)
|
|
246
|
+
expand_from_table(model, hash)
|
|
247
|
+
end
|
|
248
|
+
|
|
249
|
+
# RS 3001 §Definition single-line form (`definition "text"`) arrives
|
|
250
|
+
# as a quoted-string capture; unwrap it for the entity's definition.
|
|
251
|
+
def expand_definition_text(model, hash)
|
|
252
|
+
return unless hash.key?(:definition) && model.class.attributes.key?(:definition)
|
|
253
|
+
|
|
254
|
+
model.definition = unquote(hash.delete(:definition))
|
|
255
|
+
end
|
|
256
|
+
|
|
257
|
+
# A mapping section arrives as a bare member hash
|
|
258
|
+
# { format:, maps: [{ wire:, to: }] }; expand to one Mapping entry
|
|
259
|
+
# per map line, each carrying the format, and strip the section keys
|
|
260
|
+
def expand_mapping_section(model, hash)
|
|
261
|
+
section = mapping_section_from(hash)
|
|
262
|
+
return unless section
|
|
263
|
+
|
|
264
|
+
section[:maps].to_a.each do |line|
|
|
265
|
+
model.wire_mappings << build(:mapping,
|
|
266
|
+
format: section[:format],
|
|
267
|
+
wire: unquote(line[:wire]),
|
|
268
|
+
to: unquote(line[:to]))
|
|
269
|
+
end
|
|
270
|
+
end
|
|
271
|
+
|
|
272
|
+
def mapping_section_from(hash)
|
|
273
|
+
return { format: unquote(hash[:format]), maps: hash.delete(:maps) } if hash.key?(:maps)
|
|
274
|
+
|
|
275
|
+
hash.delete(:mappings)
|
|
276
|
+
end
|
|
277
|
+
|
|
278
|
+
# quoted_string captures nest their value under :string
|
|
279
|
+
def unquote(value)
|
|
280
|
+
value.is_a?(Hash) && value.key?(:string) ? value[:string] : value
|
|
281
|
+
end
|
|
282
|
+
|
|
283
|
+
# L5/L7 declaration members: string_format sections (class- or
|
|
284
|
+
# models-level), derive lines, and enum member_fields blocks
|
|
285
|
+
def expand_class_declarations(model, hash)
|
|
286
|
+
if (section = hash.delete(:string_format))
|
|
287
|
+
model.string_formats << build_string_format(section)
|
|
288
|
+
end
|
|
289
|
+
if (section = hash.delete(:default_string_formats))
|
|
290
|
+
model.default_string_formats << build_string_format(section)
|
|
291
|
+
end
|
|
292
|
+
return unless (derived = hash.delete(:derived_field))
|
|
293
|
+
|
|
294
|
+
model.derived_fields << build(:derived_field,
|
|
295
|
+
name: derived[:name],
|
|
296
|
+
type: unquote(derived[:type]))
|
|
297
|
+
end
|
|
298
|
+
|
|
299
|
+
# L7a: from_table declares the enum's members as an artifact table
|
|
300
|
+
# { from_table_name:, from_table_artifact? } — expansion happens at
|
|
301
|
+
# compile time (deterministic materialization, no runtime data dep)
|
|
302
|
+
def expand_from_table(model, hash)
|
|
303
|
+
decl = hash.delete(:from_table_decl)
|
|
304
|
+
return unless decl && model.is_a?(Enum)
|
|
305
|
+
|
|
306
|
+
# the members repetition splits the declaration across iteration
|
|
307
|
+
# hashes (name part, artifact part) — merge the parts
|
|
308
|
+
parts = decl.is_a?(Array) ? decl : [decl]
|
|
309
|
+
decl = parts.each_with_object({}) do |part, acc|
|
|
310
|
+
acc.merge!(part) { |_key, _old, new_val| new_val }
|
|
311
|
+
end
|
|
312
|
+
model.from_table_name = unquote(decl[:from_table_name])
|
|
313
|
+
model.from_table_artifact = unquote(decl[:artifact_table]) if decl[:artifact_table]
|
|
314
|
+
end
|
|
315
|
+
|
|
316
|
+
def expand_enum_member_fields(model, hash)
|
|
317
|
+
decl = hash.delete(:member_fields_decl)
|
|
318
|
+
return unless decl && model.is_a?(Enum)
|
|
319
|
+
|
|
320
|
+
decl[:member_fields].to_a.each do |field|
|
|
321
|
+
model.member_fields << build(:member_field,
|
|
322
|
+
name: field[:name],
|
|
323
|
+
type: unquote(field[:member_field_type]))
|
|
324
|
+
end
|
|
325
|
+
end
|
|
326
|
+
|
|
327
|
+
def build_string_format(section)
|
|
328
|
+
items = section[:items].to_a.each_with_object({}) do |item, acc|
|
|
329
|
+
acc[item[:key].to_sym] = unquote(item[:value])
|
|
330
|
+
end
|
|
331
|
+
build(:string_format,
|
|
332
|
+
format: unquote(section[:format]),
|
|
333
|
+
artifact: items[:artifact],
|
|
334
|
+
root: items[:root])
|
|
335
|
+
end
|
|
336
|
+
|
|
109
337
|
def ensure_collection(model, key)
|
|
110
338
|
model.public_send(key) || begin
|
|
111
339
|
model.public_send("#{key}=", [])
|
|
@@ -116,12 +344,10 @@ module Lutaml
|
|
|
116
344
|
def remap_filter_keys(hash, model)
|
|
117
345
|
return unless model.is_a?(Document)
|
|
118
346
|
|
|
119
|
-
if hash.key?(:show_list)
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
hash[:hide_filter] = build_view_filter(hash.delete(:hide_list))
|
|
124
|
-
end
|
|
347
|
+
hash[:show_filter] = build_view_filter(hash.delete(:show_list)) if hash.key?(:show_list)
|
|
348
|
+
return unless hash.key?(:hide_list)
|
|
349
|
+
|
|
350
|
+
hash[:hide_filter] = build_view_filter(hash.delete(:hide_list))
|
|
125
351
|
end
|
|
126
352
|
|
|
127
353
|
def build_view_filter(entity_names)
|