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.
Files changed (66) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/opal.yml +36 -0
  3. data/.gitignore +3 -0
  4. data/Gemfile +1 -0
  5. data/Rakefile +3 -1
  6. data/lib/unitsdb/cli.rb +5 -41
  7. data/lib/unitsdb/commands/_modify.rb +1 -34
  8. data/lib/unitsdb/commands/check_si/si_formatter.rb +6 -6
  9. data/lib/unitsdb/commands/check_si/si_matcher.rb +202 -292
  10. data/lib/unitsdb/commands/check_si/si_updater.rb +16 -36
  11. data/lib/unitsdb/commands/entity_presenter.rb +98 -0
  12. data/lib/unitsdb/commands/get.rb +16 -113
  13. data/lib/unitsdb/commands/qudt/formatter.rb +16 -27
  14. data/lib/unitsdb/commands/qudt/matcher.rb +18 -28
  15. data/lib/unitsdb/commands/qudt/updater.rb +8 -11
  16. data/lib/unitsdb/commands/qudt.rb +1 -34
  17. data/lib/unitsdb/commands/search.rb +33 -188
  18. data/lib/unitsdb/commands/thor.rb +41 -0
  19. data/lib/unitsdb/commands/ucum/formatter.rb +9 -18
  20. data/lib/unitsdb/commands/ucum/matcher.rb +4 -4
  21. data/lib/unitsdb/commands/ucum/updater.rb +3 -5
  22. data/lib/unitsdb/commands/ucum.rb +1 -34
  23. data/lib/unitsdb/commands/validate/qudt_references.rb +29 -70
  24. data/lib/unitsdb/commands/validate/references.rb +5 -303
  25. data/lib/unitsdb/commands/validate/si_references.rb +30 -66
  26. data/lib/unitsdb/commands/validate/ucum_references.rb +30 -64
  27. data/lib/unitsdb/commands/validate.rb +1 -36
  28. data/lib/unitsdb/commands.rb +2 -0
  29. data/lib/unitsdb/config.rb +170 -4
  30. data/lib/unitsdb/database/loader.rb +135 -0
  31. data/lib/unitsdb/database/reference_validator.rb +227 -0
  32. data/lib/unitsdb/database/uniqueness_validator.rb +80 -0
  33. data/lib/unitsdb/database.rb +127 -588
  34. data/lib/unitsdb/dimension.rb +2 -27
  35. data/lib/unitsdb/dimension_details.rb +2 -0
  36. data/lib/unitsdb/dimension_reference.rb +2 -0
  37. data/lib/unitsdb/dimensions.rb +2 -2
  38. data/lib/unitsdb/external_reference.rb +2 -0
  39. data/lib/unitsdb/identifier.rb +2 -0
  40. data/lib/unitsdb/localized_string.rb +2 -0
  41. data/lib/unitsdb/opal.rb +43 -0
  42. data/lib/unitsdb/prefix.rb +2 -13
  43. data/lib/unitsdb/prefix_reference.rb +2 -0
  44. data/lib/unitsdb/prefixes.rb +2 -2
  45. data/lib/unitsdb/quantities.rb +2 -1
  46. data/lib/unitsdb/quantity.rb +2 -2
  47. data/lib/unitsdb/quantity_reference.rb +2 -2
  48. data/lib/unitsdb/qudt.rb +5 -0
  49. data/lib/unitsdb/root_unit_reference.rb +2 -2
  50. data/lib/unitsdb/scale.rb +2 -2
  51. data/lib/unitsdb/scale_properties.rb +2 -1
  52. data/lib/unitsdb/scale_reference.rb +2 -0
  53. data/lib/unitsdb/scales.rb +2 -1
  54. data/lib/unitsdb/si_derived_base.rb +2 -1
  55. data/lib/unitsdb/symbol_presentations.rb +2 -2
  56. data/lib/unitsdb/ucum.rb +7 -0
  57. data/lib/unitsdb/unit.rb +2 -34
  58. data/lib/unitsdb/unit_reference.rb +2 -0
  59. data/lib/unitsdb/unit_system.rb +2 -2
  60. data/lib/unitsdb/unit_system_reference.rb +2 -0
  61. data/lib/unitsdb/unit_systems.rb +2 -2
  62. data/lib/unitsdb/units.rb +2 -2
  63. data/lib/unitsdb/version.rb +1 -1
  64. data/lib/unitsdb.rb +134 -27
  65. data/unitsdb.gemspec +1 -0
  66. 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
- # Build registry of all valid IDs
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 build_id_registry(db)
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
- # Check for duplicate SI references
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 check_si_references(db)
24
- duplicates = {}
25
-
26
- # Check units
27
- check_entity_si_references(db.units, "units", duplicates)
28
-
29
- # Check quantities
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 check_entity_si_references(entities, entity_type, duplicates)
39
- # Track SI references by URI
40
- si_refs = {}
41
-
33
+ def scan_collection(entities)
34
+ by_uri = {}
42
35
  entities.each_with_index do |entity, index|
43
- # Skip if no references
44
- next unless entity.respond_to?(:references) && entity.references
36
+ refs = entity.references || []
37
+ refs.each do |ref|
38
+ next unless ref.authority == AUTHORITY
45
39
 
46
- # Check each reference
47
- entity.references.each do |ref|
48
- # Only interested in si-digital-framework references
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 display_duplicate_results(duplicates)
50
+ def display(duplicates)
79
51
  if duplicates.empty?
80
- puts "No duplicate SI references found! Each SI reference URI is used by at most one entity of each type."
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 #{entities.size} entities:"
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
- # Check for duplicate UCUM references
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 check_ucum_references(db)
24
- duplicates = {}
25
-
26
- # Check units
27
- check_entity_ucum_references(db.units, "units", duplicates)
28
-
29
- # Check prefixes
30
- check_entity_ucum_references(db.prefixes, "prefixes", duplicates)
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 check_entity_ucum_references(entities, entity_type, duplicates)
36
- # Track UCUM references by code
37
- ucum_refs = {}
38
-
33
+ def scan_collection(entities)
34
+ by_code = {}
39
35
  entities.each_with_index do |entity, index|
40
- # Skip if no external references
41
- next unless entity.respond_to?(:external_references) && entity.external_references
36
+ refs = entity.references || []
37
+ refs.each do |ref|
38
+ next unless ref.authority == AUTHORITY
42
39
 
43
- # Check each external reference
44
- entity.external_references.each do |ref|
45
- # Only interested in ucum references
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 display_duplicate_results(duplicates)
50
+ def display(duplicates)
77
51
  if duplicates.empty?
78
- puts "No duplicate UCUM references found! Each UCUM reference code is used by at most one entity of each type."
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 #{entities.size} entities:"
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
@@ -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"