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.
- checksums.yaml +4 -4
- data/.github/workflows/release.yml +8 -1
- data/.gitignore +2 -0
- data/.gitmodules +4 -3
- data/.rubocop.yml +13 -8
- data/.rubocop_todo.yml +217 -100
- data/CLAUDE.md +55 -0
- data/Gemfile +4 -1
- data/README.adoc +283 -16
- data/data/dimensions.yaml +1864 -0
- data/data/prefixes.yaml +874 -0
- data/data/quantities.yaml +3715 -0
- data/data/scales.yaml +97 -0
- data/data/schemas/dimensions-schema.yaml +153 -0
- data/data/schemas/prefixes-schema.yaml +155 -0
- data/data/schemas/quantities-schema.yaml +117 -0
- data/data/schemas/scales-schema.yaml +106 -0
- data/data/schemas/unit_systems-schema.yaml +116 -0
- data/data/schemas/units-schema.yaml +215 -0
- data/data/unit_systems.yaml +78 -0
- data/data/units.yaml +14052 -0
- data/exe/unitsdb +7 -1
- data/lib/unitsdb/cli.rb +42 -15
- data/lib/unitsdb/commands/_modify.rb +40 -4
- data/lib/unitsdb/commands/base.rb +6 -2
- data/lib/unitsdb/commands/check_si/si_formatter.rb +488 -0
- data/lib/unitsdb/commands/check_si/si_matcher.rb +487 -0
- data/lib/unitsdb/commands/check_si/si_ttl_parser.rb +103 -0
- data/lib/unitsdb/commands/check_si/si_updater.rb +254 -0
- data/lib/unitsdb/commands/check_si.rb +54 -35
- data/lib/unitsdb/commands/get.rb +11 -10
- data/lib/unitsdb/commands/normalize.rb +21 -7
- data/lib/unitsdb/commands/qudt/check.rb +150 -0
- data/lib/unitsdb/commands/qudt/formatter.rb +194 -0
- data/lib/unitsdb/commands/qudt/matcher.rb +746 -0
- data/lib/unitsdb/commands/qudt/ttl_parser.rb +403 -0
- data/lib/unitsdb/commands/qudt/update.rb +126 -0
- data/lib/unitsdb/commands/qudt/updater.rb +189 -0
- data/lib/unitsdb/commands/qudt.rb +82 -0
- data/lib/unitsdb/commands/release.rb +12 -9
- data/lib/unitsdb/commands/search.rb +12 -11
- data/lib/unitsdb/commands/ucum/check.rb +42 -29
- data/lib/unitsdb/commands/ucum/formatter.rb +2 -1
- data/lib/unitsdb/commands/ucum/matcher.rb +23 -9
- data/lib/unitsdb/commands/ucum/update.rb +14 -13
- data/lib/unitsdb/commands/ucum/updater.rb +40 -6
- data/lib/unitsdb/commands/ucum/xml_parser.rb +0 -2
- data/lib/unitsdb/commands/ucum.rb +44 -4
- data/lib/unitsdb/commands/validate/identifiers.rb +2 -4
- data/lib/unitsdb/commands/validate/qudt_references.rb +111 -0
- data/lib/unitsdb/commands/validate/references.rb +36 -19
- data/lib/unitsdb/commands/validate/si_references.rb +3 -5
- data/lib/unitsdb/commands/validate/ucum_references.rb +105 -0
- data/lib/unitsdb/commands/validate.rb +67 -11
- data/lib/unitsdb/commands.rb +20 -0
- data/lib/unitsdb/config.rb +114 -2
- data/lib/unitsdb/database.rb +160 -123
- data/lib/unitsdb/dimension.rb +3 -4
- data/lib/unitsdb/dimension_details.rb +2 -1
- data/lib/unitsdb/dimension_reference.rb +2 -0
- data/lib/unitsdb/dimensions.rb +2 -2
- data/lib/unitsdb/errors.rb +7 -0
- 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/prefix.rb +2 -4
- data/lib/unitsdb/prefix_reference.rb +2 -2
- data/lib/unitsdb/prefixes.rb +2 -1
- data/lib/unitsdb/quantities.rb +2 -2
- data/lib/unitsdb/quantity.rb +2 -6
- data/lib/unitsdb/quantity_reference.rb +2 -0
- data/lib/unitsdb/qudt.rb +105 -0
- data/lib/unitsdb/root_unit_reference.rb +2 -3
- data/lib/unitsdb/scale.rb +2 -4
- data/lib/unitsdb/scale_properties.rb +2 -0
- data/lib/unitsdb/scale_reference.rb +2 -2
- data/lib/unitsdb/scales.rb +2 -2
- data/lib/unitsdb/si_derived_base.rb +2 -2
- data/lib/unitsdb/symbol_presentations.rb +2 -0
- data/lib/unitsdb/ucum.rb +21 -10
- data/lib/unitsdb/unit.rb +2 -10
- data/lib/unitsdb/unit_reference.rb +2 -2
- data/lib/unitsdb/unit_system.rb +3 -3
- data/lib/unitsdb/unit_system_reference.rb +2 -2
- data/lib/unitsdb/unit_systems.rb +2 -2
- data/lib/unitsdb/units.rb +2 -2
- data/lib/unitsdb/utils.rb +32 -21
- data/lib/unitsdb/version.rb +5 -1
- data/lib/unitsdb.rb +62 -14
- data/unitsdb.gemspec +6 -3
- metadata +52 -13
- data/lib/unitsdb/commands/si_formatter.rb +0 -485
- data/lib/unitsdb/commands/si_matcher.rb +0 -470
- data/lib/unitsdb/commands/si_ttl_parser.rb +0 -100
- 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
|
-
|
|
30
|
-
|
|
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
|
-
|
|
37
|
-
|
|
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],
|
|
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],
|
|
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")
|
|
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
|
-
|
|
37
|
-
|
|
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
|
-
|
|
68
|
-
|
|
65
|
+
raise Unitsdb::Errors::DatabaseLoadError,
|
|
66
|
+
"Failed to load database: #{e.message}"
|
|
69
67
|
rescue StandardError => e
|
|
70
|
-
|
|
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 ||
|
|
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 ||
|
|
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
|
-
|
|
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 ?
|
|
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,
|
|
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,
|
|
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,
|
|
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,
|
|
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,
|
|
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,
|
|
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
|
-
|
|
70
|
-
|
|
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,
|
|
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
|
-
|
|
81
|
-
|
|
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
|
-
|
|
87
|
-
|
|
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,
|
|
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(
|
|
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,
|
|
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,
|
|
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,
|
|
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(
|
|
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,
|
|
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,
|
|
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,
|
|
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,
|
|
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,
|
|
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,
|
|
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,
|
|
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,
|
|
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,
|
|
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
|
|
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
|
-
|
|
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
|
|
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
|