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
@@ -0,0 +1,189 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "yaml"
4
+ require "fileutils"
5
+
6
+ module Unitsdb
7
+ module Commands
8
+ module Qudt
9
+ # Updater for adding QUDT references to UnitsDB entities
10
+ module Updater
11
+ QUDT_AUTHORITY = "qudt"
12
+
13
+ module_function
14
+
15
+ # Update references in UnitsDB entities with QUDT references
16
+ def update_references(entity_type, matches, db_entities, output_file,
17
+ include_potential = false)
18
+ puts "Updating QUDT references for #{entity_type}..."
19
+
20
+ # Get the original YAML file path from the database entities
21
+ original_yaml_file = get_original_yaml_file(db_entities, output_file)
22
+
23
+ # Load the original YAML file as plain data structures
24
+ yaml_content = File.read(original_yaml_file)
25
+ output_data = YAML.safe_load(yaml_content)
26
+
27
+ # Create a map of entity IDs to their QUDT references
28
+ entity_references = {}
29
+
30
+ # Process each match
31
+ matches.each do |match|
32
+ db_entity = match[:db_entity]
33
+ qudt_entity = match[:qudt_entity]
34
+
35
+ # Skip potential matches unless specified
36
+ next if match[:potential] && !include_potential
37
+
38
+ # Skip if entity has been manually verified
39
+ if manually_verified?(db_entity)
40
+ puts "Skipping manually verified entity: #{get_entity_id(db_entity)}"
41
+ next
42
+ end
43
+
44
+ # Get entity ID
45
+ entity_id = get_entity_id(db_entity)
46
+ next unless entity_id
47
+
48
+ # Store reference data as plain hash
49
+ entity_references[entity_id] = {
50
+ "uri" => qudt_entity.uri,
51
+ "type" => "informative",
52
+ "authority" => QUDT_AUTHORITY,
53
+ }
54
+ end
55
+
56
+ # Update the YAML content
57
+ output_data[entity_type].each do |entity_yaml|
58
+ # Find entity by ID
59
+ entity_id = if entity_yaml["identifiers"]
60
+ begin
61
+ entity_yaml["identifiers"].first["id"]
62
+ rescue StandardError
63
+ nil
64
+ end
65
+ end
66
+
67
+ next unless entity_id && entity_references.key?(entity_id)
68
+
69
+ # Initialize references array if it doesn't exist
70
+ entity_yaml["references"] ||= []
71
+
72
+ # Add new references
73
+ if (ext_ref = entity_references[entity_id])
74
+ if entity_yaml["references"].any? do |ref|
75
+ ref["uri"] == ext_ref["uri"] && ref["authority"] == ext_ref["authority"]
76
+ end
77
+ # Skip if reference already exists
78
+ puts "Reference already exists for entity ID: #{entity_id}"
79
+ else
80
+ # Add the reference
81
+ puts "Adding reference for entity ID: #{entity_id}, URI: #{ext_ref['uri']}, Authority: #{ext_ref['authority']}"
82
+ entity_yaml["references"] << ext_ref
83
+ end
84
+ end
85
+ end
86
+
87
+ # Write to YAML file
88
+ write_yaml_file(output_file, output_data)
89
+
90
+ puts "Added #{entity_references.values.size} QUDT references to #{entity_type}"
91
+ end
92
+
93
+ # Helper to write YAML file
94
+ def write_yaml_file(output_file, output_data)
95
+ # Ensure the output directory exists
96
+ output_dir = File.dirname(output_file)
97
+ FileUtils.mkdir_p(output_dir)
98
+
99
+ # Write to YAML file with proper formatting
100
+ yaml_content = output_data.to_yaml
101
+
102
+ # Preserve existing schema header or add default one
103
+ yaml_content = preserve_schema_header(output_file, yaml_content)
104
+
105
+ File.write(output_file, yaml_content)
106
+ end
107
+
108
+ # Preserve existing schema header or add default one
109
+ def preserve_schema_header(original_file, yaml_content)
110
+ schema_header = nil
111
+
112
+ # Extract existing schema header if file exists
113
+ if File.exist?(original_file)
114
+ original_content = File.read(original_file)
115
+ if (match = original_content.match(/^# yaml-language-server: \$schema=.+$/))
116
+ schema_header = match[0]
117
+ end
118
+ end
119
+
120
+ # Remove any existing schema header from new content to avoid duplication
121
+ yaml_content = yaml_content.gsub(
122
+ /^# yaml-language-server: \$schema=.+$\n/, ""
123
+ )
124
+
125
+ # Add preserved or default schema header
126
+ if schema_header
127
+ "#{schema_header}\n#{yaml_content}"
128
+ else
129
+ entity_type = File.basename(original_file, ".yaml")
130
+ "# yaml-language-server: $schema=schemas/#{entity_type}-schema.yaml\n#{yaml_content}"
131
+ end
132
+ end
133
+
134
+ # Get the original YAML file path
135
+ def get_original_yaml_file(_db_entities, output_file)
136
+ # The database path should be available from the update command
137
+ # We need to construct the path to the original YAML file
138
+ entity_type = File.basename(output_file, ".yaml")
139
+
140
+ # Try to find the original file in the database directory
141
+ # Look for it relative to where we expect it to be
142
+ # nil
143
+
144
+ # Try to get database directory from environment or assume it's the fixtures
145
+ database_dir = ENV["UNITSDB_DATABASE_PATH"] ||
146
+ File.join(File.dirname(__FILE__),
147
+ "../../../data")
148
+
149
+ original_yaml_file = File.join(database_dir, "#{entity_type}.yaml")
150
+
151
+ # If that doesn't exist, try to find it relative to the current working directory
152
+ unless File.exist?(original_yaml_file)
153
+ original_yaml_file = File.join("data",
154
+ "#{entity_type}.yaml")
155
+ end
156
+
157
+ # If still not found, create a fallback
158
+ unless File.exist?(original_yaml_file)
159
+ puts "Warning: Could not find original YAML file. Creating empty template."
160
+ original_yaml_file = output_file
161
+ FileUtils.mkdir_p(File.dirname(original_yaml_file))
162
+ File.write(original_yaml_file, { entity_type => [] }.to_yaml)
163
+ end
164
+
165
+ puts "Using original YAML file: #{original_yaml_file}"
166
+ original_yaml_file
167
+ end
168
+
169
+ # Get entity ID (either from identifiers array or directly)
170
+ def get_entity_id(entity)
171
+ if entity.respond_to?(:identifiers) && entity.identifiers && !entity.identifiers.empty?
172
+ entity.identifiers.first.id
173
+ elsif entity.respond_to?(:id)
174
+ entity.id
175
+ end
176
+ end
177
+
178
+ # Check if an entity has been manually verified (has a special flag)
179
+ def manually_verified?(entity)
180
+ return false unless entity.respond_to?(:references) && entity.references
181
+
182
+ entity.references.any? do |ref|
183
+ ref.authority == QUDT_AUTHORITY && ref.respond_to?(:verified) && ref.verified
184
+ end
185
+ end
186
+ end
187
+ end
188
+ end
189
+ end
@@ -0,0 +1,82 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "thor"
4
+
5
+ module Unitsdb
6
+ module Commands
7
+ module Qudt
8
+ autoload :Check, "unitsdb/commands/qudt/check"
9
+ autoload :Formatter, "unitsdb/commands/qudt/formatter"
10
+ autoload :TtlParser, "unitsdb/commands/qudt/ttl_parser"
11
+ autoload :Update, "unitsdb/commands/qudt/update"
12
+ autoload :Updater, "unitsdb/commands/qudt/updater"
13
+ end
14
+
15
+ class QudtCommand < Thor
16
+ # Inherit trace option from parent CLI
17
+ class_option :trace, type: :boolean, default: false,
18
+ desc: "Show full backtrace on error"
19
+
20
+ desc "check", "Check QUDT references in UnitsDB"
21
+ option :entity_type, type: :string, aliases: "-e",
22
+ desc: "Entity type to check (units, quantities, dimensions, unit_systems). If not specified, all types are checked"
23
+ option :ttl_dir, type: :string, aliases: "-t",
24
+ desc: "Path to directory containing QUDT TTL files. If not specified, vocabularies will be downloaded from online sources"
25
+ option :output_updated_database, type: :string, aliases: "-o",
26
+ desc: "Directory path to write updated YAML files with added QUDT references"
27
+ option :direction, type: :string, default: "both", aliases: "-r",
28
+ desc: "Direction to check: 'to_qudt' (UnitsDB→QUDT), 'from_qudt' (QUDT→UnitsDB), or 'both'"
29
+ option :include_potential_matches, type: :boolean, default: false, aliases: "-p",
30
+ desc: "Include potential matches when updating references (default: false)"
31
+ option :database, type: :string, required: true, aliases: "-d",
32
+ desc: "Path to UnitsDB database (required)"
33
+ def check
34
+ run_command(Qudt::Check, options)
35
+ end
36
+
37
+ desc "update", "Update UnitsDB with QUDT references"
38
+ option :entity_type, type: :string, aliases: "-e",
39
+ desc: "Entity type to update (units, quantities, dimensions, unit_systems). If not specified, all types are updated"
40
+ option :ttl_dir, type: :string, aliases: "-t",
41
+ desc: "Path to directory containing QUDT TTL files. If not specified, vocabularies will be downloaded from online sources"
42
+ option :output_dir, type: :string, aliases: "-o",
43
+ desc: "Directory path to write updated YAML files (defaults to database path)"
44
+ option :include_potential_matches, type: :boolean, default: false, aliases: "-p",
45
+ desc: "Include potential matches when updating references (default: false)"
46
+ option :database, type: :string, required: true, aliases: "-d",
47
+ desc: "Path to UnitsDB database (required)"
48
+ def update
49
+ run_command(Qudt::Update, options)
50
+ end
51
+
52
+ private
53
+
54
+ def run_command(command_class, options)
55
+ command = command_class.new(options)
56
+ command.run
57
+ rescue Unitsdb::Errors::CLIRuntimeError => e
58
+ handle_cli_error(e)
59
+ rescue StandardError => e
60
+ handle_error(e)
61
+ end
62
+
63
+ def handle_cli_error(error)
64
+ if options[:trace]
65
+ raise error
66
+ else
67
+ warn "Error: #{error.message}"
68
+ exit 1
69
+ end
70
+ end
71
+
72
+ def handle_error(error)
73
+ if options[:trace]
74
+ raise error
75
+ else
76
+ warn "Error: #{error.message}"
77
+ exit 1
78
+ end
79
+ end
80
+ end
81
+ end
82
+ end
@@ -26,29 +26,30 @@ module Unitsdb
26
26
  create_unified_yaml(db)
27
27
  create_zip_archive(db)
28
28
  else
29
- puts "Invalid format option: #{@options[:format]}"
30
- puts "Valid options are: 'yaml', 'zip', or 'all'"
31
- exit(1)
29
+ raise Unitsdb::Errors::InvalidFormatError,
30
+ "Invalid format '#{@options[:format]}': must be 'yaml', 'zip', or 'all'"
32
31
  end
33
32
 
34
33
  puts "Release files created successfully in #{@options[:output_dir]}"
35
34
  rescue Unitsdb::Errors::DatabaseError => e
36
- puts "Error: #{e.message}"
37
- exit(1)
35
+ raise Unitsdb::Errors::DatabaseLoadError,
36
+ "Failed to create release: #{e.message}"
38
37
  end
39
38
 
40
39
  private
41
40
 
42
41
  def create_unified_yaml(db)
43
42
  # Create a unified YAML file with all database components
44
- output_path = File.join(@options[:output_dir], "unitsdb-#{@options[:version]}.yaml")
43
+ output_path = File.join(@options[:output_dir],
44
+ "unitsdb-#{@options[:version]}.yaml")
45
45
  File.write(output_path, db.to_yaml)
46
46
  puts "Created unified YAML file: #{output_path}"
47
47
  end
48
48
 
49
49
  def create_zip_archive(db)
50
50
  # Create a ZIP archive with individual YAML files
51
- output_path = File.join(@options[:output_dir], "unitsdb-#{@options[:version]}.zip")
51
+ output_path = File.join(@options[:output_dir],
52
+ "unitsdb-#{@options[:version]}.zip")
52
53
 
53
54
  Zip::File.open(output_path, Zip::File::CREATE) do |zipfile|
54
55
  {
@@ -56,12 +57,14 @@ module Unitsdb
56
57
  unit_systems: Unitsdb::UnitSystems,
57
58
  units: Unitsdb::Units,
58
59
  prefixes: Unitsdb::Prefixes,
59
- quantities: Unitsdb::Quantities
60
+ quantities: Unitsdb::Quantities,
60
61
  }.each_pair do |access_method, collection_klass|
61
62
  db.send(access_method).tap do |data|
62
63
  collection = collection_klass.new(access_method => data)
63
64
  collection.version = @options[:version]
64
- zipfile.get_output_stream("#{access_method}.yaml") { |f| f.write(collection.to_yaml) }
65
+ zipfile.get_output_stream("#{access_method}.yaml") do |f|
66
+ f.write(collection.to_yaml)
67
+ end
65
68
  end
66
69
  end
67
70
  end
@@ -1,8 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require_relative "base"
4
3
  require "json"
5
- require_relative "../errors"
6
4
 
7
5
  module Unitsdb
8
6
  module Commands
@@ -33,8 +31,8 @@ module Unitsdb
33
31
  puts entity.send("to_#{format.downcase}")
34
32
  return
35
33
  rescue NoMethodError
36
- puts "Error: Unable to convert entity to #{format} format"
37
- exit(1)
34
+ raise Unitsdb::Errors::InvalidFormatError,
35
+ "Unable to convert entity to #{format.upcase} format: output format not supported for this entity type"
38
36
  end
39
37
  end
40
38
 
@@ -64,11 +62,10 @@ module Unitsdb
64
62
  print_entity_with_ids(entity)
65
63
  end
66
64
  rescue Unitsdb::Errors::DatabaseError => e
67
- puts "Error: #{e.message}"
68
- exit(1)
65
+ raise Unitsdb::Errors::DatabaseLoadError,
66
+ "Failed to load database: #{e.message}"
69
67
  rescue StandardError => e
70
- puts "Error searching database: #{e.message}"
71
- exit(1)
68
+ raise Unitsdb::Errors::CLIRuntimeError, "Search failed: #{e.message}"
72
69
  end
73
70
  end
74
71
 
@@ -93,7 +90,7 @@ module Unitsdb
93
90
  else
94
91
  puts " IDs:"
95
92
  identifiers.each do |id|
96
- puts " - #{id.id} (Type: #{id.type || "N/A"})"
93
+ puts " - #{id.id} (Type: #{id.type || 'N/A'})"
97
94
  end
98
95
  end
99
96
 
@@ -122,7 +119,7 @@ module Unitsdb
122
119
  if entity.identifiers&.any?
123
120
  puts " - Identifiers:"
124
121
  entity.identifiers.each do |id|
125
- puts " - #{id.id} (Type: #{id.type || "N/A"})"
122
+ puts " - #{id.id} (Type: #{id.type || 'N/A'})"
126
123
  end
127
124
  else
128
125
  puts " - Identifiers: None"
@@ -132,7 +129,11 @@ module Unitsdb
132
129
  case entity
133
130
  when Unitsdb::Unit
134
131
  puts " - Symbols:" if entity.respond_to?(:symbols) && entity.symbols&.any?
135
- entity.symbols.each { |s| puts " - #{s}" } if entity.respond_to?(:symbols) && entity.symbols&.any?
132
+ if entity.respond_to?(:symbols) && entity.symbols&.any?
133
+ entity.symbols.each do |s|
134
+ puts " - #{s}"
135
+ end
136
+ end
136
137
 
137
138
  puts " - Definition: #{entity.definition}" if entity.respond_to?(:definition) && entity.definition
138
139
 
@@ -1,12 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require_relative "../base"
4
- require_relative "../../database"
5
- require_relative "../../errors"
6
- require_relative "xml_parser"
7
- require_relative "formatter"
8
- require_relative "matcher"
9
- require_relative "updater"
10
3
  require "fileutils"
11
4
 
12
5
  module Unitsdb
@@ -34,90 +27,110 @@ module Unitsdb
34
27
  @db = Unitsdb::Database.from_db(database_path)
35
28
 
36
29
  puts "Using UCUM file: #{ucum_file}"
37
- puts "Include potential matches: #{include_potential ? "Yes" : "No"}"
30
+ puts "Include potential matches: #{include_potential ? 'Yes' : 'No'}"
38
31
 
39
32
  # Parse UCUM XML file
40
33
  ucum_data = XmlParser.parse_ucum_file(ucum_file)
41
34
 
42
35
  # Process entity types
43
- process_entities(entity_type, ucum_data, direction, output_dir, include_potential)
36
+ process_entities(entity_type, ucum_data, direction, output_dir,
37
+ include_potential)
44
38
  end
45
39
 
46
40
  private
47
41
 
48
42
  # Process all entity types or a specific one
49
- def process_entities(entity_type, ucum_data, direction, output_dir, include_potential)
43
+ def process_entities(entity_type, ucum_data, direction, output_dir,
44
+ include_potential)
50
45
  if entity_type && ENTITY_TYPES.include?(entity_type)
51
- process_entity_type(entity_type, ucum_data, direction, output_dir, include_potential)
46
+ process_entity_type(entity_type, ucum_data, direction, output_dir,
47
+ include_potential)
52
48
  else
53
49
  ENTITY_TYPES.each do |type|
54
- process_entity_type(type, ucum_data, direction, output_dir, include_potential)
50
+ process_entity_type(type, ucum_data, direction, output_dir,
51
+ include_potential)
55
52
  end
56
53
  end
57
54
  end
58
55
 
59
56
  # Process a specific entity type
60
- def process_entity_type(entity_type, ucum_data, direction, output_dir, include_potential = false)
57
+ def process_entity_type(entity_type, ucum_data, direction, output_dir,
58
+ include_potential = false)
61
59
  puts "\n========== Processing #{entity_type.upcase} References ==========\n"
62
60
 
63
61
  db_entities = @db.send(entity_type)
64
- ucum_entities = XmlParser.get_entities_from_ucum(entity_type, ucum_data)
62
+ ucum_entities = XmlParser.get_entities_from_ucum(entity_type,
63
+ ucum_data)
65
64
 
66
65
  puts "Found #{ucum_entities.size} #{entity_type} in UCUM"
67
66
  puts "Found #{db_entities.size} #{entity_type} in database"
68
67
 
69
- check_from_ucum(entity_type, ucum_entities, db_entities, output_dir, include_potential) if %w[from_ucum
70
- both].include?(direction)
68
+ if %w[from_ucum
69
+ both].include?(direction)
70
+ check_from_ucum(entity_type, ucum_entities, db_entities, output_dir,
71
+ include_potential)
72
+ end
71
73
 
72
74
  return unless %w[to_ucum both].include?(direction)
73
75
 
74
- check_to_ucum(entity_type, ucum_entities, db_entities, output_dir, include_potential)
76
+ check_to_ucum(entity_type, ucum_entities, db_entities, output_dir,
77
+ include_potential)
75
78
  end
76
79
 
77
80
  # Validation helpers
78
81
  def validate_parameters(direction, ucum_file)
79
82
  unless %w[to_ucum from_ucum both].include?(direction)
80
- puts "Invalid direction: #{direction}. Must be one of: to_ucum, from_ucum, both"
81
- exit(1)
83
+ raise Unitsdb::Errors::InvalidParameterError,
84
+ "Invalid direction '#{direction}': must be 'to_ucum', 'from_ucum', or 'both'"
82
85
  end
83
86
 
84
87
  return if File.exist?(ucum_file)
85
88
 
86
- puts "UCUM file not found: #{ucum_file}"
87
- exit(1)
89
+ raise Unitsdb::Errors::FileNotFoundError,
90
+ "UCUM file not found: #{ucum_file}"
88
91
  end
89
92
 
90
93
  # Direction handler: UCUM → UnitsDB
91
- def check_from_ucum(entity_type, ucum_entities, db_entities, output_dir, include_potential = false)
94
+ def check_from_ucum(entity_type, ucum_entities, db_entities,
95
+ output_dir, include_potential = false)
92
96
  Formatter.print_direction_header("UCUM → UnitsDB")
93
97
 
94
- matches, missing_matches, unmatched_ucum = Matcher.match_ucum_to_db(entity_type, ucum_entities, db_entities)
98
+ matches, missing_matches, unmatched_ucum = Matcher.match_ucum_to_db(
99
+ entity_type, ucum_entities, db_entities
100
+ )
95
101
 
96
102
  # Print results
97
- Formatter.display_ucum_results(entity_type, matches, missing_matches, unmatched_ucum)
103
+ Formatter.display_ucum_results(entity_type, matches, missing_matches,
104
+ unmatched_ucum)
98
105
 
99
106
  # Update references if needed
100
107
  return unless output_dir && !missing_matches.empty?
101
108
 
102
109
  output_file = File.join(output_dir, "#{entity_type}.yaml")
103
- Updater.update_references(entity_type, missing_matches, db_entities, output_file, include_potential)
110
+ Updater.update_references(entity_type, missing_matches, db_entities,
111
+ output_file, include_potential)
104
112
  puts "\nUpdated references written to #{output_file}"
105
113
  end
106
114
 
107
115
  # Direction handler: UnitsDB → UCUM
108
- def check_to_ucum(entity_type, ucum_entities, db_entities, output_dir, include_potential = false)
116
+ def check_to_ucum(entity_type, ucum_entities, db_entities, output_dir,
117
+ include_potential = false)
109
118
  Formatter.print_direction_header("UnitsDB → UCUM")
110
119
 
111
- matches, missing_refs, unmatched_db = Matcher.match_db_to_ucum(entity_type, ucum_entities, db_entities)
120
+ matches, missing_refs, unmatched_db = Matcher.match_db_to_ucum(
121
+ entity_type, ucum_entities, db_entities
122
+ )
112
123
 
113
124
  # Print results
114
- Formatter.display_db_results(entity_type, matches, missing_refs, unmatched_db)
125
+ Formatter.display_db_results(entity_type, matches, missing_refs,
126
+ unmatched_db)
115
127
 
116
128
  # Update references if needed
117
129
  return unless output_dir && !missing_refs.empty?
118
130
 
119
131
  output_file = File.join(output_dir, "#{entity_type}.yaml")
120
- Updater.update_references(entity_type, missing_refs, db_entities, output_file, include_potential)
132
+ Updater.update_references(entity_type, missing_refs, db_entities,
133
+ output_file, include_potential)
121
134
  puts "\nUpdated references written to #{output_file}"
122
135
  end
123
136
  end
@@ -13,7 +13,8 @@ module Unitsdb
13
13
  end
14
14
 
15
15
  # Display results for UCUM → UnitsDB matching
16
- def display_ucum_results(entity_type, matches, missing_matches, unmatched_ucum)
16
+ def display_ucum_results(entity_type, matches, missing_matches,
17
+ unmatched_ucum)
17
18
  puts "\nResults for #{entity_type.capitalize} (UCUM → UnitsDB):"
18
19
  puts " Matched: #{matches.size}"
19
20
  puts " Missing matches (could be added): #{missing_matches.size}"
@@ -18,12 +18,15 @@ module Unitsdb
18
18
 
19
19
  # Process each UCUM entity
20
20
  ucum_entities.each do |ucum_entity|
21
- match_data = find_db_match_for_ucum(ucum_entity, db_entities, entity_type)
21
+ match_data = find_db_match_for_ucum(ucum_entity, db_entities,
22
+ entity_type)
22
23
 
23
24
  if match_data[:match]
24
- matches << { ucum_entity: ucum_entity, db_entity: match_data[:match] }
25
+ matches << { ucum_entity: ucum_entity,
26
+ db_entity: match_data[:match] }
25
27
  elsif match_data[:potential_match]
26
- missing_matches << { ucum_entity: ucum_entity, db_entity: match_data[:potential_match] }
28
+ missing_matches << { ucum_entity: ucum_entity,
29
+ db_entity: match_data[:potential_match] }
27
30
  else
28
31
  unmatched_ucum << ucum_entity
29
32
  end
@@ -45,14 +48,18 @@ module Unitsdb
45
48
  db_entities.send(entity_type).each do |db_entity|
46
49
  # Skip entities that already have UCUM references
47
50
  if has_ucum_reference?(db_entity)
48
- matches << { db_entity: db_entity, ucum_entity: find_referenced_ucum_entity(db_entity, ucum_entities) }
51
+ matches << { db_entity: db_entity,
52
+ ucum_entity: find_referenced_ucum_entity(db_entity,
53
+ ucum_entities) }
49
54
  next
50
55
  end
51
56
 
52
- match_data = find_ucum_match_for_db(db_entity, ucum_entities, entity_type)
57
+ match_data = find_ucum_match_for_db(db_entity, ucum_entities,
58
+ entity_type)
53
59
 
54
60
  if match_data[:match]
55
- missing_refs << { db_entity: db_entity, ucum_entity: match_data[:match] }
61
+ missing_refs << { db_entity: db_entity,
62
+ ucum_entity: match_data[:match] }
56
63
  else
57
64
  unmatched_db << db_entity
58
65
  end
@@ -166,7 +173,9 @@ module Unitsdb
166
173
 
167
174
  # Try exact name match first
168
175
  if db_prefix.names && !db_prefix.names.empty?
169
- db_prefix_names = db_prefix.names.map { |name_obj| name_obj.value.downcase }
176
+ db_prefix_names = db_prefix.names.map do |name_obj|
177
+ name_obj.value.downcase
178
+ end
170
179
 
171
180
  name_match = ucum_prefixes.find do |ucum_prefix|
172
181
  db_prefix_names.include?(ucum_prefix.name.downcase)
@@ -249,7 +258,10 @@ module Unitsdb
249
258
  end
250
259
  end
251
260
 
252
- result[:potential_match] = property_matches.first if property_matches.any?
261
+ if property_matches.any?
262
+ result[:potential_match] =
263
+ property_matches.first
264
+ end
253
265
  end
254
266
 
255
267
  result
@@ -261,7 +273,9 @@ module Unitsdb
261
273
 
262
274
  # Try name match first
263
275
  if db_unit.names && !db_unit.names.empty?
264
- db_unit_names = db_unit.names.map { |name_obj| name_obj.value.downcase }
276
+ db_unit_names = db_unit.names.map do |name_obj|
277
+ name_obj.value.downcase
278
+ end
265
279
 
266
280
  name_match = ucum_units.find do |ucum_unit|
267
281
  case ucum_unit