unitsdb 2.1.1 → 2.2.2

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 (95) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/release.yml +8 -1
  3. data/.gitignore +2 -0
  4. data/.gitmodules +4 -3
  5. data/.rubocop.yml +13 -8
  6. data/.rubocop_todo.yml +217 -100
  7. data/CLAUDE.md +55 -0
  8. data/Gemfile +4 -1
  9. data/README.adoc +283 -16
  10. data/data/dimensions.yaml +1864 -0
  11. data/data/prefixes.yaml +874 -0
  12. data/data/quantities.yaml +3715 -0
  13. data/data/scales.yaml +97 -0
  14. data/data/schemas/dimensions-schema.yaml +153 -0
  15. data/data/schemas/prefixes-schema.yaml +155 -0
  16. data/data/schemas/quantities-schema.yaml +117 -0
  17. data/data/schemas/scales-schema.yaml +106 -0
  18. data/data/schemas/unit_systems-schema.yaml +116 -0
  19. data/data/schemas/units-schema.yaml +215 -0
  20. data/data/unit_systems.yaml +78 -0
  21. data/data/units.yaml +14052 -0
  22. data/exe/unitsdb +7 -1
  23. data/lib/unitsdb/cli.rb +42 -15
  24. data/lib/unitsdb/commands/_modify.rb +40 -4
  25. data/lib/unitsdb/commands/base.rb +6 -2
  26. data/lib/unitsdb/commands/check_si/si_formatter.rb +488 -0
  27. data/lib/unitsdb/commands/check_si/si_matcher.rb +487 -0
  28. data/lib/unitsdb/commands/check_si/si_ttl_parser.rb +103 -0
  29. data/lib/unitsdb/commands/check_si/si_updater.rb +254 -0
  30. data/lib/unitsdb/commands/check_si.rb +54 -35
  31. data/lib/unitsdb/commands/get.rb +11 -10
  32. data/lib/unitsdb/commands/normalize.rb +21 -7
  33. data/lib/unitsdb/commands/qudt/check.rb +150 -0
  34. data/lib/unitsdb/commands/qudt/formatter.rb +194 -0
  35. data/lib/unitsdb/commands/qudt/matcher.rb +746 -0
  36. data/lib/unitsdb/commands/qudt/ttl_parser.rb +403 -0
  37. data/lib/unitsdb/commands/qudt/update.rb +126 -0
  38. data/lib/unitsdb/commands/qudt/updater.rb +189 -0
  39. data/lib/unitsdb/commands/qudt.rb +82 -0
  40. data/lib/unitsdb/commands/release.rb +12 -9
  41. data/lib/unitsdb/commands/search.rb +12 -11
  42. data/lib/unitsdb/commands/ucum/check.rb +42 -29
  43. data/lib/unitsdb/commands/ucum/formatter.rb +2 -1
  44. data/lib/unitsdb/commands/ucum/matcher.rb +23 -9
  45. data/lib/unitsdb/commands/ucum/update.rb +14 -13
  46. data/lib/unitsdb/commands/ucum/updater.rb +40 -6
  47. data/lib/unitsdb/commands/ucum/xml_parser.rb +0 -2
  48. data/lib/unitsdb/commands/ucum.rb +44 -4
  49. data/lib/unitsdb/commands/validate/identifiers.rb +2 -4
  50. data/lib/unitsdb/commands/validate/qudt_references.rb +111 -0
  51. data/lib/unitsdb/commands/validate/references.rb +36 -19
  52. data/lib/unitsdb/commands/validate/si_references.rb +3 -5
  53. data/lib/unitsdb/commands/validate/ucum_references.rb +105 -0
  54. data/lib/unitsdb/commands/validate.rb +67 -11
  55. data/lib/unitsdb/commands.rb +20 -0
  56. data/lib/unitsdb/config.rb +114 -2
  57. data/lib/unitsdb/database.rb +160 -123
  58. data/lib/unitsdb/dimension.rb +3 -4
  59. data/lib/unitsdb/dimension_details.rb +2 -1
  60. data/lib/unitsdb/dimension_reference.rb +2 -0
  61. data/lib/unitsdb/dimensions.rb +2 -2
  62. data/lib/unitsdb/errors.rb +7 -0
  63. data/lib/unitsdb/external_reference.rb +2 -0
  64. data/lib/unitsdb/identifier.rb +2 -0
  65. data/lib/unitsdb/localized_string.rb +2 -0
  66. data/lib/unitsdb/prefix.rb +2 -4
  67. data/lib/unitsdb/prefix_reference.rb +2 -2
  68. data/lib/unitsdb/prefixes.rb +2 -1
  69. data/lib/unitsdb/quantities.rb +2 -2
  70. data/lib/unitsdb/quantity.rb +2 -6
  71. data/lib/unitsdb/quantity_reference.rb +2 -0
  72. data/lib/unitsdb/qudt.rb +105 -0
  73. data/lib/unitsdb/root_unit_reference.rb +2 -3
  74. data/lib/unitsdb/scale.rb +2 -4
  75. data/lib/unitsdb/scale_properties.rb +2 -0
  76. data/lib/unitsdb/scale_reference.rb +2 -2
  77. data/lib/unitsdb/scales.rb +2 -2
  78. data/lib/unitsdb/si_derived_base.rb +2 -2
  79. data/lib/unitsdb/symbol_presentations.rb +2 -0
  80. data/lib/unitsdb/ucum.rb +21 -10
  81. data/lib/unitsdb/unit.rb +2 -10
  82. data/lib/unitsdb/unit_reference.rb +2 -2
  83. data/lib/unitsdb/unit_system.rb +3 -3
  84. data/lib/unitsdb/unit_system_reference.rb +2 -2
  85. data/lib/unitsdb/unit_systems.rb +2 -2
  86. data/lib/unitsdb/units.rb +2 -2
  87. data/lib/unitsdb/utils.rb +32 -21
  88. data/lib/unitsdb/version.rb +5 -1
  89. data/lib/unitsdb.rb +62 -14
  90. data/unitsdb.gemspec +6 -3
  91. metadata +52 -13
  92. data/lib/unitsdb/commands/si_formatter.rb +0 -485
  93. data/lib/unitsdb/commands/si_matcher.rb +0 -470
  94. data/lib/unitsdb/commands/si_ttl_parser.rb +0 -100
  95. data/lib/unitsdb/commands/si_updater.rb +0 -212
@@ -1,16 +1,18 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require_relative "unit"
4
- require_relative "prefix"
5
- require_relative "quantity"
6
- require_relative "dimension"
7
- require_relative "unit_system"
8
- require_relative "errors"
9
-
10
3
  module Unitsdb
11
4
  class Database < Lutaml::Model::Serializable
12
5
  # model Config.model_for(:units)
13
6
 
7
+ DATABASE_FILES = {
8
+ "prefixes" => "prefixes.yaml",
9
+ "dimensions" => "dimensions.yaml",
10
+ "units" => "units.yaml",
11
+ "quantities" => "quantities.yaml",
12
+ "unit_systems" => "unit_systems.yaml",
13
+ }.freeze
14
+ SUPPORTED_SCHEMA_VERSION = "2.0.0"
15
+
14
16
  attribute :schema_version, :string
15
17
  attribute :version, :string
16
18
  attribute :units, Unit, collection: true
@@ -25,7 +27,11 @@ module Unitsdb
25
27
  # @return [Object, nil] the first entity with matching identifier or nil if not found
26
28
  def find_by_type(id:, type:)
27
29
  collection = send(type.to_s)
28
- collection.find { |entity| entity.identifiers&.any? { |identifier| identifier.id == id } }
30
+ collection.find do |entity|
31
+ entity.identifiers&.any? do |identifier|
32
+ identifier.id == id
33
+ end
34
+ end
29
35
  end
30
36
 
31
37
  # Find an entity by its identifier id across all entity types
@@ -33,7 +39,8 @@ module Unitsdb
33
39
  # @param type [String, nil] optional identifier type to match
34
40
  # @return [Object, nil] the first entity with matching identifier or nil if not found
35
41
  def get_by_id(id:, type: nil)
36
- %w[units prefixes quantities dimensions unit_systems].each do |collection_name|
42
+ %w[units prefixes quantities dimensions
43
+ unit_systems].each do |collection_name|
37
44
  next unless respond_to?(collection_name)
38
45
 
39
46
  collection = send(collection_name)
@@ -63,7 +70,12 @@ module Unitsdb
63
70
  results = []
64
71
 
65
72
  # Define which collections to search based on type parameter
66
- collections = type ? [type.to_s] : %w[units prefixes quantities dimensions unit_systems]
73
+ collections = if type
74
+ [type.to_s]
75
+ else
76
+ %w[units prefixes quantities
77
+ dimensions unit_systems]
78
+ end
67
79
 
68
80
  collections.each do |collection_name|
69
81
  next unless respond_to?(collection_name)
@@ -71,7 +83,9 @@ module Unitsdb
71
83
  collection = send(collection_name)
72
84
  collection.each do |entity|
73
85
  # Search in identifiers
74
- if entity.identifiers&.any? { |identifier| identifier.id.to_s.downcase.include?(text.downcase) }
86
+ if entity.identifiers&.any? do |identifier|
87
+ identifier.id.to_s.downcase.include?(text.downcase)
88
+ end
75
89
  results << entity
76
90
  next
77
91
  end
@@ -86,14 +100,14 @@ module Unitsdb
86
100
 
87
101
  # Search in short description
88
102
  if entity.respond_to?(:short) && entity.short &&
89
- entity.short.to_s.downcase.include?(text.downcase)
103
+ entity.short.to_s.downcase.include?(text.downcase)
90
104
  results << entity
91
105
  next
92
106
  end
93
107
 
94
108
  # Special case for prefix name (prefixes don't have names array)
95
109
  next unless collection_name == "prefixes" && entity.respond_to?(:name) &&
96
- entity.name.to_s.downcase.include?(text.downcase)
110
+ entity.name.to_s.downcase.include?(text.downcase)
97
111
 
98
112
  results << entity
99
113
  next
@@ -116,7 +130,8 @@ module Unitsdb
116
130
  collections = entity_type ? [entity_type.to_s] : %w[units prefixes]
117
131
 
118
132
  collections.each do |collection_name|
119
- next unless respond_to?(collection_name) && %w[units prefixes].include?(collection_name)
133
+ next unless respond_to?(collection_name) && %w[units
134
+ prefixes].include?(collection_name)
120
135
 
121
136
  collection = send(collection_name)
122
137
  collection.each do |entity|
@@ -158,11 +173,16 @@ module Unitsdb
158
173
 
159
174
  result = {
160
175
  exact: [],
161
- symbol_match: []
176
+ symbol_match: [],
162
177
  }
163
178
 
164
179
  # Define collections to search based on entity_type parameter
165
- collections = entity_type ? [entity_type.to_s] : %w[units prefixes quantities dimensions unit_systems]
180
+ collections = if entity_type
181
+ [entity_type.to_s]
182
+ else
183
+ %w[units prefixes
184
+ quantities dimensions unit_systems]
185
+ end
166
186
 
167
187
  collections.each do |collection_name|
168
188
  next unless respond_to?(collection_name)
@@ -174,23 +194,25 @@ module Unitsdb
174
194
  if %w[exact all].include?(match_type)
175
195
  # Match by short
176
196
  if entity.respond_to?(:short) && entity.short &&
177
- entity.short.downcase == value.downcase
197
+ entity.short.downcase == value.downcase
178
198
  result[:exact] << {
179
199
  entity: entity,
180
200
  match_desc: "short_to_name",
181
- details: "UnitsDB short '#{entity.short}' matches '#{value}'"
201
+ details: "UnitsDB short '#{entity.short}' matches '#{value}'",
182
202
  }
183
203
  next
184
204
  end
185
205
 
186
206
  # Match by names
187
207
  if entity.respond_to?(:names) && entity.names
188
- matching_name = entity.names.find { |name| name.value.to_s.downcase == value.downcase }
208
+ matching_name = entity.names.find do |name|
209
+ name.value.to_s.downcase == value.downcase
210
+ end
189
211
  if matching_name
190
212
  result[:exact] << {
191
213
  entity: entity,
192
214
  match_desc: "name_to_name",
193
- details: "UnitsDB name '#{matching_name.value}' (#{matching_name.lang}) matches '#{value}'"
215
+ details: "UnitsDB name '#{matching_name.value}' (#{matching_name.lang}) matches '#{value}'",
194
216
  }
195
217
  next
196
218
  end
@@ -199,7 +221,7 @@ module Unitsdb
199
221
 
200
222
  # For symbol matches - only applicable to units and prefixes
201
223
  if %w[symbol all].include?(match_type) &&
202
- %w[units prefixes].include?(collection_name)
224
+ %w[units prefixes].include?(collection_name)
203
225
  if collection_name == "units" && entity.respond_to?(:symbols) && entity.symbols
204
226
  # Units can have multiple symbols
205
227
  matching_symbol = entity.symbols.find do |sym|
@@ -211,7 +233,7 @@ module Unitsdb
211
233
  result[:symbol_match] << {
212
234
  entity: entity,
213
235
  match_desc: "symbol_match",
214
- details: "UnitsDB symbol '#{matching_symbol.ascii}' matches '#{value}'"
236
+ details: "UnitsDB symbol '#{matching_symbol.ascii}' matches '#{value}'",
215
237
  }
216
238
  end
217
239
  elsif collection_name == "prefixes" && entity.respond_to?(:symbols) && entity.symbols
@@ -225,7 +247,7 @@ module Unitsdb
225
247
  result[:symbol_match] << {
226
248
  entity: entity,
227
249
  match_desc: "symbol_match",
228
- details: "UnitsDB symbol '#{matching_symbol.ascii}' matches '#{value}'"
250
+ details: "UnitsDB symbol '#{matching_symbol.ascii}' matches '#{value}'",
229
251
  }
230
252
  end
231
253
  end
@@ -243,7 +265,7 @@ module Unitsdb
243
265
  def validate_uniqueness
244
266
  results = {
245
267
  short: {},
246
- id: {}
268
+ id: {},
247
269
  }
248
270
 
249
271
  # Validate short names for applicable collections
@@ -277,116 +299,114 @@ module Unitsdb
277
299
  invalid_refs
278
300
  end
279
301
 
280
- def self.from_db(dir_path)
281
- # If dir_path is a relative path, make it relative to the current working directory
282
- db_path = dir_path
283
- puts "Database directory path: #{db_path}"
284
-
285
- # Check if the directory exists
286
- raise Errors::DatabaseNotFoundError, "Database directory not found: #{db_path}" unless Dir.exist?(db_path)
302
+ def self.from_db(dir_path, context: Unitsdb::Config.context_id)
303
+ context_id = context.to_sym
304
+ if context_id == Unitsdb::Config.context_id &&
305
+ Unitsdb::Config.find_context(context_id).nil?
306
+ Unitsdb::Config.context(context_id)
307
+ end
287
308
 
288
- # Define required files
289
- required_files = %w[prefixes.yaml dimensions.yaml units.yaml quantities.yaml unit_systems.yaml]
290
- yaml_files = required_files.map { |file| File.join(dir_path, file) }
309
+ db_path = File.expand_path(dir_path.to_s)
310
+ unless Dir.exist?(db_path)
311
+ raise Errors::DatabaseNotFoundError,
312
+ "Database directory not found: #{db_path}"
313
+ end
291
314
 
292
- # Check if all required files exist
293
- missing_files = required_files.reject { |file| File.exist?(File.join(dir_path, file)) }
315
+ missing_files = DATABASE_FILES.values.reject do |filename|
316
+ File.exist?(File.join(db_path, filename))
317
+ end
294
318
 
295
319
  if missing_files.any?
296
320
  raise Errors::DatabaseFileNotFoundError,
297
- "Missing required database files: #{missing_files.join(", ")}"
321
+ "Missing required database files: #{missing_files.join(', ')}"
298
322
  end
299
323
 
300
- # Ensure we have path properly joined with filenames
301
- prefixes_yaml = yaml_files[0]
302
- dimensions_yaml = yaml_files[1]
303
- units_yaml = yaml_files[2]
304
- quantities_yaml = yaml_files[3]
305
- unit_systems_yaml = yaml_files[4]
306
-
307
- # Debug paths
308
- if ENV["DEBUG"]
309
- puts "[UnitsDB] Loading YAML files from directory: #{dir_path}"
310
- puts " - #{prefixes_yaml}"
311
- puts " - #{dimensions_yaml}"
312
- puts " - #{units_yaml}"
313
- puts " - #{quantities_yaml}"
314
- puts " - #{unit_systems_yaml}"
324
+ documents = load_database_documents(db_path)
325
+ schema_version = validate_schema_versions!(documents)
326
+ combined_hash = build_database_hash(documents, schema_version)
327
+
328
+ Lutaml::Model::GlobalContext.with_context(context_id) do
329
+ if Unitsdb::Config.register(context_id)
330
+ from_hash(combined_hash, register: context_id)
331
+ else
332
+ from_hash(combined_hash)
333
+ end
315
334
  end
335
+ end
316
336
 
317
- # Load YAML files with better error handling
318
- begin
319
- prefixes_hash = YAML.safe_load(File.read(prefixes_yaml))
320
- dimensions_hash = YAML.safe_load(File.read(dimensions_yaml))
321
- units_hash = YAML.safe_load(File.read(units_yaml))
322
- quantities_hash = YAML.safe_load(File.read(quantities_yaml))
323
- unit_systems_hash = YAML.safe_load(File.read(unit_systems_yaml))
324
- rescue Errno::ENOENT => e
325
- raise Errors::DatabaseFileNotFoundError, "Failed to read database file: #{e.message}"
326
- rescue Psych::SyntaxError => e
327
- raise Errors::DatabaseFileInvalidError, "Invalid YAML in database file: #{e.message}"
328
- rescue StandardError => e
329
- raise Errors::DatabaseLoadError, "Error loading database: #{e.message}"
337
+ def self.load_database_documents(db_path)
338
+ puts "[UnitsDB] Loading YAML files from directory: #{db_path}" if ENV["UNITSDB_DEBUG"]
339
+ DATABASE_FILES.transform_values do |filename|
340
+ puts " - #{File.join(db_path, filename)}" if ENV["UNITSDB_DEBUG"]
341
+ load_database_yaml(File.join(db_path, filename), filename)
342
+ end
343
+ end
344
+
345
+ def self.load_database_yaml(path, filename)
346
+ document = YAML.safe_load_file(path)
347
+
348
+ unless document.is_a?(Hash)
349
+ raise Errors::DatabaseFileInvalidError,
350
+ "Invalid YAML structure in #{filename}: expected a mapping"
330
351
  end
331
352
 
332
- # Verify all files have schema_version field
333
- missing_schema = []
334
- missing_schema << "prefixes.yaml" unless prefixes_hash.key?("schema_version")
335
- missing_schema << "dimensions.yaml" unless dimensions_hash.key?("schema_version")
336
- missing_schema << "units.yaml" unless units_hash.key?("schema_version")
337
- missing_schema << "quantities.yaml" unless quantities_hash.key?("schema_version")
338
- missing_schema << "unit_systems.yaml" unless unit_systems_hash.key?("schema_version")
353
+ document
354
+ rescue Errno::ENOENT => e
355
+ raise Errors::DatabaseFileNotFoundError,
356
+ "Failed to read database file: #{e.message}"
357
+ rescue Psych::SyntaxError => e
358
+ raise Errors::DatabaseFileInvalidError,
359
+ "Invalid YAML in database file: #{e.message}"
360
+ rescue Errors::DatabaseError
361
+ raise
362
+ rescue StandardError => e
363
+ raise Errors::DatabaseLoadError,
364
+ "Error loading database file #{filename}: #{e.message}"
365
+ end
366
+ private_class_method :load_database_documents, :load_database_yaml
339
367
 
340
- if missing_schema.any?
368
+ def self.validate_schema_versions!(documents)
369
+ versions = DATABASE_FILES.each_with_object({}) do |(collection_key, filename), result|
370
+ document = documents.fetch(collection_key)
371
+ result[filename] = document.fetch("schema_version")
372
+ rescue KeyError
341
373
  raise Errors::DatabaseFileInvalidError,
342
- "Missing schema_version in files: #{missing_schema.join(", ")}"
374
+ "Missing schema_version in #{filename}"
343
375
  end
344
376
 
345
- # Extract versions from each file
346
- prefixes_version = prefixes_hash["schema_version"]
347
- dimensions_version = dimensions_hash["schema_version"]
348
- units_version = units_hash["schema_version"]
349
- quantities_version = quantities_hash["schema_version"]
350
- unit_systems_version = unit_systems_hash["schema_version"]
351
-
352
- # Check if all versions match
353
- versions = [
354
- prefixes_version,
355
- dimensions_version,
356
- units_version,
357
- quantities_version,
358
- unit_systems_version
359
- ]
360
-
361
- unless versions.uniq.size == 1
362
- version_info = {
363
- "prefixes.yaml" => prefixes_version,
364
- "dimensions.yaml" => dimensions_version,
365
- "units.yaml" => units_version,
366
- "quantities.yaml" => quantities_version,
367
- "unit_systems.yaml" => unit_systems_version
368
- }
369
- raise Errors::VersionMismatchError, "Version mismatch in database files: #{version_info.inspect}"
377
+ unless versions.values.uniq.size == 1
378
+ raise Errors::VersionMismatchError,
379
+ "Version mismatch in database files: #{versions.inspect}"
370
380
  end
371
381
 
372
- # Check if the version is supported
373
- version = versions.first
374
- unless version == "2.0.0"
382
+ version = versions.values.first
383
+ unless version == SUPPORTED_SCHEMA_VERSION
375
384
  raise Errors::UnsupportedVersionError,
376
- "Unsupported database version: #{version}. Only version 2.0.0 is supported."
385
+ "Unsupported database version: #{version}. Only version #{SUPPORTED_SCHEMA_VERSION} is supported."
377
386
  end
378
387
 
379
- combined_yaml = {
380
- "schema_version" => prefixes_version,
381
- "prefixes" => prefixes_hash["prefixes"],
382
- "dimensions" => dimensions_hash["dimensions"],
383
- "units" => units_hash["units"],
384
- "quantities" => quantities_hash["quantities"],
385
- "unit_systems" => unit_systems_hash["unit_systems"]
386
- }.to_yaml
388
+ version
389
+ end
390
+
391
+ def self.build_database_hash(documents, schema_version)
392
+ {
393
+ "schema_version" => schema_version,
394
+ }.merge(
395
+ DATABASE_FILES.keys.to_h do |collection_key|
396
+ document = documents.fetch(collection_key)
397
+ [collection_key, fetch_collection!(document, collection_key)]
398
+ end,
399
+ )
400
+ end
387
401
 
388
- from_yaml(combined_yaml)
402
+ def self.fetch_collection!(document, collection_key)
403
+ document.fetch(collection_key)
404
+ rescue KeyError
405
+ raise Errors::DatabaseFileInvalidError,
406
+ "Missing #{collection_key} collection in #{DATABASE_FILES.fetch(collection_key)}"
389
407
  end
408
+ private_class_method :validate_schema_versions!, :build_database_hash,
409
+ :fetch_collection!
390
410
 
391
411
  private
392
412
 
@@ -533,7 +553,8 @@ module Unitsdb
533
553
  ref_type = "dimensions"
534
554
  ref_path = "dimensions:index:#{index}:dimension_reference"
535
555
 
536
- validate_reference(ref_id, ref_type, ref_path, registry, invalid_refs, "dimensions")
556
+ validate_reference(ref_id, ref_type, ref_path, registry, invalid_refs,
557
+ "dimensions")
537
558
  end
538
559
  end
539
560
 
@@ -545,7 +566,8 @@ module Unitsdb
545
566
  ref_type = "unit_systems"
546
567
  ref_path = "units:index:#{index}:unit_system_reference[#{idx}]"
547
568
 
548
- validate_reference(ref_id, ref_type, ref_path, registry, invalid_refs, "units")
569
+ validate_reference(ref_id, ref_type, ref_path, registry,
570
+ invalid_refs, "units")
549
571
  end
550
572
  end
551
573
  end
@@ -558,7 +580,8 @@ module Unitsdb
558
580
  ref_type = "quantities"
559
581
  ref_path = "units:index:#{index}:quantity_references[#{idx}]"
560
582
 
561
- validate_reference(ref_id, ref_type, ref_path, registry, invalid_refs, "units")
583
+ validate_reference(ref_id, ref_type, ref_path, registry,
584
+ invalid_refs, "units")
562
585
  end
563
586
  end
564
587
  end
@@ -575,7 +598,8 @@ module Unitsdb
575
598
  ref_type = "units"
576
599
  ref_path = "units:index:#{index}:root_units.#{idx}.unit_reference"
577
600
 
578
- validate_reference(ref_id, ref_type, ref_path, registry, invalid_refs, "units")
601
+ validate_reference(ref_id, ref_type, ref_path, registry,
602
+ invalid_refs, "units")
579
603
 
580
604
  # Check prefix reference if present
581
605
  next unless root_unit.respond_to?(:prefix_reference) && root_unit.prefix_reference
@@ -584,7 +608,8 @@ module Unitsdb
584
608
  ref_type = "prefixes"
585
609
  ref_path = "units:index:#{index}:root_units.#{idx}.prefix_reference"
586
610
 
587
- validate_reference(ref_id, ref_type, ref_path, registry, invalid_refs, "units")
611
+ validate_reference(ref_id, ref_type, ref_path, registry,
612
+ invalid_refs, "units")
588
613
  end
589
614
  end
590
615
  end
@@ -608,8 +633,12 @@ module Unitsdb
608
633
  # 3. Try alternate ID formats for unit systems (e.g., SI_base vs si-base)
609
634
  if !valid && type == "unitsml" && ref_type == "unit_systems" && registry.key?(ref_type) && (
610
635
  registry[ref_type].keys.any? { |k| k.end_with?(":#{id}") } ||
611
- registry[ref_type].keys.any? { |k| k.end_with?(":SI_#{id.sub("si-", "")}") } ||
612
- registry[ref_type].keys.any? { |k| k.end_with?(":non-SI_#{id.sub("nonsi-", "")}") }
636
+ registry[ref_type].keys.any? do |k|
637
+ k.end_with?(":SI_#{id.sub('si-', '')}")
638
+ end ||
639
+ registry[ref_type].keys.any? do |k|
640
+ k.end_with?(":non-SI_#{id.sub('nonsi-', '')}")
641
+ end
613
642
  )
614
643
  # Special handling for unit_systems between unitsml and nist types
615
644
  valid = true
@@ -617,7 +646,8 @@ module Unitsdb
617
646
 
618
647
  unless valid
619
648
  invalid_refs[file_type] ||= {}
620
- invalid_refs[file_type][ref_path] = { id: id, type: type, ref_type: ref_type }
649
+ invalid_refs[file_type][ref_path] =
650
+ { id: id, type: type, ref_type: ref_type }
621
651
  end
622
652
  # Handle references that are objects with id and type in a hash
623
653
  elsif ref_id.is_a?(Hash) && ref_id.key?("id") && ref_id.key?("type")
@@ -637,8 +667,12 @@ module Unitsdb
637
667
  # 3. Try alternate ID formats for unit systems (e.g., SI_base vs si-base)
638
668
  if !valid && type == "unitsml" && ref_type == "unit_systems" && registry.key?(ref_type) && (
639
669
  registry[ref_type].keys.any? { |k| k.end_with?(":#{id}") } ||
640
- registry[ref_type].keys.any? { |k| k.end_with?(":SI_#{id.sub("si-", "")}") } ||
641
- registry[ref_type].keys.any? { |k| k.end_with?(":non-SI_#{id.sub("nonsi-", "")}") }
670
+ registry[ref_type].keys.any? do |k|
671
+ k.end_with?(":SI_#{id.sub('si-', '')}")
672
+ end ||
673
+ registry[ref_type].keys.any? do |k|
674
+ k.end_with?(":non-SI_#{id.sub('nonsi-', '')}")
675
+ end
642
676
  )
643
677
  # Special handling for unit_systems between unitsml and nist types
644
678
  valid = true
@@ -646,7 +680,8 @@ module Unitsdb
646
680
 
647
681
  unless valid
648
682
  invalid_refs[file_type] ||= {}
649
- invalid_refs[file_type][ref_path] = { id: id, type: type, ref_type: ref_type }
683
+ invalid_refs[file_type][ref_path] =
684
+ { id: id, type: type, ref_type: ref_type }
650
685
  end
651
686
  else
652
687
  # Handle plain string references (legacy format)
@@ -659,4 +694,6 @@ module Unitsdb
659
694
  end
660
695
  end
661
696
  end
697
+
698
+ Config.register_model(Database, id: :database)
662
699
  end
@@ -1,9 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require_relative "identifier"
4
- require_relative "dimension_details"
5
- require_relative "quantity"
6
- require_relative "localized_string"
7
3
  # NISTd1:
8
4
  # length:
9
5
  # power: 1
@@ -45,5 +41,8 @@ module Unitsdb
45
41
  attribute :plane_angle, DimensionDetails
46
42
  attribute :short, :string
47
43
  attribute :names, LocalizedString, collection: true
44
+ attribute :references, ExternalReference, collection: true
48
45
  end
46
+
47
+ Config.register_model(Dimension, id: :dimension)
49
48
  end
@@ -10,11 +10,12 @@
10
10
  # mathml: "<mi mathvariant='sans-serif'>M</mi>"
11
11
  # unicode: "\U0001D5AC"
12
12
 
13
- require_relative "symbol_presentations"
14
13
  module Unitsdb
15
14
  class DimensionDetails < Lutaml::Model::Serializable
16
15
  attribute :power, :integer
17
16
  attribute :symbol, :string
18
17
  attribute :symbols, SymbolPresentations, collection: true
19
18
  end
19
+
20
+ Config.register_model(DimensionDetails, id: :dimension_details)
20
21
  end
@@ -5,4 +5,6 @@ module Unitsdb
5
5
  attribute :id, :string
6
6
  attribute :type, :string
7
7
  end
8
+
9
+ Config.register_model(DimensionReference, id: :dimension_reference)
8
10
  end
@@ -1,7 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require_relative "dimension"
4
-
5
3
  module Unitsdb
6
4
  class Dimensions < Lutaml::Model::Serializable
7
5
  # model Config.model_for(:dimensions)
@@ -10,4 +8,6 @@ module Unitsdb
10
8
  attribute :version, :string
11
9
  attribute :dimensions, Dimension, collection: true
12
10
  end
11
+
12
+ Config.register_model(Dimensions, id: :dimensions)
13
13
  end
@@ -9,5 +9,12 @@ module Unitsdb
9
9
  class DatabaseLoadError < DatabaseError; end
10
10
  class VersionMismatchError < DatabaseError; end
11
11
  class UnsupportedVersionError < DatabaseError; end
12
+
13
+ # CLI-specific errors
14
+ class CLIRuntimeError < StandardError; end
15
+ class InvalidParameterError < CLIRuntimeError; end
16
+ class FileNotFoundError < CLIRuntimeError; end
17
+ class ValidationError < CLIRuntimeError; end
18
+ class InvalidFormatError < CLIRuntimeError; end
12
19
  end
13
20
  end
@@ -11,4 +11,6 @@ module Unitsdb
11
11
  attribute :type, :string, values: %w[normative informative]
12
12
  attribute :authority, :string
13
13
  end
14
+
15
+ Config.register_model(ExternalReference, id: :external_reference)
14
16
  end
@@ -5,4 +5,6 @@ module Unitsdb
5
5
  attribute :id, :string
6
6
  attribute :type, :string
7
7
  end
8
+
9
+ Config.register_model(Identifier, id: :identifier)
8
10
  end
@@ -14,4 +14,6 @@ module Unitsdb
14
14
  value&.downcase
15
15
  end
16
16
  end
17
+
18
+ Config.register_model(LocalizedString, id: :localized_string)
17
19
  end
@@ -1,9 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require_relative "identifier"
4
- require_relative "symbol_presentations"
5
- require_relative "external_reference"
6
- require_relative "localized_string"
7
3
  # ---
8
4
  # NISTp10_30:
9
5
  # name: quetta
@@ -27,4 +23,6 @@ module Unitsdb
27
23
  attribute :power, :integer
28
24
  attribute :references, ExternalReference, collection: true
29
25
  end
26
+
27
+ Config.register_model(Prefix, id: :prefix)
30
28
  end
@@ -1,10 +1,10 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require_relative "identifier"
4
-
5
3
  module Unitsdb
6
4
  class PrefixReference < Identifier
7
5
  attribute :id, :string
8
6
  attribute :type, :string
9
7
  end
8
+
9
+ Config.register_model(PrefixReference, id: :prefix_reference)
10
10
  end
@@ -1,6 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require_relative "prefix"
4
3
  # ---
5
4
  # NISTp10_30:
6
5
  # name: quetta
@@ -20,4 +19,6 @@ module Unitsdb
20
19
  attribute :version, :string
21
20
  attribute :prefixes, Prefix, collection: true
22
21
  end
22
+
23
+ Config.register_model(Prefixes, id: :prefixes)
23
24
  end
@@ -1,7 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require_relative "quantity"
4
-
5
3
  module Unitsdb
6
4
  class Quantities < Lutaml::Model::Serializable
7
5
  # model Config.model_for(:quantities)
@@ -9,4 +7,6 @@ module Unitsdb
9
7
  attribute :version, :string
10
8
  attribute :quantities, Quantity, collection: true
11
9
  end
10
+
11
+ Config.register_model(Quantities, id: :quantities)
12
12
  end
@@ -1,11 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require_relative "identifier"
4
- require_relative "unit_reference"
5
- require_relative "dimension_reference"
6
- require_relative "external_reference"
7
- require_relative "localized_string"
8
-
9
3
  module Unitsdb
10
4
  class Quantity < Lutaml::Model::Serializable
11
5
  # model Config.model_for(:quantity)
@@ -18,4 +12,6 @@ module Unitsdb
18
12
  attribute :dimension_reference, DimensionReference
19
13
  attribute :references, ExternalReference, collection: true
20
14
  end
15
+
16
+ Config.register_model(Quantity, id: :quantity)
21
17
  end