lutaml-lml 0.3.3 → 0.3.5
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/exe/lutaml-lml-lsp +11 -0
- data/lib/lutaml/lml/cli.rb +166 -17
- data/lib/lutaml/lml/data_processor/attribute_processing.rb +4 -2
- data/lib/lutaml/lml/data_processor/value_declaration_processing.rb +1 -0
- data/lib/lutaml/lml/data_processor.rb +8 -1
- data/lib/lutaml/lml/diagnostic.rb +149 -0
- data/lib/lutaml/lml/document_builder.rb +63 -21
- data/lib/lutaml/lml/format/adapter/standard_adapter.rb +22 -23
- data/lib/lutaml/lml/formatter/canonical.rb +736 -0
- data/lib/lutaml/lml/formatter.rb +2 -0
- data/lib/lutaml/lml/grammar/lml.artifact.json +2946 -2512
- data/lib/lutaml/lml/grammar/lml.parg +47 -9
- data/lib/lutaml/lml/grammar.rb +13 -0
- data/lib/lutaml/lml/import_resolver.rb +13 -4
- data/lib/lutaml/lml/lsp/conversions.rb +78 -0
- data/lib/lutaml/lml/lsp/keywords.rb +43 -0
- data/lib/lutaml/lml/lsp/server.rb +378 -0
- data/lib/lutaml/lml/lsp/store.rb +68 -0
- data/lib/lutaml/lml/lsp/transport.rb +53 -0
- data/lib/lutaml/lml/lsp.rb +16 -0
- data/lib/lutaml/lml/model_compiler/serialization_mappings.rb +36 -10
- data/lib/lutaml/lml/model_compiler.rb +25 -2
- data/lib/lutaml/lml/models/association.rb +3 -0
- data/lib/lutaml/lml/models/constraint.rb +3 -0
- data/lib/lutaml/lml/models/data_type.rb +3 -0
- data/lib/lutaml/lml/models/diagram.rb +3 -0
- data/lib/lutaml/lml/models/document.rb +10 -3
- data/lib/lutaml/lml/models/enum.rb +4 -1
- data/lib/lutaml/lml/models/instance.rb +3 -0
- data/lib/lutaml/lml/models/instance_collection.rb +3 -0
- data/lib/lutaml/lml/models/member_field.rb +2 -0
- data/lib/lutaml/lml/models/namespace.rb +2 -0
- data/lib/lutaml/lml/models/operation.rb +4 -1
- data/lib/lutaml/lml/models/package.rb +3 -0
- data/lib/lutaml/lml/models/primitive_type.rb +6 -3
- data/lib/lutaml/lml/models/serialization_mapping.rb +2 -0
- data/lib/lutaml/lml/models/top_element_attribute.rb +6 -0
- data/lib/lutaml/lml/models/uml_class.rb +6 -0
- data/lib/lutaml/lml/models/value.rb +3 -0
- data/lib/lutaml/lml/pipeline.rb +25 -8
- data/lib/lutaml/lml/positioned.rb +13 -0
- data/lib/lutaml/lml/preprocessor.rb +44 -13
- data/lib/lutaml/lml/quoting.rb +60 -0
- data/lib/lutaml/lml/source_position.rb +20 -0
- data/lib/lutaml/lml/symbol_index.rb +200 -0
- data/lib/lutaml/lml/transform.rb +49 -2
- data/lib/lutaml/lml/validator.rb +134 -21
- data/lib/lutaml/lml/version.rb +1 -1
- data/lib/lutaml/lml.rb +18 -1
- metadata +18 -4
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Lutaml
|
|
4
|
+
module Lml
|
|
5
|
+
module Lsp
|
|
6
|
+
# The open-document store. Every change reparses the full text
|
|
7
|
+
# (full-document sync) and recomputes diagnostics.
|
|
8
|
+
class Store
|
|
9
|
+
Entry = Struct.new(:uri, :text, :version, :model, :diagnostics,
|
|
10
|
+
keyword_init: true)
|
|
11
|
+
|
|
12
|
+
def initialize
|
|
13
|
+
@entries = {}
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def open(uri:, text:, version: nil)
|
|
17
|
+
entry = Entry.new(uri: uri, text: text, version: version)
|
|
18
|
+
parse(entry)
|
|
19
|
+
@entries[uri] = entry
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def change(uri:, text:, version: nil)
|
|
23
|
+
entry = @entries[uri]
|
|
24
|
+
return nil unless entry
|
|
25
|
+
|
|
26
|
+
entry.text = text
|
|
27
|
+
entry.version = version
|
|
28
|
+
parse(entry)
|
|
29
|
+
entry
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def close(uri)
|
|
33
|
+
@entries.delete(uri)
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def [](uri)
|
|
37
|
+
@entries[uri]
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
private
|
|
41
|
+
|
|
42
|
+
def parse(entry)
|
|
43
|
+
path = Conversions.uri_to_path(entry.uri)
|
|
44
|
+
entry.model = Lutaml::Lml.parse_document(entry.text)
|
|
45
|
+
entry.model.source_path = path
|
|
46
|
+
entry.diagnostics = Validator.diagnostics(entry.model)
|
|
47
|
+
rescue Lutaml::Lml::Error => e
|
|
48
|
+
entry.model = nil
|
|
49
|
+
entry.diagnostics = [diagnostic_for(e, path)]
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def diagnostic_for(error, path)
|
|
53
|
+
record = error.diagnostic
|
|
54
|
+
return record if record&.file
|
|
55
|
+
|
|
56
|
+
Diagnostic.build(
|
|
57
|
+
record&.code || 'parse/LML-001',
|
|
58
|
+
message: record&.message || error.message.lines.first.to_s.strip,
|
|
59
|
+
file: path,
|
|
60
|
+
line: record&.line,
|
|
61
|
+
column: record&.column,
|
|
62
|
+
length: record&.length,
|
|
63
|
+
)
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
end
|
|
67
|
+
end
|
|
68
|
+
end
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "json"
|
|
4
|
+
|
|
5
|
+
module Lutaml
|
|
6
|
+
module Lml
|
|
7
|
+
module Lsp
|
|
8
|
+
# LSP base protocol framing: `Content-Length: N\r\n\r\n<json>`.
|
|
9
|
+
# Reads one message at a time and writes responses to the output
|
|
10
|
+
# stream. stdout is the protocol channel; stderr is for logging.
|
|
11
|
+
class Transport
|
|
12
|
+
def initialize(input: $stdin, output: $stdout)
|
|
13
|
+
@input = input
|
|
14
|
+
@output = output
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def read_message
|
|
18
|
+
headers = read_headers
|
|
19
|
+
return nil unless headers
|
|
20
|
+
|
|
21
|
+
length = headers["content-length"]
|
|
22
|
+
return nil unless length
|
|
23
|
+
|
|
24
|
+
body = @input.read(length.to_i)
|
|
25
|
+
body.nil? || body.empty? ? nil : JSON.parse(body)
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def write_message(payload)
|
|
29
|
+
body = JSON.generate(payload)
|
|
30
|
+
@output.write("Content-Length: #{body.bytesize}\r\n\r\n#{body}")
|
|
31
|
+
@output.flush
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
private
|
|
35
|
+
|
|
36
|
+
def read_headers
|
|
37
|
+
headers = {}
|
|
38
|
+
loop do
|
|
39
|
+
line = @input.gets
|
|
40
|
+
return nil if line.nil?
|
|
41
|
+
|
|
42
|
+
line = line.chomp
|
|
43
|
+
break if line.empty?
|
|
44
|
+
|
|
45
|
+
key, value = line.split(":", 2)
|
|
46
|
+
headers[key.to_s.strip.downcase] = value.to_s.strip
|
|
47
|
+
end
|
|
48
|
+
headers
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
end
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Lutaml
|
|
4
|
+
module Lml
|
|
5
|
+
# Language Server Protocol support: a stdio JSON-RPC server exposing
|
|
6
|
+
# diagnostics, symbols, navigation, hover, completion, and canonical
|
|
7
|
+
# formatting over the parsed document model.
|
|
8
|
+
module Lsp
|
|
9
|
+
autoload :Transport, "lutaml/lml/lsp/transport"
|
|
10
|
+
autoload :Server, "lutaml/lml/lsp/server"
|
|
11
|
+
autoload :Store, "lutaml/lml/lsp/store"
|
|
12
|
+
autoload :Keywords, "lutaml/lml/lsp/keywords"
|
|
13
|
+
autoload :Conversions, "lutaml/lml/lsp/conversions"
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
end
|
|
@@ -49,18 +49,40 @@ module Lutaml
|
|
|
49
49
|
end
|
|
50
50
|
end
|
|
51
51
|
|
|
52
|
+
# Default XML mapping: every attribute an element, except those
|
|
53
|
+
# declared `as :attribute`; an in-class `namespace "uri"` becomes
|
|
54
|
+
# the class's XML namespace (issue #49).
|
|
52
55
|
def apply_xml_mapping(klass, root_name)
|
|
53
56
|
attr_names = klass.attributes.keys
|
|
54
|
-
|
|
55
|
-
|
|
57
|
+
def_obj = @class_defs[root_name]
|
|
58
|
+
roles = xml_roles_of(def_obj)
|
|
59
|
+
ns_class = inline_namespace_class(def_obj)
|
|
56
60
|
klass.xml do |mapping|
|
|
57
61
|
mapping.element(root_name)
|
|
62
|
+
mapping.namespace(ns_class) if ns_class
|
|
58
63
|
attr_names.each do |attr_name|
|
|
59
|
-
|
|
64
|
+
if roles[attr_name] == 'attribute'
|
|
65
|
+
mapping.map_attribute(attr_name.to_s, to: attr_name)
|
|
66
|
+
else
|
|
67
|
+
mapping.map_element(attr_name.to_s, to: attr_name)
|
|
68
|
+
end
|
|
60
69
|
end
|
|
61
70
|
end
|
|
62
71
|
end
|
|
63
72
|
|
|
73
|
+
def xml_roles_of(def_obj)
|
|
74
|
+
Array(def_obj&.attributes).each_with_object({}) do |attr, roles|
|
|
75
|
+
roles[attr.name.to_sym] = attr.xml_role if attr.xml_role
|
|
76
|
+
end
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
def inline_namespace_class(def_obj)
|
|
80
|
+
decl = def_obj.xml_namespace if def_obj.is_a?(UmlClass)
|
|
81
|
+
return nil unless decl&.uri
|
|
82
|
+
|
|
83
|
+
build_namespace_class("#{decl.uri}|#{decl.prefix}", decl.uri, decl.prefix.to_s)
|
|
84
|
+
end
|
|
85
|
+
|
|
64
86
|
# RS 3010 extension: key-value mapping blocks for the declared
|
|
65
87
|
# formats (yaml/json/toml share lutaml-model's key_value model).
|
|
66
88
|
|
|
@@ -89,7 +111,7 @@ module Lutaml
|
|
|
89
111
|
check_namespace_ambiguity(mapping)
|
|
90
112
|
ns_class = namespace_class_for(mapping.namespace_ref) if mapping.namespace_ref
|
|
91
113
|
rule_ns = mapping.rules.select { |r| r.kind == 'namespace' }
|
|
92
|
-
|
|
114
|
+
.to_h { |r| [r.field, namespace_class_for(r.field)] }
|
|
93
115
|
klass.xml do |m|
|
|
94
116
|
m.element(mapping.element_name) if mapping.element_name
|
|
95
117
|
m.namespace(ns_class) if ns_class
|
|
@@ -149,12 +171,16 @@ module Lutaml
|
|
|
149
171
|
declared = @declared_namespaces.find { |ns| ns.name.to_s == ref }
|
|
150
172
|
raise ArgumentError, "unknown namespace '#{ref}'" unless declared
|
|
151
173
|
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
174
|
+
build_namespace_class(ref, declared.uri, declared.prefix.to_s)
|
|
175
|
+
end
|
|
176
|
+
|
|
177
|
+
def build_namespace_class(key, uri, prefix)
|
|
178
|
+
require 'lutaml/xml/namespace' unless defined?(Lutaml::Xml::Namespace)
|
|
179
|
+
|
|
180
|
+
@namespace_classes[key] ||= Class.new(Lutaml::Xml::Namespace).tap do |ns_class|
|
|
181
|
+
ns_class.define_singleton_method(:uri) { uri }
|
|
182
|
+
ns_class.define_singleton_method(:prefix_default) { prefix }
|
|
183
|
+
end
|
|
158
184
|
end
|
|
159
185
|
end
|
|
160
186
|
end
|
|
@@ -59,6 +59,7 @@ module Lutaml
|
|
|
59
59
|
@declared_namespaces = []
|
|
60
60
|
@serialization_mappings = []
|
|
61
61
|
@namespace_classes = {}
|
|
62
|
+
@class_defs = {}
|
|
62
63
|
end
|
|
63
64
|
|
|
64
65
|
def compile(input)
|
|
@@ -73,6 +74,17 @@ module Lutaml
|
|
|
73
74
|
return {} unless doc.instance
|
|
74
75
|
|
|
75
76
|
hydrate_instance(doc.instance)
|
|
77
|
+
rescue Lutaml::Lml::Error
|
|
78
|
+
raise
|
|
79
|
+
rescue StandardError => e
|
|
80
|
+
raise HydrationError.new(
|
|
81
|
+
"hydration failed: #{e.message}",
|
|
82
|
+
diagnostic: Diagnostic.build(
|
|
83
|
+
'hydration/LML-H01',
|
|
84
|
+
message: e.message,
|
|
85
|
+
file: input.is_a?(Document) ? input.source_path : nil,
|
|
86
|
+
),
|
|
87
|
+
)
|
|
76
88
|
end
|
|
77
89
|
|
|
78
90
|
def compiled_classes
|
|
@@ -129,6 +141,7 @@ module Lutaml
|
|
|
129
141
|
|
|
130
142
|
def compile_class(klass_def)
|
|
131
143
|
name = klass_def.name.to_s
|
|
144
|
+
@class_defs[name] = klass_def
|
|
132
145
|
compiled_klass = build_compiled_class(klass_def)
|
|
133
146
|
apply_declared_mappings(compiled_klass, klass_def)
|
|
134
147
|
declare_derived_fields(compiled_klass, klass_def)
|
|
@@ -236,7 +249,11 @@ module Lutaml
|
|
|
236
249
|
return Float::INFINITY if UNBOUNDED_TOKENS.include?(val.to_s)
|
|
237
250
|
return val.to_i if val.to_s.match?(/\A\d+\z/)
|
|
238
251
|
|
|
239
|
-
|
|
252
|
+
message = "Unrecognized cardinality token: #{val.inspect}"
|
|
253
|
+
raise ValidationError.new(
|
|
254
|
+
message,
|
|
255
|
+
diagnostic: Diagnostic.build('contract/LML-C04', message: message),
|
|
256
|
+
)
|
|
240
257
|
end
|
|
241
258
|
|
|
242
259
|
def declare_derived_fields(compiled_klass, def_obj)
|
|
@@ -278,11 +295,17 @@ module Lutaml
|
|
|
278
295
|
|
|
279
296
|
def register(name, klass)
|
|
280
297
|
@compiled[name] = klass
|
|
281
|
-
return unless @namespace
|
|
298
|
+
return name_anonymous_class(klass, name) unless @namespace
|
|
282
299
|
|
|
283
300
|
namespace_module.const_set(name, klass)
|
|
284
301
|
end
|
|
285
302
|
|
|
303
|
+
# Without a target namespace the class stays anonymous, and
|
|
304
|
+
# lutaml-model's schema generators need a class name for $defs/$ref.
|
|
305
|
+
def name_anonymous_class(klass, name)
|
|
306
|
+
klass.define_singleton_method(:name) { name }
|
|
307
|
+
end
|
|
308
|
+
|
|
286
309
|
def namespace_module
|
|
287
310
|
return @namespace if @namespace.is_a?(Module)
|
|
288
311
|
|
|
@@ -2,7 +2,10 @@
|
|
|
2
2
|
|
|
3
3
|
module Lutaml
|
|
4
4
|
module Lml
|
|
5
|
+
# A user-defined data type declaration.
|
|
5
6
|
class DataType < Lutaml::Model::Serializable
|
|
7
|
+
include Lutaml::Lml::Positioned
|
|
8
|
+
|
|
6
9
|
# From TopElement
|
|
7
10
|
attribute :name, :string
|
|
8
11
|
attribute :wire_mappings, "Lutaml::Lml::Mapping", collection: true, default: -> { [] }
|
|
@@ -2,9 +2,18 @@
|
|
|
2
2
|
|
|
3
3
|
module Lutaml
|
|
4
4
|
module Lml
|
|
5
|
+
# The parsed LML document model.
|
|
5
6
|
class Document < Lutaml::Model::Serializable
|
|
7
|
+
include Lutaml::Lml::Positioned
|
|
8
|
+
|
|
9
|
+
# Non-serialized navigation index (definitions and uses).
|
|
10
|
+
def symbols
|
|
11
|
+
@symbols ||= SymbolIndex.new(self)
|
|
12
|
+
end
|
|
13
|
+
|
|
6
14
|
# Core attributes
|
|
7
15
|
attribute :name, :string
|
|
16
|
+
attribute :lml_version, :string
|
|
8
17
|
attribute :title, :string
|
|
9
18
|
attribute :caption, :string
|
|
10
19
|
attribute :groups, "Lutaml::Lml::Group", collection: true, default: -> { [] }
|
|
@@ -47,9 +56,7 @@ module Lutaml
|
|
|
47
56
|
def merge_instances(incoming)
|
|
48
57
|
names = @instances.collections.map(&:name)
|
|
49
58
|
incoming.collections.each do |collection|
|
|
50
|
-
if names.include?(collection.name)
|
|
51
|
-
raise Lutaml::Lml::Error, "duplicate collection name '#{collection.name}'"
|
|
52
|
-
end
|
|
59
|
+
raise Lutaml::Lml::Error, "duplicate collection name '#{collection.name}'" if names.include?(collection.name)
|
|
53
60
|
end
|
|
54
61
|
@instances.instances.concat(incoming.instances)
|
|
55
62
|
@instances.imports.concat(incoming.imports)
|
|
@@ -2,7 +2,10 @@
|
|
|
2
2
|
|
|
3
3
|
module Lutaml
|
|
4
4
|
module Lml
|
|
5
|
+
# An enumeration declaration with its value members.
|
|
5
6
|
class Enum < Lutaml::Model::Serializable
|
|
7
|
+
include Lutaml::Lml::Positioned
|
|
8
|
+
|
|
6
9
|
# From TopElement
|
|
7
10
|
attribute :name, :string
|
|
8
11
|
attribute :definition, Lutaml::Lml::Types::TextType
|
|
@@ -15,7 +18,7 @@ module Lutaml
|
|
|
15
18
|
|
|
16
19
|
# From Enum
|
|
17
20
|
attribute :attributes, "Lutaml::Lml::TopElementAttribute", collection: true,
|
|
18
|
-
|
|
21
|
+
default: -> { [] }
|
|
19
22
|
attribute :modifier, :string
|
|
20
23
|
attribute :member_fields, "Lutaml::Lml::MemberField", collection: true, default: -> { [] }
|
|
21
24
|
attribute :from_table_name, :string
|
|
@@ -2,7 +2,10 @@
|
|
|
2
2
|
|
|
3
3
|
module Lutaml
|
|
4
4
|
module Lml
|
|
5
|
+
# A named instances collection with its instances and imports/exports.
|
|
5
6
|
class InstanceCollection < Lutaml::Model::Serializable
|
|
7
|
+
include Lutaml::Lml::Positioned
|
|
8
|
+
|
|
6
9
|
attribute :name, :string
|
|
7
10
|
attribute :instances, "Lutaml::Lml::Instance", collection: true, default: []
|
|
8
11
|
attribute :imports, "Lutaml::Lml::InstancesImport", collection: true, default: []
|
|
@@ -5,6 +5,8 @@ module Lutaml
|
|
|
5
5
|
# A typed payload field declared on an enum:
|
|
6
6
|
# member_fields { abbreviation String urn_segment String }
|
|
7
7
|
class MemberField < Lutaml::Model::Serializable
|
|
8
|
+
include Lutaml::Lml::Positioned
|
|
9
|
+
|
|
8
10
|
attribute :name, :string
|
|
9
11
|
attribute :type, :string
|
|
10
12
|
end
|
|
@@ -6,6 +6,8 @@ module Lutaml
|
|
|
6
6
|
# The compiler generates the corresponding Lutaml::Model::XmlNamespace
|
|
7
7
|
# subclass for the compiled model namespace.
|
|
8
8
|
class Namespace < Lutaml::Model::Serializable
|
|
9
|
+
include Lutaml::Lml::Positioned
|
|
10
|
+
|
|
9
11
|
attribute :name, :string
|
|
10
12
|
attribute :uri, :string
|
|
11
13
|
attribute :prefix, :string
|
|
@@ -2,7 +2,10 @@
|
|
|
2
2
|
|
|
3
3
|
module Lutaml
|
|
4
4
|
module Lml
|
|
5
|
+
# A class operation with parameters and a return type.
|
|
5
6
|
class Operation < Lutaml::Model::Serializable
|
|
7
|
+
include Lutaml::Lml::Positioned
|
|
8
|
+
|
|
6
9
|
# From TopElement
|
|
7
10
|
attribute :name, :string
|
|
8
11
|
attribute :definition, Lutaml::Lml::Types::TextType
|
|
@@ -17,7 +20,7 @@ module Lutaml
|
|
|
17
20
|
attribute :is_static, :boolean, default: false
|
|
18
21
|
attribute :is_abstract, :boolean, default: false
|
|
19
22
|
attribute :owned_parameter, "Lutaml::Lml::OperationParameter", collection: true,
|
|
20
|
-
|
|
23
|
+
default: -> { [] }
|
|
21
24
|
end
|
|
22
25
|
end
|
|
23
26
|
end
|
|
@@ -2,7 +2,10 @@
|
|
|
2
2
|
|
|
3
3
|
module Lutaml
|
|
4
4
|
module Lml
|
|
5
|
+
# A primitive type declaration.
|
|
5
6
|
class PrimitiveType < Lutaml::Model::Serializable
|
|
7
|
+
include Lutaml::Lml::Positioned
|
|
8
|
+
|
|
6
9
|
# Primitives in LML are name-only entities, but the formatter
|
|
7
10
|
# accesses the same attributes as DataType via format_class.
|
|
8
11
|
attribute :name, :string
|
|
@@ -12,11 +15,11 @@ module Lutaml
|
|
|
12
15
|
attribute :visibility, :string, default: "public"
|
|
13
16
|
attribute :is_abstract, :boolean, default: false
|
|
14
17
|
attribute :attributes, "Lutaml::Lml::TopElementAttribute", collection: true,
|
|
15
|
-
|
|
18
|
+
default: -> { [] }
|
|
16
19
|
attribute :operations, "Lutaml::Lml::Operation", collection: true,
|
|
17
|
-
|
|
20
|
+
default: -> { [] }
|
|
18
21
|
attribute :associations, "Lutaml::Lml::Association", collection: true,
|
|
19
|
-
|
|
22
|
+
default: -> { [] }
|
|
20
23
|
|
|
21
24
|
def self.entity_type
|
|
22
25
|
:primitives
|
|
@@ -6,6 +6,8 @@ module Lutaml
|
|
|
6
6
|
# its class and linked by target name —
|
|
7
7
|
# mapping xml Ceramic { element "ceramic" … }
|
|
8
8
|
class SerializationMapping < Lutaml::Model::Serializable
|
|
9
|
+
include Lutaml::Lml::Positioned
|
|
10
|
+
|
|
9
11
|
attribute :format, :string
|
|
10
12
|
attribute :target, :string
|
|
11
13
|
attribute :element_name, :string
|
|
@@ -2,7 +2,10 @@
|
|
|
2
2
|
|
|
3
3
|
module Lutaml
|
|
4
4
|
module Lml
|
|
5
|
+
# An attribute declaration or instance assignment entry.
|
|
5
6
|
class TopElementAttribute < Lutaml::Model::Serializable
|
|
7
|
+
include Lutaml::Lml::Positioned
|
|
8
|
+
|
|
6
9
|
# Core attributes
|
|
7
10
|
attribute :name, :string
|
|
8
11
|
attribute :visibility, :string, default: "public"
|
|
@@ -21,6 +24,8 @@ module Lutaml
|
|
|
21
24
|
attribute :default, :string
|
|
22
25
|
attribute :value_set, :string, collection: true, default: -> { [] }
|
|
23
26
|
attribute :pattern, :string
|
|
27
|
+
# `as :element` / `as :attribute`: the XML node kind (issue #49)
|
|
28
|
+
attribute :xml_role, :string
|
|
24
29
|
|
|
25
30
|
# LML-specific attributes
|
|
26
31
|
attribute :properties, "Lutaml::Lml::TopElementAttribute", collection: true, default: -> { [] }
|
|
@@ -57,6 +62,7 @@ module Lutaml
|
|
|
57
62
|
map "default", to: :default
|
|
58
63
|
map "value_set", to: :value_set
|
|
59
64
|
map "pattern", to: :pattern
|
|
65
|
+
map "xml_role", to: :xml_role
|
|
60
66
|
map "properties", to: :properties
|
|
61
67
|
map "value", to: :value, with: { to: :value_to, from: :value_from }
|
|
62
68
|
map "attributes", to: :attributes
|
|
@@ -2,7 +2,10 @@
|
|
|
2
2
|
|
|
3
3
|
module Lutaml
|
|
4
4
|
module Lml
|
|
5
|
+
# A class declaration with its members.
|
|
5
6
|
class UmlClass < Lutaml::Model::Serializable
|
|
7
|
+
include Lutaml::Lml::Positioned
|
|
8
|
+
|
|
6
9
|
# From TopElement. TextType unescapes \{ \} and normalizes lines
|
|
7
10
|
# on every assignment (builder, yaml, direct).
|
|
8
11
|
attribute :name, :string
|
|
@@ -29,6 +32,8 @@ module Lutaml
|
|
|
29
32
|
attribute :serialization_mappings, "Lutaml::Lml::SerializationMapping", collection: true, default: -> { [] }
|
|
30
33
|
|
|
31
34
|
# LML-specific
|
|
35
|
+
# In-class `namespace "uri"` declaration (issue #49)
|
|
36
|
+
attribute :xml_namespace, "Lutaml::Lml::Namespace"
|
|
32
37
|
attribute :parent_class, :string
|
|
33
38
|
|
|
34
39
|
yaml do
|
|
@@ -39,6 +44,7 @@ module Lutaml
|
|
|
39
44
|
map "stereotype", to: :stereotype
|
|
40
45
|
map "visibility", to: :visibility
|
|
41
46
|
map "type", to: :type
|
|
47
|
+
map "xml_namespace", to: :xml_namespace
|
|
42
48
|
map "attributes", to: :attributes
|
|
43
49
|
map "operations", to: :operations
|
|
44
50
|
map "constraints", to: :constraints
|
data/lib/lutaml/lml/pipeline.rb
CHANGED
|
@@ -29,7 +29,18 @@ module Lutaml
|
|
|
29
29
|
def parse_raw(data)
|
|
30
30
|
Transform.new.apply(Parser.new.parse(data))
|
|
31
31
|
rescue Parsanol::ParseFailed => e
|
|
32
|
-
raise(
|
|
32
|
+
raise ParsingError.new(failure_message(e), diagnostic: parse_diagnostic(e))
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def parse_diagnostic(error)
|
|
36
|
+
line, column = failure_location(error)
|
|
37
|
+
Diagnostic.build(
|
|
38
|
+
'parse/LML-001',
|
|
39
|
+
message: error.message.lines.first.to_s.strip,
|
|
40
|
+
file: @input.path,
|
|
41
|
+
line: line,
|
|
42
|
+
column: column,
|
|
43
|
+
)
|
|
33
44
|
end
|
|
34
45
|
|
|
35
46
|
# Parsanol releases up to 1.3.57 annotate native failures with a
|
|
@@ -38,18 +49,24 @@ module Lutaml
|
|
|
38
49
|
# rebuild the location when the marker is present.
|
|
39
50
|
def failure_message(error)
|
|
40
51
|
detail = error.message.lines.first.to_s.strip
|
|
52
|
+
line, column = failure_location(error)
|
|
53
|
+
location = (" at line #{line} char #{column}." if line && !detail.match?(/at line \d+ char \d+/))
|
|
54
|
+
"#{detail}#{location}\ncause: #{error.parse_failure_cause&.ascii_tree}"
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
def failure_location(error)
|
|
41
58
|
cause = error.parse_failure_cause
|
|
42
59
|
offset = error.message[/@@parsanol_pos:(\d+)/, 1]&.to_i
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
end
|
|
48
|
-
"#{detail}#{location}\ncause: #{cause&.ascii_tree}"
|
|
60
|
+
return cause.source.line_and_column(offset) if offset && cause&.source
|
|
61
|
+
|
|
62
|
+
match = error.message.match(/at line (\d+) char (\d+)/)
|
|
63
|
+
match ? [match[1].to_i, match[2].to_i] : [nil, nil]
|
|
49
64
|
end
|
|
50
65
|
|
|
51
66
|
def build_document(hash)
|
|
52
|
-
DocumentBuilder.new(DocumentBuilder::DEFAULT_REGISTRY).build(:document, hash)
|
|
67
|
+
document = DocumentBuilder.new(DocumentBuilder::DEFAULT_REGISTRY).build(:document, hash)
|
|
68
|
+
document.source_path = @input.path
|
|
69
|
+
document
|
|
53
70
|
end
|
|
54
71
|
end
|
|
55
72
|
end
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Lutaml
|
|
4
|
+
module Lml
|
|
5
|
+
# Non-serialized source metadata carried by parsed entities. These
|
|
6
|
+
# accessors are deliberately not lutaml-model attributes: positions
|
|
7
|
+
# and the source path are diagnostics metadata, not document data,
|
|
8
|
+
# and must not appear in to_hash/YAML/JSON output.
|
|
9
|
+
module Positioned
|
|
10
|
+
attr_accessor :source_position, :source_path
|
|
11
|
+
end
|
|
12
|
+
end
|
|
13
|
+
end
|