unitsdb 2.2.2 → 2.2.4
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/opal.yml +36 -0
- data/.gitignore +3 -0
- data/Gemfile +2 -1
- data/Rakefile +3 -1
- data/lib/unitsdb/cli.rb +5 -41
- data/lib/unitsdb/commands/_modify.rb +1 -34
- data/lib/unitsdb/commands/check_si/si_formatter.rb +6 -6
- data/lib/unitsdb/commands/check_si/si_matcher.rb +202 -292
- data/lib/unitsdb/commands/check_si/si_updater.rb +16 -36
- data/lib/unitsdb/commands/entity_presenter.rb +98 -0
- data/lib/unitsdb/commands/get.rb +16 -113
- data/lib/unitsdb/commands/qudt/formatter.rb +16 -27
- data/lib/unitsdb/commands/qudt/matcher.rb +18 -28
- data/lib/unitsdb/commands/qudt/updater.rb +8 -11
- data/lib/unitsdb/commands/qudt.rb +1 -34
- data/lib/unitsdb/commands/search.rb +33 -188
- data/lib/unitsdb/commands/thor.rb +41 -0
- data/lib/unitsdb/commands/ucum/formatter.rb +9 -18
- data/lib/unitsdb/commands/ucum/matcher.rb +4 -4
- data/lib/unitsdb/commands/ucum/updater.rb +3 -5
- data/lib/unitsdb/commands/ucum.rb +1 -34
- data/lib/unitsdb/commands/validate/qudt_references.rb +29 -70
- data/lib/unitsdb/commands/validate/references.rb +5 -303
- data/lib/unitsdb/commands/validate/si_references.rb +30 -66
- data/lib/unitsdb/commands/validate/ucum_references.rb +30 -64
- data/lib/unitsdb/commands/validate.rb +1 -36
- data/lib/unitsdb/commands.rb +2 -0
- data/lib/unitsdb/config.rb +83 -29
- data/lib/unitsdb/database/loader.rb +135 -0
- data/lib/unitsdb/database/reference_validator.rb +227 -0
- data/lib/unitsdb/database/uniqueness_validator.rb +80 -0
- data/lib/unitsdb/database.rb +124 -584
- data/lib/unitsdb/dimension.rb +0 -27
- data/lib/unitsdb/dimensions.rb +0 -2
- data/lib/unitsdb/opal.rb +43 -0
- data/lib/unitsdb/prefix.rb +0 -13
- data/lib/unitsdb/prefixes.rb +0 -2
- data/lib/unitsdb/quantities.rb +0 -1
- data/lib/unitsdb/quantity.rb +0 -2
- data/lib/unitsdb/quantity_reference.rb +0 -2
- data/lib/unitsdb/root_unit_reference.rb +0 -2
- data/lib/unitsdb/scale.rb +0 -2
- data/lib/unitsdb/scale_properties.rb +0 -1
- data/lib/unitsdb/scales.rb +0 -1
- data/lib/unitsdb/si_derived_base.rb +0 -1
- data/lib/unitsdb/symbol_presentations.rb +0 -2
- data/lib/unitsdb/unit.rb +0 -34
- data/lib/unitsdb/unit_system.rb +0 -2
- data/lib/unitsdb/unit_systems.rb +0 -2
- data/lib/unitsdb/units.rb +0 -2
- data/lib/unitsdb/version.rb +1 -1
- data/lib/unitsdb.rb +142 -35
- data/unitsdb.gemspec +1 -0
- metadata +23 -2
|
@@ -1,219 +1,64 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require "json"
|
|
4
|
-
|
|
5
3
|
module Unitsdb
|
|
6
4
|
module Commands
|
|
7
5
|
class Search < Base
|
|
8
6
|
def run(query)
|
|
9
|
-
# Database path is guaranteed by Thor's global option
|
|
10
|
-
|
|
11
7
|
type = @options[:type]
|
|
12
8
|
id = @options[:id]
|
|
13
9
|
id_type = @options[:id_type]
|
|
14
10
|
format = @options[:format] || "text"
|
|
15
11
|
|
|
16
|
-
|
|
17
|
-
database = load_database(@options[:database])
|
|
18
|
-
|
|
19
|
-
# Search by ID (early return)
|
|
20
|
-
if id
|
|
21
|
-
entity = database.get_by_id(id: id, type: id_type)
|
|
22
|
-
|
|
23
|
-
unless entity
|
|
24
|
-
puts "No entity found with ID: '#{id}'"
|
|
25
|
-
return
|
|
26
|
-
end
|
|
27
|
-
|
|
28
|
-
# Use the same output logic as the Get command
|
|
29
|
-
if %w[json yaml].include?(format.downcase)
|
|
30
|
-
begin
|
|
31
|
-
puts entity.send("to_#{format.downcase}")
|
|
32
|
-
return
|
|
33
|
-
rescue NoMethodError
|
|
34
|
-
raise Unitsdb::Errors::InvalidFormatError,
|
|
35
|
-
"Unable to convert entity to #{format.upcase} format: output format not supported for this entity type"
|
|
36
|
-
end
|
|
37
|
-
end
|
|
38
|
-
|
|
39
|
-
print_entity_details(entity)
|
|
40
|
-
return
|
|
41
|
-
end
|
|
42
|
-
|
|
43
|
-
# Regular text search
|
|
44
|
-
results = database.search(text: query, type: type)
|
|
45
|
-
|
|
46
|
-
# Early return for empty results
|
|
47
|
-
if results.empty?
|
|
48
|
-
puts "No results found for '#{query}'"
|
|
49
|
-
return
|
|
50
|
-
end
|
|
51
|
-
|
|
52
|
-
# Format-specific output
|
|
53
|
-
if %w[json yaml].include?(format.downcase)
|
|
54
|
-
temp_db = create_temporary_database(results)
|
|
55
|
-
puts temp_db.send("to_#{format.downcase}")
|
|
56
|
-
return
|
|
57
|
-
end
|
|
12
|
+
database = load_database(@options[:database])
|
|
58
13
|
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
print_entity_with_ids(entity)
|
|
63
|
-
end
|
|
64
|
-
rescue Unitsdb::Errors::DatabaseError => e
|
|
65
|
-
raise Unitsdb::Errors::DatabaseLoadError,
|
|
66
|
-
"Failed to load database: #{e.message}"
|
|
67
|
-
rescue StandardError => e
|
|
68
|
-
raise Unitsdb::Errors::CLIRuntimeError, "Search failed: #{e.message}"
|
|
14
|
+
if id
|
|
15
|
+
lookup_by_id(database, id, id_type, format)
|
|
16
|
+
return
|
|
69
17
|
end
|
|
70
|
-
end
|
|
71
|
-
|
|
72
|
-
private
|
|
73
|
-
|
|
74
|
-
def print_entity_with_ids(entity)
|
|
75
|
-
# Determine entity type
|
|
76
|
-
entity_type = get_entity_type(entity)
|
|
77
|
-
|
|
78
|
-
# Get name
|
|
79
|
-
name = get_entity_name(entity)
|
|
80
18
|
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
puts " - #{entity_type}: #{name}"
|
|
86
|
-
|
|
87
|
-
# Print each identifier on its own line for better readability
|
|
88
|
-
if identifiers.empty?
|
|
89
|
-
puts " ID: None"
|
|
90
|
-
else
|
|
91
|
-
puts " IDs:"
|
|
92
|
-
identifiers.each do |id|
|
|
93
|
-
puts " - #{id.id} (Type: #{id.type || 'N/A'})"
|
|
94
|
-
end
|
|
19
|
+
results = database.search(text: query, type: type)
|
|
20
|
+
if results.empty?
|
|
21
|
+
puts "No results found for '#{query}'"
|
|
22
|
+
return
|
|
95
23
|
end
|
|
96
24
|
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
25
|
+
print_results(results, query, format)
|
|
26
|
+
rescue Unitsdb::Errors::DatabaseError => e
|
|
27
|
+
raise Unitsdb::Errors::DatabaseLoadError,
|
|
28
|
+
"Failed to load database: #{e.message}"
|
|
29
|
+
rescue StandardError => e
|
|
30
|
+
raise Unitsdb::Errors::CLIRuntimeError, "Search failed: #{e.message}"
|
|
102
31
|
end
|
|
103
32
|
|
|
104
|
-
|
|
105
|
-
# Determine entity type
|
|
106
|
-
entity_type = get_entity_type(entity)
|
|
107
|
-
|
|
108
|
-
# Get name
|
|
109
|
-
name = get_entity_name(entity)
|
|
110
|
-
|
|
111
|
-
puts "Entity details:"
|
|
112
|
-
puts " - Type: #{entity_type}"
|
|
113
|
-
puts " - Name: #{name}"
|
|
114
|
-
|
|
115
|
-
# Print description if available
|
|
116
|
-
puts " - Description: #{entity.short}" if entity.respond_to?(:short) && entity.short && entity.short != name
|
|
117
|
-
|
|
118
|
-
# Print all identifiers
|
|
119
|
-
if entity.identifiers&.any?
|
|
120
|
-
puts " - Identifiers:"
|
|
121
|
-
entity.identifiers.each do |id|
|
|
122
|
-
puts " - #{id.id} (Type: #{id.type || 'N/A'})"
|
|
123
|
-
end
|
|
124
|
-
else
|
|
125
|
-
puts " - Identifiers: None"
|
|
126
|
-
end
|
|
127
|
-
|
|
128
|
-
# Print additional properties based on entity type
|
|
129
|
-
case entity
|
|
130
|
-
when Unitsdb::Unit
|
|
131
|
-
puts " - Symbols:" if entity.respond_to?(:symbols) && entity.symbols&.any?
|
|
132
|
-
if entity.respond_to?(:symbols) && entity.symbols&.any?
|
|
133
|
-
entity.symbols.each do |s|
|
|
134
|
-
puts " - #{s}"
|
|
135
|
-
end
|
|
136
|
-
end
|
|
137
|
-
|
|
138
|
-
puts " - Definition: #{entity.definition}" if entity.respond_to?(:definition) && entity.definition
|
|
139
|
-
|
|
140
|
-
if entity.respond_to?(:dimensions) && entity.dimensions&.any?
|
|
141
|
-
puts " - Dimensions:"
|
|
142
|
-
entity.dimensions.each { |d| puts " - #{d}" }
|
|
143
|
-
end
|
|
144
|
-
when Unitsdb::Quantity
|
|
145
|
-
puts " - Dimensions: #{entity.dimension}" if entity.respond_to?(:dimension) && entity.dimension
|
|
146
|
-
when Unitsdb::Prefix
|
|
147
|
-
puts " - Value: #{entity.value}" if entity.respond_to?(:value) && entity.value
|
|
148
|
-
puts " - Symbol: #{entity.symbol}" if entity.respond_to?(:symbol) && entity.symbol
|
|
149
|
-
when Unitsdb::Dimension
|
|
150
|
-
# Any dimension-specific properties
|
|
151
|
-
when Unitsdb::UnitSystem
|
|
152
|
-
puts " - Organization: #{entity.organization}" if entity.respond_to?(:organization) && entity.organization
|
|
153
|
-
end
|
|
154
|
-
|
|
155
|
-
# Print references if available
|
|
156
|
-
return unless entity.respond_to?(:references) && entity.references&.any?
|
|
33
|
+
private
|
|
157
34
|
|
|
158
|
-
|
|
159
|
-
entity.
|
|
160
|
-
|
|
35
|
+
def lookup_by_id(database, id, id_type, format)
|
|
36
|
+
entity = database.get_by_id(id: id, type: id_type)
|
|
37
|
+
if entity.nil?
|
|
38
|
+
puts "No entity found with ID: '#{id}'"
|
|
39
|
+
return
|
|
161
40
|
end
|
|
162
|
-
end
|
|
163
41
|
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
when Unitsdb::Unit
|
|
167
|
-
"Unit"
|
|
168
|
-
when Unitsdb::Prefix
|
|
169
|
-
"Prefix"
|
|
170
|
-
when Unitsdb::Quantity
|
|
171
|
-
"Quantity"
|
|
172
|
-
when Unitsdb::Dimension
|
|
173
|
-
"Dimension"
|
|
174
|
-
when Unitsdb::UnitSystem
|
|
175
|
-
"UnitSystem"
|
|
42
|
+
if %w[json yaml].include?(format.downcase)
|
|
43
|
+
print_serialized(entity, format.downcase)
|
|
176
44
|
else
|
|
177
|
-
|
|
45
|
+
EntityPresenter.new(entity).print_details
|
|
178
46
|
end
|
|
179
47
|
end
|
|
180
48
|
|
|
181
|
-
def
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
49
|
+
def print_results(results, query, format)
|
|
50
|
+
if %w[json yaml].include?(format.downcase)
|
|
51
|
+
temp_db = Database.empty_for_results(results)
|
|
52
|
+
print_serialized(temp_db, format.downcase)
|
|
53
|
+
return
|
|
54
|
+
end
|
|
186
55
|
|
|
187
|
-
"
|
|
56
|
+
puts "Found #{results.size} result(s) for '#{query}':"
|
|
57
|
+
results.each { |entity| EntityPresenter.new(entity).print_summary }
|
|
188
58
|
end
|
|
189
59
|
|
|
190
|
-
def
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
# Initialize collections
|
|
194
|
-
temp_db.units = []
|
|
195
|
-
temp_db.prefixes = []
|
|
196
|
-
temp_db.quantities = []
|
|
197
|
-
temp_db.dimensions = []
|
|
198
|
-
temp_db.unit_systems = []
|
|
199
|
-
|
|
200
|
-
# Add results to appropriate collection based on type using case statement
|
|
201
|
-
results.each do |entity|
|
|
202
|
-
case entity
|
|
203
|
-
when Unitsdb::Unit
|
|
204
|
-
temp_db.units << entity
|
|
205
|
-
when Unitsdb::Prefix
|
|
206
|
-
temp_db.prefixes << entity
|
|
207
|
-
when Unitsdb::Quantity
|
|
208
|
-
temp_db.quantities << entity
|
|
209
|
-
when Unitsdb::Dimension
|
|
210
|
-
temp_db.dimensions << entity
|
|
211
|
-
when Unitsdb::UnitSystem
|
|
212
|
-
temp_db.unit_systems << entity
|
|
213
|
-
end
|
|
214
|
-
end
|
|
215
|
-
|
|
216
|
-
temp_db
|
|
60
|
+
def print_serialized(entity, format)
|
|
61
|
+
puts entity.public_send("to_#{format}")
|
|
217
62
|
end
|
|
218
63
|
end
|
|
219
64
|
end
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "thor"
|
|
4
|
+
|
|
5
|
+
module Unitsdb
|
|
6
|
+
module Commands
|
|
7
|
+
# Base class for every Thor-based CLI surface in the gem. Owns the
|
|
8
|
+
# shared `--trace` option, the `exit_on_failure?` policy, the
|
|
9
|
+
# `run_command` dispatch helper, and the single error handler
|
|
10
|
+
# (`handle_error`). Subclasses inherit these and provide only their
|
|
11
|
+
# own `desc`/`option`/subcommand declarations.
|
|
12
|
+
class Thor < ::Thor
|
|
13
|
+
class_option :trace, type: :boolean, default: false,
|
|
14
|
+
desc: "Show full backtrace on error"
|
|
15
|
+
|
|
16
|
+
def self.exit_on_failure?
|
|
17
|
+
true
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
private
|
|
21
|
+
|
|
22
|
+
# Instantiate `command_class` with `options` plus any extra
|
|
23
|
+
# positional args, then call its `run` (or other public method).
|
|
24
|
+
# All exceptions route through `handle_error`.
|
|
25
|
+
def run_command(command_class, options, *args, method: :run)
|
|
26
|
+
command_class.new(options).public_send(method, *args)
|
|
27
|
+
rescue StandardError => e
|
|
28
|
+
handle_error(e)
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
# Re-raise when `--trace` is set so the user sees the full
|
|
32
|
+
# backtrace. Otherwise warn-and-exit-1 for a clean CLI UX.
|
|
33
|
+
def handle_error(error)
|
|
34
|
+
raise error if options[:trace]
|
|
35
|
+
|
|
36
|
+
warn "Error: #{error.message}"
|
|
37
|
+
exit 1
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
end
|
|
@@ -48,7 +48,7 @@ unmatched_ucum)
|
|
|
48
48
|
db_id = get_db_entity_id(db_entity)
|
|
49
49
|
db_name = get_db_entity_name(db_entity)
|
|
50
50
|
ucum_name = get_ucum_entity_name(ucum_entity)
|
|
51
|
-
ucum_code = ucum_entity.
|
|
51
|
+
ucum_code = ucum_entity.code_sensitive || "unknown"
|
|
52
52
|
|
|
53
53
|
case ucum_entity
|
|
54
54
|
when Unitsdb::UcumPrefix
|
|
@@ -88,7 +88,7 @@ unmatched_ucum)
|
|
|
88
88
|
db_id = get_db_entity_id(db_entity)
|
|
89
89
|
db_name = get_db_entity_name(db_entity)
|
|
90
90
|
ucum_name = get_ucum_entity_name(ucum_entity)
|
|
91
|
-
ucum_code = ucum_entity.
|
|
91
|
+
ucum_code = ucum_entity.code_sensitive || "unknown"
|
|
92
92
|
|
|
93
93
|
case ucum_entity
|
|
94
94
|
when Unitsdb::UcumPrefix
|
|
@@ -103,26 +103,17 @@ unmatched_ucum)
|
|
|
103
103
|
|
|
104
104
|
# Helper to get db entity id
|
|
105
105
|
def get_db_entity_id(entity)
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
entity.id
|
|
110
|
-
else
|
|
111
|
-
"unknown-id"
|
|
112
|
-
end
|
|
106
|
+
return entity.identifiers.first.id unless entity.identifiers.empty?
|
|
107
|
+
|
|
108
|
+
entity.short || "unknown-id"
|
|
113
109
|
end
|
|
114
110
|
|
|
115
111
|
# Helper to get db entity name
|
|
116
112
|
def get_db_entity_name(entity)
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
elsif entity.respond_to?(:name)
|
|
122
|
-
entity.name
|
|
123
|
-
else
|
|
124
|
-
"unknown-name"
|
|
125
|
-
end
|
|
113
|
+
return entity.names.first.value unless entity.names.empty?
|
|
114
|
+
return entity.short if entity.short
|
|
115
|
+
|
|
116
|
+
"unknown-name"
|
|
126
117
|
end
|
|
127
118
|
|
|
128
119
|
# Helper to get ucum entity name
|
|
@@ -70,14 +70,14 @@ module Unitsdb
|
|
|
70
70
|
|
|
71
71
|
# Check if a UnitsDB entity already has a UCUM reference
|
|
72
72
|
def has_ucum_reference?(entity)
|
|
73
|
-
return false unless entity.
|
|
73
|
+
return false unless entity.references
|
|
74
74
|
|
|
75
75
|
entity.references.any? { |ref| ref.authority == "ucum" }
|
|
76
76
|
end
|
|
77
77
|
|
|
78
78
|
# Find the referenced UCUM entity based on the reference URI
|
|
79
79
|
def find_referenced_ucum_entity(db_entity, ucum_entities)
|
|
80
|
-
return nil unless db_entity.
|
|
80
|
+
return nil unless db_entity.references
|
|
81
81
|
|
|
82
82
|
ucum_ref = db_entity.references.find { |ref| ref.authority == "ucum" }
|
|
83
83
|
return nil unless ucum_ref
|
|
@@ -87,8 +87,8 @@ module Unitsdb
|
|
|
87
87
|
end
|
|
88
88
|
|
|
89
89
|
# Get the ID of a UnitsDB entity
|
|
90
|
-
def get_entity_id(
|
|
91
|
-
|
|
90
|
+
def get_entity_id(_entity)
|
|
91
|
+
nil
|
|
92
92
|
end
|
|
93
93
|
|
|
94
94
|
# Find a matching UnitsDB entity for a UCUM entity
|
|
@@ -120,11 +120,9 @@ include_potential = false)
|
|
|
120
120
|
|
|
121
121
|
# Get entity ID (either from identifiers array or directly)
|
|
122
122
|
def get_entity_id(entity)
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
entity.id
|
|
127
|
-
end
|
|
123
|
+
return entity.identifiers.first.id unless entity.identifiers.empty?
|
|
124
|
+
|
|
125
|
+
entity.short
|
|
128
126
|
end
|
|
129
127
|
end
|
|
130
128
|
end
|
|
@@ -13,11 +13,7 @@ module Unitsdb
|
|
|
13
13
|
autoload :XmlParser, "unitsdb/commands/ucum/xml_parser"
|
|
14
14
|
end
|
|
15
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
|
-
|
|
16
|
+
class UcumCommand < Unitsdb::Commands::Thor
|
|
21
17
|
desc "check", "Check UCUM references in UnitsDB"
|
|
22
18
|
option :entity_type, type: :string, aliases: "-e",
|
|
23
19
|
desc: "Entity type to check (units, prefixes). If not specified, all types are checked"
|
|
@@ -49,35 +45,6 @@ module Unitsdb
|
|
|
49
45
|
def update
|
|
50
46
|
run_command(Ucum::Update, options)
|
|
51
47
|
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
48
|
end
|
|
82
49
|
end
|
|
83
50
|
end
|
|
@@ -3,16 +3,15 @@
|
|
|
3
3
|
module Unitsdb
|
|
4
4
|
module Commands
|
|
5
5
|
module Validate
|
|
6
|
+
# `unitsdb validate qudt_references`. Checks that no QUDT
|
|
7
|
+
# reference URI is used by more than one entity of a given type.
|
|
6
8
|
class QudtReferences < Unitsdb::Commands::Base
|
|
9
|
+
AUTHORITY = "qudt"
|
|
10
|
+
|
|
7
11
|
def run
|
|
8
|
-
# Load the database
|
|
9
12
|
db = load_database(@options[:database])
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
duplicates = check_qudt_references(db)
|
|
13
|
-
|
|
14
|
-
# Display results
|
|
15
|
-
display_duplicate_results(duplicates)
|
|
13
|
+
duplicates = scan(db)
|
|
14
|
+
display(duplicates)
|
|
16
15
|
rescue Unitsdb::Errors::DatabaseError => e
|
|
17
16
|
raise Unitsdb::Errors::ValidationError,
|
|
18
17
|
"Failed to validate QUDT references: #{e.message}"
|
|
@@ -20,90 +19,50 @@ module Unitsdb
|
|
|
20
19
|
|
|
21
20
|
private
|
|
22
21
|
|
|
23
|
-
def
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
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
|
|
22
|
+
def scan(db)
|
|
23
|
+
{}.tap do |duplicates|
|
|
24
|
+
Unitsdb::Database::COLLECTIONS.each do |name|
|
|
25
|
+
collection = db.collection(name) || []
|
|
26
|
+
dup = scan_collection(collection)
|
|
27
|
+
duplicates[name.to_s] = dup unless dup.empty?
|
|
28
|
+
end
|
|
29
|
+
end
|
|
40
30
|
end
|
|
41
31
|
|
|
42
|
-
def
|
|
43
|
-
|
|
44
|
-
qudt_refs = {}
|
|
45
|
-
|
|
32
|
+
def scan_collection(entities)
|
|
33
|
+
by_uri = {}
|
|
46
34
|
entities.each_with_index do |entity, index|
|
|
47
|
-
|
|
48
|
-
|
|
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
|
|
35
|
+
refs = entity.references || []
|
|
36
|
+
refs.each do |ref|
|
|
37
|
+
next unless ref.authority == AUTHORITY
|
|
61
38
|
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
entity_id: entity_id,
|
|
66
|
-
entity_name: entity.respond_to?(:names) ? entity.names.first : entity.short,
|
|
39
|
+
(by_uri[ref.uri] ||= []) << {
|
|
40
|
+
entity_id: entity.identifiers.first&.id || entity.short,
|
|
41
|
+
entity_name: entity.names.first&.value || entity.short,
|
|
67
42
|
index: index,
|
|
68
43
|
}
|
|
69
44
|
end
|
|
70
45
|
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
|
|
46
|
+
by_uri.reject { |_, refs| refs.size == 1 }
|
|
80
47
|
end
|
|
81
48
|
|
|
82
|
-
def
|
|
49
|
+
def display(duplicates)
|
|
83
50
|
if duplicates.empty?
|
|
84
|
-
puts "No duplicate QUDT references found!
|
|
51
|
+
puts "No duplicate QUDT references found! " \
|
|
52
|
+
"Each QUDT reference URI is used by at most one entity of each type."
|
|
85
53
|
return
|
|
86
54
|
end
|
|
87
55
|
|
|
88
56
|
puts "Found duplicate QUDT references:"
|
|
89
|
-
|
|
90
57
|
duplicates.each do |entity_type, uri_duplicates|
|
|
91
58
|
puts "\n #{entity_type.capitalize}:"
|
|
92
|
-
|
|
93
|
-
uri_duplicates.each do |uri, entities|
|
|
59
|
+
uri_duplicates.each do |uri, refs|
|
|
94
60
|
puts " QUDT URI: #{uri}"
|
|
95
|
-
puts " Used by #{
|
|
96
|
-
|
|
97
|
-
entities.each do |entity|
|
|
98
|
-
puts " - #{entity[:entity_id]} (#{entity[:entity_name]}) at index #{entity[:index]}"
|
|
99
|
-
end
|
|
61
|
+
puts " Used by #{refs.size} entities:"
|
|
62
|
+
refs.each { |r| puts " - #{r[:entity_id]} (#{r[:entity_name]}) at index #{r[:index]}" }
|
|
100
63
|
puts ""
|
|
101
64
|
end
|
|
102
65
|
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
66
|
end
|
|
108
67
|
end
|
|
109
68
|
end
|