expressir 2.4.31-aarch64-linux → 2.4.36-aarch64-linux
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/.github/workflows/conformance.yml +34 -0
- data/.gitignore +3 -0
- data/Gemfile +1 -1
- data/TODO.eengine/00-overview.md +39 -0
- data/TODO.eengine/01-resolution-diagnostics.md +30 -0
- data/TODO.eengine/02-check-catalog-completion.md +53 -0
- data/TODO.eengine/03-entity-computations.md +22 -0
- data/TODO.eengine/04-shtolo-rule-audit.md +32 -0
- data/TODO.eengine/05-shtolo-longform-flatten.md +12 -0
- data/TODO.eengine/06-population-p21.md +34 -0
- data/TODO.eengine/07-schema-compare-patch.md +25 -0
- data/TODO.eengine/08-official-corpus-gate.md +7 -0
- data/TODO.eengine/09-mim-mapping-parser.md +9 -0
- data/TODO.parity-ee/00-overview.md +1 -1
- data/TODO.sota/00-overview.md +57 -0
- data/TODO.sota/01-type-checking.md +115 -0
- data/TODO.sota/02-evaluator.md +86 -0
- data/TODO.sota/03-conformance.md +53 -0
- data/TODO.sota/04-lsp.md +70 -0
- data/TODO.sota/05-interop.md +55 -0
- data/TODO.sota/06-dialects.md +57 -0
- data/TODO.sota/07-reference-artifacts.md +55 -0
- data/TODO.suma-improvements/07-part28-xml.md +10 -13
- data/docs/_guides/interop/index.adoc +25 -0
- data/docs/_guides/interop/json-schema.adoc +88 -0
- data/docs/_guides/interop/owl-rdf.adoc +87 -0
- data/docs/_guides/reference/checker-notes.adoc +55 -0
- data/docs/_guides/reference/checker.adoc +36 -0
- data/docs/_guides/reference/evaluator.adoc +40 -0
- data/docs/_guides/reference/grammar-crosswalk.adoc +299 -0
- data/docs/_guides/reference/index.adoc +22 -0
- data/docs/_guides/reference/lsp.adoc +41 -0
- data/docs/_guides/reference/p21.adoc +30 -0
- data/docs/_guides/reference/shtolo.adoc +36 -0
- data/exe/expressir-lsp +6 -0
- data/expressir.gemspec +2 -2
- data/lib/expressir/3.2/expressir_core.so +0 -0
- data/lib/expressir/3.3/expressir_core.so +0 -0
- data/lib/expressir/3.4/expressir_core.so +0 -0
- data/lib/expressir/4.0/expressir_core.so +0 -0
- data/lib/expressir/cli.rb +25 -0
- data/lib/expressir/commands/check.rb +3 -1
- data/lib/expressir/commands/export_json_schema.rb +29 -0
- data/lib/expressir/commands/export_owl.rb +26 -0
- data/lib/expressir/commands/smrl_xml_index.rb +25 -0
- data/lib/expressir/commands/validate.rb +3 -0
- data/lib/expressir/commands.rb +3 -0
- data/lib/expressir/conformance/trend.rb +100 -0
- data/lib/expressir/conformance.rb +8 -0
- data/lib/expressir/eval/environment.rb +83 -0
- data/lib/expressir/eval/expression.rb +610 -0
- data/lib/expressir/eval/interpreter.rb +316 -0
- data/lib/expressir/eval/rules.rb +88 -0
- data/lib/expressir/eval/values.rb +74 -0
- data/lib/expressir/eval.rb +17 -0
- data/lib/expressir/express/builder.rb +17 -5
- data/lib/expressir/express/builders/entity_decl_builder.rb +17 -5
- data/lib/expressir/express/builders/expression_builder.rb +9 -1
- data/lib/expressir/express/checker.rb +970 -5
- data/lib/expressir/express/dialect.rb +44 -0
- data/lib/expressir/express/formatters/declarations_formatter.rb +21 -14
- data/lib/expressir/express/formatters/expressions_formatter.rb +4 -1
- data/lib/expressir/express/parser.rb +9 -4
- data/lib/expressir/express/pretty_formatter.rb +9 -14
- data/lib/expressir/express/schema_compare.rb +194 -0
- data/lib/expressir/express/shtolo.rb +194 -63
- data/lib/expressir/express.rb +3 -0
- data/lib/expressir/interop/json_schema.rb +195 -0
- data/lib/expressir/interop/owl.rb +216 -0
- data/lib/expressir/interop.rb +13 -0
- data/lib/expressir/lsp/document.rb +226 -0
- data/lib/expressir/lsp/server.rb +184 -0
- data/lib/expressir/lsp/workspace.rb +402 -0
- data/lib/expressir/lsp.rb +11 -0
- data/lib/expressir/model/declarations/entity.rb +132 -0
- data/lib/expressir/model/declarations/schema.rb +37 -6
- data/lib/expressir/p21/builder.rb +92 -0
- data/lib/expressir/p21/grammar.rb +107 -0
- data/lib/expressir/p21/parser.rb +16 -0
- data/lib/expressir/p21/population.rb +35 -0
- data/lib/expressir/p21/validator.rb +213 -0
- data/lib/expressir/p21.rb +17 -0
- data/lib/expressir/reference/grammar_crosswalk.rb +122 -0
- data/lib/expressir/reference/grammar_crosswalk.yml +800 -0
- data/lib/expressir/reference/note_catalog.rb +54 -0
- data/lib/expressir/reference.rb +10 -0
- data/lib/expressir/version.rb +1 -1
- data/lib/expressir.rb +5 -0
- data/lib/tasks/conformance.rake +27 -0
- data/lib/tasks/reference.rake +25 -0
- metadata +68 -4
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Expressir
|
|
4
|
+
module Express
|
|
5
|
+
# Edition awareness for ISO 10303-11. The grammar accepts the union
|
|
6
|
+
# of both editions' text syntax; edition gating is reported by the
|
|
7
|
+
# Checker so a 1994 consumer sees exactly which declarations use
|
|
8
|
+
# 2004-only constructs, and a 2004 consumer sees the edition-1
|
|
9
|
+
# syntax the 2004 edition deprecates (Annex K).
|
|
10
|
+
module Dialect
|
|
11
|
+
EDITION_1994 = :"10303-11:1994"
|
|
12
|
+
EDITION_2004 = :"10303-11:2004"
|
|
13
|
+
DIALECTS = [EDITION_1994, EDITION_2004].freeze
|
|
14
|
+
DEFAULT = EDITION_2004
|
|
15
|
+
|
|
16
|
+
# The audited change list lives in
|
|
17
|
+
# spec/fixtures/dialects/iso-10303-11-2004-changes.yaml; every
|
|
18
|
+
# construct there maps to a note id below.
|
|
19
|
+
NOTE_IDS = %i[
|
|
20
|
+
check_dialect_2004_only
|
|
21
|
+
check_dialect_deprecated
|
|
22
|
+
check_dialect_mixed
|
|
23
|
+
].freeze
|
|
24
|
+
|
|
25
|
+
module_function
|
|
26
|
+
|
|
27
|
+
def normalize(value)
|
|
28
|
+
return DEFAULT if value.nil?
|
|
29
|
+
|
|
30
|
+
candidate = value.to_s
|
|
31
|
+
dialect = DIALECTS.find do |entry|
|
|
32
|
+
entry.to_s == candidate || entry.to_s.end_with?(":#{candidate}")
|
|
33
|
+
end
|
|
34
|
+
return dialect if dialect
|
|
35
|
+
|
|
36
|
+
raise Expressir::InvalidOptionError.new(
|
|
37
|
+
"dialect",
|
|
38
|
+
value,
|
|
39
|
+
valid_options: DIALECTS.map(&:to_s),
|
|
40
|
+
)
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
end
|
|
@@ -110,20 +110,15 @@ module Expressir
|
|
|
110
110
|
end
|
|
111
111
|
|
|
112
112
|
def format_declarations_entity(node)
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
x.kind == Model::Declarations::Attribute::DERIVED
|
|
123
|
-
end
|
|
124
|
-
inverse_attributes = node.attributes.select do |x|
|
|
125
|
-
x.kind == Model::Declarations::Attribute::INVERSE
|
|
126
|
-
end
|
|
113
|
+
attrs = entity_attribute_items(node)
|
|
114
|
+
explicit_attributes = attrs.select do |x|
|
|
115
|
+
x.kind == Model::Declarations::Attribute::EXPLICIT
|
|
116
|
+
end
|
|
117
|
+
derived_attributes = attrs.select do |x|
|
|
118
|
+
x.kind == Model::Declarations::Attribute::DERIVED
|
|
119
|
+
end
|
|
120
|
+
inverse_attributes = attrs.select do |x|
|
|
121
|
+
x.kind == Model::Declarations::Attribute::INVERSE
|
|
127
122
|
end
|
|
128
123
|
|
|
129
124
|
indent_const = self.class.const_get(:INDENT)
|
|
@@ -231,6 +226,18 @@ module Expressir
|
|
|
231
226
|
].join("\n")
|
|
232
227
|
end
|
|
233
228
|
|
|
229
|
+
# Entity#attributes is the Ruby-parse layout (every attribute kind
|
|
230
|
+
# in one list). The core path parks derived/inverse in their own
|
|
231
|
+
# collections instead. Both may also arrive as Array-of-Array when
|
|
232
|
+
# a builder nested list productions. Normalize to a flat list of
|
|
233
|
+
# Attribute-like nodes before selecting on `kind`.
|
|
234
|
+
def entity_attribute_items(node)
|
|
235
|
+
items = Array(node.attributes)
|
|
236
|
+
items += Array(node.derived_attributes) if node.respond_to?(:derived_attributes)
|
|
237
|
+
items += Array(node.inverse_attributes) if node.respond_to?(:inverse_attributes)
|
|
238
|
+
items.flatten.compact.uniq(&:object_id)
|
|
239
|
+
end
|
|
240
|
+
|
|
234
241
|
def format_declarations_function(node)
|
|
235
242
|
indent_char = self.class.const_get(:INDENT_CHAR)
|
|
236
243
|
[
|
|
@@ -154,7 +154,10 @@ module Expressir
|
|
|
154
154
|
|
|
155
155
|
def format_expressions_unary_expression(node)
|
|
156
156
|
needs_parens = node.operand.is_a?(Model::Expressions::BinaryExpression) ||
|
|
157
|
-
node.operand.is_a?(Model::Expressions::Interval)
|
|
157
|
+
node.operand.is_a?(Model::Expressions::Interval) ||
|
|
158
|
+
# Nested unary must stay parenthesized: "- -x" is not a
|
|
159
|
+
# simple factor, and "--x" opens a comment.
|
|
160
|
+
node.operand.is_a?(Model::Expressions::UnaryExpression)
|
|
158
161
|
|
|
159
162
|
[
|
|
160
163
|
case node.operator
|
|
@@ -56,8 +56,11 @@ module Expressir
|
|
|
56
56
|
# @raise [Error::SchemaParseFailure] if the schema file fails to parse
|
|
57
57
|
def self.from_file(file, skip_references: nil, include_source: nil,
|
|
58
58
|
root_path: nil, use_native: nil,
|
|
59
|
-
use_core: nil) # rubocop:disable Metrics/AbcSize
|
|
60
|
-
|
|
59
|
+
use_core: nil, dialect: nil) # rubocop:disable Metrics/AbcSize
|
|
60
|
+
Dialect.normalize(dialect)
|
|
61
|
+
# use_native: false pins the Ruby grammar path; the core path
|
|
62
|
+
# must not silently take over that request.
|
|
63
|
+
use_core = Core::NATIVE_AVAILABLE if use_core.nil? && use_native != false
|
|
61
64
|
if use_core && Core::NATIVE_AVAILABLE
|
|
62
65
|
return from_file_core(file, skip_references: skip_references,
|
|
63
66
|
include_source: include_source,
|
|
@@ -115,7 +118,8 @@ module Expressir
|
|
|
115
118
|
# @return [Model::Repository] Repository containing all parsed ExpFiles
|
|
116
119
|
def self.from_files(files, skip_references: nil, include_source: nil,
|
|
117
120
|
root_path: nil, use_native: nil, max_processes: nil,
|
|
118
|
-
compiled_set: nil, &progress)
|
|
121
|
+
compiled_set: nil, dialect: nil, &progress)
|
|
122
|
+
Dialect.normalize(dialect)
|
|
119
123
|
set_path = compiled_set || ENV.fetch("EXPRESSIR_COMPILED_SET", nil)
|
|
120
124
|
if set_path && core_set_available?
|
|
121
125
|
repo = from_compiled_set(set_path, files,
|
|
@@ -457,7 +461,8 @@ include_source: nil, root_path: nil, use_native: nil, &block)
|
|
|
457
461
|
# @return [Model::ExpFile] Parsed ExpFile
|
|
458
462
|
# @raise [Error::SchemaParseFailure] if the content fails to parse
|
|
459
463
|
def self.from_exp(content, skip_references: nil, include_source: nil,
|
|
460
|
-
use_native: nil, use_streaming: false)
|
|
464
|
+
use_native: nil, use_streaming: false, dialect: nil)
|
|
465
|
+
Dialect.normalize(dialect)
|
|
461
466
|
content = strip_bom(content)
|
|
462
467
|
if use_streaming
|
|
463
468
|
raise Error::StreamingUnsupportedError
|
|
@@ -112,20 +112,15 @@ module Expressir
|
|
|
112
112
|
# @param node [Model::Declarations::Entity] Entity node
|
|
113
113
|
# @return [String] Formatted entity
|
|
114
114
|
def format_declarations_entity(node)
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
x.kind == Model::Declarations::Attribute::DERIVED
|
|
125
|
-
end
|
|
126
|
-
inverse_attributes = node.attributes.select do |x|
|
|
127
|
-
x.kind == Model::Declarations::Attribute::INVERSE
|
|
128
|
-
end
|
|
115
|
+
attrs = entity_attribute_items(node)
|
|
116
|
+
explicit_attributes = attrs.select do |x|
|
|
117
|
+
x.kind == Model::Declarations::Attribute::EXPLICIT
|
|
118
|
+
end
|
|
119
|
+
derived_attributes = attrs.select do |x|
|
|
120
|
+
x.kind == Model::Declarations::Attribute::DERIVED
|
|
121
|
+
end
|
|
122
|
+
inverse_attributes = attrs.select do |x|
|
|
123
|
+
x.kind == Model::Declarations::Attribute::INVERSE
|
|
129
124
|
end
|
|
130
125
|
|
|
131
126
|
indent_str = " " * @config.indent
|
|
@@ -0,0 +1,194 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Expressir
|
|
4
|
+
module Express
|
|
5
|
+
# Structural diff of two schemata with a machine-applicable patch
|
|
6
|
+
# (TODO.eengine/07; eeng --compare/--patch semantics, plugin/p11
|
|
7
|
+
# compare entry). Declarations match by canonical path
|
|
8
|
+
# `schema.item`; entity attributes get their own sub-deltas at
|
|
9
|
+
# `schema.item.attribute` carrying types, optionality and
|
|
10
|
+
# aggregates inside the signature.
|
|
11
|
+
#
|
|
12
|
+
# Interfaces are out of scope: the patch target is a flattened
|
|
13
|
+
# (longform) schema, which has none.
|
|
14
|
+
module SchemaCompare
|
|
15
|
+
# `node` carries the revised declaration for :added and :changed
|
|
16
|
+
# (nil for :removed) so {SchemaPatch.apply} can rebuild.
|
|
17
|
+
Delta = Struct.new(:op, :path, :node, keyword_init: true)
|
|
18
|
+
|
|
19
|
+
# Resolution artifacts and presentation metadata are not part of
|
|
20
|
+
# a declaration's structure.
|
|
21
|
+
SIGNATURE_IGNORE = %w[source include_source remarks remark_items
|
|
22
|
+
ref base_path file].freeze
|
|
23
|
+
|
|
24
|
+
COLLECTIONS = %i[constants types entities subtype_constraints
|
|
25
|
+
functions rules procedures].freeze
|
|
26
|
+
|
|
27
|
+
NODE_COLLECTION = {
|
|
28
|
+
"Expressir::Model::Declarations::Constant" => :constants,
|
|
29
|
+
"Expressir::Model::Declarations::Type" => :types,
|
|
30
|
+
"Expressir::Model::Declarations::Entity" => :entities,
|
|
31
|
+
"Expressir::Model::Declarations::SubtypeConstraint" => :subtype_constraints,
|
|
32
|
+
"Expressir::Model::Declarations::Function" => :functions,
|
|
33
|
+
"Expressir::Model::Declarations::Rule" => :rules,
|
|
34
|
+
"Expressir::Model::Declarations::Procedure" => :procedures,
|
|
35
|
+
}.freeze
|
|
36
|
+
|
|
37
|
+
module_function
|
|
38
|
+
|
|
39
|
+
def compare(base, revised)
|
|
40
|
+
COLLECTIONS.flat_map { |collection| collection_deltas(base, revised, collection) }
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def collection_deltas(base, revised, collection)
|
|
44
|
+
base_items = index(base, collection)
|
|
45
|
+
revised_items = index(revised, collection)
|
|
46
|
+
deltas = (revised_items.keys - base_items.keys).map do |path|
|
|
47
|
+
Delta.new(op: :added, path: path, node: revised_items[path])
|
|
48
|
+
end
|
|
49
|
+
(base_items.keys - revised_items.keys).each do |path|
|
|
50
|
+
deltas << Delta.new(op: :removed, path: path, node: nil)
|
|
51
|
+
end
|
|
52
|
+
(base_items.keys & revised_items.keys).each do |path|
|
|
53
|
+
old_item = base_items[path]
|
|
54
|
+
new_item = revised_items[path]
|
|
55
|
+
next if signature(old_item) == signature(new_item)
|
|
56
|
+
|
|
57
|
+
deltas << Delta.new(op: :changed, path: path, node: new_item)
|
|
58
|
+
next unless new_item.is_a?(Model::Declarations::Entity)
|
|
59
|
+
|
|
60
|
+
deltas.concat(attribute_deltas(old_item, new_item, path))
|
|
61
|
+
end
|
|
62
|
+
deltas
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
def attribute_deltas(old_item, new_item, path)
|
|
66
|
+
old_attrs = index_by_id(Array(old_item.attributes))
|
|
67
|
+
new_attrs = index_by_id(Array(new_item.attributes))
|
|
68
|
+
deltas = (new_attrs.keys - old_attrs.keys).map do |name|
|
|
69
|
+
Delta.new(op: :added, path: "#{path}.#{name}",
|
|
70
|
+
node: new_attrs[name])
|
|
71
|
+
end
|
|
72
|
+
(old_attrs.keys - new_attrs.keys).each do |name|
|
|
73
|
+
deltas << Delta.new(op: :removed, path: "#{path}.#{name}", node: nil)
|
|
74
|
+
end
|
|
75
|
+
(old_attrs.keys & new_attrs.keys).each do |name|
|
|
76
|
+
next if signature(old_attrs[name]) == signature(new_attrs[name])
|
|
77
|
+
|
|
78
|
+
deltas << Delta.new(op: :changed, path: "#{path}.#{name}",
|
|
79
|
+
node: new_attrs[name])
|
|
80
|
+
end
|
|
81
|
+
deltas
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
def index_by_id(items)
|
|
85
|
+
items.filter_map { |item| [item.id&.downcase, item] if item.id }.to_h
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
def index(schema, collection)
|
|
89
|
+
index_by_id(Array(schema.send(collection)))
|
|
90
|
+
.transform_keys { |key| "#{schema.id.downcase}.#{key}" }
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
def signature(node)
|
|
94
|
+
strip(node.to_hash)
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
def strip(value)
|
|
98
|
+
case value
|
|
99
|
+
when Hash
|
|
100
|
+
value.each_with_object({}) do |(key, inner), out|
|
|
101
|
+
next if SIGNATURE_IGNORE.include?(key.to_s)
|
|
102
|
+
|
|
103
|
+
out[key] = strip(inner)
|
|
104
|
+
end
|
|
105
|
+
when Array then value.map { |inner| strip(inner) }
|
|
106
|
+
else value
|
|
107
|
+
end
|
|
108
|
+
end
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
# Applies {SchemaCompare} deltas to a schema model, returning the
|
|
112
|
+
# revised schema. The base schema is deep-copied through the
|
|
113
|
+
# lutaml-model round trip; the input schema is never mutated.
|
|
114
|
+
module SchemaPatch
|
|
115
|
+
module_function
|
|
116
|
+
|
|
117
|
+
def apply(base, deltas)
|
|
118
|
+
revised = base.class.from_hash(base.to_hash)
|
|
119
|
+
item_deltas, attribute_deltas = deltas.partition do |delta|
|
|
120
|
+
delta.path.split(".").size == 2
|
|
121
|
+
end
|
|
122
|
+
item_deltas.each { |delta| apply_item(revised, delta) }
|
|
123
|
+
# An item-level :added/:changed node already carries the final
|
|
124
|
+
# attribute list; its sub-deltas are informational only.
|
|
125
|
+
replaced_ops = %i[added changed].freeze
|
|
126
|
+
replaced = item_deltas
|
|
127
|
+
.select { |delta| replaced_ops.include?(delta.op) }
|
|
128
|
+
.map(&:path)
|
|
129
|
+
attribute_deltas
|
|
130
|
+
.reject { |delta| replaced.include?(delta.path.split(".")[0..1].join(".")) }
|
|
131
|
+
.each { |delta| apply_attribute(revised, delta) }
|
|
132
|
+
revised
|
|
133
|
+
end
|
|
134
|
+
|
|
135
|
+
def apply_item(schema, delta)
|
|
136
|
+
item_key = delta.path.split(".").last.downcase
|
|
137
|
+
collection = if delta.op == :added
|
|
138
|
+
schema.send(SchemaCompare::NODE_COLLECTION.fetch(delta.node.class.name))
|
|
139
|
+
else
|
|
140
|
+
live_collection(schema, item_key)
|
|
141
|
+
end
|
|
142
|
+
return unless collection
|
|
143
|
+
|
|
144
|
+
case delta.op
|
|
145
|
+
when :added then collection << delta.node
|
|
146
|
+
when :removed then collection.delete_if { |item| key(item) == item_key }
|
|
147
|
+
when :changed
|
|
148
|
+
index = collection.index { |item| key(item) == item_key }
|
|
149
|
+
return unless index
|
|
150
|
+
|
|
151
|
+
collection.delete_at(index)
|
|
152
|
+
collection.insert(index, delta.node)
|
|
153
|
+
end
|
|
154
|
+
end
|
|
155
|
+
|
|
156
|
+
def apply_attribute(schema, delta)
|
|
157
|
+
segments = delta.path.split(".")
|
|
158
|
+
item = SchemaCompare.index_by_id(all_declarations(schema))[segments[1].downcase]
|
|
159
|
+
return unless item
|
|
160
|
+
|
|
161
|
+
attr_key = segments[2]
|
|
162
|
+
attrs = Array(item.attributes)
|
|
163
|
+
case delta.op
|
|
164
|
+
when :added
|
|
165
|
+
item.attributes = [*attrs, delta.node]
|
|
166
|
+
when :removed
|
|
167
|
+
item.attributes = attrs.reject { |attr| key(attr) == attr_key }
|
|
168
|
+
when :changed
|
|
169
|
+
index = attrs.index { |attr| key(attr) == attr_key }
|
|
170
|
+
return unless index
|
|
171
|
+
|
|
172
|
+
item.attributes = attrs
|
|
173
|
+
item.attributes.delete_at(index)
|
|
174
|
+
item.attributes.insert(index, delta.node)
|
|
175
|
+
end
|
|
176
|
+
end
|
|
177
|
+
|
|
178
|
+
def live_collection(schema, item_key)
|
|
179
|
+
SchemaCompare::COLLECTIONS.map { |name| schema.send(name) }
|
|
180
|
+
.find do |collection|
|
|
181
|
+
Array(collection).any? { |item| key(item) == item_key }
|
|
182
|
+
end
|
|
183
|
+
end
|
|
184
|
+
|
|
185
|
+
def all_declarations(schema)
|
|
186
|
+
SchemaCompare::COLLECTIONS.flat_map { |name| Array(schema.send(name)) }
|
|
187
|
+
end
|
|
188
|
+
|
|
189
|
+
def key(item)
|
|
190
|
+
item.id&.downcase
|
|
191
|
+
end
|
|
192
|
+
end
|
|
193
|
+
end
|
|
194
|
+
end
|