expressir 2.4.17 → 2.4.18
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/expressir/commands/mapping_validate.rb +30 -10
- data/lib/expressir/mapping/refpath.rb +338 -0
- data/lib/expressir/mapping.rb +20 -0
- data/lib/expressir/version.rb +1 -1
- metadata +2 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 71c43838ba6c2853f84bfa1faca26760abe4919d35b98895ef224866bd49da62
|
|
4
|
+
data.tar.gz: 715a55be95f4ead656b98e0df59ccf06c2036eb532d6990a8ea036ee5f0f78c4
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: dac93ad5688fc8e2d3814fd6933b352ea35d78b89f157e99f30114ddafb07b53aaeb80aaa45fd63540a9ee400ac1aacc3724800446f868472499d9d4cf501629
|
|
7
|
+
data.tar.gz: 12cd7ef87afa0510215d0c1315e68ba5bbaed5a910255ebfa40e18b328b25f87ef3a24f93ce1073a798ce0e748e6344861f05900f863377ed801d9f1f66e7bab
|
|
@@ -2,27 +2,47 @@
|
|
|
2
2
|
|
|
3
3
|
module Expressir
|
|
4
4
|
module Commands
|
|
5
|
-
# `expressir mapping validate` (#88): load a module mapping.yaml
|
|
6
|
-
#
|
|
7
|
-
# interface closure. Exits 1 when unknown
|
|
5
|
+
# `expressir mapping validate` (#88): load a module mapping.yaml,
|
|
6
|
+
# resolve every <<express:...>> link and every reference path
|
|
7
|
+
# against the ARM/MIM interface closure. Exits 1 when unknown
|
|
8
|
+
# links or refpath issues remain.
|
|
8
9
|
class MappingValidate < Base
|
|
9
10
|
def run(path)
|
|
10
11
|
document = Expressir::Mapping.load_file(path)
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
if unknown.empty?
|
|
14
|
-
say "all links resolve"
|
|
15
|
-
return
|
|
16
|
-
end
|
|
12
|
+
repository = repository_for(path)
|
|
17
13
|
|
|
14
|
+
unknown = Expressir::Mapping.unknown_links(document, repository)
|
|
18
15
|
unknown.each do |link|
|
|
19
16
|
say "unknown: #{link.text}"
|
|
20
17
|
end
|
|
21
|
-
|
|
18
|
+
|
|
19
|
+
issues = refpath_issues(document, repository)
|
|
20
|
+
issues.each do |location, issue|
|
|
21
|
+
say "refpath #{location} [step #{issue.step}]: #{issue.message}"
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
total = unknown.size + issues.size
|
|
25
|
+
unless total.zero?
|
|
26
|
+
raise Thor::Error, "#{unknown.size} unknown link(s), " \
|
|
27
|
+
"#{issues.size} refpath issue(s)"
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
say "all links and reference paths resolve"
|
|
22
31
|
end
|
|
23
32
|
|
|
24
33
|
private
|
|
25
34
|
|
|
35
|
+
def refpath_issues(document, repository)
|
|
36
|
+
Expressir::Mapping.refpaths(document).flat_map do |location, content|
|
|
37
|
+
parsed = Expressir::Mapping::RefPath.parse(content)
|
|
38
|
+
parsed.parse_errors.map do |error|
|
|
39
|
+
[location, Expressir::Mapping::RefPath::Issue.new(step: nil,
|
|
40
|
+
message: error)]
|
|
41
|
+
end + Expressir::Mapping::RefPath.validate(parsed, repository)
|
|
42
|
+
.map { |issue| [location, issue] }
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
|
|
26
46
|
def repository_for(path)
|
|
27
47
|
dir = File.dirname(File.expand_path(path))
|
|
28
48
|
arm = Dir[File.join(dir, "arm.exp")].first
|
|
@@ -0,0 +1,338 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Expressir
|
|
4
|
+
module Mapping
|
|
5
|
+
# Formalization, parser, and validator for MIM reference paths
|
|
6
|
+
# (expressir#88): the "Reference path" sections of module
|
|
7
|
+
# mapping.yaml files, written in the ISO 10303 module mapping
|
|
8
|
+
# notation. Until now the notation was prose for humans only; this
|
|
9
|
+
# makes it machine-readable and checks it against real schemas.
|
|
10
|
+
#
|
|
11
|
+
# Grammar (corpus-verified over all module mapping.yaml refpaths):
|
|
12
|
+
# path := step+
|
|
13
|
+
# step := [link] node [index] [constraint]
|
|
14
|
+
# | MARKER
|
|
15
|
+
# link := "->" | "<-" | "<=" | "=>" | "=" NAME
|
|
16
|
+
# (relation to the previous node)
|
|
17
|
+
# node := NAME ["." ATTRIBUTE]
|
|
18
|
+
# index := "[" ("i" | DIGITS) "]"
|
|
19
|
+
# MARKER := "{" | "}" | "[" | "]" | "(" | ")" | ":" | "|" | "!"
|
|
20
|
+
# | "," | "/" | "*" (grouping/annotation; no
|
|
21
|
+
# node semantics; never
|
|
22
|
+
# breaks a pending link)
|
|
23
|
+
# constraint := "=" STRING
|
|
24
|
+
#
|
|
25
|
+
# Operator semantics per the module conventions:
|
|
26
|
+
# -> the attribute named before -> references the entity or
|
|
27
|
+
# select type named after ->
|
|
28
|
+
# <- the attribute named before <- is referenced by the type
|
|
29
|
+
# named after <-
|
|
30
|
+
# <= the entity named before <= is a subtype of the entity after
|
|
31
|
+
# => the entity named before => is a supertype of the entity after
|
|
32
|
+
# = the node before = is of the type named after (used inside
|
|
33
|
+
# parenthesized alternatives, e.g. `(unit = named_unit)`)
|
|
34
|
+
# [i] the attribute named before [i] is an aggregate; any element
|
|
35
|
+
# of that aggregate is referred to
|
|
36
|
+
module RefPath
|
|
37
|
+
# Wire-name mapping is declared here so a parsed path can itself
|
|
38
|
+
# be serialized with lutaml-model.
|
|
39
|
+
class Step < Lutaml::Model::Serializable
|
|
40
|
+
attribute :operator, :string
|
|
41
|
+
attribute :name, :string
|
|
42
|
+
attribute :attribute, :string
|
|
43
|
+
attribute :index, :string
|
|
44
|
+
attribute :literal, :string
|
|
45
|
+
|
|
46
|
+
key_value do
|
|
47
|
+
map "operator", to: :operator
|
|
48
|
+
map "name", to: :name
|
|
49
|
+
map "attribute", to: :attribute
|
|
50
|
+
map "index", to: :index
|
|
51
|
+
map "literal", to: :literal
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
class Path < Lutaml::Model::Serializable
|
|
56
|
+
attribute :steps, Step, collection: true
|
|
57
|
+
attribute :parse_errors, :string, collection: true
|
|
58
|
+
|
|
59
|
+
key_value do
|
|
60
|
+
map "steps", to: :steps
|
|
61
|
+
map "parse_errors", to: :parse_errors
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
TOKEN = /->|<-|<=|=>|[{}\[\]()=:|!,*\/]|'(?:''|[^'])*'|\w+(?:\.\w+)?/
|
|
66
|
+
MARKERS = ["{", "}", "[", "]", "(", ")", ":", "|", "!", ",", "/",
|
|
67
|
+
"*"].freeze
|
|
68
|
+
LINKS = ["->", "<-", "<=", "=>"].freeze
|
|
69
|
+
DECLARATION_COLLECTIONS = %i[types entities].freeze
|
|
70
|
+
private_constant :TOKEN, :MARKERS, :LINKS, :DECLARATION_COLLECTIONS
|
|
71
|
+
|
|
72
|
+
Issue = Struct.new(:step, :message, keyword_init: true)
|
|
73
|
+
|
|
74
|
+
module_function
|
|
75
|
+
|
|
76
|
+
# @param content [String] the mapping.yaml refpath content block
|
|
77
|
+
# @return [Path]
|
|
78
|
+
def parse(content)
|
|
79
|
+
tokens = content.to_s.scan(TOKEN)
|
|
80
|
+
steps = []
|
|
81
|
+
errors = []
|
|
82
|
+
pending_link = nil
|
|
83
|
+
pending_type_assign = false
|
|
84
|
+
i = 0
|
|
85
|
+
while i < tokens.length
|
|
86
|
+
token = tokens[i]
|
|
87
|
+
case token
|
|
88
|
+
when *LINKS
|
|
89
|
+
pending_link = token
|
|
90
|
+
when "="
|
|
91
|
+
literal = tokens[i + 1]
|
|
92
|
+
if literal&.start_with?("'")
|
|
93
|
+
attach(steps, errors, :literal, literal)
|
|
94
|
+
i += 1
|
|
95
|
+
elsif literal.nil?
|
|
96
|
+
errors << "constraint '=' with nothing after it"
|
|
97
|
+
else
|
|
98
|
+
# `x = Type` type assignment: the following node (however
|
|
99
|
+
# many markers it sits behind, e.g. /MAPPING_OF(X)/)
|
|
100
|
+
# carries the "=" link.
|
|
101
|
+
pending_type_assign = true
|
|
102
|
+
end
|
|
103
|
+
when "["
|
|
104
|
+
if index_token?(tokens[i + 1])
|
|
105
|
+
attach(steps, errors, :index, tokens[i + 1])
|
|
106
|
+
i += 2 # the index token and the closing "]"
|
|
107
|
+
else
|
|
108
|
+
steps << Step.new(operator: "[")
|
|
109
|
+
end
|
|
110
|
+
when *MARKERS
|
|
111
|
+
steps << Step.new(operator: token)
|
|
112
|
+
else
|
|
113
|
+
name, _, attribute = token.partition(".")
|
|
114
|
+
if name.casecmp("MAPPING_OF").zero? && tokens[i + 1] == "("
|
|
115
|
+
# notation wrapper: /MAPPING_OF(X)/ refers to X; the
|
|
116
|
+
# wrapper itself carries no node semantics
|
|
117
|
+
steps << Step.new(operator: token)
|
|
118
|
+
else
|
|
119
|
+
operator = pending_link || ("=" if pending_type_assign)
|
|
120
|
+
steps << Step.new(operator: operator, name: name,
|
|
121
|
+
attribute: attribute.empty? ? nil : attribute)
|
|
122
|
+
pending_link = nil
|
|
123
|
+
pending_type_assign = false
|
|
124
|
+
end
|
|
125
|
+
end
|
|
126
|
+
i += 1
|
|
127
|
+
end
|
|
128
|
+
Path.new(steps: steps, parse_errors: errors)
|
|
129
|
+
end
|
|
130
|
+
|
|
131
|
+
def index_token?(token)
|
|
132
|
+
token == "i" || token&.match?(/\A\d+\z/)
|
|
133
|
+
end
|
|
134
|
+
|
|
135
|
+
# Attach an index or constraint literal to the most recent node
|
|
136
|
+
# step that does not carry one yet.
|
|
137
|
+
def attach(steps, errors, field, value)
|
|
138
|
+
step = steps.reverse.each.find { |s| s.name && !s.public_send(field) }
|
|
139
|
+
if step
|
|
140
|
+
step.public_send("#{field}=", value)
|
|
141
|
+
else
|
|
142
|
+
case field
|
|
143
|
+
when :index then errors << "aggregate index [#{value}] has no node"
|
|
144
|
+
when :literal then errors << "constraint #{value} has no node"
|
|
145
|
+
end
|
|
146
|
+
end
|
|
147
|
+
end
|
|
148
|
+
|
|
149
|
+
# Validate a parsed path against a repository. Issues reference
|
|
150
|
+
# the step index they attach to.
|
|
151
|
+
# @return [Array<Issue>]
|
|
152
|
+
def validate(path, repository)
|
|
153
|
+
by_name = name_index(repository)
|
|
154
|
+
issues = []
|
|
155
|
+
prev = nil
|
|
156
|
+
path.steps.each_with_index do |step, pos|
|
|
157
|
+
if MARKERS.include?(step.operator)
|
|
158
|
+
prev = nil
|
|
159
|
+
next
|
|
160
|
+
end
|
|
161
|
+
|
|
162
|
+
issues.concat(check_step(step, pos, prev, by_name))
|
|
163
|
+
prev = step if step.name
|
|
164
|
+
end
|
|
165
|
+
issues
|
|
166
|
+
end
|
|
167
|
+
|
|
168
|
+
def name_index(repository)
|
|
169
|
+
repository.schemas.flat_map do |schema|
|
|
170
|
+
DECLARATION_COLLECTIONS.flat_map do |coll|
|
|
171
|
+
Array(schema.public_send(coll))
|
|
172
|
+
.select { |d| d.respond_to?(:id) && d.id }
|
|
173
|
+
.map { |d| [d.id.safe_downcase, [schema, d]] }
|
|
174
|
+
end
|
|
175
|
+
end.to_h
|
|
176
|
+
end
|
|
177
|
+
|
|
178
|
+
def check_step(step, pos, prev, by_name)
|
|
179
|
+
found = by_name[step.name&.safe_downcase]
|
|
180
|
+
return [Issue.new(step: pos, message: "unknown type '#{step.name}'")] unless found
|
|
181
|
+
|
|
182
|
+
issues = attribute_issues(step, pos, found[1], by_name)
|
|
183
|
+
issues.concat(link_issues(step, pos, prev, by_name))
|
|
184
|
+
issues
|
|
185
|
+
end
|
|
186
|
+
|
|
187
|
+
def attribute_issues(step, pos, decl, by_name)
|
|
188
|
+
return [] unless step.attribute
|
|
189
|
+
|
|
190
|
+
unless decl.is_a?(Model::Declarations::Entity)
|
|
191
|
+
return [Issue.new(step: pos,
|
|
192
|
+
message: "'#{step.name}' is not an entity, " \
|
|
193
|
+
"cannot carry '#{step.name}.#{step.attribute}'")]
|
|
194
|
+
end
|
|
195
|
+
|
|
196
|
+
attribute = find_attribute(decl, step.attribute, by_name)
|
|
197
|
+
unless attribute
|
|
198
|
+
return [Issue.new(step: pos,
|
|
199
|
+
message: "entity '#{step.name}' has no attribute " \
|
|
200
|
+
"'#{step.attribute}'")]
|
|
201
|
+
end
|
|
202
|
+
|
|
203
|
+
aggregate_issue(step, pos, attribute)
|
|
204
|
+
end
|
|
205
|
+
|
|
206
|
+
# Direct or inherited attribute: inheritance follows the
|
|
207
|
+
# subtype_of chain, resolved through the repository-wide index.
|
|
208
|
+
def find_attribute(entity, attribute_id, by_name)
|
|
209
|
+
key = attribute_id.safe_downcase
|
|
210
|
+
supertype_chain(entity, by_name).lazy.flat_map do |candidate|
|
|
211
|
+
Array(candidate.attributes)
|
|
212
|
+
end.find { |a| a.id&.safe_downcase == key }
|
|
213
|
+
end
|
|
214
|
+
|
|
215
|
+
def supertype_chain(entity, by_name)
|
|
216
|
+
seen = {}.compare_by_identity
|
|
217
|
+
queue = [entity]
|
|
218
|
+
result = []
|
|
219
|
+
until queue.empty?
|
|
220
|
+
current = queue.shift
|
|
221
|
+
next if seen[current]
|
|
222
|
+
|
|
223
|
+
seen[current] = true
|
|
224
|
+
result << current
|
|
225
|
+
current.subtype_of.to_a.each do |ref|
|
|
226
|
+
sup = by_name[ref.respond_to?(:id) ? ref.id&.safe_downcase : nil]&.last
|
|
227
|
+
queue << sup if sup.is_a?(Model::Declarations::Entity)
|
|
228
|
+
end
|
|
229
|
+
end
|
|
230
|
+
result
|
|
231
|
+
end
|
|
232
|
+
|
|
233
|
+
def aggregate_issue(step, pos, attribute)
|
|
234
|
+
return [] unless step.index
|
|
235
|
+
return [] if attribute.type.respond_to?(:bound1)
|
|
236
|
+
|
|
237
|
+
[Issue.new(step: pos,
|
|
238
|
+
message: "'#{step.name}.#{step.attribute}' is not an " \
|
|
239
|
+
"aggregate but carries [#{step.index}]")]
|
|
240
|
+
end
|
|
241
|
+
|
|
242
|
+
def link_issues(step, pos, prev, by_name)
|
|
243
|
+
return [] unless LINKS.include?(step.operator)
|
|
244
|
+
unless prev && step.name
|
|
245
|
+
return [Issue.new(step: pos,
|
|
246
|
+
message: "#{step.operator} with no preceding node")]
|
|
247
|
+
end
|
|
248
|
+
|
|
249
|
+
case step.operator
|
|
250
|
+
when "->", "<-" then attribute_link_issues(step, pos, prev)
|
|
251
|
+
when "<=" then subtype_link_issues(step, pos, prev, by_name)
|
|
252
|
+
when "=>" then supertype_link_issues(step, pos, prev, by_name)
|
|
253
|
+
end
|
|
254
|
+
end
|
|
255
|
+
|
|
256
|
+
def attribute_link_issues(_step, pos, prev)
|
|
257
|
+
unless prev.attribute
|
|
258
|
+
return [Issue.new(step: pos,
|
|
259
|
+
message: "'->' requires an attribute-qualified " \
|
|
260
|
+
"node before it, got '#{prev.name}'")]
|
|
261
|
+
end
|
|
262
|
+
|
|
263
|
+
[]
|
|
264
|
+
end
|
|
265
|
+
|
|
266
|
+
# `a <= b`: a is a subtype of b.
|
|
267
|
+
def subtype_link_issues(step, pos, prev, by_name)
|
|
268
|
+
source = by_name[prev.name&.safe_downcase]&.last
|
|
269
|
+
target = by_name[step.name&.safe_downcase]&.last
|
|
270
|
+
return [] unless source.is_a?(Model::Declarations::Entity) &&
|
|
271
|
+
target.is_a?(Model::Declarations::Entity)
|
|
272
|
+
|
|
273
|
+
return [] if supertype_ids(source, by_name)
|
|
274
|
+
.any? { |id| id&.safe_downcase == step.name&.safe_downcase }
|
|
275
|
+
|
|
276
|
+
[Issue.new(step: pos,
|
|
277
|
+
message: "'#{prev.name}' is not a subtype of '#{step.name}'")]
|
|
278
|
+
end
|
|
279
|
+
|
|
280
|
+
# `a => b`: a is a supertype of b.
|
|
281
|
+
def supertype_link_issues(step, pos, prev, by_name)
|
|
282
|
+
source = by_name[step.name&.safe_downcase]&.last
|
|
283
|
+
target = by_name[prev.name&.safe_downcase]&.last
|
|
284
|
+
return [] unless source.is_a?(Model::Declarations::Entity) &&
|
|
285
|
+
target.is_a?(Model::Declarations::Entity)
|
|
286
|
+
|
|
287
|
+
return [] if supertype_ids(source, by_name)
|
|
288
|
+
.any? { |id| id&.safe_downcase == prev.name&.safe_downcase }
|
|
289
|
+
|
|
290
|
+
[Issue.new(step: pos,
|
|
291
|
+
message: "'#{prev.name}' is not a supertype of '#{step.name}'")]
|
|
292
|
+
end
|
|
293
|
+
|
|
294
|
+
# Supertype entity names of +entity+: the subtype_of references
|
|
295
|
+
# plus the leaves of the supertype expression tree, resolved
|
|
296
|
+
# through the repository-wide name index so inherited chains
|
|
297
|
+
# (including cross-schema USE FROM) walk correctly.
|
|
298
|
+
def supertype_ids(entity, by_name)
|
|
299
|
+
seen = {}.compare_by_identity
|
|
300
|
+
queue = [entity]
|
|
301
|
+
names = []
|
|
302
|
+
until queue.empty?
|
|
303
|
+
current = queue.shift
|
|
304
|
+
next if seen[current]
|
|
305
|
+
|
|
306
|
+
seen[current] = true
|
|
307
|
+
direct = current.subtype_of.to_a.filter_map do |ref|
|
|
308
|
+
ref.respond_to?(:id) ? ref.id : nil
|
|
309
|
+
end
|
|
310
|
+
leaves = expression_leaves(current.supertype_expression)
|
|
311
|
+
.filter_map { |leaf| leaf.respond_to?(:id) ? leaf.id : nil }
|
|
312
|
+
names.concat(direct)
|
|
313
|
+
names.concat(leaves)
|
|
314
|
+
direct.concat(leaves).each do |id|
|
|
315
|
+
sup = by_name[id&.safe_downcase]&.last
|
|
316
|
+
queue << sup if sup.is_a?(Model::Declarations::Entity)
|
|
317
|
+
end
|
|
318
|
+
end
|
|
319
|
+
names.uniq
|
|
320
|
+
end
|
|
321
|
+
|
|
322
|
+
# Leaf reference nodes of a supertype expression tree.
|
|
323
|
+
def expression_leaves(expr)
|
|
324
|
+
return [] unless expr
|
|
325
|
+
|
|
326
|
+
if expr.respond_to?(:operands)
|
|
327
|
+
Array(expr.operands).flat_map { |o| expression_leaves(o) }
|
|
328
|
+
elsif expr.respond_to?(:operand1)
|
|
329
|
+
expression_leaves(expr.operand1) + expression_leaves(expr.operand2)
|
|
330
|
+
elsif expr.respond_to?(:id) && expr.id
|
|
331
|
+
[expr]
|
|
332
|
+
else
|
|
333
|
+
[]
|
|
334
|
+
end
|
|
335
|
+
end
|
|
336
|
+
end
|
|
337
|
+
end
|
|
338
|
+
end
|
data/lib/expressir/mapping.rb
CHANGED
|
@@ -13,6 +13,8 @@ module Expressir
|
|
|
13
13
|
# are ignored by the mapping and survive round-trips untouched in
|
|
14
14
|
# the source files.
|
|
15
15
|
module Mapping
|
|
16
|
+
autoload :RefPath, "#{__dir__}/mapping/refpath"
|
|
17
|
+
|
|
16
18
|
EXPRESS_LINK = /<<express:([^,>]+)(?:,([^>]+))?>>/
|
|
17
19
|
|
|
18
20
|
class ReferencePath < Lutaml::Model::Serializable
|
|
@@ -137,5 +139,23 @@ module Expressir
|
|
|
137
139
|
element.aa.to_a.flat_map { |aa| [aa.attribute, aa.aimelt] }
|
|
138
140
|
end.compact
|
|
139
141
|
end
|
|
142
|
+
|
|
143
|
+
# Every reference path in the document as [location, content]
|
|
144
|
+
# pairs; location names the application element (and attribute,
|
|
145
|
+
# for attribute-level paths) the path belongs to.
|
|
146
|
+
def refpaths(document)
|
|
147
|
+
document.ae.to_a.flat_map do |element|
|
|
148
|
+
entries = []
|
|
149
|
+
if element.refpath&.content
|
|
150
|
+
entries << [element.entity.to_s, element.refpath.content]
|
|
151
|
+
end
|
|
152
|
+
element.aa.to_a.each do |aa|
|
|
153
|
+
next unless aa.refpath&.content
|
|
154
|
+
|
|
155
|
+
entries << ["#{element.entity}.#{aa.attribute}", aa.refpath.content]
|
|
156
|
+
end
|
|
157
|
+
entries
|
|
158
|
+
end
|
|
159
|
+
end
|
|
140
160
|
end
|
|
141
161
|
end
|
data/lib/expressir/version.rb
CHANGED
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: expressir
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 2.4.
|
|
4
|
+
version: 2.4.18
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Ribose Inc.
|
|
@@ -537,6 +537,7 @@ files:
|
|
|
537
537
|
- lib/expressir/manifest/resolver.rb
|
|
538
538
|
- lib/expressir/manifest/validator.rb
|
|
539
539
|
- lib/expressir/mapping.rb
|
|
540
|
+
- lib/expressir/mapping/refpath.rb
|
|
540
541
|
- lib/expressir/model.rb
|
|
541
542
|
- lib/expressir/model/cache.rb
|
|
542
543
|
- lib/expressir/model/concerns.rb
|