unitsdb 0.1.1 → 2.1.0
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/dependent-repos.json +5 -0
- data/.github/workflows/depenedent-gems.yml +16 -0
- data/.gitmodules +3 -0
- data/.rspec +2 -1
- data/.rubocop_todo.yml +88 -12
- data/Gemfile +2 -2
- data/README.adoc +697 -1
- data/exe/unitsdb +7 -0
- data/lib/unitsdb/cli.rb +81 -0
- data/lib/unitsdb/commands/_modify.rb +22 -0
- data/lib/unitsdb/commands/base.rb +52 -0
- data/lib/unitsdb/commands/check_si.rb +124 -0
- data/lib/unitsdb/commands/get.rb +133 -0
- data/lib/unitsdb/commands/normalize.rb +81 -0
- data/lib/unitsdb/commands/release.rb +112 -0
- data/lib/unitsdb/commands/search.rb +219 -0
- data/lib/unitsdb/commands/si_formatter.rb +485 -0
- data/lib/unitsdb/commands/si_matcher.rb +470 -0
- data/lib/unitsdb/commands/si_ttl_parser.rb +100 -0
- data/lib/unitsdb/commands/si_updater.rb +212 -0
- data/lib/unitsdb/commands/validate/identifiers.rb +40 -0
- data/lib/unitsdb/commands/validate/references.rb +316 -0
- data/lib/unitsdb/commands/validate/si_references.rb +115 -0
- data/lib/unitsdb/commands/validate.rb +40 -0
- data/lib/unitsdb/config.rb +19 -0
- data/lib/unitsdb/database.rb +661 -0
- data/lib/unitsdb/dimension.rb +19 -25
- data/lib/unitsdb/dimension_details.rb +20 -0
- data/lib/unitsdb/dimension_reference.rb +8 -0
- data/lib/unitsdb/dimensions.rb +3 -6
- data/lib/unitsdb/errors.rb +13 -0
- data/lib/unitsdb/external_reference.rb +14 -0
- data/lib/unitsdb/identifier.rb +8 -0
- data/lib/unitsdb/localized_string.rb +17 -0
- data/lib/unitsdb/prefix.rb +11 -12
- data/lib/unitsdb/prefix_reference.rb +10 -0
- data/lib/unitsdb/prefixes.rb +3 -6
- data/lib/unitsdb/quantities.rb +3 -27
- data/lib/unitsdb/quantity.rb +12 -24
- data/lib/unitsdb/quantity_reference.rb +4 -7
- data/lib/unitsdb/root_unit_reference.rb +14 -0
- data/lib/unitsdb/scale.rb +17 -0
- data/lib/unitsdb/scale_properties.rb +12 -0
- data/lib/unitsdb/scale_reference.rb +10 -0
- data/lib/unitsdb/scales.rb +11 -0
- data/lib/unitsdb/si_derived_base.rb +13 -14
- data/lib/unitsdb/symbol_presentations.rb +14 -0
- data/lib/unitsdb/unit.rb +20 -26
- data/lib/unitsdb/unit_reference.rb +5 -8
- data/lib/unitsdb/unit_system.rb +8 -10
- data/lib/unitsdb/unit_system_reference.rb +10 -0
- data/lib/unitsdb/unit_systems.rb +3 -16
- data/lib/unitsdb/units.rb +3 -6
- data/lib/unitsdb/utils.rb +84 -0
- data/lib/unitsdb/version.rb +1 -1
- data/lib/unitsdb.rb +13 -10
- data/unitsdb.gemspec +6 -1
- metadata +112 -12
- data/lib/unitsdb/dimension_quantity.rb +0 -28
- data/lib/unitsdb/dimension_symbol.rb +0 -22
- data/lib/unitsdb/prefix_symbol.rb +0 -12
- data/lib/unitsdb/root_unit.rb +0 -17
- data/lib/unitsdb/root_units.rb +0 -20
- data/lib/unitsdb/symbol.rb +0 -17
- data/lib/unitsdb/unit_symbol.rb +0 -15
- data/lib/unitsdb/unitsdb.rb +0 -6
@@ -0,0 +1,219 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative "base"
|
4
|
+
require "json"
|
5
|
+
require_relative "../errors"
|
6
|
+
|
7
|
+
module Unitsdb
|
8
|
+
module Commands
|
9
|
+
class Search < Base
|
10
|
+
def run(query)
|
11
|
+
# Database path is guaranteed by Thor's global option
|
12
|
+
|
13
|
+
type = @options[:type]
|
14
|
+
id = @options[:id]
|
15
|
+
id_type = @options[:id_type]
|
16
|
+
format = @options[:format] || "text"
|
17
|
+
|
18
|
+
begin
|
19
|
+
database = load_database(@options[:database])
|
20
|
+
|
21
|
+
# Search by ID (early return)
|
22
|
+
if id
|
23
|
+
entity = database.get_by_id(id: id, type: id_type)
|
24
|
+
|
25
|
+
unless entity
|
26
|
+
puts "No entity found with ID: '#{id}'"
|
27
|
+
return
|
28
|
+
end
|
29
|
+
|
30
|
+
# Use the same output logic as the Get command
|
31
|
+
if %w[json yaml].include?(format.downcase)
|
32
|
+
begin
|
33
|
+
puts entity.send("to_#{format.downcase}")
|
34
|
+
return
|
35
|
+
rescue NoMethodError
|
36
|
+
puts "Error: Unable to convert entity to #{format} format"
|
37
|
+
exit(1)
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
print_entity_details(entity)
|
42
|
+
return
|
43
|
+
end
|
44
|
+
|
45
|
+
# Regular text search
|
46
|
+
results = database.search(text: query, type: type)
|
47
|
+
|
48
|
+
# Early return for empty results
|
49
|
+
if results.empty?
|
50
|
+
puts "No results found for '#{query}'"
|
51
|
+
return
|
52
|
+
end
|
53
|
+
|
54
|
+
# Format-specific output
|
55
|
+
if %w[json yaml].include?(format.downcase)
|
56
|
+
temp_db = create_temporary_database(results)
|
57
|
+
puts temp_db.send("to_#{format.downcase}")
|
58
|
+
return
|
59
|
+
end
|
60
|
+
|
61
|
+
# Default text output
|
62
|
+
puts "Found #{results.size} result(s) for '#{query}':"
|
63
|
+
results.each do |entity|
|
64
|
+
print_entity_with_ids(entity)
|
65
|
+
end
|
66
|
+
rescue Unitsdb::Errors::DatabaseError => e
|
67
|
+
puts "Error: #{e.message}"
|
68
|
+
exit(1)
|
69
|
+
rescue StandardError => e
|
70
|
+
puts "Error searching database: #{e.message}"
|
71
|
+
exit(1)
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
private
|
76
|
+
|
77
|
+
def print_entity_with_ids(entity)
|
78
|
+
# Determine entity type
|
79
|
+
entity_type = get_entity_type(entity)
|
80
|
+
|
81
|
+
# Get name
|
82
|
+
name = get_entity_name(entity)
|
83
|
+
|
84
|
+
# Get all identifiers
|
85
|
+
identifiers = entity.identifiers || []
|
86
|
+
|
87
|
+
# Print entity information
|
88
|
+
puts " - #{entity_type}: #{name}"
|
89
|
+
|
90
|
+
# Print each identifier on its own line for better readability
|
91
|
+
if identifiers.empty?
|
92
|
+
puts " ID: None"
|
93
|
+
else
|
94
|
+
puts " IDs:"
|
95
|
+
identifiers.each do |id|
|
96
|
+
puts " - #{id.id} (Type: #{id.type || "N/A"})"
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
# If entity has a short description, print it
|
101
|
+
puts " Description: #{entity.short}" if entity.respond_to?(:short) && entity.short && entity.short != name
|
102
|
+
|
103
|
+
# Add a blank line for readability
|
104
|
+
puts ""
|
105
|
+
end
|
106
|
+
|
107
|
+
def print_entity_details(entity)
|
108
|
+
# Determine entity type
|
109
|
+
entity_type = get_entity_type(entity)
|
110
|
+
|
111
|
+
# Get name
|
112
|
+
name = get_entity_name(entity)
|
113
|
+
|
114
|
+
puts "Entity details:"
|
115
|
+
puts " - Type: #{entity_type}"
|
116
|
+
puts " - Name: #{name}"
|
117
|
+
|
118
|
+
# Print description if available
|
119
|
+
puts " - Description: #{entity.short}" if entity.respond_to?(:short) && entity.short && entity.short != name
|
120
|
+
|
121
|
+
# Print all identifiers
|
122
|
+
if entity.identifiers&.any?
|
123
|
+
puts " - Identifiers:"
|
124
|
+
entity.identifiers.each do |id|
|
125
|
+
puts " - #{id.id} (Type: #{id.type || "N/A"})"
|
126
|
+
end
|
127
|
+
else
|
128
|
+
puts " - Identifiers: None"
|
129
|
+
end
|
130
|
+
|
131
|
+
# Print additional properties based on entity type
|
132
|
+
case entity
|
133
|
+
when Unitsdb::Unit
|
134
|
+
puts " - Symbols:" if entity.respond_to?(:symbols) && entity.symbols&.any?
|
135
|
+
entity.symbols.each { |s| puts " - #{s}" } if entity.respond_to?(:symbols) && entity.symbols&.any?
|
136
|
+
|
137
|
+
puts " - Definition: #{entity.definition}" if entity.respond_to?(:definition) && entity.definition
|
138
|
+
|
139
|
+
if entity.respond_to?(:dimensions) && entity.dimensions&.any?
|
140
|
+
puts " - Dimensions:"
|
141
|
+
entity.dimensions.each { |d| puts " - #{d}" }
|
142
|
+
end
|
143
|
+
when Unitsdb::Quantity
|
144
|
+
puts " - Dimensions: #{entity.dimension}" if entity.respond_to?(:dimension) && entity.dimension
|
145
|
+
when Unitsdb::Prefix
|
146
|
+
puts " - Value: #{entity.value}" if entity.respond_to?(:value) && entity.value
|
147
|
+
puts " - Symbol: #{entity.symbol}" if entity.respond_to?(:symbol) && entity.symbol
|
148
|
+
when Unitsdb::Dimension
|
149
|
+
# Any dimension-specific properties
|
150
|
+
when Unitsdb::UnitSystem
|
151
|
+
puts " - Organization: #{entity.organization}" if entity.respond_to?(:organization) && entity.organization
|
152
|
+
end
|
153
|
+
|
154
|
+
# Print references if available
|
155
|
+
return unless entity.respond_to?(:references) && entity.references&.any?
|
156
|
+
|
157
|
+
puts " - References:"
|
158
|
+
entity.references.each do |ref|
|
159
|
+
puts " - #{ref.type}: #{ref.id}"
|
160
|
+
end
|
161
|
+
end
|
162
|
+
|
163
|
+
def get_entity_type(entity)
|
164
|
+
case entity
|
165
|
+
when Unitsdb::Unit
|
166
|
+
"Unit"
|
167
|
+
when Unitsdb::Prefix
|
168
|
+
"Prefix"
|
169
|
+
when Unitsdb::Quantity
|
170
|
+
"Quantity"
|
171
|
+
when Unitsdb::Dimension
|
172
|
+
"Dimension"
|
173
|
+
when Unitsdb::UnitSystem
|
174
|
+
"UnitSystem"
|
175
|
+
else
|
176
|
+
"Unknown"
|
177
|
+
end
|
178
|
+
end
|
179
|
+
|
180
|
+
def get_entity_name(entity)
|
181
|
+
# Using early returns is still preferable for simple conditions
|
182
|
+
return entity.names.first if entity.respond_to?(:names) && entity.names&.any?
|
183
|
+
return entity.name if entity.respond_to?(:name) && entity.name
|
184
|
+
return entity.short if entity.respond_to?(:short) && entity.short
|
185
|
+
|
186
|
+
"N/A" # Default if no name found
|
187
|
+
end
|
188
|
+
|
189
|
+
def create_temporary_database(results)
|
190
|
+
temp_db = Unitsdb::Database.new
|
191
|
+
|
192
|
+
# Initialize collections
|
193
|
+
temp_db.units = []
|
194
|
+
temp_db.prefixes = []
|
195
|
+
temp_db.quantities = []
|
196
|
+
temp_db.dimensions = []
|
197
|
+
temp_db.unit_systems = []
|
198
|
+
|
199
|
+
# Add results to appropriate collection based on type using case statement
|
200
|
+
results.each do |entity|
|
201
|
+
case entity
|
202
|
+
when Unitsdb::Unit
|
203
|
+
temp_db.units << entity
|
204
|
+
when Unitsdb::Prefix
|
205
|
+
temp_db.prefixes << entity
|
206
|
+
when Unitsdb::Quantity
|
207
|
+
temp_db.quantities << entity
|
208
|
+
when Unitsdb::Dimension
|
209
|
+
temp_db.dimensions << entity
|
210
|
+
when Unitsdb::UnitSystem
|
211
|
+
temp_db.unit_systems << entity
|
212
|
+
end
|
213
|
+
end
|
214
|
+
|
215
|
+
temp_db
|
216
|
+
end
|
217
|
+
end
|
218
|
+
end
|
219
|
+
end
|
@@ -0,0 +1,485 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "terminal-table"
|
4
|
+
require_relative "si_ttl_parser"
|
5
|
+
|
6
|
+
module Unitsdb
|
7
|
+
module Commands
|
8
|
+
# Formatter for SI check results
|
9
|
+
module SiFormatter
|
10
|
+
module_function
|
11
|
+
|
12
|
+
# Display TTL → DB results
|
13
|
+
def display_si_results(entity_type, matches, missing_matches, unmatched_ttl)
|
14
|
+
puts "\n=== #{entity_type.capitalize} with matching SI references ==="
|
15
|
+
if matches.empty?
|
16
|
+
puts "None"
|
17
|
+
else
|
18
|
+
rows = []
|
19
|
+
matches.each do |match|
|
20
|
+
si_suffix = SiTtlParser.extract_identifying_suffix(match[:si_uri])
|
21
|
+
rows << [
|
22
|
+
"UnitsDB: #{match[:entity_id]}",
|
23
|
+
"(#{match[:entity_name] || "unnamed"})"
|
24
|
+
]
|
25
|
+
rows << [
|
26
|
+
"SI TTL: #{si_suffix}",
|
27
|
+
"(#{match[:si_label] || match[:si_name] || "unnamed"})"
|
28
|
+
]
|
29
|
+
rows << :separator unless match == matches.last
|
30
|
+
end
|
31
|
+
|
32
|
+
table = Terminal::Table.new(
|
33
|
+
title: "Valid SI Reference Mappings",
|
34
|
+
rows: rows
|
35
|
+
)
|
36
|
+
puts table
|
37
|
+
end
|
38
|
+
|
39
|
+
puts "\n=== #{entity_type.capitalize} without SI references ==="
|
40
|
+
if missing_matches.empty?
|
41
|
+
puts "None"
|
42
|
+
else
|
43
|
+
# Split matches into exact and potential
|
44
|
+
exact_matches = []
|
45
|
+
potential_matches = []
|
46
|
+
|
47
|
+
missing_matches.each do |match|
|
48
|
+
# Get match details
|
49
|
+
match_details = match[:match_details]
|
50
|
+
match_desc = match_details&.dig(:match_desc) || ""
|
51
|
+
|
52
|
+
# Symbol matches and partial matches should always be potential matches
|
53
|
+
if %w[symbol_match partial_match].include?(match_desc)
|
54
|
+
potential_matches << match
|
55
|
+
elsif match_details&.dig(:exact) == false
|
56
|
+
potential_matches << match
|
57
|
+
else
|
58
|
+
exact_matches << match
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
# Display exact matches
|
63
|
+
puts "\n=== Exact Matches (#{exact_matches.size}) ==="
|
64
|
+
if exact_matches.empty?
|
65
|
+
puts "None"
|
66
|
+
else
|
67
|
+
rows = []
|
68
|
+
exact_matches.each do |match|
|
69
|
+
# First row: UnitsDB entity
|
70
|
+
rows << [
|
71
|
+
"UnitsDB: #{match[:entity_id]}",
|
72
|
+
"(#{match[:entity_name] || "unnamed"})"
|
73
|
+
]
|
74
|
+
|
75
|
+
# Handle multiple SI matches in a single cell if present
|
76
|
+
if match[:multiple_si]
|
77
|
+
# Ensure no duplicate URIs
|
78
|
+
si_text_parts = []
|
79
|
+
si_label_parts = []
|
80
|
+
seen_uris = {}
|
81
|
+
|
82
|
+
match[:multiple_si].each do |si_data|
|
83
|
+
uri = si_data[:uri]
|
84
|
+
next if seen_uris[uri] # Skip if we've already seen this URI
|
85
|
+
|
86
|
+
seen_uris[uri] = true
|
87
|
+
|
88
|
+
suffix = SiTtlParser.extract_identifying_suffix(uri)
|
89
|
+
si_text_parts << suffix
|
90
|
+
si_label_parts << (si_data[:label] || si_data[:name])
|
91
|
+
end
|
92
|
+
|
93
|
+
rows << [
|
94
|
+
"SI TTL: #{si_text_parts.join(", ")}",
|
95
|
+
"(#{si_label_parts.join(", ")})"
|
96
|
+
]
|
97
|
+
else
|
98
|
+
# Second row: SI TTL suffix and label/name
|
99
|
+
si_suffix = SiTtlParser.extract_identifying_suffix(match[:si_uri])
|
100
|
+
rows << [
|
101
|
+
"SI TTL: #{si_suffix}",
|
102
|
+
"(#{match[:si_label] || match[:si_name] || "unnamed"})"
|
103
|
+
]
|
104
|
+
end
|
105
|
+
|
106
|
+
# Status line with match type
|
107
|
+
match_details = match[:match_details]
|
108
|
+
match_desc = match_details&.dig(:match_desc) || ""
|
109
|
+
match_info = format_match_info(match_desc)
|
110
|
+
status_text = match_info.empty? ? "Missing reference" : "Missing reference (#{match_info})"
|
111
|
+
|
112
|
+
rows << [
|
113
|
+
"Status: #{status_text}",
|
114
|
+
"✗"
|
115
|
+
]
|
116
|
+
rows << :separator unless match == exact_matches.last
|
117
|
+
end
|
118
|
+
|
119
|
+
table = Terminal::Table.new(
|
120
|
+
title: "Exact Match Missing SI References",
|
121
|
+
rows: rows
|
122
|
+
)
|
123
|
+
puts table
|
124
|
+
end
|
125
|
+
|
126
|
+
# Display potential matches
|
127
|
+
puts "\n=== Potential Matches (#{potential_matches.size}) ==="
|
128
|
+
if potential_matches.empty?
|
129
|
+
puts "None"
|
130
|
+
else
|
131
|
+
rows = []
|
132
|
+
potential_matches.each do |match|
|
133
|
+
# First row: UnitsDB entity
|
134
|
+
rows << [
|
135
|
+
"UnitsDB: #{match[:entity_id]}",
|
136
|
+
"(#{match[:entity_name] || "unnamed"})"
|
137
|
+
]
|
138
|
+
|
139
|
+
# Handle multiple SI matches in a single cell if present
|
140
|
+
if match[:multiple_si]
|
141
|
+
# Ensure no duplicate URIs
|
142
|
+
si_text_parts = []
|
143
|
+
seen_uris = {}
|
144
|
+
|
145
|
+
match[:multiple_si].each do |si_data|
|
146
|
+
uri = si_data[:uri]
|
147
|
+
next if seen_uris[uri] # Skip if we've already seen this URI
|
148
|
+
|
149
|
+
seen_uris[uri] = true
|
150
|
+
|
151
|
+
suffix = SiTtlParser.extract_identifying_suffix(uri)
|
152
|
+
si_text_parts << "#{suffix} (#{si_data[:label] || si_data[:name]})"
|
153
|
+
end
|
154
|
+
|
155
|
+
rows << [
|
156
|
+
"SI TTL: #{si_text_parts.join(", ")}",
|
157
|
+
""
|
158
|
+
]
|
159
|
+
else
|
160
|
+
# Single TTL entity
|
161
|
+
si_suffix = SiTtlParser.extract_identifying_suffix(match[:si_uri])
|
162
|
+
rows << [
|
163
|
+
"SI TTL: #{si_suffix}",
|
164
|
+
"(#{match[:si_label] || match[:si_name] || "unnamed"})"
|
165
|
+
]
|
166
|
+
end
|
167
|
+
|
168
|
+
# Status line
|
169
|
+
match_details = match[:match_details]
|
170
|
+
match_desc = match_details&.dig(:match_desc) || ""
|
171
|
+
match_info = format_match_info(match_desc)
|
172
|
+
status_text = match_info.empty? ? "Missing reference" : "Missing reference"
|
173
|
+
|
174
|
+
rows << [
|
175
|
+
"Status: #{status_text}",
|
176
|
+
"✗"
|
177
|
+
]
|
178
|
+
rows << :separator unless match == potential_matches.last
|
179
|
+
end
|
180
|
+
|
181
|
+
table = Terminal::Table.new(
|
182
|
+
title: "Potential Match Missing SI References",
|
183
|
+
rows: rows
|
184
|
+
)
|
185
|
+
puts table
|
186
|
+
end
|
187
|
+
end
|
188
|
+
|
189
|
+
puts "\n=== SI #{entity_type.capitalize} not mapped to our database ==="
|
190
|
+
if unmatched_ttl.empty?
|
191
|
+
puts "None (All TTL entities are referenced - Good job!)"
|
192
|
+
else
|
193
|
+
# Group unmatched ttl entities by their URI to avoid duplicates
|
194
|
+
grouped_unmatched = {}
|
195
|
+
|
196
|
+
unmatched_ttl.each do |entity|
|
197
|
+
uri = entity[:uri]
|
198
|
+
grouped_unmatched[uri] = entity unless grouped_unmatched.key?(uri)
|
199
|
+
end
|
200
|
+
|
201
|
+
rows = []
|
202
|
+
unique_entities = grouped_unmatched.values
|
203
|
+
|
204
|
+
unique_entities.each do |entity|
|
205
|
+
# Create the SI TTL row
|
206
|
+
si_suffix = SiTtlParser.extract_identifying_suffix(entity[:uri])
|
207
|
+
ttl_row = ["SI TTL: #{si_suffix}", "(#{entity[:label] || entity[:name] || "unnamed"})"]
|
208
|
+
|
209
|
+
rows << ttl_row
|
210
|
+
rows << [
|
211
|
+
"Status: No matching UnitsDB entity",
|
212
|
+
"?"
|
213
|
+
]
|
214
|
+
rows << :separator unless entity == unique_entities.last
|
215
|
+
end
|
216
|
+
|
217
|
+
table = Terminal::Table.new(
|
218
|
+
title: "Unmapped SI Entities",
|
219
|
+
rows: rows
|
220
|
+
)
|
221
|
+
puts table
|
222
|
+
end
|
223
|
+
end
|
224
|
+
|
225
|
+
# Display DB → TTL results
|
226
|
+
def display_db_results(entity_type, matches, missing_refs, unmatched_db)
|
227
|
+
puts "\n=== Summary of database entities referencing SI ==="
|
228
|
+
puts "#{entity_type.capitalize} with SI references: #{matches.size}"
|
229
|
+
puts "#{entity_type.capitalize} missing SI references: #{missing_refs.size}"
|
230
|
+
puts "Database #{entity_type} not matching any SI entity: #{unmatched_db.size}"
|
231
|
+
|
232
|
+
# Show entities with valid references
|
233
|
+
unless matches.empty?
|
234
|
+
puts "\n=== #{entity_type.capitalize} with SI references ==="
|
235
|
+
rows = []
|
236
|
+
matches.each do |match|
|
237
|
+
db_entity = match[:db_entity]
|
238
|
+
entity_id = match[:entity_id] || db_entity.short
|
239
|
+
entity_name = db_entity.respond_to?(:names) ? db_entity.names&.first : "unnamed"
|
240
|
+
si_suffix = SiTtlParser.extract_identifying_suffix(match[:ttl_uri])
|
241
|
+
|
242
|
+
ttl_label = match[:ttl_entity] ? (match[:ttl_entity][:label] || match[:ttl_entity][:name]) : "Unknown"
|
243
|
+
|
244
|
+
rows << [
|
245
|
+
"UnitsDB: #{entity_id}",
|
246
|
+
"(#{entity_name})"
|
247
|
+
]
|
248
|
+
rows << [
|
249
|
+
"SI TTL: #{si_suffix}",
|
250
|
+
"(#{ttl_label})"
|
251
|
+
]
|
252
|
+
rows << :separator unless match == matches.last
|
253
|
+
end
|
254
|
+
|
255
|
+
table = Terminal::Table.new(
|
256
|
+
title: "Valid SI References",
|
257
|
+
rows: rows
|
258
|
+
)
|
259
|
+
puts table
|
260
|
+
end
|
261
|
+
|
262
|
+
puts "\n=== #{entity_type.capitalize} that should reference SI ==="
|
263
|
+
if missing_refs.empty?
|
264
|
+
puts "None"
|
265
|
+
else
|
266
|
+
# Split missing_refs into exact and potential matches
|
267
|
+
exact_matches = []
|
268
|
+
potential_matches = []
|
269
|
+
|
270
|
+
missing_refs.each do |match|
|
271
|
+
# Determine match type
|
272
|
+
ttl_entities = match[:ttl_entities]
|
273
|
+
uri = ttl_entities.first[:uri]
|
274
|
+
match_type = "Exact match" # Default
|
275
|
+
match_type = match[:match_types][uri] if match[:match_types] && match[:match_types][uri]
|
276
|
+
|
277
|
+
# Get match description if available
|
278
|
+
entity_id = match[:db_entity].short
|
279
|
+
match_pair_key = "#{entity_id}:#{ttl_entities.first[:uri]}"
|
280
|
+
match_details = Unitsdb::Commands::SiMatcher.instance_variable_get(:@match_details)&.dig(match_pair_key)
|
281
|
+
match_desc = match_details[:match_desc] if match_details && match_details[:match_desc]
|
282
|
+
|
283
|
+
# Symbol matches and partial matches should always be potential matches
|
284
|
+
if %w[symbol_match partial_match].include?(match_desc)
|
285
|
+
potential_matches << match
|
286
|
+
elsif match_type == "Exact match"
|
287
|
+
exact_matches << match
|
288
|
+
else
|
289
|
+
potential_matches << match
|
290
|
+
end
|
291
|
+
end
|
292
|
+
|
293
|
+
# Display exact matches
|
294
|
+
puts "\n=== Exact Matches (#{exact_matches.size}) ==="
|
295
|
+
if exact_matches.empty?
|
296
|
+
puts "None"
|
297
|
+
else
|
298
|
+
rows = []
|
299
|
+
exact_matches.each do |match|
|
300
|
+
db_entity = match[:db_entity]
|
301
|
+
entity_id = match[:entity_id] || db_entity.short
|
302
|
+
entity_name = db_entity.respond_to?(:names) ? db_entity.names&.first : "unnamed"
|
303
|
+
|
304
|
+
# Handle multiple TTL entities in a single row
|
305
|
+
ttl_entities = match[:ttl_entities]
|
306
|
+
if ttl_entities.size == 1
|
307
|
+
# Single TTL entity
|
308
|
+
ttl_entity = ttl_entities.first
|
309
|
+
si_suffix = SiTtlParser.extract_identifying_suffix(ttl_entity[:uri])
|
310
|
+
|
311
|
+
rows << [
|
312
|
+
"UnitsDB: #{entity_id}",
|
313
|
+
"(#{entity_name})"
|
314
|
+
]
|
315
|
+
rows << [
|
316
|
+
"SI TTL: #{si_suffix}",
|
317
|
+
"(#{ttl_entity[:label] || ttl_entity[:name] || "unnamed"})"
|
318
|
+
]
|
319
|
+
else
|
320
|
+
# Multiple TTL entities, combine them - ensure no duplicates
|
321
|
+
si_text_parts = []
|
322
|
+
seen_uris = {}
|
323
|
+
|
324
|
+
ttl_entities.each do |ttl_entity|
|
325
|
+
uri = ttl_entity[:uri]
|
326
|
+
next if seen_uris[uri] # Skip if we've already seen this URI
|
327
|
+
|
328
|
+
seen_uris[uri] = true
|
329
|
+
|
330
|
+
suffix = SiTtlParser.extract_identifying_suffix(uri)
|
331
|
+
si_text_parts << "#{suffix} (#{ttl_entity[:label] || ttl_entity[:name] || "unnamed"})"
|
332
|
+
end
|
333
|
+
|
334
|
+
si_text = si_text_parts.join(", ")
|
335
|
+
|
336
|
+
rows << [
|
337
|
+
"UnitsDB: #{entity_id}",
|
338
|
+
"(#{entity_name})"
|
339
|
+
]
|
340
|
+
rows << [
|
341
|
+
"SI TTL: #{si_text}",
|
342
|
+
""
|
343
|
+
]
|
344
|
+
end
|
345
|
+
|
346
|
+
# Get match details for this match
|
347
|
+
match_pair_key = "#{db_entity.short}:#{ttl_entities.first[:uri]}"
|
348
|
+
match_details = Unitsdb::Commands::SiMatcher.instance_variable_get(:@match_details)&.dig(match_pair_key)
|
349
|
+
|
350
|
+
# Format match info
|
351
|
+
match_info = ""
|
352
|
+
match_info = format_match_info(match_details[:match_desc]) if match_details
|
353
|
+
|
354
|
+
status_text = match_info.empty? ? "Missing reference" : "Missing reference (#{match_info})"
|
355
|
+
rows << [
|
356
|
+
"Status: #{status_text}",
|
357
|
+
"✗"
|
358
|
+
]
|
359
|
+
rows << :separator unless match == exact_matches.last
|
360
|
+
end
|
361
|
+
|
362
|
+
table = Terminal::Table.new(
|
363
|
+
title: "Exact Match Missing SI References",
|
364
|
+
rows: rows
|
365
|
+
)
|
366
|
+
puts table
|
367
|
+
end
|
368
|
+
|
369
|
+
# Display potential matches
|
370
|
+
puts "\n=== Potential Matches (#{potential_matches.size}) ==="
|
371
|
+
if potential_matches.empty?
|
372
|
+
puts "None"
|
373
|
+
else
|
374
|
+
rows = []
|
375
|
+
potential_matches.each do |match|
|
376
|
+
db_entity = match[:db_entity]
|
377
|
+
entity_id = match[:entity_id] || db_entity.short
|
378
|
+
entity_name = db_entity.respond_to?(:names) ? db_entity.names&.first : "unnamed"
|
379
|
+
|
380
|
+
# Handle multiple TTL entities in a single row
|
381
|
+
ttl_entities = match[:ttl_entities]
|
382
|
+
if ttl_entities.size == 1
|
383
|
+
# Single TTL entity
|
384
|
+
ttl_entity = ttl_entities.first
|
385
|
+
si_suffix = SiTtlParser.extract_identifying_suffix(ttl_entity[:uri])
|
386
|
+
|
387
|
+
rows << [
|
388
|
+
"UnitsDB: #{entity_id}",
|
389
|
+
"(#{entity_name})"
|
390
|
+
]
|
391
|
+
rows << [
|
392
|
+
"SI TTL: #{si_suffix}",
|
393
|
+
"(#{ttl_entity[:label] || ttl_entity[:name] || "unnamed"})"
|
394
|
+
]
|
395
|
+
else
|
396
|
+
# Multiple TTL entities, combine them - ensure no duplicates
|
397
|
+
si_text_parts = []
|
398
|
+
seen_uris = {}
|
399
|
+
|
400
|
+
ttl_entities.each do |ttl_entity|
|
401
|
+
uri = ttl_entity[:uri]
|
402
|
+
next if seen_uris[uri] # Skip if we've already seen this URI
|
403
|
+
|
404
|
+
seen_uris[uri] = true
|
405
|
+
|
406
|
+
suffix = SiTtlParser.extract_identifying_suffix(uri)
|
407
|
+
si_text_parts << "#{suffix} (#{ttl_entity[:label] || ttl_entity[:name] || "unnamed"})"
|
408
|
+
end
|
409
|
+
|
410
|
+
si_text = si_text_parts.join(", ")
|
411
|
+
|
412
|
+
rows << [
|
413
|
+
"UnitsDB: #{entity_id}",
|
414
|
+
"(#{entity_name})"
|
415
|
+
]
|
416
|
+
rows << [
|
417
|
+
"SI TTL: #{si_text}",
|
418
|
+
""
|
419
|
+
]
|
420
|
+
end
|
421
|
+
|
422
|
+
# Get match details
|
423
|
+
match_pair_key = "#{db_entity.short}:#{ttl_entities.first[:uri]}"
|
424
|
+
match_details = Unitsdb::Commands::SiMatcher.instance_variable_get(:@match_details)&.dig(match_pair_key)
|
425
|
+
|
426
|
+
# Format match info
|
427
|
+
match_info = ""
|
428
|
+
match_info = format_match_info(match_details[:match_desc]) if match_details
|
429
|
+
|
430
|
+
status_text = match_info.empty? ? "Missing reference" : "Missing reference (#{match_info})"
|
431
|
+
rows << [
|
432
|
+
"Status: #{status_text}",
|
433
|
+
"✗"
|
434
|
+
]
|
435
|
+
rows << :separator unless match == potential_matches.last
|
436
|
+
end
|
437
|
+
|
438
|
+
table = Terminal::Table.new(
|
439
|
+
title: "Potential Match Missing SI References",
|
440
|
+
rows: rows
|
441
|
+
)
|
442
|
+
puts table
|
443
|
+
end
|
444
|
+
end
|
445
|
+
end
|
446
|
+
|
447
|
+
# Print direction header
|
448
|
+
def print_direction_header(direction)
|
449
|
+
case direction
|
450
|
+
when "SI → UnitsDB"
|
451
|
+
puts "\n=== Checking SI → UnitsDB (TTL entities referenced by database) ==="
|
452
|
+
when "UnitsDB → SI"
|
453
|
+
puts "\n=== Checking UnitsDB → SI (database entities referencing TTL) ==="
|
454
|
+
end
|
455
|
+
|
456
|
+
puts "\n=== Instructions for #{direction} direction ==="
|
457
|
+
case direction
|
458
|
+
when "SI → UnitsDB"
|
459
|
+
puts "If you are the UnitsDB Register Manager, please ensure that all SI entities have proper references in the UnitsDB database."
|
460
|
+
puts "For each missing reference, add a reference with the appropriate URI and 'authority: \"si-digital-framework\"'."
|
461
|
+
when "UnitsDB → SI"
|
462
|
+
puts "If you are the UnitsDB Register Manager, please add SI references to UnitsDB entities that should have them."
|
463
|
+
puts "For each entity that should reference SI, add a reference with 'authority: \"si-digital-framework\"' and the SI TTL URI."
|
464
|
+
end
|
465
|
+
end
|
466
|
+
|
467
|
+
def set_match_details(details)
|
468
|
+
@match_details = details
|
469
|
+
end
|
470
|
+
|
471
|
+
# Format match info for display
|
472
|
+
def format_match_info(match_desc)
|
473
|
+
{
|
474
|
+
"short_to_name" => "short → name",
|
475
|
+
"short_to_label" => "short → label",
|
476
|
+
"name_to_name" => "name → name",
|
477
|
+
"name_to_label" => "name → label",
|
478
|
+
"name_to_alt_label" => "name → alt_label",
|
479
|
+
"symbol_match" => "symbol → symbol",
|
480
|
+
"partial_match" => "partial match"
|
481
|
+
}[match_desc] || ""
|
482
|
+
end
|
483
|
+
end
|
484
|
+
end
|
485
|
+
end
|