unitsdb 2.2.1 → 2.2.4
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/opal.yml +36 -0
- data/.gitignore +3 -0
- data/Gemfile +1 -0
- data/Rakefile +3 -1
- data/lib/unitsdb/cli.rb +5 -41
- data/lib/unitsdb/commands/_modify.rb +1 -34
- data/lib/unitsdb/commands/check_si/si_formatter.rb +6 -6
- data/lib/unitsdb/commands/check_si/si_matcher.rb +202 -292
- data/lib/unitsdb/commands/check_si/si_updater.rb +16 -36
- data/lib/unitsdb/commands/entity_presenter.rb +98 -0
- data/lib/unitsdb/commands/get.rb +16 -113
- data/lib/unitsdb/commands/qudt/formatter.rb +16 -27
- data/lib/unitsdb/commands/qudt/matcher.rb +18 -28
- data/lib/unitsdb/commands/qudt/updater.rb +8 -11
- data/lib/unitsdb/commands/qudt.rb +1 -34
- data/lib/unitsdb/commands/search.rb +33 -188
- data/lib/unitsdb/commands/thor.rb +41 -0
- data/lib/unitsdb/commands/ucum/formatter.rb +9 -18
- data/lib/unitsdb/commands/ucum/matcher.rb +4 -4
- data/lib/unitsdb/commands/ucum/updater.rb +3 -5
- data/lib/unitsdb/commands/ucum.rb +1 -34
- data/lib/unitsdb/commands/validate/qudt_references.rb +29 -70
- data/lib/unitsdb/commands/validate/references.rb +5 -303
- data/lib/unitsdb/commands/validate/si_references.rb +30 -66
- data/lib/unitsdb/commands/validate/ucum_references.rb +30 -64
- data/lib/unitsdb/commands/validate.rb +1 -36
- data/lib/unitsdb/commands.rb +2 -0
- data/lib/unitsdb/config.rb +170 -4
- data/lib/unitsdb/database/loader.rb +135 -0
- data/lib/unitsdb/database/reference_validator.rb +227 -0
- data/lib/unitsdb/database/uniqueness_validator.rb +80 -0
- data/lib/unitsdb/database.rb +127 -588
- data/lib/unitsdb/dimension.rb +2 -27
- data/lib/unitsdb/dimension_details.rb +2 -0
- data/lib/unitsdb/dimension_reference.rb +2 -0
- data/lib/unitsdb/dimensions.rb +2 -2
- data/lib/unitsdb/external_reference.rb +2 -0
- data/lib/unitsdb/identifier.rb +2 -0
- data/lib/unitsdb/localized_string.rb +2 -0
- data/lib/unitsdb/opal.rb +43 -0
- data/lib/unitsdb/prefix.rb +2 -13
- data/lib/unitsdb/prefix_reference.rb +2 -0
- data/lib/unitsdb/prefixes.rb +2 -2
- data/lib/unitsdb/quantities.rb +2 -1
- data/lib/unitsdb/quantity.rb +2 -2
- data/lib/unitsdb/quantity_reference.rb +2 -2
- data/lib/unitsdb/qudt.rb +5 -0
- data/lib/unitsdb/root_unit_reference.rb +2 -2
- data/lib/unitsdb/scale.rb +2 -2
- data/lib/unitsdb/scale_properties.rb +2 -1
- data/lib/unitsdb/scale_reference.rb +2 -0
- data/lib/unitsdb/scales.rb +2 -1
- data/lib/unitsdb/si_derived_base.rb +2 -1
- data/lib/unitsdb/symbol_presentations.rb +2 -2
- data/lib/unitsdb/ucum.rb +7 -0
- data/lib/unitsdb/unit.rb +2 -34
- data/lib/unitsdb/unit_reference.rb +2 -0
- data/lib/unitsdb/unit_system.rb +2 -2
- data/lib/unitsdb/unit_system_reference.rb +2 -0
- data/lib/unitsdb/unit_systems.rb +2 -2
- data/lib/unitsdb/units.rb +2 -2
- data/lib/unitsdb/version.rb +1 -1
- data/lib/unitsdb.rb +134 -27
- data/unitsdb.gemspec +1 -0
- metadata +23 -2
|
@@ -3,19 +3,13 @@
|
|
|
3
3
|
module Unitsdb
|
|
4
4
|
module Commands
|
|
5
5
|
module Validate
|
|
6
|
+
# `unitsdb validate references`. Thin presenter around
|
|
7
|
+
# Database::ReferenceValidator — owns output formatting only.
|
|
6
8
|
class References < Unitsdb::Commands::Base
|
|
7
9
|
def run
|
|
8
|
-
# Load the database
|
|
9
10
|
db = load_database(@options[:database])
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
registry = build_id_registry(db)
|
|
13
|
-
|
|
14
|
-
# Check all references
|
|
15
|
-
invalid_refs = check_references(db, registry)
|
|
16
|
-
|
|
17
|
-
# Display results
|
|
18
|
-
display_reference_results(invalid_refs, registry)
|
|
11
|
+
result = Unitsdb::Database::ReferenceValidator.new(db).validate
|
|
12
|
+
display_result(result.invalid)
|
|
19
13
|
rescue Unitsdb::Errors::DatabaseError => e
|
|
20
14
|
raise Unitsdb::Errors::ValidationError,
|
|
21
15
|
"Failed to validate references: #{e.message}"
|
|
@@ -23,309 +17,17 @@ module Unitsdb
|
|
|
23
17
|
|
|
24
18
|
private
|
|
25
19
|
|
|
26
|
-
def
|
|
27
|
-
registry = {}
|
|
28
|
-
|
|
29
|
-
# Add all unit identifiers to the registry
|
|
30
|
-
registry["units"] = {}
|
|
31
|
-
db.units.each_with_index do |unit, index|
|
|
32
|
-
unit.identifiers.each do |identifier|
|
|
33
|
-
next unless identifier.id && identifier.type
|
|
34
|
-
|
|
35
|
-
# Add the composite key (type:id)
|
|
36
|
-
composite_key = "#{identifier.type}:#{identifier.id}"
|
|
37
|
-
registry["units"][composite_key] = "index:#{index}"
|
|
38
|
-
|
|
39
|
-
# Also add just the ID for backward compatibility
|
|
40
|
-
registry["units"][identifier.id] = "index:#{index}"
|
|
41
|
-
end
|
|
42
|
-
end
|
|
43
|
-
|
|
44
|
-
# Add dimension identifiers
|
|
45
|
-
registry["dimensions"] = {}
|
|
46
|
-
db.dimensions.each_with_index do |dimension, index|
|
|
47
|
-
dimension.identifiers.each do |identifier|
|
|
48
|
-
next unless identifier.id && identifier.type
|
|
49
|
-
|
|
50
|
-
composite_key = "#{identifier.type}:#{identifier.id}"
|
|
51
|
-
registry["dimensions"][composite_key] = "index:#{index}"
|
|
52
|
-
registry["dimensions"][identifier.id] = "index:#{index}"
|
|
53
|
-
end
|
|
54
|
-
|
|
55
|
-
# Also track dimensions by short name
|
|
56
|
-
if dimension.respond_to?(:short) && dimension.short
|
|
57
|
-
registry["dimensions_short"] ||= {}
|
|
58
|
-
registry["dimensions_short"][dimension.short] = "index:#{index}"
|
|
59
|
-
end
|
|
60
|
-
end
|
|
61
|
-
|
|
62
|
-
# Add quantity identifiers
|
|
63
|
-
registry["quantities"] = {}
|
|
64
|
-
db.quantities.each_with_index do |quantity, index|
|
|
65
|
-
quantity.identifiers.each do |identifier|
|
|
66
|
-
next unless identifier.id && identifier.type
|
|
67
|
-
|
|
68
|
-
composite_key = "#{identifier.type}:#{identifier.id}"
|
|
69
|
-
registry["quantities"][composite_key] = "index:#{index}"
|
|
70
|
-
registry["quantities"][identifier.id] = "index:#{index}"
|
|
71
|
-
end
|
|
72
|
-
end
|
|
73
|
-
|
|
74
|
-
# Add prefix identifiers
|
|
75
|
-
registry["prefixes"] = {}
|
|
76
|
-
db.prefixes.each_with_index do |prefix, index|
|
|
77
|
-
prefix.identifiers.each do |identifier|
|
|
78
|
-
next unless identifier.id && identifier.type
|
|
79
|
-
|
|
80
|
-
composite_key = "#{identifier.type}:#{identifier.id}"
|
|
81
|
-
registry["prefixes"][composite_key] = "index:#{index}"
|
|
82
|
-
registry["prefixes"][identifier.id] = "index:#{index}"
|
|
83
|
-
end
|
|
84
|
-
end
|
|
85
|
-
|
|
86
|
-
# Add unit system identifiers
|
|
87
|
-
registry["unit_systems"] = {}
|
|
88
|
-
db.unit_systems.each_with_index do |unit_system, index|
|
|
89
|
-
unit_system.identifiers.each do |identifier|
|
|
90
|
-
next unless identifier.id && identifier.type
|
|
91
|
-
|
|
92
|
-
composite_key = "#{identifier.type}:#{identifier.id}"
|
|
93
|
-
registry["unit_systems"][composite_key] = "index:#{index}"
|
|
94
|
-
registry["unit_systems"][identifier.id] = "index:#{index}"
|
|
95
|
-
end
|
|
96
|
-
|
|
97
|
-
# Also track unit systems by short name
|
|
98
|
-
if unit_system.respond_to?(:short) && unit_system.short
|
|
99
|
-
registry["unit_systems_short"] ||= {}
|
|
100
|
-
registry["unit_systems_short"][unit_system.short] =
|
|
101
|
-
"index:#{index}"
|
|
102
|
-
end
|
|
103
|
-
end
|
|
104
|
-
|
|
105
|
-
# Debug registry if requested
|
|
106
|
-
if @options[:debug_registry]
|
|
107
|
-
puts "Registry contents:"
|
|
108
|
-
registry.each do |type, ids|
|
|
109
|
-
puts " #{type}:"
|
|
110
|
-
ids.each do |id, location|
|
|
111
|
-
puts " #{id} => #{location}"
|
|
112
|
-
end
|
|
113
|
-
end
|
|
114
|
-
end
|
|
115
|
-
|
|
116
|
-
registry
|
|
117
|
-
end
|
|
118
|
-
|
|
119
|
-
def check_references(db, registry)
|
|
120
|
-
invalid_refs = {}
|
|
121
|
-
|
|
122
|
-
# Check unit references in dimensions
|
|
123
|
-
check_dimension_references(db, registry, invalid_refs)
|
|
124
|
-
|
|
125
|
-
# Check unit_system references
|
|
126
|
-
check_unit_system_references(db, registry, invalid_refs)
|
|
127
|
-
|
|
128
|
-
# Check quantity references
|
|
129
|
-
check_quantity_references(db, registry, invalid_refs)
|
|
130
|
-
|
|
131
|
-
# Check root unit references in units
|
|
132
|
-
check_root_unit_references(db, registry, invalid_refs)
|
|
133
|
-
|
|
134
|
-
invalid_refs
|
|
135
|
-
end
|
|
136
|
-
|
|
137
|
-
def check_dimension_references(db, registry, invalid_refs)
|
|
138
|
-
db.dimensions.each_with_index do |dimension, index|
|
|
139
|
-
next unless dimension.respond_to?(:dimension_reference) && dimension.dimension_reference
|
|
140
|
-
|
|
141
|
-
ref_id = dimension.dimension_reference
|
|
142
|
-
ref_type = "dimensions"
|
|
143
|
-
ref_path = "dimensions:index:#{index}:dimension_reference"
|
|
144
|
-
|
|
145
|
-
validate_reference(ref_id, ref_type, ref_path, registry,
|
|
146
|
-
invalid_refs, "dimensions")
|
|
147
|
-
end
|
|
148
|
-
end
|
|
149
|
-
|
|
150
|
-
def check_unit_system_references(db, registry, invalid_refs)
|
|
151
|
-
db.units.each_with_index do |unit, index|
|
|
152
|
-
next unless unit.respond_to?(:unit_system_reference) && unit.unit_system_reference
|
|
153
|
-
|
|
154
|
-
unit.unit_system_reference.each_with_index do |ref_id, idx|
|
|
155
|
-
ref_type = "unit_systems"
|
|
156
|
-
ref_path = "units:index:#{index}:unit_system_reference[#{idx}]"
|
|
157
|
-
|
|
158
|
-
validate_reference(ref_id, ref_type, ref_path, registry,
|
|
159
|
-
invalid_refs, "units")
|
|
160
|
-
end
|
|
161
|
-
end
|
|
162
|
-
end
|
|
163
|
-
|
|
164
|
-
def check_quantity_references(db, registry, invalid_refs)
|
|
165
|
-
db.units.each_with_index do |unit, index|
|
|
166
|
-
next unless unit.respond_to?(:quantity_references) && unit.quantity_references
|
|
167
|
-
|
|
168
|
-
unit.quantity_references.each_with_index do |ref_id, idx|
|
|
169
|
-
ref_type = "quantities"
|
|
170
|
-
ref_path = "units:index:#{index}:quantity_references[#{idx}]"
|
|
171
|
-
|
|
172
|
-
validate_reference(ref_id, ref_type, ref_path, registry,
|
|
173
|
-
invalid_refs, "units")
|
|
174
|
-
end
|
|
175
|
-
end
|
|
176
|
-
end
|
|
177
|
-
|
|
178
|
-
def check_root_unit_references(db, registry, invalid_refs)
|
|
179
|
-
db.units.each_with_index do |unit, index|
|
|
180
|
-
next unless unit.respond_to?(:root_units) && unit.root_units
|
|
181
|
-
|
|
182
|
-
unit.root_units.each_with_index do |root_unit, idx|
|
|
183
|
-
next unless root_unit.respond_to?(:unit_reference) && root_unit.unit_reference
|
|
184
|
-
|
|
185
|
-
# Check unit reference
|
|
186
|
-
ref_id = root_unit.unit_reference
|
|
187
|
-
ref_type = "units"
|
|
188
|
-
ref_path = "units:index:#{index}:root_units.#{idx}.unit_reference"
|
|
189
|
-
|
|
190
|
-
validate_reference(ref_id, ref_type, ref_path, registry,
|
|
191
|
-
invalid_refs, "units")
|
|
192
|
-
|
|
193
|
-
# Check prefix reference if present
|
|
194
|
-
next unless root_unit.respond_to?(:prefix_reference) && root_unit.prefix_reference
|
|
195
|
-
|
|
196
|
-
ref_id = root_unit.prefix_reference
|
|
197
|
-
ref_type = "prefixes"
|
|
198
|
-
ref_path = "units:index:#{index}:root_units.#{idx}.prefix_reference"
|
|
199
|
-
|
|
200
|
-
validate_reference(ref_id, ref_type, ref_path, registry,
|
|
201
|
-
invalid_refs, "units")
|
|
202
|
-
end
|
|
203
|
-
end
|
|
204
|
-
end
|
|
205
|
-
|
|
206
|
-
def validate_reference(ref_id, ref_type, ref_path, registry,
|
|
207
|
-
invalid_refs, file_type)
|
|
208
|
-
# Handle references that are objects with id and type (could be a hash or an object)
|
|
209
|
-
if ref_id.respond_to?(:id) && ref_id.respond_to?(:type)
|
|
210
|
-
id = ref_id.id
|
|
211
|
-
type = ref_id.type
|
|
212
|
-
composite_key = "#{type}:#{id}"
|
|
213
|
-
|
|
214
|
-
# Try multiple lookup strategies
|
|
215
|
-
valid = false
|
|
216
|
-
|
|
217
|
-
# 1. Try exact composite key match
|
|
218
|
-
valid = true if registry.key?(ref_type) && registry[ref_type].key?(composite_key)
|
|
219
|
-
|
|
220
|
-
# 2. Try just ID match if composite didn't work
|
|
221
|
-
valid = true if !valid && registry.key?(ref_type) && registry[ref_type].key?(id)
|
|
222
|
-
|
|
223
|
-
# 3. Try alternate ID formats for unit systems (e.g., SI_base vs si-base)
|
|
224
|
-
if !valid && type == "unitsml" && ref_type == "unit_systems" && registry.key?(ref_type) && (
|
|
225
|
-
registry[ref_type].keys.any? { |k| k.end_with?(":#{id}") } ||
|
|
226
|
-
registry[ref_type].keys.any? do |k|
|
|
227
|
-
k.end_with?(":SI_#{id.sub('si-', '')}")
|
|
228
|
-
end ||
|
|
229
|
-
registry[ref_type].keys.any? do |k|
|
|
230
|
-
k.end_with?(":non-SI_#{id.sub('nonsi-', '')}")
|
|
231
|
-
end
|
|
232
|
-
)
|
|
233
|
-
# Special handling for unit_systems between unitsml and nist types
|
|
234
|
-
valid = true
|
|
235
|
-
end
|
|
236
|
-
|
|
237
|
-
if valid
|
|
238
|
-
puts "Valid reference: #{id} (#{type}) at #{file_type}:#{ref_path}" if @options[:print_valid]
|
|
239
|
-
else
|
|
240
|
-
invalid_refs[file_type] ||= {}
|
|
241
|
-
invalid_refs[file_type][ref_path] =
|
|
242
|
-
{ id: id, type: type, ref_type: ref_type }
|
|
243
|
-
end
|
|
244
|
-
# Handle references that are objects with id and type in a hash
|
|
245
|
-
elsif ref_id.is_a?(Hash) && ref_id.key?("id") && ref_id.key?("type")
|
|
246
|
-
id = ref_id["id"]
|
|
247
|
-
type = ref_id["type"]
|
|
248
|
-
composite_key = "#{type}:#{id}"
|
|
249
|
-
|
|
250
|
-
# Try multiple lookup strategies
|
|
251
|
-
valid = false
|
|
252
|
-
|
|
253
|
-
# 1. Try exact composite key match
|
|
254
|
-
valid = true if registry.key?(ref_type) && registry[ref_type].key?(composite_key)
|
|
255
|
-
|
|
256
|
-
# 2. Try just ID match if composite didn't work
|
|
257
|
-
valid = true if !valid && registry.key?(ref_type) && registry[ref_type].key?(id)
|
|
258
|
-
|
|
259
|
-
# 3. Try alternate ID formats for unit systems (e.g., SI_base vs si-base)
|
|
260
|
-
if !valid && type == "unitsml" && ref_type == "unit_systems" && registry.key?(ref_type) && (
|
|
261
|
-
registry[ref_type].keys.any? { |k| k.end_with?(":#{id}") } ||
|
|
262
|
-
registry[ref_type].keys.any? do |k|
|
|
263
|
-
k.end_with?(":SI_#{id.sub('si-', '')}")
|
|
264
|
-
end ||
|
|
265
|
-
registry[ref_type].keys.any? do |k|
|
|
266
|
-
k.end_with?(":non-SI_#{id.sub('nonsi-', '')}")
|
|
267
|
-
end
|
|
268
|
-
)
|
|
269
|
-
# Special handling for unit_systems between unitsml and nist types
|
|
270
|
-
valid = true
|
|
271
|
-
end
|
|
272
|
-
|
|
273
|
-
if valid
|
|
274
|
-
puts "Valid reference: #{id} (#{type}) at #{file_type}:#{ref_path}" if @options[:print_valid]
|
|
275
|
-
else
|
|
276
|
-
invalid_refs[file_type] ||= {}
|
|
277
|
-
invalid_refs[file_type][ref_path] =
|
|
278
|
-
{ id: id, type: type, ref_type: ref_type }
|
|
279
|
-
end
|
|
280
|
-
else
|
|
281
|
-
# Handle plain string references (legacy format)
|
|
282
|
-
valid = registry.key?(ref_type) && registry[ref_type].key?(ref_id)
|
|
283
|
-
|
|
284
|
-
if valid
|
|
285
|
-
puts "Valid reference: #{ref_id} (#{ref_type}) at #{file_type}:#{ref_path}" if @options[:print_valid]
|
|
286
|
-
else
|
|
287
|
-
invalid_refs[file_type] ||= {}
|
|
288
|
-
invalid_refs[file_type][ref_path] = { id: ref_id, type: ref_type }
|
|
289
|
-
end
|
|
290
|
-
end
|
|
291
|
-
end
|
|
292
|
-
|
|
293
|
-
def display_reference_results(invalid_refs, registry)
|
|
20
|
+
def display_result(invalid_refs)
|
|
294
21
|
if invalid_refs.empty?
|
|
295
22
|
puts "All references are valid!"
|
|
296
23
|
return
|
|
297
24
|
end
|
|
298
25
|
|
|
299
26
|
puts "Found invalid references:"
|
|
300
|
-
|
|
301
|
-
# Display registry contents if debug_registry is enabled
|
|
302
|
-
# This is needed for the failing test
|
|
303
|
-
if @options[:debug_registry]
|
|
304
|
-
puts "\nRegistry contents:"
|
|
305
|
-
registry.each do |type, ids|
|
|
306
|
-
next if ids.empty?
|
|
307
|
-
|
|
308
|
-
puts " #{type}:"
|
|
309
|
-
ids.each do |id, location|
|
|
310
|
-
puts " #{id}: {type: #{type.sub('s',
|
|
311
|
-
'')}, source: #{location}}"
|
|
312
|
-
end
|
|
313
|
-
end
|
|
314
|
-
end
|
|
315
27
|
invalid_refs.each do |file, refs|
|
|
316
28
|
puts " #{file}:"
|
|
317
29
|
refs.each do |path, ref|
|
|
318
30
|
puts " #{path} => '#{ref[:id]}' (#{ref[:type]})"
|
|
319
|
-
|
|
320
|
-
# Suggest corrections
|
|
321
|
-
next unless registry.key?(ref[:ref_type])
|
|
322
|
-
|
|
323
|
-
similar_ids = Unitsdb::Utils.find_similar_ids(ref[:id],
|
|
324
|
-
registry[ref[:ref_type]].keys)
|
|
325
|
-
if similar_ids.any?
|
|
326
|
-
puts " Did you mean one of these?"
|
|
327
|
-
similar_ids.each { |id| puts " - #{id}" }
|
|
328
|
-
end
|
|
329
31
|
end
|
|
330
32
|
end
|
|
331
33
|
end
|
|
@@ -3,16 +3,17 @@
|
|
|
3
3
|
module Unitsdb
|
|
4
4
|
module Commands
|
|
5
5
|
module Validate
|
|
6
|
+
# `unitsdb validate si_references`. Checks that no SI
|
|
7
|
+
# digital-framework reference URI is used by more than one
|
|
8
|
+
# Unit, Quantity, or Prefix.
|
|
6
9
|
class SiReferences < Unitsdb::Commands::Base
|
|
10
|
+
AUTHORITY = "si-digital-framework"
|
|
11
|
+
ENTITY_COLLECTIONS = %i[units quantities prefixes].freeze
|
|
12
|
+
|
|
7
13
|
def run
|
|
8
|
-
# Load the database
|
|
9
14
|
db = load_database(@options[:database])
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
duplicates = check_si_references(db)
|
|
13
|
-
|
|
14
|
-
# Display results
|
|
15
|
-
display_duplicate_results(duplicates)
|
|
15
|
+
duplicates = scan(db)
|
|
16
|
+
display(duplicates)
|
|
16
17
|
rescue Unitsdb::Errors::DatabaseError => e
|
|
17
18
|
raise Unitsdb::Errors::ValidationError,
|
|
18
19
|
"Failed to validate SI references: #{e.message}"
|
|
@@ -20,86 +21,49 @@ module Unitsdb
|
|
|
20
21
|
|
|
21
22
|
private
|
|
22
23
|
|
|
23
|
-
def
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
check_entity_si_references(db.quantities, "quantities", duplicates)
|
|
31
|
-
|
|
32
|
-
# Check prefixes
|
|
33
|
-
check_entity_si_references(db.prefixes, "prefixes", duplicates)
|
|
34
|
-
|
|
35
|
-
duplicates
|
|
24
|
+
def scan(db)
|
|
25
|
+
{}.tap do |duplicates|
|
|
26
|
+
ENTITY_COLLECTIONS.each do |name|
|
|
27
|
+
dup = scan_collection(db.collection(name))
|
|
28
|
+
duplicates[name.to_s] = dup unless dup.empty?
|
|
29
|
+
end
|
|
30
|
+
end
|
|
36
31
|
end
|
|
37
32
|
|
|
38
|
-
def
|
|
39
|
-
|
|
40
|
-
si_refs = {}
|
|
41
|
-
|
|
33
|
+
def scan_collection(entities)
|
|
34
|
+
by_uri = {}
|
|
42
35
|
entities.each_with_index do |entity, index|
|
|
43
|
-
|
|
44
|
-
|
|
36
|
+
refs = entity.references || []
|
|
37
|
+
refs.each do |ref|
|
|
38
|
+
next unless ref.authority == AUTHORITY
|
|
45
39
|
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
next unless ref.authority == "si-digital-framework"
|
|
50
|
-
|
|
51
|
-
# Get entity info for display
|
|
52
|
-
entity_id = if entity.respond_to?(:identifiers) && entity.identifiers&.first.respond_to?(:id)
|
|
53
|
-
entity.identifiers.first.id
|
|
54
|
-
else
|
|
55
|
-
entity.short
|
|
56
|
-
end
|
|
57
|
-
|
|
58
|
-
# Track this reference
|
|
59
|
-
si_refs[ref.uri] ||= []
|
|
60
|
-
si_refs[ref.uri] << {
|
|
61
|
-
entity_id: entity_id,
|
|
62
|
-
entity_name: entity.respond_to?(:names) ? entity.names.first : entity.short,
|
|
40
|
+
(by_uri[ref.uri] ||= []) << {
|
|
41
|
+
entity_id: entity.identifiers.first&.id || entity.short,
|
|
42
|
+
entity_name: entity.names.first || entity.short,
|
|
63
43
|
index: index,
|
|
64
44
|
}
|
|
65
45
|
end
|
|
66
46
|
end
|
|
67
|
-
|
|
68
|
-
# Find duplicates (URIs with more than one entity)
|
|
69
|
-
si_refs.each do |uri, entities|
|
|
70
|
-
next unless entities.size > 1
|
|
71
|
-
|
|
72
|
-
# Record this duplicate
|
|
73
|
-
duplicates[entity_type] ||= {}
|
|
74
|
-
duplicates[entity_type][uri] = entities
|
|
75
|
-
end
|
|
47
|
+
by_uri.reject { |_, refs| refs.size == 1 }
|
|
76
48
|
end
|
|
77
49
|
|
|
78
|
-
def
|
|
50
|
+
def display(duplicates)
|
|
79
51
|
if duplicates.empty?
|
|
80
|
-
puts "No duplicate SI references found!
|
|
52
|
+
puts "No duplicate SI references found! " \
|
|
53
|
+
"Each SI reference URI is used by at most one entity of each type."
|
|
81
54
|
return
|
|
82
55
|
end
|
|
83
56
|
|
|
84
57
|
puts "Found duplicate SI references:"
|
|
85
|
-
|
|
86
58
|
duplicates.each do |entity_type, uri_duplicates|
|
|
87
59
|
puts "\n #{entity_type.capitalize}:"
|
|
88
|
-
|
|
89
|
-
uri_duplicates.each do |uri, entities|
|
|
60
|
+
uri_duplicates.each do |uri, refs|
|
|
90
61
|
puts " SI URI: #{uri}"
|
|
91
|
-
puts " Used by #{
|
|
92
|
-
|
|
93
|
-
entities.each do |entity|
|
|
94
|
-
puts " - #{entity[:entity_id]} (#{entity[:entity_name]}) at index #{entity[:index]}"
|
|
95
|
-
end
|
|
62
|
+
puts " Used by #{refs.size} entities:"
|
|
63
|
+
refs.each { |r| puts " - #{r[:entity_id]} (#{r[:entity_name]}) at index #{r[:index]}" }
|
|
96
64
|
puts ""
|
|
97
65
|
end
|
|
98
66
|
end
|
|
99
|
-
|
|
100
|
-
puts "\nEach SI digital framework reference should be used by at most one entity of each type."
|
|
101
|
-
puts "Please fix the duplicates by either removing the reference from all but one entity,"
|
|
102
|
-
puts "or by updating the references to use different URIs appropriate for each entity."
|
|
103
67
|
end
|
|
104
68
|
end
|
|
105
69
|
end
|
|
@@ -3,16 +3,16 @@
|
|
|
3
3
|
module Unitsdb
|
|
4
4
|
module Commands
|
|
5
5
|
module Validate
|
|
6
|
+
# `unitsdb validate ucum_references`. Checks that no UCUM
|
|
7
|
+
# reference code is used by more than one Unit or Prefix.
|
|
6
8
|
class UcumReferences < Unitsdb::Commands::Base
|
|
9
|
+
AUTHORITY = "ucum"
|
|
10
|
+
ENTITY_COLLECTIONS = %i[units prefixes].freeze
|
|
11
|
+
|
|
7
12
|
def run
|
|
8
|
-
# Load the database
|
|
9
13
|
db = load_database(@options[:database])
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
duplicates = check_ucum_references(db)
|
|
13
|
-
|
|
14
|
-
# Display results
|
|
15
|
-
display_duplicate_results(duplicates)
|
|
14
|
+
duplicates = scan(db)
|
|
15
|
+
display(duplicates)
|
|
16
16
|
rescue Unitsdb::Errors::DatabaseError => e
|
|
17
17
|
raise Unitsdb::Errors::ValidationError,
|
|
18
18
|
"Failed to validate UCUM references: #{e.message}"
|
|
@@ -20,84 +20,50 @@ module Unitsdb
|
|
|
20
20
|
|
|
21
21
|
private
|
|
22
22
|
|
|
23
|
-
def
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
duplicates
|
|
23
|
+
def scan(db)
|
|
24
|
+
{}.tap do |duplicates|
|
|
25
|
+
ENTITY_COLLECTIONS.each do |name|
|
|
26
|
+
collection = db.collection(name) || []
|
|
27
|
+
dup = scan_collection(collection)
|
|
28
|
+
duplicates[name.to_s] = dup unless dup.empty?
|
|
29
|
+
end
|
|
30
|
+
end
|
|
33
31
|
end
|
|
34
32
|
|
|
35
|
-
def
|
|
36
|
-
|
|
37
|
-
ucum_refs = {}
|
|
38
|
-
|
|
33
|
+
def scan_collection(entities)
|
|
34
|
+
by_code = {}
|
|
39
35
|
entities.each_with_index do |entity, index|
|
|
40
|
-
|
|
41
|
-
|
|
36
|
+
refs = entity.references || []
|
|
37
|
+
refs.each do |ref|
|
|
38
|
+
next unless ref.authority == AUTHORITY
|
|
42
39
|
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
next unless ref.authority == "ucum"
|
|
47
|
-
|
|
48
|
-
# Get entity info for display
|
|
49
|
-
entity_id = entity.respond_to?(:id) ? entity.id : entity.short
|
|
50
|
-
entity_name = if entity.respond_to?(:names) && entity.names&.first
|
|
51
|
-
entity.names.first.respond_to?(:name) ? entity.names.first.name : entity.names.first
|
|
52
|
-
else
|
|
53
|
-
entity.short
|
|
54
|
-
end
|
|
55
|
-
|
|
56
|
-
# Track this reference
|
|
57
|
-
ucum_refs[ref.code] ||= []
|
|
58
|
-
ucum_refs[ref.code] << {
|
|
59
|
-
entity_id: entity_id,
|
|
60
|
-
entity_name: entity_name,
|
|
40
|
+
(by_code[ref.uri] ||= []) << {
|
|
41
|
+
entity_id: entity.identifiers.first&.id || entity.short,
|
|
42
|
+
entity_name: entity.names.first&.value || entity.short,
|
|
61
43
|
index: index,
|
|
62
44
|
}
|
|
63
45
|
end
|
|
64
46
|
end
|
|
65
|
-
|
|
66
|
-
# Find duplicates (codes with more than one entity)
|
|
67
|
-
ucum_refs.each do |code, entities|
|
|
68
|
-
next unless entities.size > 1
|
|
69
|
-
|
|
70
|
-
# Record this duplicate
|
|
71
|
-
duplicates[entity_type] ||= {}
|
|
72
|
-
duplicates[entity_type][code] = entities
|
|
73
|
-
end
|
|
47
|
+
by_code.reject { |_, refs| refs.size == 1 }
|
|
74
48
|
end
|
|
75
49
|
|
|
76
|
-
def
|
|
50
|
+
def display(duplicates)
|
|
77
51
|
if duplicates.empty?
|
|
78
|
-
puts "No duplicate UCUM references found!
|
|
52
|
+
puts "No duplicate UCUM references found! " \
|
|
53
|
+
"Each UCUM reference code is used by at most one entity of each type."
|
|
79
54
|
return
|
|
80
55
|
end
|
|
81
56
|
|
|
82
57
|
puts "Found duplicate UCUM references:"
|
|
83
|
-
|
|
84
58
|
duplicates.each do |entity_type, code_duplicates|
|
|
85
59
|
puts "\n #{entity_type.capitalize}:"
|
|
86
|
-
|
|
87
|
-
code_duplicates.each do |code, entities|
|
|
60
|
+
code_duplicates.each do |code, refs|
|
|
88
61
|
puts " UCUM Code: #{code}"
|
|
89
|
-
puts " Used by #{
|
|
90
|
-
|
|
91
|
-
entities.each do |entity|
|
|
92
|
-
puts " - #{entity[:entity_id]} (#{entity[:entity_name]}) at index #{entity[:index]}"
|
|
93
|
-
end
|
|
62
|
+
puts " Used by #{refs.size} entities:"
|
|
63
|
+
refs.each { |r| puts " - #{r[:entity_id]} (#{r[:entity_name]}) at index #{r[:index]}" }
|
|
94
64
|
puts ""
|
|
95
65
|
end
|
|
96
66
|
end
|
|
97
|
-
|
|
98
|
-
puts "\nEach UCUM reference should be used by at most one entity of each type."
|
|
99
|
-
puts "Please fix the duplicates by either removing the reference from all but one entity,"
|
|
100
|
-
puts "or by updating the references to use different codes appropriate for each entity."
|
|
101
67
|
end
|
|
102
68
|
end
|
|
103
69
|
end
|
|
@@ -1,7 +1,5 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require "thor"
|
|
4
|
-
|
|
5
3
|
module Unitsdb
|
|
6
4
|
module Commands
|
|
7
5
|
module Validate
|
|
@@ -12,11 +10,7 @@ module Unitsdb
|
|
|
12
10
|
autoload :UcumReferences, "unitsdb/commands/validate/ucum_references"
|
|
13
11
|
end
|
|
14
12
|
|
|
15
|
-
class ValidateCommand < Thor
|
|
16
|
-
# Inherit trace option from parent CLI
|
|
17
|
-
class_option :trace, type: :boolean, default: false,
|
|
18
|
-
desc: "Show full backtrace on error"
|
|
19
|
-
|
|
13
|
+
class ValidateCommand < Unitsdb::Commands::Thor
|
|
20
14
|
desc "references", "Validate that all references exist"
|
|
21
15
|
option :debug_registry, type: :boolean,
|
|
22
16
|
desc: "Show registry contents for debugging"
|
|
@@ -62,35 +56,6 @@ module Unitsdb
|
|
|
62
56
|
def ucum_references
|
|
63
57
|
run_command(Commands::Validate::UcumReferences, options)
|
|
64
58
|
end
|
|
65
|
-
|
|
66
|
-
private
|
|
67
|
-
|
|
68
|
-
def run_command(command_class, options)
|
|
69
|
-
command = command_class.new(options)
|
|
70
|
-
command.run
|
|
71
|
-
rescue Unitsdb::Errors::CLIRuntimeError => e
|
|
72
|
-
handle_cli_error(e)
|
|
73
|
-
rescue StandardError => e
|
|
74
|
-
handle_error(e)
|
|
75
|
-
end
|
|
76
|
-
|
|
77
|
-
def handle_cli_error(error)
|
|
78
|
-
if options[:trace]
|
|
79
|
-
raise error
|
|
80
|
-
else
|
|
81
|
-
warn "Error: #{error.message}"
|
|
82
|
-
exit 1
|
|
83
|
-
end
|
|
84
|
-
end
|
|
85
|
-
|
|
86
|
-
def handle_error(error)
|
|
87
|
-
if options[:trace]
|
|
88
|
-
raise error
|
|
89
|
-
else
|
|
90
|
-
warn "Error: #{error.message}"
|
|
91
|
-
exit 1
|
|
92
|
-
end
|
|
93
|
-
end
|
|
94
59
|
end
|
|
95
60
|
end
|
|
96
61
|
end
|
data/lib/unitsdb/commands.rb
CHANGED
|
@@ -2,10 +2,12 @@
|
|
|
2
2
|
|
|
3
3
|
module Unitsdb
|
|
4
4
|
module Commands
|
|
5
|
+
autoload :Thor, "unitsdb/commands/thor"
|
|
5
6
|
autoload :ModifyCommand, "unitsdb/commands/_modify"
|
|
6
7
|
autoload :Base, "unitsdb/commands/base"
|
|
7
8
|
autoload :CheckSi, "unitsdb/commands/check_si"
|
|
8
9
|
autoload :CheckSiCommand, "unitsdb/commands/check_si"
|
|
10
|
+
autoload :EntityPresenter, "unitsdb/commands/entity_presenter"
|
|
9
11
|
autoload :Get, "unitsdb/commands/get"
|
|
10
12
|
autoload :Normalize, "unitsdb/commands/normalize"
|
|
11
13
|
autoload :Qudt, "unitsdb/commands/qudt"
|