lutaml-lml 0.2.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 +4 -4
- data/lib/lutaml/lml/cli.rb +8 -1
- data/lib/lutaml/lml/document_builder.rb +72 -3
- data/lib/lutaml/lml/format/adapter/standard_adapter.rb +17 -4
- data/lib/lutaml/lml/formatter/elk.rb +42 -0
- data/lib/lutaml/lml/formatter/graphviz/node_formatter.rb +2 -1
- data/lib/lutaml/lml/formatter.rb +1 -0
- data/lib/lutaml/lml/grammar/lml.artifact.json +6982 -4538
- data/lib/lutaml/lml/grammar/lml.parg +209 -110
- data/lib/lutaml/lml/layout/elk_engine.rb +15 -0
- data/lib/lutaml/lml/layout/elk_graph_builder.rb +129 -0
- data/lib/lutaml/lml/layout/elk_svg_markers.rb +45 -0
- data/lib/lutaml/lml/layout/elk_svg_renderer.rb +119 -0
- data/lib/lutaml/lml/layout/graph_viz_engine.rb +1 -1
- data/lib/lutaml/lml/layout.rb +4 -0
- data/lib/lutaml/lml/model_compiler.rb +24 -3
- data/lib/lutaml/lml/models/document.rb +29 -0
- data/lib/lutaml/lml/models/operation_parameter.rb +1 -0
- data/lib/lutaml/lml/models/reference.rb +23 -0
- data/lib/lutaml/lml/models/top_element_attribute.rb +12 -0
- data/lib/lutaml/lml/models/uml_class.rb +1 -1
- data/lib/lutaml/lml/preprocessor.rb +44 -79
- data/lib/lutaml/lml/transform.rb +1 -1
- data/lib/lutaml/lml/validator.rb +86 -18
- data/lib/lutaml/lml/version.rb +1 -1
- data/lib/lutaml/lml.rb +1 -0
- metadata +10 -4
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 41662922310b1ec54e30485a3ec783c99f385f5e80eb0e4a98277b72ac5a71dc
|
|
4
|
+
data.tar.gz: 2b6030ded9c8bb82941ceefdf5e1df9a8531af5a61888faee4ecbc23df0ca961
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 94f5275ce72d247c2842a60b265bdc8980cf82b49ce45d1970b606a0a9329505776235efb2df3247165b40cd5aaccc977b9aadcf77eb4ad044119ec71e4ea0a7
|
|
7
|
+
data.tar.gz: bfe8561322f20e76736e00a6c67ab862207e344637097f270a1111c7a5edbc540f8137989858e8fe3584dbaded200fe150c35da83523a637d26523f9cc64f8e6
|
data/lib/lutaml/lml/cli.rb
CHANGED
|
@@ -37,7 +37,12 @@ module Lutaml
|
|
|
37
37
|
|
|
38
38
|
def initialize(*args)
|
|
39
39
|
super
|
|
40
|
-
@formatter =
|
|
40
|
+
@formatter = case options[:layout]
|
|
41
|
+
when 'elk'
|
|
42
|
+
::Lutaml::Formatter::Elk.new
|
|
43
|
+
else
|
|
44
|
+
::Lutaml::Formatter::Graphviz.new
|
|
45
|
+
end
|
|
41
46
|
@out_object = $stdout
|
|
42
47
|
end
|
|
43
48
|
|
|
@@ -71,6 +76,8 @@ module Lutaml
|
|
|
71
76
|
desc: 'Node attributes (key=value,key2=value2)'
|
|
72
77
|
method_option :all, type: :string, aliases: '-a',
|
|
73
78
|
desc: 'Set attributes for graph, edge, and node'
|
|
79
|
+
method_option :layout, type: :string, enum: %w[graphviz elk], default: 'graphviz',
|
|
80
|
+
desc: 'Layout engine (elk requires the elkrb gem)'
|
|
74
81
|
def generate(*paths)
|
|
75
82
|
assert_input_paths(paths)
|
|
76
83
|
|
|
@@ -72,6 +72,16 @@ module Lutaml
|
|
|
72
72
|
def set_model(model, hash)
|
|
73
73
|
hash = build_members(model, hash)
|
|
74
74
|
set_model_attributes(model, hash)
|
|
75
|
+
expand_class_specializer(model, hash)
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
# The prefix specializer (`abstract class Foo`) expands to the
|
|
79
|
+
# canonical `abstract true` declaration (RS 3001 par. Declaration
|
|
80
|
+
# modifiers); the class-level modifier capture is not a block member.
|
|
81
|
+
def expand_class_specializer(model, hash)
|
|
82
|
+
return unless hash[:modifier].to_s == 'abstract' && model.class.attributes.key?(:is_abstract)
|
|
83
|
+
|
|
84
|
+
model.is_abstract = true
|
|
75
85
|
end
|
|
76
86
|
|
|
77
87
|
def set_model_attributes(model, hash)
|
|
@@ -83,12 +93,24 @@ module Lutaml
|
|
|
83
93
|
def build_members(model, hash)
|
|
84
94
|
members = hash.delete(:members)
|
|
85
95
|
members.to_a.each do |member_hash|
|
|
96
|
+
record_model_version(model, member_hash)
|
|
86
97
|
add_members(model, member_hash)
|
|
87
98
|
set_model_attributes(model, member_hash)
|
|
88
99
|
end
|
|
89
100
|
hash
|
|
90
101
|
end
|
|
91
102
|
|
|
103
|
+
# A `models <Name> version "x.y.z"` header travels with the block's
|
|
104
|
+
# member hash; the block itself flattens into the document, so the
|
|
105
|
+
# version is recorded on the document before the name is dropped.
|
|
106
|
+
def record_model_version(model, hash)
|
|
107
|
+
return unless model.class.attributes.key?(:model_versions)
|
|
108
|
+
return unless hash.key?(:version)
|
|
109
|
+
|
|
110
|
+
name = hash[:name].to_s
|
|
111
|
+
model.model_versions[name] = unquote(hash.delete(:version)).to_s
|
|
112
|
+
end
|
|
113
|
+
|
|
92
114
|
def apply_attribute(model, key, value)
|
|
93
115
|
return unless model.class.attributes.key?(key.to_sym)
|
|
94
116
|
|
|
@@ -159,9 +181,7 @@ module Lutaml
|
|
|
159
181
|
rules = rules.filter_map do |entry|
|
|
160
182
|
next nil unless entry.is_a?(Hash)
|
|
161
183
|
|
|
162
|
-
if entry[:wire] && entry[:to]
|
|
163
|
-
next { kind: 'map', wire: unquote(entry[:wire]), field: unquote(entry[:to]) }
|
|
164
|
-
end
|
|
184
|
+
next { kind: 'map', wire: unquote(entry[:wire]), field: unquote(entry[:to]) } if entry[:wire] && entry[:to]
|
|
165
185
|
|
|
166
186
|
first_key = entry.keys.first
|
|
167
187
|
kind = first_key.to_s
|
|
@@ -172,6 +192,7 @@ module Lutaml
|
|
|
172
192
|
|
|
173
193
|
term = unquote(body)
|
|
174
194
|
next { kind: kind_n, wire: term, field: term } if %w[element attribute map].include?(kind_n)
|
|
195
|
+
|
|
175
196
|
if kind_n == 'namespace'
|
|
176
197
|
mapping[:namespace_ref] = term
|
|
177
198
|
next nil
|
|
@@ -237,6 +258,7 @@ module Lutaml
|
|
|
237
258
|
# `value` members, mapping sections, class declarations, enum
|
|
238
259
|
# member_fields and from_table all arrive as bare member hashes.
|
|
239
260
|
def expand_declared_members(model, hash)
|
|
261
|
+
expand_abstract_declaration(model, hash)
|
|
240
262
|
expand_definition_text(model, hash)
|
|
241
263
|
expand_value_declaration(model, hash)
|
|
242
264
|
expand_value_set_declaration(model, hash)
|
|
@@ -244,10 +266,57 @@ module Lutaml
|
|
|
244
266
|
expand_class_declarations(model, hash)
|
|
245
267
|
expand_enum_member_fields(model, hash)
|
|
246
268
|
expand_from_table(model, hash)
|
|
269
|
+
expand_operation_members(model, hash)
|
|
270
|
+
end
|
|
271
|
+
|
|
272
|
+
# Operation members arrive with grammar capture names
|
|
273
|
+
# (`visibility_modifier`, `param_default`) and per-iteration
|
|
274
|
+
# stereotype hashes; normalize to the model's field names.
|
|
275
|
+
def expand_operation_members(model, hash)
|
|
276
|
+
ops = hash.delete(:operations)
|
|
277
|
+
return unless ops && model.class.attributes.key?(:operations)
|
|
278
|
+
|
|
279
|
+
Array(ops.is_a?(Hash) ? [ops] : ops).each do |op|
|
|
280
|
+
model.operations << build(:operation, normalize_operation_hash(op))
|
|
281
|
+
end
|
|
282
|
+
end
|
|
283
|
+
|
|
284
|
+
def normalize_operation_hash(op)
|
|
285
|
+
op = op.dup
|
|
286
|
+
if (vis = op.delete(:visibility_modifier))
|
|
287
|
+
op[:visibility] = Transform::VISIBILITY_MAP.fetch(vis.to_s, 'public')
|
|
288
|
+
end
|
|
289
|
+
stereo = op[:stereotype]
|
|
290
|
+
if stereo
|
|
291
|
+
items = stereo.is_a?(Array) ? stereo : [stereo]
|
|
292
|
+
op[:stereotype] = items.map { |x| x.is_a?(Hash) ? x[:stereotype].to_s : x.to_s }
|
|
293
|
+
end
|
|
294
|
+
params = op[:owned_parameter]
|
|
295
|
+
if params
|
|
296
|
+
list = params.is_a?(Array) ? params : [params]
|
|
297
|
+
op[:owned_parameter] = list.map { |param| normalize_operation_param(param) }
|
|
298
|
+
end
|
|
299
|
+
op
|
|
300
|
+
end
|
|
301
|
+
|
|
302
|
+
def normalize_operation_param(param)
|
|
303
|
+
param = param.dup
|
|
304
|
+
if (default = param.delete(:param_default))
|
|
305
|
+
param[:default] = default.is_a?(Hash) ? default[:string].to_s : default.to_s
|
|
306
|
+
end
|
|
307
|
+
param
|
|
247
308
|
end
|
|
248
309
|
|
|
249
310
|
# RS 3001 §Definition single-line form (`definition "text"`) arrives
|
|
250
311
|
# as a quoted-string capture; unwrap it for the entity's definition.
|
|
312
|
+
# RS 3001 declaration modifiers: `abstract true` maps onto the
|
|
313
|
+
# is_abstract field (the visibility declaration flows through the
|
|
314
|
+
# generic attribute path).
|
|
315
|
+
def expand_abstract_declaration(model, hash)
|
|
316
|
+
value = hash.delete(:abstract)
|
|
317
|
+
model.is_abstract = true if value.to_s == 'true' && model.class.attributes.key?(:is_abstract)
|
|
318
|
+
end
|
|
319
|
+
|
|
251
320
|
def expand_definition_text(model, hash)
|
|
252
321
|
return unless hash.key?(:definition) && model.class.attributes.key?(:definition)
|
|
253
322
|
|
|
@@ -70,6 +70,8 @@ module Lutaml
|
|
|
70
70
|
end
|
|
71
71
|
|
|
72
72
|
def format_value(prefix, key, value, indent)
|
|
73
|
+
return "#{prefix}#{key} = ref:(#{value[:reference]})" if reference_value?(value)
|
|
74
|
+
|
|
73
75
|
case value
|
|
74
76
|
when Array
|
|
75
77
|
format_array(prefix, key, value, indent)
|
|
@@ -84,11 +86,15 @@ module Lutaml
|
|
|
84
86
|
|
|
85
87
|
def format_array(prefix, key, items, indent)
|
|
86
88
|
elements = items.map do |item|
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
format_nested_instance(item, indent + 1)
|
|
89
|
+
if reference_value?(item)
|
|
90
|
+
" ref:(#{item[:reference]})"
|
|
90
91
|
else
|
|
91
|
-
|
|
92
|
+
case item
|
|
93
|
+
when Hash
|
|
94
|
+
format_nested_instance(item, indent + 1)
|
|
95
|
+
else
|
|
96
|
+
" #{quote_value(item)}"
|
|
97
|
+
end
|
|
92
98
|
end
|
|
93
99
|
end
|
|
94
100
|
|
|
@@ -111,6 +117,13 @@ module Lutaml
|
|
|
111
117
|
"#{prefix}instance#{type_clause} {\n#{inner}\n#{prefix}}"
|
|
112
118
|
end
|
|
113
119
|
|
|
120
|
+
# RS 3001 par. Value type: a `reference` map is the wire form of a
|
|
121
|
+
# reference literal; it emits as `ref:(path)`, never as a nested
|
|
122
|
+
# instance.
|
|
123
|
+
def reference_value?(value)
|
|
124
|
+
value.is_a?(Hash) && value.keys == [:reference]
|
|
125
|
+
end
|
|
126
|
+
|
|
114
127
|
def quote_value(val)
|
|
115
128
|
return val.to_s if val.is_a?(Numeric) || val.is_a?(TrueClass) || val.is_a?(FalseClass)
|
|
116
129
|
str = val.to_s
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Lutaml
|
|
4
|
+
module Formatter
|
|
5
|
+
# ELK-layout diagram formatter: builds an Elkrb::Graph from the
|
|
6
|
+
# document, lays it out in-process (no external tools) and emits SVG,
|
|
7
|
+
# or converts the SVG to PS/EPS/EMF through vectory.
|
|
8
|
+
class Elk
|
|
9
|
+
VALID_TYPES = %i[svg ps eps emf].freeze
|
|
10
|
+
|
|
11
|
+
attr_reader :type
|
|
12
|
+
|
|
13
|
+
def initialize(_attributes = {})
|
|
14
|
+
@type = :svg
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def type=(value)
|
|
18
|
+
@type = value.to_sym
|
|
19
|
+
return if VALID_TYPES.include?(@type)
|
|
20
|
+
|
|
21
|
+
raise ArgumentError,
|
|
22
|
+
"unsupported output type #{@type.inspect} (valid: #{VALID_TYPES.join(', ')})"
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def format(document)
|
|
26
|
+
graph = Lutaml::Layout::ElkGraphBuilder.new.build(document)
|
|
27
|
+
svg = Lutaml::Layout::ElkEngine.new(input: graph).render(:svg)
|
|
28
|
+
@type == :svg ? svg : convert(svg)
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
private
|
|
32
|
+
|
|
33
|
+
def convert(svg)
|
|
34
|
+
require 'vectory'
|
|
35
|
+
Vectory::Svg.new(svg).public_send(:"to_#{@type}").content
|
|
36
|
+
rescue LoadError
|
|
37
|
+
raise Error, 'The vectory gem is required for PS/EPS/EMF output: ' \
|
|
38
|
+
'add `gem "vectory"` to your Gemfile'
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
end
|