unitsdb 2.1.1 → 2.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.github/workflows/release.yml +8 -1
- data/.gitignore +2 -0
- data/.gitmodules +4 -3
- data/.rubocop.yml +13 -8
- data/.rubocop_todo.yml +217 -100
- data/CLAUDE.md +55 -0
- data/Gemfile +4 -1
- data/README.adoc +283 -16
- data/data/dimensions.yaml +1864 -0
- data/data/prefixes.yaml +874 -0
- data/data/quantities.yaml +3715 -0
- data/data/scales.yaml +97 -0
- data/data/schemas/dimensions-schema.yaml +153 -0
- data/data/schemas/prefixes-schema.yaml +155 -0
- data/data/schemas/quantities-schema.yaml +117 -0
- data/data/schemas/scales-schema.yaml +106 -0
- data/data/schemas/unit_systems-schema.yaml +116 -0
- data/data/schemas/units-schema.yaml +215 -0
- data/data/unit_systems.yaml +78 -0
- data/data/units.yaml +14052 -0
- data/exe/unitsdb +7 -1
- data/lib/unitsdb/cli.rb +42 -15
- data/lib/unitsdb/commands/_modify.rb +40 -4
- data/lib/unitsdb/commands/base.rb +6 -2
- data/lib/unitsdb/commands/check_si/si_formatter.rb +488 -0
- data/lib/unitsdb/commands/check_si/si_matcher.rb +487 -0
- data/lib/unitsdb/commands/check_si/si_ttl_parser.rb +103 -0
- data/lib/unitsdb/commands/check_si/si_updater.rb +254 -0
- data/lib/unitsdb/commands/check_si.rb +54 -35
- data/lib/unitsdb/commands/get.rb +11 -10
- data/lib/unitsdb/commands/normalize.rb +21 -7
- data/lib/unitsdb/commands/qudt/check.rb +150 -0
- data/lib/unitsdb/commands/qudt/formatter.rb +194 -0
- data/lib/unitsdb/commands/qudt/matcher.rb +746 -0
- data/lib/unitsdb/commands/qudt/ttl_parser.rb +403 -0
- data/lib/unitsdb/commands/qudt/update.rb +126 -0
- data/lib/unitsdb/commands/qudt/updater.rb +189 -0
- data/lib/unitsdb/commands/qudt.rb +82 -0
- data/lib/unitsdb/commands/release.rb +12 -9
- data/lib/unitsdb/commands/search.rb +12 -11
- data/lib/unitsdb/commands/ucum/check.rb +42 -29
- data/lib/unitsdb/commands/ucum/formatter.rb +2 -1
- data/lib/unitsdb/commands/ucum/matcher.rb +23 -9
- data/lib/unitsdb/commands/ucum/update.rb +14 -13
- data/lib/unitsdb/commands/ucum/updater.rb +40 -6
- data/lib/unitsdb/commands/ucum/xml_parser.rb +0 -2
- data/lib/unitsdb/commands/ucum.rb +44 -4
- data/lib/unitsdb/commands/validate/identifiers.rb +2 -4
- data/lib/unitsdb/commands/validate/qudt_references.rb +111 -0
- data/lib/unitsdb/commands/validate/references.rb +36 -19
- data/lib/unitsdb/commands/validate/si_references.rb +3 -5
- data/lib/unitsdb/commands/validate/ucum_references.rb +105 -0
- data/lib/unitsdb/commands/validate.rb +67 -11
- data/lib/unitsdb/commands.rb +20 -0
- data/lib/unitsdb/database.rb +90 -52
- data/lib/unitsdb/dimension.rb +1 -4
- data/lib/unitsdb/dimension_details.rb +0 -1
- data/lib/unitsdb/dimensions.rb +0 -2
- data/lib/unitsdb/errors.rb +7 -0
- data/lib/unitsdb/prefix.rb +0 -4
- data/lib/unitsdb/prefix_reference.rb +0 -2
- data/lib/unitsdb/prefixes.rb +0 -1
- data/lib/unitsdb/quantities.rb +0 -2
- data/lib/unitsdb/quantity.rb +0 -6
- data/lib/unitsdb/qudt.rb +100 -0
- data/lib/unitsdb/root_unit_reference.rb +0 -3
- data/lib/unitsdb/scale.rb +0 -4
- data/lib/unitsdb/scale_reference.rb +0 -2
- data/lib/unitsdb/scales.rb +0 -2
- data/lib/unitsdb/si_derived_base.rb +0 -2
- data/lib/unitsdb/ucum.rb +14 -10
- data/lib/unitsdb/unit.rb +0 -10
- data/lib/unitsdb/unit_reference.rb +0 -2
- data/lib/unitsdb/unit_system.rb +1 -3
- data/lib/unitsdb/unit_system_reference.rb +0 -2
- data/lib/unitsdb/unit_systems.rb +0 -2
- data/lib/unitsdb/units.rb +0 -2
- data/lib/unitsdb/utils.rb +32 -21
- data/lib/unitsdb/version.rb +5 -1
- data/lib/unitsdb.rb +62 -14
- data/unitsdb.gemspec +6 -3
- metadata +52 -13
- data/lib/unitsdb/commands/si_formatter.rb +0 -485
- data/lib/unitsdb/commands/si_matcher.rb +0 -470
- data/lib/unitsdb/commands/si_ttl_parser.rb +0 -100
- data/lib/unitsdb/commands/si_updater.rb +0 -212
data/exe/unitsdb
CHANGED
|
@@ -4,4 +4,10 @@
|
|
|
4
4
|
require "unitsdb"
|
|
5
5
|
require "unitsdb/cli"
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
begin
|
|
8
|
+
Unitsdb::Cli.start(ARGV)
|
|
9
|
+
rescue SystemExit => e
|
|
10
|
+
# Only print if not already handled (exit code 0 = normal exit)
|
|
11
|
+
# Exit code 1 from our commands is already handled, don't duplicate
|
|
12
|
+
raise if e.status.zero?
|
|
13
|
+
end
|
data/lib/unitsdb/cli.rb
CHANGED
|
@@ -1,14 +1,15 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
require "thor"
|
|
4
|
-
require_relative "commands/base"
|
|
5
|
-
require_relative "commands/validate"
|
|
6
|
-
require_relative "commands/_modify"
|
|
7
|
-
require_relative "commands/ucum"
|
|
8
4
|
require "fileutils"
|
|
9
5
|
|
|
10
6
|
module Unitsdb
|
|
11
|
-
class
|
|
7
|
+
class Cli < Thor
|
|
8
|
+
# Enable --trace globally for all subcommands
|
|
9
|
+
# When enabled, Thor shows full backtraces on error
|
|
10
|
+
class_option :trace, type: :boolean, default: false,
|
|
11
|
+
desc: "Show full backtrace on error"
|
|
12
|
+
|
|
12
13
|
# Fix Thor deprecation warning
|
|
13
14
|
def self.exit_on_failure?
|
|
14
15
|
true
|
|
@@ -17,10 +18,14 @@ module Unitsdb
|
|
|
17
18
|
desc "ucum SUBCOMMAND", "UCUM-related commands"
|
|
18
19
|
subcommand "ucum", Commands::UcumCommand
|
|
19
20
|
|
|
21
|
+
desc "qudt SUBCOMMAND", "QUDT-related commands"
|
|
22
|
+
subcommand "qudt", Commands::QudtCommand
|
|
23
|
+
|
|
20
24
|
desc "_modify SUBCOMMAND", "Commands that modify the database"
|
|
21
25
|
subcommand "_modify", Commands::ModifyCommand
|
|
22
26
|
|
|
23
|
-
desc "validate SUBCOMMAND",
|
|
27
|
+
desc "validate SUBCOMMAND",
|
|
28
|
+
"Validate database files for different conditions"
|
|
24
29
|
subcommand "validate", Commands::ValidateCommand
|
|
25
30
|
|
|
26
31
|
desc "search QUERY", "Search for entities containing the given text"
|
|
@@ -36,8 +41,7 @@ module Unitsdb
|
|
|
36
41
|
desc: "Path to UnitsDB database (required)"
|
|
37
42
|
|
|
38
43
|
def search(query)
|
|
39
|
-
|
|
40
|
-
Commands::Search.new(options).run(query)
|
|
44
|
+
run_command(Commands::Search, :run, query)
|
|
41
45
|
end
|
|
42
46
|
|
|
43
47
|
desc "get ID", "Get detailed information about an entity by ID"
|
|
@@ -48,11 +52,11 @@ module Unitsdb
|
|
|
48
52
|
option :database, type: :string, required: true, aliases: "-d",
|
|
49
53
|
desc: "Path to UnitsDB database (required)"
|
|
50
54
|
def get(id)
|
|
51
|
-
|
|
52
|
-
Commands::Get.new(options).get(id)
|
|
55
|
+
run_command(Commands::Get, :get, id)
|
|
53
56
|
end
|
|
54
57
|
|
|
55
|
-
desc "check_si",
|
|
58
|
+
desc "check_si",
|
|
59
|
+
"Check and update SI digital framework references in UnitsDB"
|
|
56
60
|
option :entity_type, type: :string, aliases: "-e",
|
|
57
61
|
desc: "Entity type to check (units, quantities, or prefixes). If not specified, all types are checked"
|
|
58
62
|
option :ttl_dir, type: :string, required: true, aliases: "-t",
|
|
@@ -67,8 +71,7 @@ module Unitsdb
|
|
|
67
71
|
desc: "Path to UnitsDB database (required)"
|
|
68
72
|
|
|
69
73
|
def check_si
|
|
70
|
-
|
|
71
|
-
Commands::CheckSi.new(options).run
|
|
74
|
+
run_command(Commands::CheckSiCommand, :run)
|
|
72
75
|
end
|
|
73
76
|
|
|
74
77
|
desc "release", "Create release files (unified YAML and/or ZIP archive)"
|
|
@@ -81,8 +84,32 @@ module Unitsdb
|
|
|
81
84
|
option :database, type: :string, required: true, aliases: "-d",
|
|
82
85
|
desc: "Path to UnitsDB database (required)"
|
|
83
86
|
def release
|
|
84
|
-
|
|
85
|
-
|
|
87
|
+
run_command(Commands::Release, :run)
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
private
|
|
91
|
+
|
|
92
|
+
def run_command(command_class, method, *args)
|
|
93
|
+
command = command_class.new(options)
|
|
94
|
+
command.send(method, *args)
|
|
95
|
+
rescue Unitsdb::Errors::CLIRuntimeError => e
|
|
96
|
+
handle_cli_error(e)
|
|
97
|
+
rescue StandardError => e
|
|
98
|
+
handle_error(e)
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
def handle_cli_error(error)
|
|
102
|
+
raise error if debugging?
|
|
103
|
+
|
|
104
|
+
warn "Error: #{error.message}"
|
|
105
|
+
exit 1
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
def handle_error(error)
|
|
109
|
+
raise error if debugging?
|
|
110
|
+
|
|
111
|
+
warn "Error: #{error.message}"
|
|
112
|
+
exit 1
|
|
86
113
|
end
|
|
87
114
|
end
|
|
88
115
|
end
|
|
@@ -5,8 +5,16 @@ require "thor"
|
|
|
5
5
|
module Unitsdb
|
|
6
6
|
module Commands
|
|
7
7
|
class ModifyCommand < Thor
|
|
8
|
-
|
|
9
|
-
|
|
8
|
+
# Inherit trace option from parent CLI
|
|
9
|
+
class_option :trace, type: :boolean, default: false,
|
|
10
|
+
desc: "Show full backtrace on error"
|
|
11
|
+
|
|
12
|
+
desc "normalize INPUT OUTPUT",
|
|
13
|
+
"Normalize a YAML file or all YAML files with --all"
|
|
14
|
+
method_option :sort, type: :string,
|
|
15
|
+
default: "nist",
|
|
16
|
+
enum: ["short", "nist", "unitsml", "none"],
|
|
17
|
+
aliases: "-s",
|
|
10
18
|
desc: "Sort units by: 'short' (name), 'nist' (ID, default), 'unitsml' (ID), or 'none'"
|
|
11
19
|
method_option :database, type: :string, required: true, aliases: "-d",
|
|
12
20
|
desc: "Path to UnitsDB database (required)"
|
|
@@ -14,8 +22,36 @@ module Unitsdb
|
|
|
14
22
|
desc: "Process all YAML files in the repository"
|
|
15
23
|
|
|
16
24
|
def normalize(input = nil, output = nil)
|
|
17
|
-
|
|
18
|
-
|
|
25
|
+
run_command(Normalize, options, input, output)
|
|
26
|
+
rescue Unitsdb::Errors::CLIRuntimeError => e
|
|
27
|
+
handle_cli_error(e)
|
|
28
|
+
rescue StandardError => e
|
|
29
|
+
handle_error(e)
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
private
|
|
33
|
+
|
|
34
|
+
def run_command(command_class, options, *args)
|
|
35
|
+
command = command_class.new(options)
|
|
36
|
+
command.run(*args)
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def handle_cli_error(error)
|
|
40
|
+
if options[:trace]
|
|
41
|
+
raise error
|
|
42
|
+
else
|
|
43
|
+
warn "Error: #{error.message}"
|
|
44
|
+
exit 1
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def handle_error(error)
|
|
49
|
+
if options[:trace]
|
|
50
|
+
raise error
|
|
51
|
+
else
|
|
52
|
+
warn "Error: #{error.message}"
|
|
53
|
+
exit 1
|
|
54
|
+
end
|
|
19
55
|
end
|
|
20
56
|
end
|
|
21
57
|
end
|
|
@@ -15,11 +15,15 @@ module Unitsdb
|
|
|
15
15
|
|
|
16
16
|
def load_database(path = nil)
|
|
17
17
|
path ||= @options[:database]
|
|
18
|
-
|
|
18
|
+
unless path
|
|
19
|
+
raise Unitsdb::Errors::DatabaseError,
|
|
20
|
+
"Database path not specified"
|
|
21
|
+
end
|
|
19
22
|
|
|
20
23
|
Unitsdb::Database.from_db(path)
|
|
21
24
|
rescue StandardError => e
|
|
22
|
-
raise Unitsdb::Errors::DatabaseError,
|
|
25
|
+
raise Unitsdb::Errors::DatabaseError,
|
|
26
|
+
"Failed to load database: #{e.message}"
|
|
23
27
|
end
|
|
24
28
|
end
|
|
25
29
|
end
|
|
@@ -0,0 +1,488 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "terminal-table"
|
|
4
|
+
|
|
5
|
+
module Unitsdb
|
|
6
|
+
module Commands
|
|
7
|
+
module CheckSi
|
|
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,
|
|
14
|
+
unmatched_ttl)
|
|
15
|
+
puts "\n=== #{entity_type.capitalize} with matching SI references ==="
|
|
16
|
+
if matches.empty?
|
|
17
|
+
puts "None"
|
|
18
|
+
else
|
|
19
|
+
rows = []
|
|
20
|
+
matches.each do |match|
|
|
21
|
+
si_suffix = SiTtlParser.extract_identifying_suffix(match[:si_uri])
|
|
22
|
+
rows << [
|
|
23
|
+
"UnitsDB: #{match[:entity_id]}",
|
|
24
|
+
"(#{match[:entity_name] || 'unnamed'})",
|
|
25
|
+
]
|
|
26
|
+
rows << [
|
|
27
|
+
"SI TTL: #{si_suffix}",
|
|
28
|
+
"(#{match[:si_label] || match[:si_name] || 'unnamed'})",
|
|
29
|
+
]
|
|
30
|
+
rows << :separator unless match == matches.last
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
table = Terminal::Table.new(
|
|
34
|
+
title: "Valid SI Reference Mappings",
|
|
35
|
+
rows: rows,
|
|
36
|
+
)
|
|
37
|
+
puts table
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
puts "\n=== #{entity_type.capitalize} without SI references ==="
|
|
41
|
+
if missing_matches.empty?
|
|
42
|
+
puts "None"
|
|
43
|
+
else
|
|
44
|
+
# Split matches into exact and potential
|
|
45
|
+
exact_matches = []
|
|
46
|
+
potential_matches = []
|
|
47
|
+
|
|
48
|
+
missing_matches.each do |match|
|
|
49
|
+
# Get match details
|
|
50
|
+
match_details = match[:match_details]
|
|
51
|
+
match_desc = match_details&.dig(:match_desc) || ""
|
|
52
|
+
|
|
53
|
+
# Symbol matches and partial matches should always be potential matches
|
|
54
|
+
if %w[symbol_match partial_match].include?(match_desc)
|
|
55
|
+
potential_matches << match
|
|
56
|
+
elsif match_details&.dig(:exact) == false
|
|
57
|
+
potential_matches << match
|
|
58
|
+
else
|
|
59
|
+
exact_matches << match
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
# Display exact matches
|
|
64
|
+
puts "\n=== Exact Matches (#{exact_matches.size}) ==="
|
|
65
|
+
if exact_matches.empty?
|
|
66
|
+
puts "None"
|
|
67
|
+
else
|
|
68
|
+
rows = []
|
|
69
|
+
exact_matches.each do |match|
|
|
70
|
+
# First row: UnitsDB entity
|
|
71
|
+
rows << [
|
|
72
|
+
"UnitsDB: #{match[:entity_id]}",
|
|
73
|
+
"(#{match[:entity_name] || 'unnamed'})",
|
|
74
|
+
]
|
|
75
|
+
|
|
76
|
+
# Handle multiple SI matches in a single cell if present
|
|
77
|
+
if match[:multiple_si]
|
|
78
|
+
# Ensure no duplicate URIs
|
|
79
|
+
si_text_parts = []
|
|
80
|
+
si_label_parts = []
|
|
81
|
+
seen_uris = {}
|
|
82
|
+
|
|
83
|
+
match[:multiple_si].each do |si_data|
|
|
84
|
+
uri = si_data[:uri]
|
|
85
|
+
next if seen_uris[uri] # Skip if we've already seen this URI
|
|
86
|
+
|
|
87
|
+
seen_uris[uri] = true
|
|
88
|
+
|
|
89
|
+
suffix = SiTtlParser.extract_identifying_suffix(uri)
|
|
90
|
+
si_text_parts << suffix
|
|
91
|
+
si_label_parts << (si_data[:label] || si_data[:name])
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
rows << [
|
|
95
|
+
"SI TTL: #{si_text_parts.join(', ')}",
|
|
96
|
+
"(#{si_label_parts.join(', ')})",
|
|
97
|
+
]
|
|
98
|
+
else
|
|
99
|
+
# Second row: SI TTL suffix and label/name
|
|
100
|
+
si_suffix = SiTtlParser.extract_identifying_suffix(match[:si_uri])
|
|
101
|
+
rows << [
|
|
102
|
+
"SI TTL: #{si_suffix}",
|
|
103
|
+
"(#{match[:si_label] || match[:si_name] || 'unnamed'})",
|
|
104
|
+
]
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
# Status line with match type
|
|
108
|
+
match_details = match[:match_details]
|
|
109
|
+
match_desc = match_details&.dig(:match_desc) || ""
|
|
110
|
+
match_info = format_match_info(match_desc)
|
|
111
|
+
status_text = match_info.empty? ? "Missing reference" : "Missing reference (#{match_info})"
|
|
112
|
+
|
|
113
|
+
rows << [
|
|
114
|
+
"Status: #{status_text}",
|
|
115
|
+
"✗",
|
|
116
|
+
]
|
|
117
|
+
rows << :separator unless match == exact_matches.last
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
table = Terminal::Table.new(
|
|
121
|
+
title: "Exact Match Missing SI References",
|
|
122
|
+
rows: rows,
|
|
123
|
+
)
|
|
124
|
+
puts table
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
# Display potential matches
|
|
128
|
+
puts "\n=== Potential Matches (#{potential_matches.size}) ==="
|
|
129
|
+
if potential_matches.empty?
|
|
130
|
+
puts "None"
|
|
131
|
+
else
|
|
132
|
+
rows = []
|
|
133
|
+
potential_matches.each do |match|
|
|
134
|
+
# First row: UnitsDB entity
|
|
135
|
+
rows << [
|
|
136
|
+
"UnitsDB: #{match[:entity_id]}",
|
|
137
|
+
"(#{match[:entity_name] || 'unnamed'})",
|
|
138
|
+
]
|
|
139
|
+
|
|
140
|
+
# Handle multiple SI matches in a single cell if present
|
|
141
|
+
if match[:multiple_si]
|
|
142
|
+
# Ensure no duplicate URIs
|
|
143
|
+
si_text_parts = []
|
|
144
|
+
seen_uris = {}
|
|
145
|
+
|
|
146
|
+
match[:multiple_si].each do |si_data|
|
|
147
|
+
uri = si_data[:uri]
|
|
148
|
+
next if seen_uris[uri] # Skip if we've already seen this URI
|
|
149
|
+
|
|
150
|
+
seen_uris[uri] = true
|
|
151
|
+
|
|
152
|
+
suffix = SiTtlParser.extract_identifying_suffix(uri)
|
|
153
|
+
si_text_parts << "#{suffix} (#{si_data[:label] || si_data[:name]})"
|
|
154
|
+
end
|
|
155
|
+
|
|
156
|
+
rows << [
|
|
157
|
+
"SI TTL: #{si_text_parts.join(', ')}",
|
|
158
|
+
"",
|
|
159
|
+
]
|
|
160
|
+
else
|
|
161
|
+
# Single TTL entity
|
|
162
|
+
si_suffix = SiTtlParser.extract_identifying_suffix(match[:si_uri])
|
|
163
|
+
rows << [
|
|
164
|
+
"SI TTL: #{si_suffix}",
|
|
165
|
+
"(#{match[:si_label] || match[:si_name] || 'unnamed'})",
|
|
166
|
+
]
|
|
167
|
+
end
|
|
168
|
+
|
|
169
|
+
# Status line
|
|
170
|
+
match_details = match[:match_details]
|
|
171
|
+
match_desc = match_details&.dig(:match_desc) || ""
|
|
172
|
+
match_info = format_match_info(match_desc)
|
|
173
|
+
status_text = match_info.empty? ? "Missing reference" : "Missing reference"
|
|
174
|
+
|
|
175
|
+
rows << [
|
|
176
|
+
"Status: #{status_text}",
|
|
177
|
+
"✗",
|
|
178
|
+
]
|
|
179
|
+
rows << :separator unless match == potential_matches.last
|
|
180
|
+
end
|
|
181
|
+
|
|
182
|
+
table = Terminal::Table.new(
|
|
183
|
+
title: "Potential Match Missing SI References",
|
|
184
|
+
rows: rows,
|
|
185
|
+
)
|
|
186
|
+
puts table
|
|
187
|
+
end
|
|
188
|
+
end
|
|
189
|
+
|
|
190
|
+
puts "\n=== SI #{entity_type.capitalize} not mapped to our database ==="
|
|
191
|
+
if unmatched_ttl.empty?
|
|
192
|
+
puts "None (All TTL entities are referenced - Good job!)"
|
|
193
|
+
else
|
|
194
|
+
# Group unmatched ttl entities by their URI to avoid duplicates
|
|
195
|
+
grouped_unmatched = {}
|
|
196
|
+
|
|
197
|
+
unmatched_ttl.each do |entity|
|
|
198
|
+
uri = entity[:uri]
|
|
199
|
+
grouped_unmatched[uri] = entity unless grouped_unmatched.key?(uri)
|
|
200
|
+
end
|
|
201
|
+
|
|
202
|
+
rows = []
|
|
203
|
+
unique_entities = grouped_unmatched.values
|
|
204
|
+
|
|
205
|
+
unique_entities.each do |entity|
|
|
206
|
+
# Create the SI TTL row
|
|
207
|
+
si_suffix = SiTtlParser.extract_identifying_suffix(entity[:uri])
|
|
208
|
+
ttl_row = ["SI TTL: #{si_suffix}",
|
|
209
|
+
"(#{entity[:label] || entity[:name] || 'unnamed'})"]
|
|
210
|
+
|
|
211
|
+
rows << ttl_row
|
|
212
|
+
rows << [
|
|
213
|
+
"Status: No matching UnitsDB entity",
|
|
214
|
+
"?",
|
|
215
|
+
]
|
|
216
|
+
rows << :separator unless entity == unique_entities.last
|
|
217
|
+
end
|
|
218
|
+
|
|
219
|
+
table = Terminal::Table.new(
|
|
220
|
+
title: "Unmapped SI Entities",
|
|
221
|
+
rows: rows,
|
|
222
|
+
)
|
|
223
|
+
puts table
|
|
224
|
+
end
|
|
225
|
+
end
|
|
226
|
+
|
|
227
|
+
# Display DB → TTL results
|
|
228
|
+
def display_db_results(entity_type, matches, missing_refs, unmatched_db)
|
|
229
|
+
puts "\n=== Summary of database entities referencing SI ==="
|
|
230
|
+
puts "#{entity_type.capitalize} with SI references: #{matches.size}"
|
|
231
|
+
puts "#{entity_type.capitalize} missing SI references: #{missing_refs.size}"
|
|
232
|
+
puts "Database #{entity_type} not matching any SI entity: #{unmatched_db.size}"
|
|
233
|
+
|
|
234
|
+
# Show entities with valid references
|
|
235
|
+
unless matches.empty?
|
|
236
|
+
puts "\n=== #{entity_type.capitalize} with SI references ==="
|
|
237
|
+
rows = []
|
|
238
|
+
matches.each do |match|
|
|
239
|
+
db_entity = match[:db_entity]
|
|
240
|
+
entity_id = match[:entity_id] || db_entity.short
|
|
241
|
+
entity_name = db_entity.respond_to?(:names) ? db_entity.names&.first : "unnamed"
|
|
242
|
+
si_suffix = SiTtlParser.extract_identifying_suffix(match[:ttl_uri])
|
|
243
|
+
|
|
244
|
+
ttl_label = match[:ttl_entity] ? (match[:ttl_entity][:label] || match[:ttl_entity][:name]) : "Unknown"
|
|
245
|
+
|
|
246
|
+
rows << [
|
|
247
|
+
"UnitsDB: #{entity_id}",
|
|
248
|
+
"(#{entity_name})",
|
|
249
|
+
]
|
|
250
|
+
rows << [
|
|
251
|
+
"SI TTL: #{si_suffix}",
|
|
252
|
+
"(#{ttl_label})",
|
|
253
|
+
]
|
|
254
|
+
rows << :separator unless match == matches.last
|
|
255
|
+
end
|
|
256
|
+
|
|
257
|
+
table = Terminal::Table.new(
|
|
258
|
+
title: "Valid SI References",
|
|
259
|
+
rows: rows,
|
|
260
|
+
)
|
|
261
|
+
puts table
|
|
262
|
+
end
|
|
263
|
+
|
|
264
|
+
puts "\n=== #{entity_type.capitalize} that should reference SI ==="
|
|
265
|
+
if missing_refs.empty?
|
|
266
|
+
puts "None"
|
|
267
|
+
else
|
|
268
|
+
# Split missing_refs into exact and potential matches
|
|
269
|
+
exact_matches = []
|
|
270
|
+
potential_matches = []
|
|
271
|
+
|
|
272
|
+
missing_refs.each do |match|
|
|
273
|
+
# Determine match type
|
|
274
|
+
ttl_entities = match[:ttl_entities]
|
|
275
|
+
uri = ttl_entities.first[:uri]
|
|
276
|
+
match_type = "Exact match" # Default
|
|
277
|
+
match_type = match[:match_types][uri] if match[:match_types] && match[:match_types][uri]
|
|
278
|
+
|
|
279
|
+
# Get match description if available
|
|
280
|
+
entity_id = match[:db_entity].short
|
|
281
|
+
match_pair_key = "#{entity_id}:#{ttl_entities.first[:uri]}"
|
|
282
|
+
match_details = Unitsdb::Commands::CheckSi::SiMatcher.instance_variable_get(:@match_details)&.dig(match_pair_key)
|
|
283
|
+
match_desc = match_details[:match_desc] if match_details && match_details[:match_desc]
|
|
284
|
+
|
|
285
|
+
# Symbol matches and partial matches should always be potential matches
|
|
286
|
+
if %w[symbol_match partial_match].include?(match_desc)
|
|
287
|
+
potential_matches << match
|
|
288
|
+
elsif match_type == "Exact match"
|
|
289
|
+
exact_matches << match
|
|
290
|
+
else
|
|
291
|
+
potential_matches << match
|
|
292
|
+
end
|
|
293
|
+
end
|
|
294
|
+
|
|
295
|
+
# Display exact matches
|
|
296
|
+
puts "\n=== Exact Matches (#{exact_matches.size}) ==="
|
|
297
|
+
if exact_matches.empty?
|
|
298
|
+
puts "None"
|
|
299
|
+
else
|
|
300
|
+
rows = []
|
|
301
|
+
exact_matches.each do |match|
|
|
302
|
+
db_entity = match[:db_entity]
|
|
303
|
+
entity_id = match[:entity_id] || db_entity.short
|
|
304
|
+
entity_name = db_entity.respond_to?(:names) ? db_entity.names&.first : "unnamed"
|
|
305
|
+
|
|
306
|
+
# Handle multiple TTL entities in a single row
|
|
307
|
+
ttl_entities = match[:ttl_entities]
|
|
308
|
+
if ttl_entities.size == 1
|
|
309
|
+
# Single TTL entity
|
|
310
|
+
ttl_entity = ttl_entities.first
|
|
311
|
+
si_suffix = SiTtlParser.extract_identifying_suffix(ttl_entity[:uri])
|
|
312
|
+
|
|
313
|
+
rows << [
|
|
314
|
+
"UnitsDB: #{entity_id}",
|
|
315
|
+
"(#{entity_name})",
|
|
316
|
+
]
|
|
317
|
+
rows << [
|
|
318
|
+
"SI TTL: #{si_suffix}",
|
|
319
|
+
"(#{ttl_entity[:label] || ttl_entity[:name] || 'unnamed'})",
|
|
320
|
+
]
|
|
321
|
+
else
|
|
322
|
+
# Multiple TTL entities, combine them - ensure no duplicates
|
|
323
|
+
si_text_parts = []
|
|
324
|
+
seen_uris = {}
|
|
325
|
+
|
|
326
|
+
ttl_entities.each do |ttl_entity|
|
|
327
|
+
uri = ttl_entity[:uri]
|
|
328
|
+
next if seen_uris[uri] # Skip if we've already seen this URI
|
|
329
|
+
|
|
330
|
+
seen_uris[uri] = true
|
|
331
|
+
|
|
332
|
+
suffix = SiTtlParser.extract_identifying_suffix(uri)
|
|
333
|
+
si_text_parts << "#{suffix} (#{ttl_entity[:label] || ttl_entity[:name] || 'unnamed'})"
|
|
334
|
+
end
|
|
335
|
+
|
|
336
|
+
si_text = si_text_parts.join(", ")
|
|
337
|
+
|
|
338
|
+
rows << [
|
|
339
|
+
"UnitsDB: #{entity_id}",
|
|
340
|
+
"(#{entity_name})",
|
|
341
|
+
]
|
|
342
|
+
rows << [
|
|
343
|
+
"SI TTL: #{si_text}",
|
|
344
|
+
"",
|
|
345
|
+
]
|
|
346
|
+
end
|
|
347
|
+
|
|
348
|
+
# Get match details for this match
|
|
349
|
+
match_pair_key = "#{db_entity.short}:#{ttl_entities.first[:uri]}"
|
|
350
|
+
match_details = Unitsdb::Commands::CheckSi::SiMatcher.instance_variable_get(:@match_details)&.dig(match_pair_key)
|
|
351
|
+
|
|
352
|
+
# Format match info
|
|
353
|
+
match_info = ""
|
|
354
|
+
match_info = format_match_info(match_details[:match_desc]) if match_details
|
|
355
|
+
|
|
356
|
+
status_text = match_info.empty? ? "Missing reference" : "Missing reference (#{match_info})"
|
|
357
|
+
rows << [
|
|
358
|
+
"Status: #{status_text}",
|
|
359
|
+
"✗",
|
|
360
|
+
]
|
|
361
|
+
rows << :separator unless match == exact_matches.last
|
|
362
|
+
end
|
|
363
|
+
|
|
364
|
+
table = Terminal::Table.new(
|
|
365
|
+
title: "Exact Match Missing SI References",
|
|
366
|
+
rows: rows,
|
|
367
|
+
)
|
|
368
|
+
puts table
|
|
369
|
+
end
|
|
370
|
+
|
|
371
|
+
# Display potential matches
|
|
372
|
+
puts "\n=== Potential Matches (#{potential_matches.size}) ==="
|
|
373
|
+
if potential_matches.empty?
|
|
374
|
+
puts "None"
|
|
375
|
+
else
|
|
376
|
+
rows = []
|
|
377
|
+
potential_matches.each do |match|
|
|
378
|
+
db_entity = match[:db_entity]
|
|
379
|
+
entity_id = match[:entity_id] || db_entity.short
|
|
380
|
+
entity_name = db_entity.respond_to?(:names) ? db_entity.names&.first : "unnamed"
|
|
381
|
+
|
|
382
|
+
# Handle multiple TTL entities in a single row
|
|
383
|
+
ttl_entities = match[:ttl_entities]
|
|
384
|
+
if ttl_entities.size == 1
|
|
385
|
+
# Single TTL entity
|
|
386
|
+
ttl_entity = ttl_entities.first
|
|
387
|
+
si_suffix = SiTtlParser.extract_identifying_suffix(ttl_entity[:uri])
|
|
388
|
+
|
|
389
|
+
rows << [
|
|
390
|
+
"UnitsDB: #{entity_id}",
|
|
391
|
+
"(#{entity_name})",
|
|
392
|
+
]
|
|
393
|
+
rows << [
|
|
394
|
+
"SI TTL: #{si_suffix}",
|
|
395
|
+
"(#{ttl_entity[:label] || ttl_entity[:name] || 'unnamed'})",
|
|
396
|
+
]
|
|
397
|
+
else
|
|
398
|
+
# Multiple TTL entities, combine them - ensure no duplicates
|
|
399
|
+
si_text_parts = []
|
|
400
|
+
seen_uris = {}
|
|
401
|
+
|
|
402
|
+
ttl_entities.each do |ttl_entity|
|
|
403
|
+
uri = ttl_entity[:uri]
|
|
404
|
+
next if seen_uris[uri] # Skip if we've already seen this URI
|
|
405
|
+
|
|
406
|
+
seen_uris[uri] = true
|
|
407
|
+
|
|
408
|
+
suffix = SiTtlParser.extract_identifying_suffix(uri)
|
|
409
|
+
si_text_parts << "#{suffix} (#{ttl_entity[:label] || ttl_entity[:name] || 'unnamed'})"
|
|
410
|
+
end
|
|
411
|
+
|
|
412
|
+
si_text = si_text_parts.join(", ")
|
|
413
|
+
|
|
414
|
+
rows << [
|
|
415
|
+
"UnitsDB: #{entity_id}",
|
|
416
|
+
"(#{entity_name})",
|
|
417
|
+
]
|
|
418
|
+
rows << [
|
|
419
|
+
"SI TTL: #{si_text}",
|
|
420
|
+
"",
|
|
421
|
+
]
|
|
422
|
+
end
|
|
423
|
+
|
|
424
|
+
# Get match details
|
|
425
|
+
match_pair_key = "#{db_entity.short}:#{ttl_entities.first[:uri]}"
|
|
426
|
+
match_details = Unitsdb::Commands::CheckSi::SiMatcher.instance_variable_get(:@match_details)&.dig(match_pair_key)
|
|
427
|
+
|
|
428
|
+
# Format match info
|
|
429
|
+
match_info = ""
|
|
430
|
+
match_info = format_match_info(match_details[:match_desc]) if match_details
|
|
431
|
+
|
|
432
|
+
status_text = match_info.empty? ? "Missing reference" : "Missing reference (#{match_info})"
|
|
433
|
+
rows << [
|
|
434
|
+
"Status: #{status_text}",
|
|
435
|
+
"✗",
|
|
436
|
+
]
|
|
437
|
+
rows << :separator unless match == potential_matches.last
|
|
438
|
+
end
|
|
439
|
+
|
|
440
|
+
table = Terminal::Table.new(
|
|
441
|
+
title: "Potential Match Missing SI References",
|
|
442
|
+
rows: rows,
|
|
443
|
+
)
|
|
444
|
+
puts table
|
|
445
|
+
end
|
|
446
|
+
end
|
|
447
|
+
end
|
|
448
|
+
|
|
449
|
+
# Print direction header
|
|
450
|
+
def print_direction_header(direction)
|
|
451
|
+
case direction
|
|
452
|
+
when "SI → UnitsDB"
|
|
453
|
+
puts "\n=== Checking SI → UnitsDB (TTL entities referenced by database) ==="
|
|
454
|
+
when "UnitsDB → SI"
|
|
455
|
+
puts "\n=== Checking UnitsDB → SI (database entities referencing TTL) ==="
|
|
456
|
+
end
|
|
457
|
+
|
|
458
|
+
puts "\n=== Instructions for #{direction} direction ==="
|
|
459
|
+
case direction
|
|
460
|
+
when "SI → UnitsDB"
|
|
461
|
+
puts "If you are the UnitsDB Register Manager, please ensure that all SI entities have proper references in the UnitsDB database."
|
|
462
|
+
puts "For each missing reference, add a reference with the appropriate URI and 'authority: \"si-digital-framework\"'."
|
|
463
|
+
when "UnitsDB → SI"
|
|
464
|
+
puts "If you are the UnitsDB Register Manager, please add SI references to UnitsDB entities that should have them."
|
|
465
|
+
puts "For each entity that should reference SI, add a reference with 'authority: \"si-digital-framework\"' and the SI TTL URI."
|
|
466
|
+
end
|
|
467
|
+
end
|
|
468
|
+
|
|
469
|
+
def set_match_details(details)
|
|
470
|
+
@match_details = details
|
|
471
|
+
end
|
|
472
|
+
|
|
473
|
+
# Format match info for display
|
|
474
|
+
def format_match_info(match_desc)
|
|
475
|
+
{
|
|
476
|
+
"short_to_name" => "short → name",
|
|
477
|
+
"short_to_label" => "short → label",
|
|
478
|
+
"name_to_name" => "name → name",
|
|
479
|
+
"name_to_label" => "name → label",
|
|
480
|
+
"name_to_alt_label" => "name → alt_label",
|
|
481
|
+
"symbol_match" => "symbol → symbol",
|
|
482
|
+
"partial_match" => "partial match",
|
|
483
|
+
}[match_desc] || ""
|
|
484
|
+
end
|
|
485
|
+
end
|
|
486
|
+
end
|
|
487
|
+
end
|
|
488
|
+
end
|