unitsdb 2.1.0 → 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 +247 -53
- data/CLAUDE.md +55 -0
- data/Gemfile +5 -1
- data/README.adoc +385 -12
- 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 +47 -13
- data/lib/unitsdb/commands/_modify.rb +41 -5
- data/lib/unitsdb/commands/base.rb +10 -32
- 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 +50 -86
- data/lib/unitsdb/commands/search.rb +12 -11
- data/lib/unitsdb/commands/ucum/check.rb +139 -0
- data/lib/unitsdb/commands/ucum/formatter.rb +142 -0
- data/lib/unitsdb/commands/ucum/matcher.rb +315 -0
- data/lib/unitsdb/commands/ucum/update.rb +85 -0
- data/lib/unitsdb/commands/ucum/updater.rb +132 -0
- data/lib/unitsdb/commands/ucum/xml_parser.rb +32 -0
- data/lib/unitsdb/commands/ucum.rb +83 -0
- data/lib/unitsdb/commands/validate/identifiers.rb +3 -3
- data/lib/unitsdb/commands/validate/qudt_references.rb +111 -0
- data/lib/unitsdb/commands/validate/references.rb +37 -18
- data/lib/unitsdb/commands/validate/si_references.rb +3 -11
- 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 +91 -52
- data/lib/unitsdb/dimension.rb +1 -4
- data/lib/unitsdb/dimension_details.rb +0 -1
- data/lib/unitsdb/dimensions.rb +1 -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 +1 -1
- data/lib/unitsdb/quantities.rb +1 -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 +1 -2
- data/lib/unitsdb/si_derived_base.rb +0 -2
- data/lib/unitsdb/ucum.rb +202 -0
- 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 +1 -2
- data/lib/unitsdb/units.rb +1 -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 -5
- metadata +60 -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,142 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Unitsdb
|
|
4
|
+
module Commands
|
|
5
|
+
module Ucum
|
|
6
|
+
# Formats output for UCUM matching results
|
|
7
|
+
module Formatter
|
|
8
|
+
module_function
|
|
9
|
+
|
|
10
|
+
# Print a direction header (UnitsDB → UCUM or UCUM → UnitsDB)
|
|
11
|
+
def print_direction_header(direction)
|
|
12
|
+
puts "\n=== #{direction} ===\n"
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
# Display results for UCUM → UnitsDB matching
|
|
16
|
+
def display_ucum_results(entity_type, matches, missing_matches,
|
|
17
|
+
unmatched_ucum)
|
|
18
|
+
puts "\nResults for #{entity_type.capitalize} (UCUM → UnitsDB):"
|
|
19
|
+
puts " Matched: #{matches.size}"
|
|
20
|
+
puts " Missing matches (could be added): #{missing_matches.size}"
|
|
21
|
+
puts " Unmatched UCUM entities: #{unmatched_ucum.size}"
|
|
22
|
+
|
|
23
|
+
unless unmatched_ucum.empty?
|
|
24
|
+
puts "\nUnmatched UCUM #{entity_type}:"
|
|
25
|
+
unmatched_ucum.each do |entity|
|
|
26
|
+
case entity
|
|
27
|
+
when Unitsdb::UcumPrefix
|
|
28
|
+
puts " - #{entity.name} (#{entity.code_sensitive})"
|
|
29
|
+
when Unitsdb::UcumBaseUnit
|
|
30
|
+
puts " - #{entity.name} (#{entity.code_sensitive}, dimension: #{entity.dimension})"
|
|
31
|
+
when Unitsdb::UcumUnit
|
|
32
|
+
name = entity.name.is_a?(Array) ? entity.name.first : entity.name
|
|
33
|
+
puts " - #{name} (#{entity.code_sensitive}, class: #{entity.klass})"
|
|
34
|
+
else
|
|
35
|
+
puts " - Unknown entity type"
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
return if missing_matches.empty?
|
|
41
|
+
|
|
42
|
+
puts "\nPotential additions (UCUM #{entity_type} that could be added to UnitsDB):"
|
|
43
|
+
missing_matches.each do |match|
|
|
44
|
+
ucum_entity = match[:ucum_entity]
|
|
45
|
+
db_entity = match[:db_entity]
|
|
46
|
+
|
|
47
|
+
# Get entity IDs and names
|
|
48
|
+
db_id = get_db_entity_id(db_entity)
|
|
49
|
+
db_name = get_db_entity_name(db_entity)
|
|
50
|
+
ucum_name = get_ucum_entity_name(ucum_entity)
|
|
51
|
+
ucum_code = ucum_entity.respond_to?(:code_sensitive) ? ucum_entity.code_sensitive : "unknown"
|
|
52
|
+
|
|
53
|
+
case ucum_entity
|
|
54
|
+
when Unitsdb::UcumPrefix
|
|
55
|
+
puts " - UnitsDB prefix '#{db_name}' (#{db_id}) → UCUM prefix '#{ucum_name}' (#{ucum_code})"
|
|
56
|
+
when Unitsdb::UcumBaseUnit
|
|
57
|
+
puts " - UnitsDB unit '#{db_name}' (#{db_id}) → UCUM base unit '#{ucum_name}' (#{ucum_code})"
|
|
58
|
+
when Unitsdb::UcumUnit
|
|
59
|
+
puts " - UnitsDB unit '#{db_name}' (#{db_id}) → UCUM unit '#{ucum_name}' (#{ucum_code})"
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
# Display results for UnitsDB → UCUM matching
|
|
65
|
+
def display_db_results(entity_type, matches, missing_refs, unmatched_db)
|
|
66
|
+
puts "\nResults for #{entity_type.capitalize} (UnitsDB → UCUM):"
|
|
67
|
+
puts " Matched: #{matches.size}"
|
|
68
|
+
puts " Missing references (could be added): #{missing_refs.size}"
|
|
69
|
+
puts " Unmatched UnitsDB entities: #{unmatched_db.size}"
|
|
70
|
+
|
|
71
|
+
unless unmatched_db.empty?
|
|
72
|
+
puts "\nUnmatched UnitsDB #{entity_type}:"
|
|
73
|
+
unmatched_db.each do |entity|
|
|
74
|
+
id = get_db_entity_id(entity)
|
|
75
|
+
name = get_db_entity_name(entity)
|
|
76
|
+
puts " - #{name} (#{id})"
|
|
77
|
+
end
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
return if missing_refs.empty?
|
|
81
|
+
|
|
82
|
+
puts "\nPotential references (UCUM references that could be added to UnitsDB):"
|
|
83
|
+
missing_refs.each do |match|
|
|
84
|
+
ucum_entity = match[:ucum_entity]
|
|
85
|
+
db_entity = match[:db_entity]
|
|
86
|
+
|
|
87
|
+
# Get entity IDs and names
|
|
88
|
+
db_id = get_db_entity_id(db_entity)
|
|
89
|
+
db_name = get_db_entity_name(db_entity)
|
|
90
|
+
ucum_name = get_ucum_entity_name(ucum_entity)
|
|
91
|
+
ucum_code = ucum_entity.respond_to?(:code_sensitive) ? ucum_entity.code_sensitive : "unknown"
|
|
92
|
+
|
|
93
|
+
case ucum_entity
|
|
94
|
+
when Unitsdb::UcumPrefix
|
|
95
|
+
puts " - UnitsDB prefix '#{db_name}' (#{db_id}) → UCUM prefix '#{ucum_name}' (#{ucum_code})"
|
|
96
|
+
when Unitsdb::UcumBaseUnit
|
|
97
|
+
puts " - UnitsDB unit '#{db_name}' (#{db_id}) → UCUM base unit '#{ucum_name}' (#{ucum_code})"
|
|
98
|
+
when Unitsdb::UcumUnit
|
|
99
|
+
puts " - UnitsDB unit '#{db_name}' (#{db_id}) → UCUM unit '#{ucum_name}' (#{ucum_code})"
|
|
100
|
+
end
|
|
101
|
+
end
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
# Helper to get db entity id
|
|
105
|
+
def get_db_entity_id(entity)
|
|
106
|
+
if entity.respond_to?(:identifiers) && entity.identifiers && !entity.identifiers.empty?
|
|
107
|
+
entity.identifiers.first.id
|
|
108
|
+
elsif entity.respond_to?(:id)
|
|
109
|
+
entity.id
|
|
110
|
+
else
|
|
111
|
+
"unknown-id"
|
|
112
|
+
end
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
# Helper to get db entity name
|
|
116
|
+
def get_db_entity_name(entity)
|
|
117
|
+
if entity.respond_to?(:names) && entity.names && !entity.names.empty?
|
|
118
|
+
entity.names.first.value
|
|
119
|
+
elsif entity.respond_to?(:short) && entity.short
|
|
120
|
+
entity.short
|
|
121
|
+
elsif entity.respond_to?(:name)
|
|
122
|
+
entity.name
|
|
123
|
+
else
|
|
124
|
+
"unknown-name"
|
|
125
|
+
end
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
# Helper to get ucum entity name
|
|
129
|
+
def get_ucum_entity_name(entity)
|
|
130
|
+
case entity
|
|
131
|
+
when Unitsdb::UcumPrefix, Unitsdb::UcumBaseUnit
|
|
132
|
+
entity.name
|
|
133
|
+
when Unitsdb::UcumUnit
|
|
134
|
+
entity.name.is_a?(Array) ? entity.name.first : entity.name
|
|
135
|
+
else
|
|
136
|
+
"unknown-name"
|
|
137
|
+
end
|
|
138
|
+
end
|
|
139
|
+
end
|
|
140
|
+
end
|
|
141
|
+
end
|
|
142
|
+
end
|
|
@@ -0,0 +1,315 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Unitsdb
|
|
4
|
+
module Commands
|
|
5
|
+
module Ucum
|
|
6
|
+
# Matcher for UCUM and UnitsDB entities
|
|
7
|
+
module Matcher
|
|
8
|
+
module_function
|
|
9
|
+
|
|
10
|
+
# Match UCUM entities to UnitsDB entities (UCUM → UnitsDB)
|
|
11
|
+
def match_ucum_to_db(entity_type, ucum_entities, db_entities)
|
|
12
|
+
puts "Matching UCUM #{entity_type} to UnitsDB #{entity_type}..."
|
|
13
|
+
|
|
14
|
+
# Initialize result arrays
|
|
15
|
+
matches = []
|
|
16
|
+
missing_matches = []
|
|
17
|
+
unmatched_ucum = []
|
|
18
|
+
|
|
19
|
+
# Process each UCUM entity
|
|
20
|
+
ucum_entities.each do |ucum_entity|
|
|
21
|
+
match_data = find_db_match_for_ucum(ucum_entity, db_entities,
|
|
22
|
+
entity_type)
|
|
23
|
+
|
|
24
|
+
if match_data[:match]
|
|
25
|
+
matches << { ucum_entity: ucum_entity,
|
|
26
|
+
db_entity: match_data[:match] }
|
|
27
|
+
elsif match_data[:potential_match]
|
|
28
|
+
missing_matches << { ucum_entity: ucum_entity,
|
|
29
|
+
db_entity: match_data[:potential_match] }
|
|
30
|
+
else
|
|
31
|
+
unmatched_ucum << ucum_entity
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
[matches, missing_matches, unmatched_ucum]
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
# Match UnitsDB entities to UCUM entities (UnitsDB → UCUM)
|
|
39
|
+
def match_db_to_ucum(entity_type, ucum_entities, db_entities)
|
|
40
|
+
puts "Matching UnitsDB #{entity_type} to UCUM #{entity_type}..."
|
|
41
|
+
|
|
42
|
+
# Initialize result arrays
|
|
43
|
+
matches = []
|
|
44
|
+
missing_refs = []
|
|
45
|
+
unmatched_db = []
|
|
46
|
+
|
|
47
|
+
# Process each UnitsDB entity
|
|
48
|
+
db_entities.send(entity_type).each do |db_entity|
|
|
49
|
+
# Skip entities that already have UCUM references
|
|
50
|
+
if has_ucum_reference?(db_entity)
|
|
51
|
+
matches << { db_entity: db_entity,
|
|
52
|
+
ucum_entity: find_referenced_ucum_entity(db_entity,
|
|
53
|
+
ucum_entities) }
|
|
54
|
+
next
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
match_data = find_ucum_match_for_db(db_entity, ucum_entities,
|
|
58
|
+
entity_type)
|
|
59
|
+
|
|
60
|
+
if match_data[:match]
|
|
61
|
+
missing_refs << { db_entity: db_entity,
|
|
62
|
+
ucum_entity: match_data[:match] }
|
|
63
|
+
else
|
|
64
|
+
unmatched_db << db_entity
|
|
65
|
+
end
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
[matches, missing_refs, unmatched_db]
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
# Check if a UnitsDB entity already has a UCUM reference
|
|
72
|
+
def has_ucum_reference?(entity)
|
|
73
|
+
return false unless entity.respond_to?(:references) && entity.references
|
|
74
|
+
|
|
75
|
+
entity.references.any? { |ref| ref.authority == "ucum" }
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
# Find the referenced UCUM entity based on the reference URI
|
|
79
|
+
def find_referenced_ucum_entity(db_entity, ucum_entities)
|
|
80
|
+
return nil unless db_entity.respond_to?(:references) && db_entity.references
|
|
81
|
+
|
|
82
|
+
ucum_ref = db_entity.references.find { |ref| ref.authority == "ucum" }
|
|
83
|
+
return nil unless ucum_ref
|
|
84
|
+
|
|
85
|
+
ref_uri = ucum_ref.uri
|
|
86
|
+
ucum_entities.find { |ucum_entity| ucum_entity.identifier == ref_uri }
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
# Get the ID of a UnitsDB entity
|
|
90
|
+
def get_entity_id(entity)
|
|
91
|
+
entity.respond_to?(:id) ? entity.id : nil
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
# Find a matching UnitsDB entity for a UCUM entity
|
|
95
|
+
def find_db_match_for_ucum(ucum_entity, db_entities, entity_type)
|
|
96
|
+
result = { match: nil, potential_match: nil }
|
|
97
|
+
|
|
98
|
+
# Different matching logic based on entity type
|
|
99
|
+
case entity_type
|
|
100
|
+
when "prefixes"
|
|
101
|
+
result = match_prefix_ucum_to_db(ucum_entity, db_entities)
|
|
102
|
+
when "units"
|
|
103
|
+
result = match_unit_ucum_to_db(ucum_entity, db_entities)
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
result
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
# Find a matching UCUM entity for a UnitsDB entity
|
|
110
|
+
def find_ucum_match_for_db(db_entity, ucum_entities, entity_type)
|
|
111
|
+
result = { match: nil }
|
|
112
|
+
|
|
113
|
+
# Different matching logic based on entity type
|
|
114
|
+
case entity_type
|
|
115
|
+
when "prefixes"
|
|
116
|
+
result = match_prefix_db_to_ucum(db_entity, ucum_entities)
|
|
117
|
+
when "units"
|
|
118
|
+
result = match_unit_db_to_ucum(db_entity, ucum_entities)
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
result
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
# Match UCUM prefix to UnitsDB prefix
|
|
125
|
+
def match_prefix_ucum_to_db(ucum_prefix, db_prefixes)
|
|
126
|
+
result = { match: nil, potential_match: nil }
|
|
127
|
+
|
|
128
|
+
# Try exact name match first
|
|
129
|
+
name_match = db_prefixes.find do |db_prefix|
|
|
130
|
+
db_prefix.names&.any? do |name_obj|
|
|
131
|
+
name_obj.value.downcase == ucum_prefix.name.downcase
|
|
132
|
+
end
|
|
133
|
+
end
|
|
134
|
+
|
|
135
|
+
if name_match
|
|
136
|
+
result[:match] = name_match
|
|
137
|
+
return result
|
|
138
|
+
end
|
|
139
|
+
|
|
140
|
+
# Try symbol match
|
|
141
|
+
symbol_match = db_prefixes.find do |db_prefix|
|
|
142
|
+
db_prefix.symbols&.any? do |symbol|
|
|
143
|
+
symbol.ascii == ucum_prefix.print_symbol ||
|
|
144
|
+
symbol.unicode == ucum_prefix.print_symbol
|
|
145
|
+
end
|
|
146
|
+
end
|
|
147
|
+
|
|
148
|
+
if symbol_match
|
|
149
|
+
result[:match] = symbol_match
|
|
150
|
+
return result
|
|
151
|
+
end
|
|
152
|
+
|
|
153
|
+
# Try value match if available (using base^power)
|
|
154
|
+
if ucum_prefix.value&.value
|
|
155
|
+
value_match = db_prefixes.find do |db_prefix|
|
|
156
|
+
if db_prefix.base && db_prefix.power
|
|
157
|
+
calculated_value = db_prefix.base**db_prefix.power
|
|
158
|
+
calculated_value.to_s == ucum_prefix.value.value
|
|
159
|
+
else
|
|
160
|
+
false
|
|
161
|
+
end
|
|
162
|
+
end
|
|
163
|
+
|
|
164
|
+
result[:potential_match] = value_match if value_match
|
|
165
|
+
end
|
|
166
|
+
|
|
167
|
+
result
|
|
168
|
+
end
|
|
169
|
+
|
|
170
|
+
# Match UnitsDB prefix to UCUM prefix
|
|
171
|
+
def match_prefix_db_to_ucum(db_prefix, ucum_prefixes)
|
|
172
|
+
result = { match: nil }
|
|
173
|
+
|
|
174
|
+
# Try exact name match first
|
|
175
|
+
if db_prefix.names && !db_prefix.names.empty?
|
|
176
|
+
db_prefix_names = db_prefix.names.map do |name_obj|
|
|
177
|
+
name_obj.value.downcase
|
|
178
|
+
end
|
|
179
|
+
|
|
180
|
+
name_match = ucum_prefixes.find do |ucum_prefix|
|
|
181
|
+
db_prefix_names.include?(ucum_prefix.name.downcase)
|
|
182
|
+
end
|
|
183
|
+
|
|
184
|
+
if name_match
|
|
185
|
+
result[:match] = name_match
|
|
186
|
+
return result
|
|
187
|
+
end
|
|
188
|
+
end
|
|
189
|
+
|
|
190
|
+
# Try symbol match
|
|
191
|
+
if db_prefix.symbols && !db_prefix.symbols.empty?
|
|
192
|
+
symbol_match = ucum_prefixes.find do |ucum_prefix|
|
|
193
|
+
db_prefix.symbols.any? do |symbol|
|
|
194
|
+
ucum_prefix.print_symbol == symbol.ascii ||
|
|
195
|
+
ucum_prefix.print_symbol == symbol.unicode
|
|
196
|
+
end
|
|
197
|
+
end
|
|
198
|
+
|
|
199
|
+
result[:match] = symbol_match if symbol_match
|
|
200
|
+
end
|
|
201
|
+
|
|
202
|
+
result
|
|
203
|
+
end
|
|
204
|
+
|
|
205
|
+
# Match UCUM unit to UnitsDB unit
|
|
206
|
+
def match_unit_ucum_to_db(ucum_unit, db_units)
|
|
207
|
+
result = { match: nil, potential_match: nil }
|
|
208
|
+
|
|
209
|
+
# Get UCUM unit name(s)
|
|
210
|
+
ucum_names = case ucum_unit
|
|
211
|
+
when Unitsdb::UcumBaseUnit
|
|
212
|
+
[ucum_unit.name]
|
|
213
|
+
when Unitsdb::UcumUnit
|
|
214
|
+
ucum_unit.name.is_a?(Array) ? ucum_unit.name : [ucum_unit.name]
|
|
215
|
+
else
|
|
216
|
+
[]
|
|
217
|
+
end
|
|
218
|
+
|
|
219
|
+
# Try name match
|
|
220
|
+
ucum_names.each do |ucum_name|
|
|
221
|
+
name_match = db_units.find do |db_unit|
|
|
222
|
+
db_unit.names&.any? do |name_obj|
|
|
223
|
+
name_obj.value.downcase == ucum_name.downcase
|
|
224
|
+
end
|
|
225
|
+
end
|
|
226
|
+
|
|
227
|
+
if name_match
|
|
228
|
+
result[:match] = name_match
|
|
229
|
+
return result
|
|
230
|
+
end
|
|
231
|
+
end
|
|
232
|
+
|
|
233
|
+
# Try symbol match
|
|
234
|
+
symbol_match = db_units.find do |db_unit|
|
|
235
|
+
db_unit.symbols&.any? do |symbol|
|
|
236
|
+
symbol.ascii == ucum_unit.print_symbol ||
|
|
237
|
+
symbol.unicode == ucum_unit.print_symbol
|
|
238
|
+
end
|
|
239
|
+
end
|
|
240
|
+
|
|
241
|
+
if symbol_match
|
|
242
|
+
result[:match] = symbol_match
|
|
243
|
+
return result
|
|
244
|
+
end
|
|
245
|
+
|
|
246
|
+
# Try property/dimension match for potential matches
|
|
247
|
+
property = case ucum_unit
|
|
248
|
+
when Unitsdb::UcumBaseUnit
|
|
249
|
+
ucum_unit.property
|
|
250
|
+
when Unitsdb::UcumUnit
|
|
251
|
+
ucum_unit.property
|
|
252
|
+
end
|
|
253
|
+
|
|
254
|
+
if property
|
|
255
|
+
property_matches = db_units.select do |db_unit|
|
|
256
|
+
db_unit.quantity_references&.any? do |qref|
|
|
257
|
+
qref.id&.downcase&.include?(property.downcase)
|
|
258
|
+
end
|
|
259
|
+
end
|
|
260
|
+
|
|
261
|
+
if property_matches.any?
|
|
262
|
+
result[:potential_match] =
|
|
263
|
+
property_matches.first
|
|
264
|
+
end
|
|
265
|
+
end
|
|
266
|
+
|
|
267
|
+
result
|
|
268
|
+
end
|
|
269
|
+
|
|
270
|
+
# Match UnitsDB unit to UCUM unit
|
|
271
|
+
def match_unit_db_to_ucum(db_unit, ucum_units)
|
|
272
|
+
result = { match: nil }
|
|
273
|
+
|
|
274
|
+
# Try name match first
|
|
275
|
+
if db_unit.names && !db_unit.names.empty?
|
|
276
|
+
db_unit_names = db_unit.names.map do |name_obj|
|
|
277
|
+
name_obj.value.downcase
|
|
278
|
+
end
|
|
279
|
+
|
|
280
|
+
name_match = ucum_units.find do |ucum_unit|
|
|
281
|
+
case ucum_unit
|
|
282
|
+
when Unitsdb::UcumBaseUnit
|
|
283
|
+
db_unit_names.include?(ucum_unit.name.downcase)
|
|
284
|
+
when Unitsdb::UcumUnit
|
|
285
|
+
ucum_names = ucum_unit.name.is_a?(Array) ? ucum_unit.name : [ucum_unit.name]
|
|
286
|
+
ucum_names.any? { |name| db_unit_names.include?(name.downcase) }
|
|
287
|
+
else
|
|
288
|
+
false
|
|
289
|
+
end
|
|
290
|
+
end
|
|
291
|
+
|
|
292
|
+
if name_match
|
|
293
|
+
result[:match] = name_match
|
|
294
|
+
return result
|
|
295
|
+
end
|
|
296
|
+
end
|
|
297
|
+
|
|
298
|
+
# Try symbol match
|
|
299
|
+
if db_unit.symbols && !db_unit.symbols.empty?
|
|
300
|
+
symbol_match = ucum_units.find do |ucum_unit|
|
|
301
|
+
db_unit.symbols.any? do |symbol|
|
|
302
|
+
ucum_unit.print_symbol == symbol.ascii ||
|
|
303
|
+
ucum_unit.print_symbol == symbol.unicode
|
|
304
|
+
end
|
|
305
|
+
end
|
|
306
|
+
|
|
307
|
+
result[:match] = symbol_match if symbol_match
|
|
308
|
+
end
|
|
309
|
+
|
|
310
|
+
result
|
|
311
|
+
end
|
|
312
|
+
end
|
|
313
|
+
end
|
|
314
|
+
end
|
|
315
|
+
end
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "fileutils"
|
|
4
|
+
|
|
5
|
+
module Unitsdb
|
|
6
|
+
module Commands
|
|
7
|
+
module Ucum
|
|
8
|
+
# Command to update UnitsDB with UCUM references
|
|
9
|
+
class Update < Base
|
|
10
|
+
# Constants
|
|
11
|
+
ENTITY_TYPES = %w[units prefixes].freeze
|
|
12
|
+
|
|
13
|
+
def run
|
|
14
|
+
# Get options
|
|
15
|
+
entity_type = @options[:entity_type]&.downcase
|
|
16
|
+
database_path = @options[:database]
|
|
17
|
+
ucum_file = @options[:ucum_file]
|
|
18
|
+
output_dir = @options[:output_dir] || database_path
|
|
19
|
+
include_potential = @options[:include_potential_matches] || false
|
|
20
|
+
|
|
21
|
+
# Validate database path
|
|
22
|
+
unless File.exist?(database_path) && Dir.exist?(database_path)
|
|
23
|
+
puts "Database directory path: #{database_path}"
|
|
24
|
+
puts "ERROR: Database directory not found: #{database_path}"
|
|
25
|
+
return 1
|
|
26
|
+
end
|
|
27
|
+
puts "Using database directory: #{database_path}"
|
|
28
|
+
|
|
29
|
+
# Validate UCUM file
|
|
30
|
+
unless File.exist?(ucum_file)
|
|
31
|
+
puts "ERROR: UCUM file not found: #{ucum_file}"
|
|
32
|
+
return 1
|
|
33
|
+
end
|
|
34
|
+
puts "Using UCUM file: #{ucum_file}"
|
|
35
|
+
puts "Include potential matches: #{include_potential ? 'Yes' : 'No'}"
|
|
36
|
+
|
|
37
|
+
# Parse UCUM XML file
|
|
38
|
+
ucum_data = XmlParser.parse_ucum_file(ucum_file)
|
|
39
|
+
|
|
40
|
+
# Process entity types
|
|
41
|
+
if entity_type && ENTITY_TYPES.include?(entity_type)
|
|
42
|
+
process_entity_type(entity_type, ucum_data, output_dir,
|
|
43
|
+
include_potential)
|
|
44
|
+
else
|
|
45
|
+
ENTITY_TYPES.each do |type|
|
|
46
|
+
process_entity_type(type, ucum_data, output_dir,
|
|
47
|
+
include_potential)
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
0
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
private
|
|
55
|
+
|
|
56
|
+
def process_entity_type(entity_type, ucum_data, output_dir,
|
|
57
|
+
include_potential)
|
|
58
|
+
puts "\n========== Processing #{entity_type.upcase} References =========="
|
|
59
|
+
|
|
60
|
+
# Get entities
|
|
61
|
+
klass = Unitsdb.const_get(entity_type.capitalize)
|
|
62
|
+
yaml_path = File.join(@options[:database], "#{entity_type}.yaml")
|
|
63
|
+
entity_collection = klass.from_yaml(File.read(yaml_path))
|
|
64
|
+
|
|
65
|
+
ucum_entities = XmlParser.get_entities_from_ucum(entity_type,
|
|
66
|
+
ucum_data)
|
|
67
|
+
|
|
68
|
+
return if ucum_entities.nil? || ucum_entities.empty?
|
|
69
|
+
|
|
70
|
+
# Match entities
|
|
71
|
+
_, missing_refs, = Matcher.match_db_to_ucum(entity_type,
|
|
72
|
+
ucum_entities, entity_collection)
|
|
73
|
+
|
|
74
|
+
# Create output directory if it doesn't exist
|
|
75
|
+
FileUtils.mkdir_p(output_dir)
|
|
76
|
+
|
|
77
|
+
# Update references in UnitsDB entities
|
|
78
|
+
output_file = File.join(output_dir, "#{entity_type}.yaml")
|
|
79
|
+
Updater.update_references(entity_type, missing_refs,
|
|
80
|
+
entity_collection, output_file, include_potential)
|
|
81
|
+
end
|
|
82
|
+
end
|
|
83
|
+
end
|
|
84
|
+
end
|
|
85
|
+
end
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "yaml"
|
|
4
|
+
require "fileutils"
|
|
5
|
+
|
|
6
|
+
module Unitsdb
|
|
7
|
+
module Commands
|
|
8
|
+
module Ucum
|
|
9
|
+
# Updater for adding UCUM references to UnitsDB entities
|
|
10
|
+
module Updater
|
|
11
|
+
UCUM_AUTHORITY = "ucum"
|
|
12
|
+
|
|
13
|
+
module_function
|
|
14
|
+
|
|
15
|
+
# Update references in UnitsDB entities with UCUM references
|
|
16
|
+
def update_references(entity_type, matches, db_entities, output_file,
|
|
17
|
+
include_potential = false)
|
|
18
|
+
puts "Updating UCUM references for #{entity_type}..."
|
|
19
|
+
|
|
20
|
+
# Create a map of entity IDs to their UCUM references
|
|
21
|
+
entity_references = {}
|
|
22
|
+
|
|
23
|
+
# Process each match
|
|
24
|
+
matches.each do |match|
|
|
25
|
+
db_entity = match[:db_entity]
|
|
26
|
+
ucum_entity = match[:ucum_entity]
|
|
27
|
+
|
|
28
|
+
# Skip potential matches unless specified
|
|
29
|
+
next if match[:potential] && !include_potential
|
|
30
|
+
|
|
31
|
+
# Get entity ID
|
|
32
|
+
entity_id = get_entity_id(db_entity)
|
|
33
|
+
next unless entity_id
|
|
34
|
+
|
|
35
|
+
# Initialize references for this entity
|
|
36
|
+
entity_references[entity_id] = ExternalReference.new(
|
|
37
|
+
uri: ucum_entity.identifier,
|
|
38
|
+
type: "informative",
|
|
39
|
+
authority: UCUM_AUTHORITY,
|
|
40
|
+
)
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
# Update the YAML content
|
|
44
|
+
db_entities.send(entity_type).each do |entity|
|
|
45
|
+
# Find entity by ID
|
|
46
|
+
entity_id = if entity.identifiers
|
|
47
|
+
begin
|
|
48
|
+
entity.identifiers.first.id
|
|
49
|
+
rescue StandardError
|
|
50
|
+
nil
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
next unless entity_id && entity_references.key?(entity_id)
|
|
55
|
+
|
|
56
|
+
# Initialize references array if it doesn't exist
|
|
57
|
+
entity.references ||= []
|
|
58
|
+
|
|
59
|
+
# Add new references
|
|
60
|
+
if (ext_ref = entity_references[entity_id])
|
|
61
|
+
if entity.references.detect do |ref|
|
|
62
|
+
ref.uri == ext_ref.uri && ref.authority == ext_ref.authority
|
|
63
|
+
end
|
|
64
|
+
# Skip if reference already exists
|
|
65
|
+
puts "Reference already exists for entity ID: #{entity_id}"
|
|
66
|
+
else
|
|
67
|
+
# Add the reference
|
|
68
|
+
puts "Adding reference for entity ID: #{entity_id}, URI: #{ext_ref.uri}, Authority: #{ext_ref.authority}"
|
|
69
|
+
entity.references << ext_ref
|
|
70
|
+
end
|
|
71
|
+
end
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
# Write to YAML file
|
|
75
|
+
write_yaml_file(output_file, db_entities)
|
|
76
|
+
|
|
77
|
+
puts "Added #{entity_references.values.flatten.size} UCUM references to #{entity_type}"
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
# Helper to write YAML file
|
|
81
|
+
def write_yaml_file(output_file, output_data)
|
|
82
|
+
# Ensure the output directory exists
|
|
83
|
+
output_dir = File.dirname(output_file)
|
|
84
|
+
FileUtils.mkdir_p(output_dir)
|
|
85
|
+
|
|
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
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
# Get entity ID (either from identifiers array or directly)
|
|
122
|
+
def get_entity_id(entity)
|
|
123
|
+
if entity.respond_to?(:identifiers) && entity.identifiers && !entity.identifiers.empty?
|
|
124
|
+
entity.identifiers.first.id
|
|
125
|
+
elsif entity.respond_to?(:id)
|
|
126
|
+
entity.id
|
|
127
|
+
end
|
|
128
|
+
end
|
|
129
|
+
end
|
|
130
|
+
end
|
|
131
|
+
end
|
|
132
|
+
end
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Unitsdb
|
|
4
|
+
module Commands
|
|
5
|
+
module Ucum
|
|
6
|
+
# Parser for UCUM XML files
|
|
7
|
+
module XmlParser
|
|
8
|
+
module_function
|
|
9
|
+
|
|
10
|
+
# Parse UCUM XML file and return parsed data
|
|
11
|
+
def parse_ucum_file(file_path)
|
|
12
|
+
puts "Parsing UCUM XML file: #{file_path}..."
|
|
13
|
+
content = File.read(file_path)
|
|
14
|
+
Unitsdb::UcumFile.from_xml(content)
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
# Get entities from parsed UCUM data based on entity type
|
|
18
|
+
def get_entities_from_ucum(entity_type, ucum_data)
|
|
19
|
+
case entity_type
|
|
20
|
+
when "prefixes"
|
|
21
|
+
ucum_data.prefixes
|
|
22
|
+
when "units"
|
|
23
|
+
# Combine base-units and units into a single array
|
|
24
|
+
ucum_data.base_units + ucum_data.units
|
|
25
|
+
else
|
|
26
|
+
[]
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
end
|