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,83 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "thor"
|
|
4
|
+
|
|
5
|
+
module Unitsdb
|
|
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
|
+
|
|
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
|
+
|
|
21
|
+
desc "check", "Check UCUM references in UnitsDB"
|
|
22
|
+
option :entity_type, type: :string, aliases: "-e",
|
|
23
|
+
desc: "Entity type to check (units, prefixes). If not specified, all types are checked"
|
|
24
|
+
option :ucum_file, type: :string, required: true, aliases: "-u",
|
|
25
|
+
desc: "Path to the UCUM essence XML file"
|
|
26
|
+
option :output_updated_database, type: :string, aliases: "-o",
|
|
27
|
+
desc: "Directory path to write updated YAML files with added UCUM references"
|
|
28
|
+
option :direction, type: :string, default: "both", aliases: "-r",
|
|
29
|
+
desc: "Direction to check: 'to_ucum' (UnitsDB→UCUM), 'from_ucum' (UCUM→UnitsDB), or 'both'"
|
|
30
|
+
option :include_potential_matches, type: :boolean, default: false, aliases: "-p",
|
|
31
|
+
desc: "Include potential matches when updating references (default: false)"
|
|
32
|
+
option :database, type: :string, required: true, aliases: "-d",
|
|
33
|
+
desc: "Path to UnitsDB database (required)"
|
|
34
|
+
def check
|
|
35
|
+
run_command(Ucum::Check, options)
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
desc "update", "Update UnitsDB with UCUM references"
|
|
39
|
+
option :entity_type, type: :string, aliases: "-e",
|
|
40
|
+
desc: "Entity type to update (units, prefixes). If not specified, all types are updated"
|
|
41
|
+
option :ucum_file, type: :string, required: true, aliases: "-u",
|
|
42
|
+
desc: "Path to the UCUM essence XML file"
|
|
43
|
+
option :output_dir, type: :string, aliases: "-o",
|
|
44
|
+
desc: "Directory path to write updated YAML files (defaults to database path)"
|
|
45
|
+
option :include_potential_matches, type: :boolean, default: false, aliases: "-p",
|
|
46
|
+
desc: "Include potential matches when updating references (default: false)"
|
|
47
|
+
option :database, type: :string, required: true, aliases: "-d",
|
|
48
|
+
desc: "Path to UnitsDB database (required)"
|
|
49
|
+
def update
|
|
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
|
|
80
|
+
end
|
|
81
|
+
end
|
|
82
|
+
end
|
|
83
|
+
end
|
|
@@ -3,15 +3,15 @@
|
|
|
3
3
|
module Unitsdb
|
|
4
4
|
module Commands
|
|
5
5
|
module Validate
|
|
6
|
-
class Identifiers < Base
|
|
6
|
+
class Identifiers < Unitsdb::Commands::Base
|
|
7
7
|
def run
|
|
8
8
|
db = load_database
|
|
9
9
|
all_dups = db.validate_uniqueness
|
|
10
10
|
|
|
11
11
|
display_results(all_dups)
|
|
12
12
|
rescue Unitsdb::Errors::DatabaseError => e
|
|
13
|
-
|
|
14
|
-
|
|
13
|
+
raise Unitsdb::Errors::ValidationError,
|
|
14
|
+
"Failed to validate identifiers: #{e.message}"
|
|
15
15
|
end
|
|
16
16
|
|
|
17
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
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
module Unitsdb
|
|
4
4
|
module Commands
|
|
5
5
|
module Validate
|
|
6
|
-
class References < Base
|
|
6
|
+
class References < Unitsdb::Commands::Base
|
|
7
7
|
def run
|
|
8
8
|
# Load the database
|
|
9
9
|
db = load_database(@options[:database])
|
|
@@ -17,8 +17,8 @@ module Unitsdb
|
|
|
17
17
|
# Display results
|
|
18
18
|
display_reference_results(invalid_refs, registry)
|
|
19
19
|
rescue Unitsdb::Errors::DatabaseError => e
|
|
20
|
-
|
|
21
|
-
|
|
20
|
+
raise Unitsdb::Errors::ValidationError,
|
|
21
|
+
"Failed to validate references: #{e.message}"
|
|
22
22
|
end
|
|
23
23
|
|
|
24
24
|
private
|
|
@@ -97,7 +97,8 @@ module Unitsdb
|
|
|
97
97
|
# Also track unit systems by short name
|
|
98
98
|
if unit_system.respond_to?(:short) && unit_system.short
|
|
99
99
|
registry["unit_systems_short"] ||= {}
|
|
100
|
-
registry["unit_systems_short"][unit_system.short] =
|
|
100
|
+
registry["unit_systems_short"][unit_system.short] =
|
|
101
|
+
"index:#{index}"
|
|
101
102
|
end
|
|
102
103
|
end
|
|
103
104
|
|
|
@@ -141,7 +142,8 @@ module Unitsdb
|
|
|
141
142
|
ref_type = "dimensions"
|
|
142
143
|
ref_path = "dimensions:index:#{index}:dimension_reference"
|
|
143
144
|
|
|
144
|
-
validate_reference(ref_id, ref_type, ref_path, registry,
|
|
145
|
+
validate_reference(ref_id, ref_type, ref_path, registry,
|
|
146
|
+
invalid_refs, "dimensions")
|
|
145
147
|
end
|
|
146
148
|
end
|
|
147
149
|
|
|
@@ -153,7 +155,8 @@ module Unitsdb
|
|
|
153
155
|
ref_type = "unit_systems"
|
|
154
156
|
ref_path = "units:index:#{index}:unit_system_reference[#{idx}]"
|
|
155
157
|
|
|
156
|
-
validate_reference(ref_id, ref_type, ref_path, registry,
|
|
158
|
+
validate_reference(ref_id, ref_type, ref_path, registry,
|
|
159
|
+
invalid_refs, "units")
|
|
157
160
|
end
|
|
158
161
|
end
|
|
159
162
|
end
|
|
@@ -166,7 +169,8 @@ module Unitsdb
|
|
|
166
169
|
ref_type = "quantities"
|
|
167
170
|
ref_path = "units:index:#{index}:quantity_references[#{idx}]"
|
|
168
171
|
|
|
169
|
-
validate_reference(ref_id, ref_type, ref_path, registry,
|
|
172
|
+
validate_reference(ref_id, ref_type, ref_path, registry,
|
|
173
|
+
invalid_refs, "units")
|
|
170
174
|
end
|
|
171
175
|
end
|
|
172
176
|
end
|
|
@@ -183,7 +187,8 @@ module Unitsdb
|
|
|
183
187
|
ref_type = "units"
|
|
184
188
|
ref_path = "units:index:#{index}:root_units.#{idx}.unit_reference"
|
|
185
189
|
|
|
186
|
-
validate_reference(ref_id, ref_type, ref_path, registry,
|
|
190
|
+
validate_reference(ref_id, ref_type, ref_path, registry,
|
|
191
|
+
invalid_refs, "units")
|
|
187
192
|
|
|
188
193
|
# Check prefix reference if present
|
|
189
194
|
next unless root_unit.respond_to?(:prefix_reference) && root_unit.prefix_reference
|
|
@@ -192,12 +197,14 @@ module Unitsdb
|
|
|
192
197
|
ref_type = "prefixes"
|
|
193
198
|
ref_path = "units:index:#{index}:root_units.#{idx}.prefix_reference"
|
|
194
199
|
|
|
195
|
-
validate_reference(ref_id, ref_type, ref_path, registry,
|
|
200
|
+
validate_reference(ref_id, ref_type, ref_path, registry,
|
|
201
|
+
invalid_refs, "units")
|
|
196
202
|
end
|
|
197
203
|
end
|
|
198
204
|
end
|
|
199
205
|
|
|
200
|
-
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)
|
|
201
208
|
# Handle references that are objects with id and type (could be a hash or an object)
|
|
202
209
|
if ref_id.respond_to?(:id) && ref_id.respond_to?(:type)
|
|
203
210
|
id = ref_id.id
|
|
@@ -216,8 +223,12 @@ module Unitsdb
|
|
|
216
223
|
# 3. Try alternate ID formats for unit systems (e.g., SI_base vs si-base)
|
|
217
224
|
if !valid && type == "unitsml" && ref_type == "unit_systems" && registry.key?(ref_type) && (
|
|
218
225
|
registry[ref_type].keys.any? { |k| k.end_with?(":#{id}") } ||
|
|
219
|
-
registry[ref_type].keys.any?
|
|
220
|
-
|
|
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
|
|
221
232
|
)
|
|
222
233
|
# Special handling for unit_systems between unitsml and nist types
|
|
223
234
|
valid = true
|
|
@@ -227,7 +238,8 @@ module Unitsdb
|
|
|
227
238
|
puts "Valid reference: #{id} (#{type}) at #{file_type}:#{ref_path}" if @options[:print_valid]
|
|
228
239
|
else
|
|
229
240
|
invalid_refs[file_type] ||= {}
|
|
230
|
-
invalid_refs[file_type][ref_path] =
|
|
241
|
+
invalid_refs[file_type][ref_path] =
|
|
242
|
+
{ id: id, type: type, ref_type: ref_type }
|
|
231
243
|
end
|
|
232
244
|
# Handle references that are objects with id and type in a hash
|
|
233
245
|
elsif ref_id.is_a?(Hash) && ref_id.key?("id") && ref_id.key?("type")
|
|
@@ -247,8 +259,12 @@ module Unitsdb
|
|
|
247
259
|
# 3. Try alternate ID formats for unit systems (e.g., SI_base vs si-base)
|
|
248
260
|
if !valid && type == "unitsml" && ref_type == "unit_systems" && registry.key?(ref_type) && (
|
|
249
261
|
registry[ref_type].keys.any? { |k| k.end_with?(":#{id}") } ||
|
|
250
|
-
registry[ref_type].keys.any?
|
|
251
|
-
|
|
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
|
|
252
268
|
)
|
|
253
269
|
# Special handling for unit_systems between unitsml and nist types
|
|
254
270
|
valid = true
|
|
@@ -258,7 +274,8 @@ module Unitsdb
|
|
|
258
274
|
puts "Valid reference: #{id} (#{type}) at #{file_type}:#{ref_path}" if @options[:print_valid]
|
|
259
275
|
else
|
|
260
276
|
invalid_refs[file_type] ||= {}
|
|
261
|
-
invalid_refs[file_type][ref_path] =
|
|
277
|
+
invalid_refs[file_type][ref_path] =
|
|
278
|
+
{ id: id, type: type, ref_type: ref_type }
|
|
262
279
|
end
|
|
263
280
|
else
|
|
264
281
|
# Handle plain string references (legacy format)
|
|
@@ -290,7 +307,8 @@ module Unitsdb
|
|
|
290
307
|
|
|
291
308
|
puts " #{type}:"
|
|
292
309
|
ids.each do |id, location|
|
|
293
|
-
puts " #{id}: {type: #{type.sub(
|
|
310
|
+
puts " #{id}: {type: #{type.sub('s',
|
|
311
|
+
'')}, source: #{location}}"
|
|
294
312
|
end
|
|
295
313
|
end
|
|
296
314
|
end
|
|
@@ -302,7 +320,8 @@ module Unitsdb
|
|
|
302
320
|
# Suggest corrections
|
|
303
321
|
next unless registry.key?(ref[:ref_type])
|
|
304
322
|
|
|
305
|
-
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)
|
|
306
325
|
if similar_ids.any?
|
|
307
326
|
puts " Did you mean one of these?"
|
|
308
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
|
|
@@ -103,12 +101,6 @@ module Unitsdb
|
|
|
103
101
|
puts "Please fix the duplicates by either removing the reference from all but one entity,"
|
|
104
102
|
puts "or by updating the references to use different URIs appropriate for each entity."
|
|
105
103
|
end
|
|
106
|
-
|
|
107
|
-
def load_database(path)
|
|
108
|
-
Unitsdb::Database.from_db(path)
|
|
109
|
-
rescue StandardError => e
|
|
110
|
-
raise Unitsdb::Errors::DatabaseError, "Failed to load database: #{e.message}"
|
|
111
|
-
end
|
|
112
104
|
end
|
|
113
105
|
end
|
|
114
106
|
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
|
|
@@ -4,16 +4,28 @@ require "thor"
|
|
|
4
4
|
|
|
5
5
|
module Unitsdb
|
|
6
6
|
module Commands
|
|
7
|
+
module Validate
|
|
8
|
+
autoload :Identifiers, "unitsdb/commands/validate/identifiers"
|
|
9
|
+
autoload :QudtReferences, "unitsdb/commands/validate/qudt_references"
|
|
10
|
+
autoload :References, "unitsdb/commands/validate/references"
|
|
11
|
+
autoload :SiReferences, "unitsdb/commands/validate/si_references"
|
|
12
|
+
autoload :UcumReferences, "unitsdb/commands/validate/ucum_references"
|
|
13
|
+
end
|
|
14
|
+
|
|
7
15
|
class ValidateCommand < Thor
|
|
16
|
+
# Inherit trace option from parent CLI
|
|
17
|
+
class_option :trace, type: :boolean, default: false,
|
|
18
|
+
desc: "Show full backtrace on error"
|
|
19
|
+
|
|
8
20
|
desc "references", "Validate that all references exist"
|
|
9
|
-
option :debug_registry, type: :boolean,
|
|
21
|
+
option :debug_registry, type: :boolean,
|
|
22
|
+
desc: "Show registry contents for debugging"
|
|
10
23
|
option :database, type: :string, required: true, aliases: "-d",
|
|
11
24
|
desc: "Path to UnitsDB database (required)"
|
|
12
|
-
option :print_valid, type: :boolean, default: false,
|
|
25
|
+
option :print_valid, type: :boolean, default: false,
|
|
26
|
+
desc: "Print valid references too"
|
|
13
27
|
def references
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
Commands::References.new(options).run
|
|
28
|
+
run_command(Commands::Validate::References, options)
|
|
17
29
|
end
|
|
18
30
|
|
|
19
31
|
desc "identifiers", "Check for uniqueness of identifier fields"
|
|
@@ -21,19 +33,63 @@ module Unitsdb
|
|
|
21
33
|
desc: "Path to UnitsDB database (required)"
|
|
22
34
|
|
|
23
35
|
def identifiers
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
Commands::Identifiers.new(options).run
|
|
36
|
+
run_command(Commands::Validate::Identifiers, options)
|
|
27
37
|
end
|
|
28
38
|
|
|
29
|
-
desc "si_references",
|
|
39
|
+
desc "si_references",
|
|
40
|
+
"Validate that each SI digital framework reference is unique per entity type"
|
|
30
41
|
option :database, type: :string, required: true, aliases: "-d",
|
|
31
42
|
desc: "Path to UnitsDB database (required)"
|
|
32
43
|
|
|
33
44
|
def si_references
|
|
34
|
-
|
|
45
|
+
run_command(Commands::Validate::SiReferences, options)
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
desc "qudt_references",
|
|
49
|
+
"Validate that each QUDT reference is unique per entity type"
|
|
50
|
+
option :database, type: :string, required: true, aliases: "-d",
|
|
51
|
+
desc: "Path to UnitsDB database (required)"
|
|
52
|
+
|
|
53
|
+
def qudt_references
|
|
54
|
+
run_command(Commands::Validate::QudtReferences, options)
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
desc "ucum_references",
|
|
58
|
+
"Validate that each UCUM reference is unique per entity type"
|
|
59
|
+
option :database, type: :string, required: true, aliases: "-d",
|
|
60
|
+
desc: "Path to UnitsDB database (required)"
|
|
61
|
+
|
|
62
|
+
def ucum_references
|
|
63
|
+
run_command(Commands::Validate::UcumReferences, options)
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
private
|
|
67
|
+
|
|
68
|
+
def run_command(command_class, options)
|
|
69
|
+
command = command_class.new(options)
|
|
70
|
+
command.run
|
|
71
|
+
rescue Unitsdb::Errors::CLIRuntimeError => e
|
|
72
|
+
handle_cli_error(e)
|
|
73
|
+
rescue StandardError => e
|
|
74
|
+
handle_error(e)
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
def handle_cli_error(error)
|
|
78
|
+
if options[:trace]
|
|
79
|
+
raise error
|
|
80
|
+
else
|
|
81
|
+
warn "Error: #{error.message}"
|
|
82
|
+
exit 1
|
|
83
|
+
end
|
|
84
|
+
end
|
|
35
85
|
|
|
36
|
-
|
|
86
|
+
def handle_error(error)
|
|
87
|
+
if options[:trace]
|
|
88
|
+
raise error
|
|
89
|
+
else
|
|
90
|
+
warn "Error: #{error.message}"
|
|
91
|
+
exit 1
|
|
92
|
+
end
|
|
37
93
|
end
|
|
38
94
|
end
|
|
39
95
|
end
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Unitsdb
|
|
4
|
+
module Commands
|
|
5
|
+
autoload :ModifyCommand, "unitsdb/commands/_modify"
|
|
6
|
+
autoload :Base, "unitsdb/commands/base"
|
|
7
|
+
autoload :CheckSi, "unitsdb/commands/check_si"
|
|
8
|
+
autoload :CheckSiCommand, "unitsdb/commands/check_si"
|
|
9
|
+
autoload :Get, "unitsdb/commands/get"
|
|
10
|
+
autoload :Normalize, "unitsdb/commands/normalize"
|
|
11
|
+
autoload :Qudt, "unitsdb/commands/qudt"
|
|
12
|
+
autoload :QudtCommand, "unitsdb/commands/qudt"
|
|
13
|
+
autoload :Release, "unitsdb/commands/release"
|
|
14
|
+
autoload :Search, "unitsdb/commands/search"
|
|
15
|
+
autoload :Ucum, "unitsdb/commands/ucum"
|
|
16
|
+
autoload :UcumCommand, "unitsdb/commands/ucum"
|
|
17
|
+
autoload :Validate, "unitsdb/commands/validate"
|
|
18
|
+
autoload :ValidateCommand, "unitsdb/commands/validate"
|
|
19
|
+
end
|
|
20
|
+
end
|