unitsdb 2.1.1 → 2.2.1
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/database.rb +90 -52
- data/lib/unitsdb/dimension.rb +1 -4
- data/lib/unitsdb/dimension_details.rb +0 -1
- data/lib/unitsdb/dimensions.rb +0 -2
- data/lib/unitsdb/errors.rb +7 -0
- data/lib/unitsdb/prefix.rb +0 -4
- data/lib/unitsdb/prefix_reference.rb +0 -2
- data/lib/unitsdb/prefixes.rb +0 -1
- data/lib/unitsdb/quantities.rb +0 -2
- data/lib/unitsdb/quantity.rb +0 -6
- data/lib/unitsdb/qudt.rb +100 -0
- data/lib/unitsdb/root_unit_reference.rb +0 -3
- data/lib/unitsdb/scale.rb +0 -4
- data/lib/unitsdb/scale_reference.rb +0 -2
- data/lib/unitsdb/scales.rb +0 -2
- data/lib/unitsdb/si_derived_base.rb +0 -2
- data/lib/unitsdb/ucum.rb +14 -10
- data/lib/unitsdb/unit.rb +0 -10
- data/lib/unitsdb/unit_reference.rb +0 -2
- data/lib/unitsdb/unit_system.rb +1 -3
- data/lib/unitsdb/unit_system_reference.rb +0 -2
- data/lib/unitsdb/unit_systems.rb +0 -2
- data/lib/unitsdb/units.rb +0 -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
|
@@ -1,10 +1,5 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require_relative "../base"
|
|
4
|
-
require_relative "../../database"
|
|
5
|
-
require_relative "xml_parser"
|
|
6
|
-
require_relative "matcher"
|
|
7
|
-
require_relative "updater"
|
|
8
3
|
require "fileutils"
|
|
9
4
|
|
|
10
5
|
module Unitsdb
|
|
@@ -37,17 +32,19 @@ module Unitsdb
|
|
|
37
32
|
return 1
|
|
38
33
|
end
|
|
39
34
|
puts "Using UCUM file: #{ucum_file}"
|
|
40
|
-
puts "Include potential matches: #{include_potential ?
|
|
35
|
+
puts "Include potential matches: #{include_potential ? 'Yes' : 'No'}"
|
|
41
36
|
|
|
42
37
|
# Parse UCUM XML file
|
|
43
38
|
ucum_data = XmlParser.parse_ucum_file(ucum_file)
|
|
44
39
|
|
|
45
40
|
# Process entity types
|
|
46
41
|
if entity_type && ENTITY_TYPES.include?(entity_type)
|
|
47
|
-
process_entity_type(entity_type, ucum_data, output_dir,
|
|
42
|
+
process_entity_type(entity_type, ucum_data, output_dir,
|
|
43
|
+
include_potential)
|
|
48
44
|
else
|
|
49
45
|
ENTITY_TYPES.each do |type|
|
|
50
|
-
process_entity_type(type, ucum_data, output_dir,
|
|
46
|
+
process_entity_type(type, ucum_data, output_dir,
|
|
47
|
+
include_potential)
|
|
51
48
|
end
|
|
52
49
|
end
|
|
53
50
|
|
|
@@ -56,7 +53,8 @@ module Unitsdb
|
|
|
56
53
|
|
|
57
54
|
private
|
|
58
55
|
|
|
59
|
-
def process_entity_type(entity_type, ucum_data, output_dir,
|
|
56
|
+
def process_entity_type(entity_type, ucum_data, output_dir,
|
|
57
|
+
include_potential)
|
|
60
58
|
puts "\n========== Processing #{entity_type.upcase} References =========="
|
|
61
59
|
|
|
62
60
|
# Get entities
|
|
@@ -64,19 +62,22 @@ module Unitsdb
|
|
|
64
62
|
yaml_path = File.join(@options[:database], "#{entity_type}.yaml")
|
|
65
63
|
entity_collection = klass.from_yaml(File.read(yaml_path))
|
|
66
64
|
|
|
67
|
-
ucum_entities = XmlParser.get_entities_from_ucum(entity_type,
|
|
65
|
+
ucum_entities = XmlParser.get_entities_from_ucum(entity_type,
|
|
66
|
+
ucum_data)
|
|
68
67
|
|
|
69
68
|
return if ucum_entities.nil? || ucum_entities.empty?
|
|
70
69
|
|
|
71
70
|
# Match entities
|
|
72
|
-
_, missing_refs, = Matcher.match_db_to_ucum(entity_type,
|
|
71
|
+
_, missing_refs, = Matcher.match_db_to_ucum(entity_type,
|
|
72
|
+
ucum_entities, entity_collection)
|
|
73
73
|
|
|
74
74
|
# Create output directory if it doesn't exist
|
|
75
|
-
FileUtils.mkdir_p(output_dir)
|
|
75
|
+
FileUtils.mkdir_p(output_dir)
|
|
76
76
|
|
|
77
77
|
# Update references in UnitsDB entities
|
|
78
78
|
output_file = File.join(output_dir, "#{entity_type}.yaml")
|
|
79
|
-
Updater.update_references(entity_type, missing_refs,
|
|
79
|
+
Updater.update_references(entity_type, missing_refs,
|
|
80
|
+
entity_collection, output_file, include_potential)
|
|
80
81
|
end
|
|
81
82
|
end
|
|
82
83
|
end
|
|
@@ -13,7 +13,8 @@ module Unitsdb
|
|
|
13
13
|
module_function
|
|
14
14
|
|
|
15
15
|
# Update references in UnitsDB entities with UCUM references
|
|
16
|
-
def update_references(entity_type, matches, db_entities, output_file,
|
|
16
|
+
def update_references(entity_type, matches, db_entities, output_file,
|
|
17
|
+
include_potential = false)
|
|
17
18
|
puts "Updating UCUM references for #{entity_type}..."
|
|
18
19
|
|
|
19
20
|
# Create a map of entity IDs to their UCUM references
|
|
@@ -35,7 +36,7 @@ module Unitsdb
|
|
|
35
36
|
entity_references[entity_id] = ExternalReference.new(
|
|
36
37
|
uri: ucum_entity.identifier,
|
|
37
38
|
type: "informative",
|
|
38
|
-
authority: UCUM_AUTHORITY
|
|
39
|
+
authority: UCUM_AUTHORITY,
|
|
39
40
|
)
|
|
40
41
|
end
|
|
41
42
|
|
|
@@ -57,7 +58,9 @@ module Unitsdb
|
|
|
57
58
|
|
|
58
59
|
# Add new references
|
|
59
60
|
if (ext_ref = entity_references[entity_id])
|
|
60
|
-
if entity.references.detect
|
|
61
|
+
if entity.references.detect do |ref|
|
|
62
|
+
ref.uri == ext_ref.uri && ref.authority == ext_ref.authority
|
|
63
|
+
end
|
|
61
64
|
# Skip if reference already exists
|
|
62
65
|
puts "Reference already exists for entity ID: #{entity_id}"
|
|
63
66
|
else
|
|
@@ -78,10 +81,41 @@ module Unitsdb
|
|
|
78
81
|
def write_yaml_file(output_file, output_data)
|
|
79
82
|
# Ensure the output directory exists
|
|
80
83
|
output_dir = File.dirname(output_file)
|
|
81
|
-
FileUtils.mkdir_p(output_dir)
|
|
84
|
+
FileUtils.mkdir_p(output_dir)
|
|
82
85
|
|
|
83
|
-
# Write to YAML file
|
|
84
|
-
|
|
86
|
+
# Write to YAML file with proper formatting
|
|
87
|
+
yaml_content = output_data.to_yaml
|
|
88
|
+
|
|
89
|
+
# Preserve existing schema header or add default one
|
|
90
|
+
yaml_content = preserve_schema_header(output_file, yaml_content)
|
|
91
|
+
|
|
92
|
+
File.write(output_file, yaml_content)
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
# Preserve existing schema header or add default one
|
|
96
|
+
def preserve_schema_header(original_file, yaml_content)
|
|
97
|
+
schema_header = nil
|
|
98
|
+
|
|
99
|
+
# Extract existing schema header if file exists
|
|
100
|
+
if File.exist?(original_file)
|
|
101
|
+
original_content = File.read(original_file)
|
|
102
|
+
if (match = original_content.match(/^# yaml-language-server: \$schema=.+$/))
|
|
103
|
+
schema_header = match[0]
|
|
104
|
+
end
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
# Remove any existing schema header from new content to avoid duplication
|
|
108
|
+
yaml_content = yaml_content.gsub(
|
|
109
|
+
/^# yaml-language-server: \$schema=.+$\n/, ""
|
|
110
|
+
)
|
|
111
|
+
|
|
112
|
+
# Add preserved or default schema header
|
|
113
|
+
if schema_header
|
|
114
|
+
"#{schema_header}\n#{yaml_content}"
|
|
115
|
+
else
|
|
116
|
+
entity_type = File.basename(original_file, ".yaml")
|
|
117
|
+
"# yaml-language-server: $schema=schemas/#{entity_type}-schema.yaml\n#{yaml_content}"
|
|
118
|
+
end
|
|
85
119
|
end
|
|
86
120
|
|
|
87
121
|
# Get entity ID (either from identifiers array or directly)
|
|
@@ -4,7 +4,20 @@ require "thor"
|
|
|
4
4
|
|
|
5
5
|
module Unitsdb
|
|
6
6
|
module Commands
|
|
7
|
+
module Ucum
|
|
8
|
+
autoload :Check, "unitsdb/commands/ucum/check"
|
|
9
|
+
autoload :Update, "unitsdb/commands/ucum/update"
|
|
10
|
+
autoload :Formatter, "unitsdb/commands/ucum/formatter"
|
|
11
|
+
autoload :Matcher, "unitsdb/commands/ucum/matcher"
|
|
12
|
+
autoload :Updater, "unitsdb/commands/ucum/updater"
|
|
13
|
+
autoload :XmlParser, "unitsdb/commands/ucum/xml_parser"
|
|
14
|
+
end
|
|
15
|
+
|
|
7
16
|
class UcumCommand < Thor
|
|
17
|
+
# Inherit trace option from parent CLI
|
|
18
|
+
class_option :trace, type: :boolean, default: false,
|
|
19
|
+
desc: "Show full backtrace on error"
|
|
20
|
+
|
|
8
21
|
desc "check", "Check UCUM references in UnitsDB"
|
|
9
22
|
option :entity_type, type: :string, aliases: "-e",
|
|
10
23
|
desc: "Entity type to check (units, prefixes). If not specified, all types are checked"
|
|
@@ -19,8 +32,7 @@ module Unitsdb
|
|
|
19
32
|
option :database, type: :string, required: true, aliases: "-d",
|
|
20
33
|
desc: "Path to UnitsDB database (required)"
|
|
21
34
|
def check
|
|
22
|
-
|
|
23
|
-
Ucum::Check.new(options).run
|
|
35
|
+
run_command(Ucum::Check, options)
|
|
24
36
|
end
|
|
25
37
|
|
|
26
38
|
desc "update", "Update UnitsDB with UCUM references"
|
|
@@ -35,8 +47,36 @@ module Unitsdb
|
|
|
35
47
|
option :database, type: :string, required: true, aliases: "-d",
|
|
36
48
|
desc: "Path to UnitsDB database (required)"
|
|
37
49
|
def update
|
|
38
|
-
|
|
39
|
-
|
|
50
|
+
run_command(Ucum::Update, options)
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
private
|
|
54
|
+
|
|
55
|
+
def run_command(command_class, options)
|
|
56
|
+
command = command_class.new(options)
|
|
57
|
+
command.run
|
|
58
|
+
rescue Unitsdb::Errors::CLIRuntimeError => e
|
|
59
|
+
handle_cli_error(e)
|
|
60
|
+
rescue StandardError => e
|
|
61
|
+
handle_error(e)
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
def handle_cli_error(error)
|
|
65
|
+
if options[:trace]
|
|
66
|
+
raise error
|
|
67
|
+
else
|
|
68
|
+
warn "Error: #{error.message}"
|
|
69
|
+
exit 1
|
|
70
|
+
end
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
def handle_error(error)
|
|
74
|
+
if options[:trace]
|
|
75
|
+
raise error
|
|
76
|
+
else
|
|
77
|
+
warn "Error: #{error.message}"
|
|
78
|
+
exit 1
|
|
79
|
+
end
|
|
40
80
|
end
|
|
41
81
|
end
|
|
42
82
|
end
|
|
@@ -1,7 +1,5 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require_relative "../base"
|
|
4
|
-
|
|
5
3
|
module Unitsdb
|
|
6
4
|
module Commands
|
|
7
5
|
module Validate
|
|
@@ -12,8 +10,8 @@ module Unitsdb
|
|
|
12
10
|
|
|
13
11
|
display_results(all_dups)
|
|
14
12
|
rescue Unitsdb::Errors::DatabaseError => e
|
|
15
|
-
|
|
16
|
-
|
|
13
|
+
raise Unitsdb::Errors::ValidationError,
|
|
14
|
+
"Failed to validate identifiers: #{e.message}"
|
|
17
15
|
end
|
|
18
16
|
|
|
19
17
|
private
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Unitsdb
|
|
4
|
+
module Commands
|
|
5
|
+
module Validate
|
|
6
|
+
class QudtReferences < Unitsdb::Commands::Base
|
|
7
|
+
def run
|
|
8
|
+
# Load the database
|
|
9
|
+
db = load_database(@options[:database])
|
|
10
|
+
|
|
11
|
+
# Check for duplicate QUDT references
|
|
12
|
+
duplicates = check_qudt_references(db)
|
|
13
|
+
|
|
14
|
+
# Display results
|
|
15
|
+
display_duplicate_results(duplicates)
|
|
16
|
+
rescue Unitsdb::Errors::DatabaseError => e
|
|
17
|
+
raise Unitsdb::Errors::ValidationError,
|
|
18
|
+
"Failed to validate QUDT references: #{e.message}"
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
private
|
|
22
|
+
|
|
23
|
+
def check_qudt_references(db)
|
|
24
|
+
duplicates = {}
|
|
25
|
+
|
|
26
|
+
# Check units
|
|
27
|
+
check_entity_qudt_references(db.units, "units", duplicates)
|
|
28
|
+
|
|
29
|
+
# Check quantities
|
|
30
|
+
check_entity_qudt_references(db.quantities, "quantities", duplicates)
|
|
31
|
+
|
|
32
|
+
# Check dimensions
|
|
33
|
+
check_entity_qudt_references(db.dimensions, "dimensions", duplicates)
|
|
34
|
+
|
|
35
|
+
# Check unit_systems
|
|
36
|
+
check_entity_qudt_references(db.unit_systems, "unit_systems",
|
|
37
|
+
duplicates)
|
|
38
|
+
|
|
39
|
+
duplicates
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def check_entity_qudt_references(entities, entity_type, duplicates)
|
|
43
|
+
# Track QUDT references by URI
|
|
44
|
+
qudt_refs = {}
|
|
45
|
+
|
|
46
|
+
entities.each_with_index do |entity, index|
|
|
47
|
+
# Skip if no references
|
|
48
|
+
next unless entity.respond_to?(:references) && entity.references
|
|
49
|
+
|
|
50
|
+
# Check each reference
|
|
51
|
+
entity.references.each do |ref|
|
|
52
|
+
# Only interested in qudt references
|
|
53
|
+
next unless ref.authority == "qudt"
|
|
54
|
+
|
|
55
|
+
# Get entity info for display
|
|
56
|
+
entity_id = if entity.respond_to?(:identifiers) && entity.identifiers&.first.respond_to?(:id)
|
|
57
|
+
entity.identifiers.first.id
|
|
58
|
+
else
|
|
59
|
+
entity.short
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
# Track this reference
|
|
63
|
+
qudt_refs[ref.uri] ||= []
|
|
64
|
+
qudt_refs[ref.uri] << {
|
|
65
|
+
entity_id: entity_id,
|
|
66
|
+
entity_name: entity.respond_to?(:names) ? entity.names.first : entity.short,
|
|
67
|
+
index: index,
|
|
68
|
+
}
|
|
69
|
+
end
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
# Find duplicates (URIs with more than one entity)
|
|
73
|
+
qudt_refs.each do |uri, entities|
|
|
74
|
+
next unless entities.size > 1
|
|
75
|
+
|
|
76
|
+
# Record this duplicate
|
|
77
|
+
duplicates[entity_type] ||= {}
|
|
78
|
+
duplicates[entity_type][uri] = entities
|
|
79
|
+
end
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
def display_duplicate_results(duplicates)
|
|
83
|
+
if duplicates.empty?
|
|
84
|
+
puts "No duplicate QUDT references found! Each QUDT reference URI is used by at most one entity of each type."
|
|
85
|
+
return
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
puts "Found duplicate QUDT references:"
|
|
89
|
+
|
|
90
|
+
duplicates.each do |entity_type, uri_duplicates|
|
|
91
|
+
puts "\n #{entity_type.capitalize}:"
|
|
92
|
+
|
|
93
|
+
uri_duplicates.each do |uri, entities|
|
|
94
|
+
puts " QUDT URI: #{uri}"
|
|
95
|
+
puts " Used by #{entities.size} entities:"
|
|
96
|
+
|
|
97
|
+
entities.each do |entity|
|
|
98
|
+
puts " - #{entity[:entity_id]} (#{entity[:entity_name]}) at index #{entity[:index]}"
|
|
99
|
+
end
|
|
100
|
+
puts ""
|
|
101
|
+
end
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
puts "\nEach QUDT reference should be used by at most one entity of each type."
|
|
105
|
+
puts "Please fix the duplicates by either removing the reference from all but one entity,"
|
|
106
|
+
puts "or by updating the references to use different URIs appropriate for each entity."
|
|
107
|
+
end
|
|
108
|
+
end
|
|
109
|
+
end
|
|
110
|
+
end
|
|
111
|
+
end
|
|
@@ -1,7 +1,5 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require_relative "../base"
|
|
4
|
-
|
|
5
3
|
module Unitsdb
|
|
6
4
|
module Commands
|
|
7
5
|
module Validate
|
|
@@ -19,8 +17,8 @@ module Unitsdb
|
|
|
19
17
|
# Display results
|
|
20
18
|
display_reference_results(invalid_refs, registry)
|
|
21
19
|
rescue Unitsdb::Errors::DatabaseError => e
|
|
22
|
-
|
|
23
|
-
|
|
20
|
+
raise Unitsdb::Errors::ValidationError,
|
|
21
|
+
"Failed to validate references: #{e.message}"
|
|
24
22
|
end
|
|
25
23
|
|
|
26
24
|
private
|
|
@@ -99,7 +97,8 @@ module Unitsdb
|
|
|
99
97
|
# Also track unit systems by short name
|
|
100
98
|
if unit_system.respond_to?(:short) && unit_system.short
|
|
101
99
|
registry["unit_systems_short"] ||= {}
|
|
102
|
-
registry["unit_systems_short"][unit_system.short] =
|
|
100
|
+
registry["unit_systems_short"][unit_system.short] =
|
|
101
|
+
"index:#{index}"
|
|
103
102
|
end
|
|
104
103
|
end
|
|
105
104
|
|
|
@@ -143,7 +142,8 @@ module Unitsdb
|
|
|
143
142
|
ref_type = "dimensions"
|
|
144
143
|
ref_path = "dimensions:index:#{index}:dimension_reference"
|
|
145
144
|
|
|
146
|
-
validate_reference(ref_id, ref_type, ref_path, registry,
|
|
145
|
+
validate_reference(ref_id, ref_type, ref_path, registry,
|
|
146
|
+
invalid_refs, "dimensions")
|
|
147
147
|
end
|
|
148
148
|
end
|
|
149
149
|
|
|
@@ -155,7 +155,8 @@ module Unitsdb
|
|
|
155
155
|
ref_type = "unit_systems"
|
|
156
156
|
ref_path = "units:index:#{index}:unit_system_reference[#{idx}]"
|
|
157
157
|
|
|
158
|
-
validate_reference(ref_id, ref_type, ref_path, registry,
|
|
158
|
+
validate_reference(ref_id, ref_type, ref_path, registry,
|
|
159
|
+
invalid_refs, "units")
|
|
159
160
|
end
|
|
160
161
|
end
|
|
161
162
|
end
|
|
@@ -168,7 +169,8 @@ module Unitsdb
|
|
|
168
169
|
ref_type = "quantities"
|
|
169
170
|
ref_path = "units:index:#{index}:quantity_references[#{idx}]"
|
|
170
171
|
|
|
171
|
-
validate_reference(ref_id, ref_type, ref_path, registry,
|
|
172
|
+
validate_reference(ref_id, ref_type, ref_path, registry,
|
|
173
|
+
invalid_refs, "units")
|
|
172
174
|
end
|
|
173
175
|
end
|
|
174
176
|
end
|
|
@@ -185,7 +187,8 @@ module Unitsdb
|
|
|
185
187
|
ref_type = "units"
|
|
186
188
|
ref_path = "units:index:#{index}:root_units.#{idx}.unit_reference"
|
|
187
189
|
|
|
188
|
-
validate_reference(ref_id, ref_type, ref_path, registry,
|
|
190
|
+
validate_reference(ref_id, ref_type, ref_path, registry,
|
|
191
|
+
invalid_refs, "units")
|
|
189
192
|
|
|
190
193
|
# Check prefix reference if present
|
|
191
194
|
next unless root_unit.respond_to?(:prefix_reference) && root_unit.prefix_reference
|
|
@@ -194,12 +197,14 @@ module Unitsdb
|
|
|
194
197
|
ref_type = "prefixes"
|
|
195
198
|
ref_path = "units:index:#{index}:root_units.#{idx}.prefix_reference"
|
|
196
199
|
|
|
197
|
-
validate_reference(ref_id, ref_type, ref_path, registry,
|
|
200
|
+
validate_reference(ref_id, ref_type, ref_path, registry,
|
|
201
|
+
invalid_refs, "units")
|
|
198
202
|
end
|
|
199
203
|
end
|
|
200
204
|
end
|
|
201
205
|
|
|
202
|
-
def validate_reference(ref_id, ref_type, ref_path, registry,
|
|
206
|
+
def validate_reference(ref_id, ref_type, ref_path, registry,
|
|
207
|
+
invalid_refs, file_type)
|
|
203
208
|
# Handle references that are objects with id and type (could be a hash or an object)
|
|
204
209
|
if ref_id.respond_to?(:id) && ref_id.respond_to?(:type)
|
|
205
210
|
id = ref_id.id
|
|
@@ -218,8 +223,12 @@ module Unitsdb
|
|
|
218
223
|
# 3. Try alternate ID formats for unit systems (e.g., SI_base vs si-base)
|
|
219
224
|
if !valid && type == "unitsml" && ref_type == "unit_systems" && registry.key?(ref_type) && (
|
|
220
225
|
registry[ref_type].keys.any? { |k| k.end_with?(":#{id}") } ||
|
|
221
|
-
registry[ref_type].keys.any?
|
|
222
|
-
|
|
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
|
|
223
232
|
)
|
|
224
233
|
# Special handling for unit_systems between unitsml and nist types
|
|
225
234
|
valid = true
|
|
@@ -229,7 +238,8 @@ module Unitsdb
|
|
|
229
238
|
puts "Valid reference: #{id} (#{type}) at #{file_type}:#{ref_path}" if @options[:print_valid]
|
|
230
239
|
else
|
|
231
240
|
invalid_refs[file_type] ||= {}
|
|
232
|
-
invalid_refs[file_type][ref_path] =
|
|
241
|
+
invalid_refs[file_type][ref_path] =
|
|
242
|
+
{ id: id, type: type, ref_type: ref_type }
|
|
233
243
|
end
|
|
234
244
|
# Handle references that are objects with id and type in a hash
|
|
235
245
|
elsif ref_id.is_a?(Hash) && ref_id.key?("id") && ref_id.key?("type")
|
|
@@ -249,8 +259,12 @@ module Unitsdb
|
|
|
249
259
|
# 3. Try alternate ID formats for unit systems (e.g., SI_base vs si-base)
|
|
250
260
|
if !valid && type == "unitsml" && ref_type == "unit_systems" && registry.key?(ref_type) && (
|
|
251
261
|
registry[ref_type].keys.any? { |k| k.end_with?(":#{id}") } ||
|
|
252
|
-
registry[ref_type].keys.any?
|
|
253
|
-
|
|
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
|
|
254
268
|
)
|
|
255
269
|
# Special handling for unit_systems between unitsml and nist types
|
|
256
270
|
valid = true
|
|
@@ -260,7 +274,8 @@ module Unitsdb
|
|
|
260
274
|
puts "Valid reference: #{id} (#{type}) at #{file_type}:#{ref_path}" if @options[:print_valid]
|
|
261
275
|
else
|
|
262
276
|
invalid_refs[file_type] ||= {}
|
|
263
|
-
invalid_refs[file_type][ref_path] =
|
|
277
|
+
invalid_refs[file_type][ref_path] =
|
|
278
|
+
{ id: id, type: type, ref_type: ref_type }
|
|
264
279
|
end
|
|
265
280
|
else
|
|
266
281
|
# Handle plain string references (legacy format)
|
|
@@ -292,7 +307,8 @@ module Unitsdb
|
|
|
292
307
|
|
|
293
308
|
puts " #{type}:"
|
|
294
309
|
ids.each do |id, location|
|
|
295
|
-
puts " #{id}: {type: #{type.sub(
|
|
310
|
+
puts " #{id}: {type: #{type.sub('s',
|
|
311
|
+
'')}, source: #{location}}"
|
|
296
312
|
end
|
|
297
313
|
end
|
|
298
314
|
end
|
|
@@ -304,7 +320,8 @@ module Unitsdb
|
|
|
304
320
|
# Suggest corrections
|
|
305
321
|
next unless registry.key?(ref[:ref_type])
|
|
306
322
|
|
|
307
|
-
similar_ids = Unitsdb::Utils.find_similar_ids(ref[:id],
|
|
323
|
+
similar_ids = Unitsdb::Utils.find_similar_ids(ref[:id],
|
|
324
|
+
registry[ref[:ref_type]].keys)
|
|
308
325
|
if similar_ids.any?
|
|
309
326
|
puts " Did you mean one of these?"
|
|
310
327
|
similar_ids.each { |id| puts " - #{id}" }
|
|
@@ -1,7 +1,5 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require_relative "../base"
|
|
4
|
-
|
|
5
3
|
module Unitsdb
|
|
6
4
|
module Commands
|
|
7
5
|
module Validate
|
|
@@ -16,8 +14,8 @@ module Unitsdb
|
|
|
16
14
|
# Display results
|
|
17
15
|
display_duplicate_results(duplicates)
|
|
18
16
|
rescue Unitsdb::Errors::DatabaseError => e
|
|
19
|
-
|
|
20
|
-
|
|
17
|
+
raise Unitsdb::Errors::ValidationError,
|
|
18
|
+
"Failed to validate SI references: #{e.message}"
|
|
21
19
|
end
|
|
22
20
|
|
|
23
21
|
private
|
|
@@ -62,7 +60,7 @@ module Unitsdb
|
|
|
62
60
|
si_refs[ref.uri] << {
|
|
63
61
|
entity_id: entity_id,
|
|
64
62
|
entity_name: entity.respond_to?(:names) ? entity.names.first : entity.short,
|
|
65
|
-
index: index
|
|
63
|
+
index: index,
|
|
66
64
|
}
|
|
67
65
|
end
|
|
68
66
|
end
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Unitsdb
|
|
4
|
+
module Commands
|
|
5
|
+
module Validate
|
|
6
|
+
class UcumReferences < Unitsdb::Commands::Base
|
|
7
|
+
def run
|
|
8
|
+
# Load the database
|
|
9
|
+
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)
|
|
16
|
+
rescue Unitsdb::Errors::DatabaseError => e
|
|
17
|
+
raise Unitsdb::Errors::ValidationError,
|
|
18
|
+
"Failed to validate UCUM references: #{e.message}"
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
private
|
|
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
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def check_entity_ucum_references(entities, entity_type, duplicates)
|
|
36
|
+
# Track UCUM references by code
|
|
37
|
+
ucum_refs = {}
|
|
38
|
+
|
|
39
|
+
entities.each_with_index do |entity, index|
|
|
40
|
+
# Skip if no external references
|
|
41
|
+
next unless entity.respond_to?(:external_references) && entity.external_references
|
|
42
|
+
|
|
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,
|
|
61
|
+
index: index,
|
|
62
|
+
}
|
|
63
|
+
end
|
|
64
|
+
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
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
def display_duplicate_results(duplicates)
|
|
77
|
+
if duplicates.empty?
|
|
78
|
+
puts "No duplicate UCUM references found! Each UCUM reference code is used by at most one entity of each type."
|
|
79
|
+
return
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
puts "Found duplicate UCUM references:"
|
|
83
|
+
|
|
84
|
+
duplicates.each do |entity_type, code_duplicates|
|
|
85
|
+
puts "\n #{entity_type.capitalize}:"
|
|
86
|
+
|
|
87
|
+
code_duplicates.each do |code, entities|
|
|
88
|
+
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
|
|
94
|
+
puts ""
|
|
95
|
+
end
|
|
96
|
+
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
|
+
end
|
|
102
|
+
end
|
|
103
|
+
end
|
|
104
|
+
end
|
|
105
|
+
end
|