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
|
@@ -0,0 +1,254 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "yaml"
|
|
4
|
+
require "fileutils"
|
|
5
|
+
|
|
6
|
+
module Unitsdb
|
|
7
|
+
module Commands
|
|
8
|
+
module CheckSi
|
|
9
|
+
# Updater for SI references in YAML
|
|
10
|
+
module SiUpdater
|
|
11
|
+
SI_AUTHORITY = "si-digital-framework"
|
|
12
|
+
|
|
13
|
+
module_function
|
|
14
|
+
|
|
15
|
+
# Update references in YAML file (TTL → DB direction)
|
|
16
|
+
def update_references(entity_type, missing_matches, db_entities,
|
|
17
|
+
output_file, include_potential = false)
|
|
18
|
+
# Use the database objects to access the data directly
|
|
19
|
+
original_yaml_file = db_entities.first.send(:yaml_file) if db_entities&.first.respond_to?(
|
|
20
|
+
:yaml_file, true
|
|
21
|
+
)
|
|
22
|
+
|
|
23
|
+
# If we can't get the path from the database object, use the output file path as a fallback
|
|
24
|
+
if original_yaml_file.nil? || !File.exist?(original_yaml_file)
|
|
25
|
+
puts "Warning: Could not determine original YAML file path. Using output file as template."
|
|
26
|
+
original_yaml_file = output_file
|
|
27
|
+
|
|
28
|
+
# Create an empty template if output file doesn't exist
|
|
29
|
+
unless File.exist?(original_yaml_file)
|
|
30
|
+
FileUtils.mkdir_p(File.dirname(original_yaml_file))
|
|
31
|
+
File.write(original_yaml_file, { entity_type => [] }.to_yaml)
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
# Load the original YAML file
|
|
36
|
+
yaml_content = File.read(original_yaml_file)
|
|
37
|
+
output_data = YAML.safe_load(yaml_content)
|
|
38
|
+
|
|
39
|
+
# Group by entity ID to avoid duplicates
|
|
40
|
+
grouped_matches = missing_matches.group_by do |match|
|
|
41
|
+
match[:entity_id]
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
# Process each entity that needs updating
|
|
45
|
+
grouped_matches.each do |entity_id, matches|
|
|
46
|
+
# Filter matches based on include_potential parameter
|
|
47
|
+
filtered_matches = matches.select do |match|
|
|
48
|
+
# Check if it's an exact match or if we're including potential matches
|
|
49
|
+
match_details = match[:match_details]
|
|
50
|
+
if match_details&.dig(:exact) == false || %w[symbol_match
|
|
51
|
+
partial_match].include?(match_details&.dig(:match_desc) || "")
|
|
52
|
+
include_potential
|
|
53
|
+
else
|
|
54
|
+
true
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
# Skip if no matches after filtering
|
|
59
|
+
next if filtered_matches.empty?
|
|
60
|
+
|
|
61
|
+
# Find the entity in the array under the entity_type key
|
|
62
|
+
entity_index = output_data[entity_type].find_index do |e|
|
|
63
|
+
# Find entity with matching identifier
|
|
64
|
+
e["identifiers"]&.any? { |id| id["id"] == entity_id }
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
next unless entity_index
|
|
68
|
+
|
|
69
|
+
# Get the entity
|
|
70
|
+
entity = output_data[entity_type][entity_index]
|
|
71
|
+
|
|
72
|
+
# Initialize references array if it doesn't exist
|
|
73
|
+
entity["references"] ||= []
|
|
74
|
+
|
|
75
|
+
# Add new references
|
|
76
|
+
filtered_matches.each do |match|
|
|
77
|
+
# If this match has multiple SI references, add them all
|
|
78
|
+
if match[:multiple_si]
|
|
79
|
+
match[:multiple_si].each do |si_data|
|
|
80
|
+
# Check if reference already exists
|
|
81
|
+
next if entity["references"].any? do |ref|
|
|
82
|
+
ref["uri"] == si_data[:uri] && ref["authority"] == SI_AUTHORITY
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
# Add new reference
|
|
86
|
+
entity["references"] << {
|
|
87
|
+
"uri" => si_data[:uri],
|
|
88
|
+
"type" => "normative",
|
|
89
|
+
"authority" => SI_AUTHORITY,
|
|
90
|
+
}
|
|
91
|
+
end
|
|
92
|
+
else
|
|
93
|
+
# Check if reference already exists
|
|
94
|
+
next if entity["references"].any? do |ref|
|
|
95
|
+
ref["uri"] == match[:si_uri] && ref["authority"] == SI_AUTHORITY
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
# Add new reference
|
|
99
|
+
entity["references"] << {
|
|
100
|
+
"uri" => match[:si_uri],
|
|
101
|
+
"type" => "normative",
|
|
102
|
+
"authority" => SI_AUTHORITY,
|
|
103
|
+
}
|
|
104
|
+
end
|
|
105
|
+
end
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
write_yaml_file(output_file, output_data)
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
# Update references in YAML file (DB → TTL direction)
|
|
112
|
+
def update_db_references(entity_type, missing_refs, output_file,
|
|
113
|
+
include_potential = false)
|
|
114
|
+
# Try to get the original YAML file from the first entity
|
|
115
|
+
first_entity = missing_refs.first&.dig(:db_entity)
|
|
116
|
+
original_yaml_file = first_entity.send(:yaml_file) if first_entity.respond_to?(
|
|
117
|
+
:yaml_file, true
|
|
118
|
+
)
|
|
119
|
+
|
|
120
|
+
# If we can't get the path from the database object, use the output file path as a fallback
|
|
121
|
+
if original_yaml_file.nil? || !File.exist?(original_yaml_file)
|
|
122
|
+
puts "Warning: Could not determine original YAML file path. Using output file as template."
|
|
123
|
+
original_yaml_file = output_file
|
|
124
|
+
|
|
125
|
+
# Create an empty template if output file doesn't exist
|
|
126
|
+
unless File.exist?(original_yaml_file)
|
|
127
|
+
FileUtils.mkdir_p(File.dirname(original_yaml_file))
|
|
128
|
+
File.write(original_yaml_file, { entity_type => [] }.to_yaml)
|
|
129
|
+
end
|
|
130
|
+
end
|
|
131
|
+
|
|
132
|
+
# Load the original YAML file
|
|
133
|
+
yaml_content = File.read(original_yaml_file)
|
|
134
|
+
output_data = YAML.safe_load(yaml_content)
|
|
135
|
+
|
|
136
|
+
# Group by entity ID to avoid duplicates
|
|
137
|
+
missing_refs_by_id = {}
|
|
138
|
+
|
|
139
|
+
missing_refs.each do |match|
|
|
140
|
+
entity_id = match[:entity_id] || match[:db_entity].short
|
|
141
|
+
ttl_entities = match[:ttl_entities]
|
|
142
|
+
match_types = match[:match_types] || {}
|
|
143
|
+
|
|
144
|
+
# Filter TTL entities based on include_potential parameter
|
|
145
|
+
filtered_ttl_entities = ttl_entities.select do |ttl_entity|
|
|
146
|
+
# Check if it's an exact match or if we're including potential matches
|
|
147
|
+
match_type = match_types[ttl_entity[:uri]] || "Exact match" # Default to exact match
|
|
148
|
+
match_pair_key = "#{entity_id}:#{ttl_entity[:uri]}"
|
|
149
|
+
match_details = Unitsdb::Commands::CheckSi::SiMatcher.instance_variable_get(:@match_details)&.dig(match_pair_key)
|
|
150
|
+
|
|
151
|
+
if match_details && %w[symbol_match
|
|
152
|
+
partial_match].include?(match_details[:match_desc])
|
|
153
|
+
include_potential
|
|
154
|
+
else
|
|
155
|
+
match_type == "Exact match" || include_potential
|
|
156
|
+
end
|
|
157
|
+
end
|
|
158
|
+
|
|
159
|
+
# Skip if no entities after filtering
|
|
160
|
+
next if filtered_ttl_entities.empty?
|
|
161
|
+
|
|
162
|
+
missing_refs_by_id[entity_id] ||= []
|
|
163
|
+
|
|
164
|
+
# Add filtered matching TTL entities for this DB entity
|
|
165
|
+
filtered_ttl_entities.each do |ttl_entity|
|
|
166
|
+
missing_refs_by_id[entity_id] << {
|
|
167
|
+
uri: ttl_entity[:uri],
|
|
168
|
+
type: "normative",
|
|
169
|
+
authority: SI_AUTHORITY,
|
|
170
|
+
}
|
|
171
|
+
end
|
|
172
|
+
end
|
|
173
|
+
|
|
174
|
+
# Update the YAML content
|
|
175
|
+
output_data[entity_type].each do |entity_yaml|
|
|
176
|
+
# Find entity by ID or short
|
|
177
|
+
entity_id = if entity_yaml["identifiers"]
|
|
178
|
+
begin
|
|
179
|
+
entity_yaml["identifiers"].first["id"]
|
|
180
|
+
rescue StandardError
|
|
181
|
+
nil
|
|
182
|
+
end
|
|
183
|
+
elsif entity_yaml["id"]
|
|
184
|
+
entity_yaml["id"]
|
|
185
|
+
end
|
|
186
|
+
|
|
187
|
+
next unless entity_id && missing_refs_by_id.key?(entity_id)
|
|
188
|
+
|
|
189
|
+
# Add references
|
|
190
|
+
entity_yaml["references"] ||= []
|
|
191
|
+
|
|
192
|
+
missing_refs_by_id[entity_id].each do |ref|
|
|
193
|
+
# Check if this reference already exists
|
|
194
|
+
next if entity_yaml["references"].any? do |existing_ref|
|
|
195
|
+
existing_ref["uri"] == ref[:uri] &&
|
|
196
|
+
existing_ref["authority"] == ref[:authority]
|
|
197
|
+
end
|
|
198
|
+
|
|
199
|
+
# Add the reference
|
|
200
|
+
entity_yaml["references"] << {
|
|
201
|
+
"uri" => ref[:uri],
|
|
202
|
+
"type" => ref[:type],
|
|
203
|
+
"authority" => ref[:authority],
|
|
204
|
+
}
|
|
205
|
+
end
|
|
206
|
+
end
|
|
207
|
+
|
|
208
|
+
write_yaml_file(output_file, output_data)
|
|
209
|
+
end
|
|
210
|
+
|
|
211
|
+
# Helper to write YAML file
|
|
212
|
+
def write_yaml_file(output_file, output_data)
|
|
213
|
+
# Ensure the output directory exists
|
|
214
|
+
output_dir = File.dirname(output_file)
|
|
215
|
+
FileUtils.mkdir_p(output_dir)
|
|
216
|
+
|
|
217
|
+
# Write to YAML file with proper formatting
|
|
218
|
+
yaml_content = output_data.to_yaml
|
|
219
|
+
|
|
220
|
+
# Preserve existing schema header or add default one
|
|
221
|
+
yaml_content = preserve_schema_header(output_file, yaml_content)
|
|
222
|
+
|
|
223
|
+
File.write(output_file, yaml_content)
|
|
224
|
+
end
|
|
225
|
+
|
|
226
|
+
# Preserve existing schema header or add default one
|
|
227
|
+
def preserve_schema_header(original_file, yaml_content)
|
|
228
|
+
schema_header = nil
|
|
229
|
+
|
|
230
|
+
# Extract existing schema header if file exists
|
|
231
|
+
if File.exist?(original_file)
|
|
232
|
+
original_content = File.read(original_file)
|
|
233
|
+
if (match = original_content.match(/^# yaml-language-server: \$schema=.+$/))
|
|
234
|
+
schema_header = match[0]
|
|
235
|
+
end
|
|
236
|
+
end
|
|
237
|
+
|
|
238
|
+
# Remove any existing schema header from new content to avoid duplication
|
|
239
|
+
yaml_content = yaml_content.gsub(
|
|
240
|
+
/^# yaml-language-server: \$schema=.+$\n/, ""
|
|
241
|
+
)
|
|
242
|
+
|
|
243
|
+
# Add preserved or default schema header
|
|
244
|
+
if schema_header
|
|
245
|
+
"#{schema_header}\n#{yaml_content}"
|
|
246
|
+
else
|
|
247
|
+
entity_type = File.basename(original_file, ".yaml")
|
|
248
|
+
"# yaml-language-server: $schema=schemas/#{entity_type}-schema.yaml\n#{yaml_content}"
|
|
249
|
+
end
|
|
250
|
+
end
|
|
251
|
+
end
|
|
252
|
+
end
|
|
253
|
+
end
|
|
254
|
+
end
|
|
@@ -1,16 +1,15 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require_relative "base"
|
|
4
|
-
require_relative "../database"
|
|
5
|
-
require_relative "../errors"
|
|
6
|
-
require_relative "si_ttl_parser"
|
|
7
|
-
require_relative "si_formatter"
|
|
8
|
-
require_relative "si_matcher"
|
|
9
|
-
require_relative "si_updater"
|
|
10
|
-
|
|
11
3
|
module Unitsdb
|
|
12
4
|
module Commands
|
|
13
|
-
|
|
5
|
+
module CheckSi
|
|
6
|
+
autoload :SiTtlParser, "unitsdb/commands/check_si/si_ttl_parser"
|
|
7
|
+
autoload :SiMatcher, "unitsdb/commands/check_si/si_matcher"
|
|
8
|
+
autoload :SiFormatter, "unitsdb/commands/check_si/si_formatter"
|
|
9
|
+
autoload :SiUpdater, "unitsdb/commands/check_si/si_updater"
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
class CheckSiCommand < Base
|
|
14
13
|
# Constants
|
|
15
14
|
ENTITY_TYPES = %w[units quantities prefixes].freeze
|
|
16
15
|
|
|
@@ -32,91 +31,111 @@ module Unitsdb
|
|
|
32
31
|
@db = Unitsdb::Database.from_db(database_path)
|
|
33
32
|
|
|
34
33
|
puts "Using TTL directory: #{ttl_dir}"
|
|
35
|
-
puts "Include potential matches: #{include_potential ?
|
|
34
|
+
puts "Include potential matches: #{include_potential ? 'Yes' : 'No'}"
|
|
36
35
|
|
|
37
36
|
# Parse TTL files
|
|
38
|
-
graph = SiTtlParser.parse_ttl_files(ttl_dir)
|
|
37
|
+
graph = ::Unitsdb::Commands::CheckSi::SiTtlParser.parse_ttl_files(ttl_dir)
|
|
39
38
|
|
|
40
39
|
# Process entity types
|
|
41
|
-
process_entities(entity_type, graph, direction, output_dir,
|
|
40
|
+
process_entities(entity_type, graph, direction, output_dir,
|
|
41
|
+
include_potential)
|
|
42
42
|
end
|
|
43
43
|
|
|
44
44
|
private
|
|
45
45
|
|
|
46
46
|
# Process all entity types or a specific one
|
|
47
|
-
def process_entities(entity_type, graph, direction, output_dir,
|
|
47
|
+
def process_entities(entity_type, graph, direction, output_dir,
|
|
48
|
+
include_potential)
|
|
48
49
|
if entity_type && ENTITY_TYPES.include?(entity_type)
|
|
49
|
-
process_entity_type(entity_type, graph, direction, output_dir,
|
|
50
|
+
process_entity_type(entity_type, graph, direction, output_dir,
|
|
51
|
+
include_potential)
|
|
50
52
|
else
|
|
51
53
|
ENTITY_TYPES.each do |type|
|
|
52
|
-
process_entity_type(type, graph, direction, output_dir,
|
|
54
|
+
process_entity_type(type, graph, direction, output_dir,
|
|
55
|
+
include_potential)
|
|
53
56
|
end
|
|
54
57
|
end
|
|
55
58
|
end
|
|
56
59
|
|
|
57
60
|
# Process a specific entity type
|
|
58
|
-
def process_entity_type(entity_type, graph, direction, output_dir,
|
|
61
|
+
def process_entity_type(entity_type, graph, direction, output_dir,
|
|
62
|
+
include_potential = false)
|
|
59
63
|
puts "\n========== Processing #{entity_type.upcase} References ==========\n"
|
|
60
64
|
|
|
61
65
|
db_entities = @db.send(entity_type)
|
|
62
|
-
ttl_entities = SiTtlParser.extract_entities_from_ttl(
|
|
66
|
+
ttl_entities = ::Unitsdb::Commands::CheckSi::SiTtlParser.extract_entities_from_ttl(
|
|
67
|
+
entity_type, graph
|
|
68
|
+
)
|
|
63
69
|
|
|
64
70
|
puts "Found #{ttl_entities.size} #{entity_type} in SI digital framework"
|
|
65
71
|
puts "Found #{db_entities.size} #{entity_type} in database"
|
|
66
72
|
|
|
67
|
-
|
|
68
|
-
|
|
73
|
+
if %w[from_si
|
|
74
|
+
both].include?(direction)
|
|
75
|
+
check_from_si(entity_type, ttl_entities, db_entities, output_dir,
|
|
76
|
+
include_potential)
|
|
77
|
+
end
|
|
69
78
|
|
|
70
79
|
return unless %w[to_si both].include?(direction)
|
|
71
80
|
|
|
72
|
-
check_to_si(entity_type, ttl_entities, db_entities, output_dir,
|
|
81
|
+
check_to_si(entity_type, ttl_entities, db_entities, output_dir,
|
|
82
|
+
include_potential)
|
|
73
83
|
end
|
|
74
84
|
|
|
75
85
|
# Validation helpers
|
|
76
86
|
def validate_parameters(direction, ttl_dir)
|
|
77
87
|
unless %w[to_si from_si both].include?(direction)
|
|
78
|
-
|
|
79
|
-
|
|
88
|
+
raise Unitsdb::Errors::InvalidParameterError,
|
|
89
|
+
"Invalid direction '#{direction}': must be 'to_si', 'from_si', or 'both'"
|
|
80
90
|
end
|
|
81
91
|
|
|
82
92
|
return if Dir.exist?(ttl_dir)
|
|
83
93
|
|
|
84
|
-
|
|
85
|
-
|
|
94
|
+
raise Unitsdb::Errors::FileNotFoundError,
|
|
95
|
+
"TTL directory not found: #{ttl_dir}"
|
|
86
96
|
end
|
|
87
97
|
|
|
88
98
|
# Direction handler: TTL → DB
|
|
89
|
-
def check_from_si(entity_type, ttl_entities, db_entities, output_dir,
|
|
90
|
-
|
|
99
|
+
def check_from_si(entity_type, ttl_entities, db_entities, output_dir,
|
|
100
|
+
include_potential = false)
|
|
101
|
+
::Unitsdb::Commands::CheckSi::SiFormatter.print_direction_header("SI → UnitsDB")
|
|
91
102
|
|
|
92
|
-
matches, missing_matches, unmatched_ttl = SiMatcher.match_ttl_to_db(
|
|
103
|
+
matches, missing_matches, unmatched_ttl = ::Unitsdb::Commands::CheckSi::SiMatcher.match_ttl_to_db(
|
|
104
|
+
entity_type, ttl_entities, db_entities
|
|
105
|
+
)
|
|
93
106
|
|
|
94
107
|
# Print results
|
|
95
|
-
SiFormatter.display_si_results(entity_type, matches, missing_matches,
|
|
108
|
+
::Unitsdb::Commands::CheckSi::SiFormatter.display_si_results(entity_type, matches, missing_matches,
|
|
109
|
+
unmatched_ttl)
|
|
96
110
|
|
|
97
111
|
# Update references if needed
|
|
98
112
|
return unless output_dir && !missing_matches.empty?
|
|
99
113
|
|
|
100
114
|
output_file = File.join(output_dir, "#{entity_type}.yaml")
|
|
101
|
-
SiUpdater.update_references(entity_type, missing_matches, db_entities, output_file, include_potential,
|
|
102
|
-
|
|
115
|
+
::Unitsdb::Commands::CheckSi::SiUpdater.update_references(entity_type, missing_matches, db_entities, output_file, include_potential,
|
|
116
|
+
database_path)
|
|
103
117
|
puts "\nUpdated references written to #{output_file}"
|
|
104
118
|
end
|
|
105
119
|
|
|
106
120
|
# Direction handler: DB → TTL
|
|
107
|
-
def check_to_si(entity_type, ttl_entities, db_entities, output_dir,
|
|
108
|
-
|
|
121
|
+
def check_to_si(entity_type, ttl_entities, db_entities, output_dir,
|
|
122
|
+
include_potential = false)
|
|
123
|
+
::Unitsdb::Commands::CheckSi::SiFormatter.print_direction_header("UnitsDB → SI")
|
|
109
124
|
|
|
110
|
-
matches, missing_refs, unmatched_db = SiMatcher.match_db_to_ttl(
|
|
125
|
+
matches, missing_refs, unmatched_db = ::Unitsdb::Commands::CheckSi::SiMatcher.match_db_to_ttl(
|
|
126
|
+
entity_type, ttl_entities, db_entities
|
|
127
|
+
)
|
|
111
128
|
|
|
112
129
|
# Print results
|
|
113
|
-
SiFormatter.display_db_results(entity_type, matches, missing_refs,
|
|
130
|
+
::Unitsdb::Commands::CheckSi::SiFormatter.display_db_results(entity_type, matches, missing_refs,
|
|
131
|
+
unmatched_db)
|
|
114
132
|
|
|
115
133
|
# Update references if needed
|
|
116
134
|
return unless output_dir && !missing_refs.empty?
|
|
117
135
|
|
|
118
136
|
output_file = File.join(output_dir, "#{entity_type}.yaml")
|
|
119
|
-
SiUpdater.update_db_references(entity_type, missing_refs, output_file,
|
|
137
|
+
::Unitsdb::Commands::CheckSi::SiUpdater.update_db_references(entity_type, missing_refs, output_file,
|
|
138
|
+
include_potential, @options[:database])
|
|
120
139
|
puts "\nUpdated references written to #{output_file}"
|
|
121
140
|
end
|
|
122
141
|
end
|
data/lib/unitsdb/commands/get.rb
CHANGED
|
@@ -1,8 +1,6 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require_relative "base"
|
|
4
3
|
require "json"
|
|
5
|
-
require_relative "../errors"
|
|
6
4
|
|
|
7
5
|
module Unitsdb
|
|
8
6
|
module Commands
|
|
@@ -29,19 +27,18 @@ module Unitsdb
|
|
|
29
27
|
puts entity.send("to_#{format.downcase}")
|
|
30
28
|
return
|
|
31
29
|
rescue NoMethodError
|
|
32
|
-
|
|
33
|
-
|
|
30
|
+
raise Unitsdb::Errors::InvalidFormatError,
|
|
31
|
+
"Unable to convert entity to #{format.upcase} format: output format not supported for this entity type"
|
|
34
32
|
end
|
|
35
33
|
end
|
|
36
34
|
|
|
37
35
|
# Default text output
|
|
38
36
|
print_entity_details(entity)
|
|
39
37
|
rescue Unitsdb::Errors::DatabaseError => e
|
|
40
|
-
|
|
41
|
-
|
|
38
|
+
raise Unitsdb::Errors::DatabaseLoadError,
|
|
39
|
+
"Failed to load database: #{e.message}"
|
|
42
40
|
rescue StandardError => e
|
|
43
|
-
|
|
44
|
-
exit(1)
|
|
41
|
+
raise Unitsdb::Errors::CLIRuntimeError, "Search failed: #{e.message}"
|
|
45
42
|
end
|
|
46
43
|
end
|
|
47
44
|
|
|
@@ -65,7 +62,7 @@ module Unitsdb
|
|
|
65
62
|
if entity.identifiers&.any?
|
|
66
63
|
puts " - Identifiers:"
|
|
67
64
|
entity.identifiers.each do |id|
|
|
68
|
-
puts " - #{id.id} (Type: #{id.type ||
|
|
65
|
+
puts " - #{id.id} (Type: #{id.type || 'N/A'})"
|
|
69
66
|
end
|
|
70
67
|
else
|
|
71
68
|
puts " - Identifiers: None"
|
|
@@ -75,7 +72,11 @@ module Unitsdb
|
|
|
75
72
|
case entity
|
|
76
73
|
when Unitsdb::Unit
|
|
77
74
|
puts " - Symbols:" if entity.respond_to?(:symbols) && entity.symbols&.any?
|
|
78
|
-
|
|
75
|
+
if entity.respond_to?(:symbols) && entity.symbols&.any?
|
|
76
|
+
entity.symbols.each do |s|
|
|
77
|
+
puts " - #{s}"
|
|
78
|
+
end
|
|
79
|
+
end
|
|
79
80
|
|
|
80
81
|
puts " - Definition: #{entity.definition}" if entity.respond_to?(:definition) && entity.definition
|
|
81
82
|
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require_relative "base"
|
|
4
3
|
require "yaml"
|
|
5
4
|
|
|
6
5
|
module Unitsdb
|
|
@@ -8,8 +7,8 @@ module Unitsdb
|
|
|
8
7
|
class Normalize < Base
|
|
9
8
|
def run(input = nil, output = nil)
|
|
10
9
|
unless @options[:all] || (input && output)
|
|
11
|
-
|
|
12
|
-
|
|
10
|
+
raise Unitsdb::Errors::InvalidParameterError,
|
|
11
|
+
"INPUT and OUTPUT files are required unless --all flag is specified"
|
|
13
12
|
end
|
|
14
13
|
|
|
15
14
|
if @options[:all]
|
|
@@ -32,8 +31,18 @@ module Unitsdb
|
|
|
32
31
|
private
|
|
33
32
|
|
|
34
33
|
def normalize_file(input, output)
|
|
34
|
+
# Read the original file content to check for schema comment
|
|
35
|
+
file_content = File.read(input)
|
|
36
|
+
|
|
37
|
+
# Check if the first line is a yaml-language-server schema comment
|
|
38
|
+
schema_comment = nil
|
|
39
|
+
lines = file_content.lines
|
|
40
|
+
if lines.first&.start_with?("# yaml-language-server:")
|
|
41
|
+
schema_comment = lines.first.chomp
|
|
42
|
+
end
|
|
43
|
+
|
|
35
44
|
# Load the original YAML to work with
|
|
36
|
-
yaml = YAML.safe_load(
|
|
45
|
+
yaml = YAML.safe_load(file_content)
|
|
37
46
|
|
|
38
47
|
# For schema 2.0.0, we need to handle the schema_version and the main collection key
|
|
39
48
|
if yaml.key?("schema_version") && yaml["schema_version"] == "2.0.0"
|
|
@@ -47,7 +56,8 @@ module Unitsdb
|
|
|
47
56
|
when "nist", "unitsml"
|
|
48
57
|
# Sort by ID (nist or unitsml)
|
|
49
58
|
id_type = @options[:sort]
|
|
50
|
-
yaml[collection_key] =
|
|
59
|
+
yaml[collection_key] =
|
|
60
|
+
sort_by_id_type(yaml[collection_key], id_type)
|
|
51
61
|
else # default to "short"
|
|
52
62
|
# Use the existing sort_yaml_keys method for default sorting
|
|
53
63
|
yaml[collection_key] = Unitsdb::Utils.sort_yaml_keys(yaml[collection_key])
|
|
@@ -58,8 +68,12 @@ module Unitsdb
|
|
|
58
68
|
yaml = Unitsdb::Utils.sort_yaml_keys(yaml)
|
|
59
69
|
end
|
|
60
70
|
|
|
61
|
-
# Write the normalized output
|
|
62
|
-
|
|
71
|
+
# Write the normalized output, preserving schema comment if present
|
|
72
|
+
output_content = yaml.to_yaml
|
|
73
|
+
if schema_comment
|
|
74
|
+
output_content = "#{schema_comment}\n#{output_content}"
|
|
75
|
+
end
|
|
76
|
+
File.write(output, output_content)
|
|
63
77
|
end
|
|
64
78
|
|
|
65
79
|
# Sort collection items by a specific ID type (nist or unitsml)
|
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "fileutils"
|
|
4
|
+
|
|
5
|
+
module Unitsdb
|
|
6
|
+
module Commands
|
|
7
|
+
module Qudt
|
|
8
|
+
class Check < Base
|
|
9
|
+
# Constants
|
|
10
|
+
ENTITY_TYPES = %w[units quantities dimensions unit_systems
|
|
11
|
+
prefixes].freeze
|
|
12
|
+
|
|
13
|
+
def run
|
|
14
|
+
# Get options
|
|
15
|
+
entity_type = @options[:entity_type]&.downcase
|
|
16
|
+
direction = @options[:direction]&.downcase || "both"
|
|
17
|
+
output_dir = @options[:output_updated_database]
|
|
18
|
+
include_potential = @options[:include_potential_matches] || false
|
|
19
|
+
database_path = @options[:database]
|
|
20
|
+
ttl_dir = @options[:ttl_dir]
|
|
21
|
+
source_type = ttl_dir ? :file : :url
|
|
22
|
+
|
|
23
|
+
# Validate parameters
|
|
24
|
+
validate_parameters(direction, ttl_dir, source_type)
|
|
25
|
+
|
|
26
|
+
# Use the path as-is without expansion
|
|
27
|
+
puts "Using database directory: #{database_path}"
|
|
28
|
+
|
|
29
|
+
@db = Unitsdb::Database.from_db(database_path)
|
|
30
|
+
|
|
31
|
+
if source_type == :file
|
|
32
|
+
puts "Using QUDT TTL directory: #{ttl_dir}"
|
|
33
|
+
else
|
|
34
|
+
puts "Downloading QUDT vocabularies from online sources"
|
|
35
|
+
end
|
|
36
|
+
puts "Include potential matches: #{include_potential ? 'Yes' : 'No'}"
|
|
37
|
+
|
|
38
|
+
# Parse QUDT vocabularies
|
|
39
|
+
qudt_data = TtlParser.parse_qudt_vocabularies(
|
|
40
|
+
source_type: source_type, ttl_dir: ttl_dir,
|
|
41
|
+
)
|
|
42
|
+
|
|
43
|
+
# Process entity types
|
|
44
|
+
process_entities(entity_type, qudt_data, direction, output_dir,
|
|
45
|
+
include_potential)
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
private
|
|
49
|
+
|
|
50
|
+
# Process all entity types or a specific one
|
|
51
|
+
def process_entities(entity_type, qudt_data, direction, output_dir,
|
|
52
|
+
include_potential)
|
|
53
|
+
if entity_type && ENTITY_TYPES.include?(entity_type)
|
|
54
|
+
process_entity_type(entity_type, qudt_data, direction, output_dir,
|
|
55
|
+
include_potential)
|
|
56
|
+
else
|
|
57
|
+
ENTITY_TYPES.each do |type|
|
|
58
|
+
process_entity_type(type, qudt_data, direction, output_dir,
|
|
59
|
+
include_potential)
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
# Process a specific entity type
|
|
65
|
+
def process_entity_type(entity_type, qudt_data, direction, output_dir,
|
|
66
|
+
include_potential = false)
|
|
67
|
+
puts "\n========== Processing #{entity_type.upcase} References ==========\n"
|
|
68
|
+
|
|
69
|
+
db_entities = @db.send(entity_type)
|
|
70
|
+
qudt_entities = TtlParser.get_entities_from_qudt(entity_type,
|
|
71
|
+
qudt_data)
|
|
72
|
+
|
|
73
|
+
puts "Found #{qudt_entities.size} #{entity_type} in QUDT"
|
|
74
|
+
puts "Found #{db_entities.size} #{entity_type} in database"
|
|
75
|
+
|
|
76
|
+
if %w[from_qudt
|
|
77
|
+
both].include?(direction)
|
|
78
|
+
check_from_qudt(entity_type, qudt_entities, db_entities, output_dir,
|
|
79
|
+
include_potential)
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
return unless %w[to_qudt both].include?(direction)
|
|
83
|
+
|
|
84
|
+
check_to_qudt(entity_type, qudt_entities, db_entities, output_dir,
|
|
85
|
+
include_potential)
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
# Validation helpers
|
|
89
|
+
def validate_parameters(direction, ttl_dir, source_type)
|
|
90
|
+
unless %w[to_qudt from_qudt both].include?(direction)
|
|
91
|
+
raise Unitsdb::Errors::InvalidParameterError,
|
|
92
|
+
"Invalid direction '#{direction}': must be 'to_qudt', 'from_qudt', or 'both'"
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
return unless source_type == :file && ttl_dir && !Dir.exist?(ttl_dir)
|
|
96
|
+
|
|
97
|
+
raise Unitsdb::Errors::FileNotFoundError,
|
|
98
|
+
"TTL directory not found: #{ttl_dir}"
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
# Direction handler: QUDT → UnitsDB
|
|
102
|
+
def check_from_qudt(entity_type, qudt_entities, db_entities,
|
|
103
|
+
output_dir, include_potential = false)
|
|
104
|
+
Formatter.print_direction_header("QUDT → UnitsDB")
|
|
105
|
+
|
|
106
|
+
matches, missing_matches, unmatched_qudt = Matcher.match_qudt_to_db(
|
|
107
|
+
entity_type, qudt_entities, db_entities
|
|
108
|
+
)
|
|
109
|
+
|
|
110
|
+
# Print results
|
|
111
|
+
Formatter.display_qudt_results(entity_type, matches, missing_matches,
|
|
112
|
+
unmatched_qudt)
|
|
113
|
+
|
|
114
|
+
# Display detailed missing QUDT entities analysis
|
|
115
|
+
Formatter.display_missing_qudt_entities(entity_type, unmatched_qudt)
|
|
116
|
+
|
|
117
|
+
# Update references if needed
|
|
118
|
+
return unless output_dir && !missing_matches.empty?
|
|
119
|
+
|
|
120
|
+
output_file = File.join(output_dir, "#{entity_type}.yaml")
|
|
121
|
+
Updater.update_references(entity_type, missing_matches, db_entities,
|
|
122
|
+
output_file, include_potential)
|
|
123
|
+
puts "\nUpdated references written to #{output_file}"
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
# Direction handler: UnitsDB → QUDT
|
|
127
|
+
def check_to_qudt(entity_type, qudt_entities, db_entities, output_dir,
|
|
128
|
+
include_potential = false)
|
|
129
|
+
Formatter.print_direction_header("UnitsDB → QUDT")
|
|
130
|
+
|
|
131
|
+
matches, missing_refs, unmatched_db = Matcher.match_db_to_qudt(
|
|
132
|
+
entity_type, qudt_entities, db_entities
|
|
133
|
+
)
|
|
134
|
+
|
|
135
|
+
# Print results
|
|
136
|
+
Formatter.display_db_results(entity_type, matches, missing_refs,
|
|
137
|
+
unmatched_db)
|
|
138
|
+
|
|
139
|
+
# Update references if needed
|
|
140
|
+
return unless output_dir && !missing_refs.empty?
|
|
141
|
+
|
|
142
|
+
output_file = File.join(output_dir, "#{entity_type}.yaml")
|
|
143
|
+
Updater.update_references(entity_type, missing_refs, db_entities,
|
|
144
|
+
output_file, include_potential)
|
|
145
|
+
puts "\nUpdated references written to #{output_file}"
|
|
146
|
+
end
|
|
147
|
+
end
|
|
148
|
+
end
|
|
149
|
+
end
|
|
150
|
+
end
|