unitsdb 2.1.0 → 2.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.github/workflows/release.yml +8 -1
- data/.gitignore +2 -0
- data/.gitmodules +4 -3
- data/.rubocop.yml +13 -8
- data/.rubocop_todo.yml +247 -53
- data/CLAUDE.md +55 -0
- data/Gemfile +5 -1
- data/README.adoc +385 -12
- data/data/dimensions.yaml +1864 -0
- data/data/prefixes.yaml +874 -0
- data/data/quantities.yaml +3715 -0
- data/data/scales.yaml +97 -0
- data/data/schemas/dimensions-schema.yaml +153 -0
- data/data/schemas/prefixes-schema.yaml +155 -0
- data/data/schemas/quantities-schema.yaml +117 -0
- data/data/schemas/scales-schema.yaml +106 -0
- data/data/schemas/unit_systems-schema.yaml +116 -0
- data/data/schemas/units-schema.yaml +215 -0
- data/data/unit_systems.yaml +78 -0
- data/data/units.yaml +14052 -0
- data/exe/unitsdb +7 -1
- data/lib/unitsdb/cli.rb +47 -13
- data/lib/unitsdb/commands/_modify.rb +41 -5
- data/lib/unitsdb/commands/base.rb +10 -32
- data/lib/unitsdb/commands/check_si/si_formatter.rb +488 -0
- data/lib/unitsdb/commands/check_si/si_matcher.rb +487 -0
- data/lib/unitsdb/commands/check_si/si_ttl_parser.rb +103 -0
- data/lib/unitsdb/commands/check_si/si_updater.rb +254 -0
- data/lib/unitsdb/commands/check_si.rb +54 -35
- data/lib/unitsdb/commands/get.rb +11 -10
- data/lib/unitsdb/commands/normalize.rb +21 -7
- data/lib/unitsdb/commands/qudt/check.rb +150 -0
- data/lib/unitsdb/commands/qudt/formatter.rb +194 -0
- data/lib/unitsdb/commands/qudt/matcher.rb +746 -0
- data/lib/unitsdb/commands/qudt/ttl_parser.rb +403 -0
- data/lib/unitsdb/commands/qudt/update.rb +126 -0
- data/lib/unitsdb/commands/qudt/updater.rb +189 -0
- data/lib/unitsdb/commands/qudt.rb +82 -0
- data/lib/unitsdb/commands/release.rb +50 -86
- data/lib/unitsdb/commands/search.rb +12 -11
- data/lib/unitsdb/commands/ucum/check.rb +139 -0
- data/lib/unitsdb/commands/ucum/formatter.rb +142 -0
- data/lib/unitsdb/commands/ucum/matcher.rb +315 -0
- data/lib/unitsdb/commands/ucum/update.rb +85 -0
- data/lib/unitsdb/commands/ucum/updater.rb +132 -0
- data/lib/unitsdb/commands/ucum/xml_parser.rb +32 -0
- data/lib/unitsdb/commands/ucum.rb +83 -0
- data/lib/unitsdb/commands/validate/identifiers.rb +3 -3
- data/lib/unitsdb/commands/validate/qudt_references.rb +111 -0
- data/lib/unitsdb/commands/validate/references.rb +37 -18
- data/lib/unitsdb/commands/validate/si_references.rb +3 -11
- data/lib/unitsdb/commands/validate/ucum_references.rb +105 -0
- data/lib/unitsdb/commands/validate.rb +67 -11
- data/lib/unitsdb/commands.rb +20 -0
- data/lib/unitsdb/database.rb +91 -52
- data/lib/unitsdb/dimension.rb +1 -4
- data/lib/unitsdb/dimension_details.rb +0 -1
- data/lib/unitsdb/dimensions.rb +1 -2
- data/lib/unitsdb/errors.rb +7 -0
- data/lib/unitsdb/prefix.rb +0 -4
- data/lib/unitsdb/prefix_reference.rb +0 -2
- data/lib/unitsdb/prefixes.rb +1 -1
- data/lib/unitsdb/quantities.rb +1 -2
- data/lib/unitsdb/quantity.rb +0 -6
- data/lib/unitsdb/qudt.rb +100 -0
- data/lib/unitsdb/root_unit_reference.rb +0 -3
- data/lib/unitsdb/scale.rb +0 -4
- data/lib/unitsdb/scale_reference.rb +0 -2
- data/lib/unitsdb/scales.rb +1 -2
- data/lib/unitsdb/si_derived_base.rb +0 -2
- data/lib/unitsdb/ucum.rb +202 -0
- data/lib/unitsdb/unit.rb +0 -10
- data/lib/unitsdb/unit_reference.rb +0 -2
- data/lib/unitsdb/unit_system.rb +1 -3
- data/lib/unitsdb/unit_system_reference.rb +0 -2
- data/lib/unitsdb/unit_systems.rb +1 -2
- data/lib/unitsdb/units.rb +1 -2
- data/lib/unitsdb/utils.rb +32 -21
- data/lib/unitsdb/version.rb +5 -1
- data/lib/unitsdb.rb +62 -14
- data/unitsdb.gemspec +6 -5
- metadata +60 -13
- data/lib/unitsdb/commands/si_formatter.rb +0 -485
- data/lib/unitsdb/commands/si_matcher.rb +0 -470
- data/lib/unitsdb/commands/si_ttl_parser.rb +0 -100
- data/lib/unitsdb/commands/si_updater.rb +0 -212
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "yaml"
|
|
4
|
+
require "fileutils"
|
|
5
|
+
|
|
6
|
+
module Unitsdb
|
|
7
|
+
module Commands
|
|
8
|
+
module Qudt
|
|
9
|
+
# Updater for adding QUDT references to UnitsDB entities
|
|
10
|
+
module Updater
|
|
11
|
+
QUDT_AUTHORITY = "qudt"
|
|
12
|
+
|
|
13
|
+
module_function
|
|
14
|
+
|
|
15
|
+
# Update references in UnitsDB entities with QUDT references
|
|
16
|
+
def update_references(entity_type, matches, db_entities, output_file,
|
|
17
|
+
include_potential = false)
|
|
18
|
+
puts "Updating QUDT references for #{entity_type}..."
|
|
19
|
+
|
|
20
|
+
# Get the original YAML file path from the database entities
|
|
21
|
+
original_yaml_file = get_original_yaml_file(db_entities, output_file)
|
|
22
|
+
|
|
23
|
+
# Load the original YAML file as plain data structures
|
|
24
|
+
yaml_content = File.read(original_yaml_file)
|
|
25
|
+
output_data = YAML.safe_load(yaml_content)
|
|
26
|
+
|
|
27
|
+
# Create a map of entity IDs to their QUDT references
|
|
28
|
+
entity_references = {}
|
|
29
|
+
|
|
30
|
+
# Process each match
|
|
31
|
+
matches.each do |match|
|
|
32
|
+
db_entity = match[:db_entity]
|
|
33
|
+
qudt_entity = match[:qudt_entity]
|
|
34
|
+
|
|
35
|
+
# Skip potential matches unless specified
|
|
36
|
+
next if match[:potential] && !include_potential
|
|
37
|
+
|
|
38
|
+
# Skip if entity has been manually verified
|
|
39
|
+
if manually_verified?(db_entity)
|
|
40
|
+
puts "Skipping manually verified entity: #{get_entity_id(db_entity)}"
|
|
41
|
+
next
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
# Get entity ID
|
|
45
|
+
entity_id = get_entity_id(db_entity)
|
|
46
|
+
next unless entity_id
|
|
47
|
+
|
|
48
|
+
# Store reference data as plain hash
|
|
49
|
+
entity_references[entity_id] = {
|
|
50
|
+
"uri" => qudt_entity.uri,
|
|
51
|
+
"type" => "informative",
|
|
52
|
+
"authority" => QUDT_AUTHORITY,
|
|
53
|
+
}
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
# Update the YAML content
|
|
57
|
+
output_data[entity_type].each do |entity_yaml|
|
|
58
|
+
# Find entity by ID
|
|
59
|
+
entity_id = if entity_yaml["identifiers"]
|
|
60
|
+
begin
|
|
61
|
+
entity_yaml["identifiers"].first["id"]
|
|
62
|
+
rescue StandardError
|
|
63
|
+
nil
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
next unless entity_id && entity_references.key?(entity_id)
|
|
68
|
+
|
|
69
|
+
# Initialize references array if it doesn't exist
|
|
70
|
+
entity_yaml["references"] ||= []
|
|
71
|
+
|
|
72
|
+
# Add new references
|
|
73
|
+
if (ext_ref = entity_references[entity_id])
|
|
74
|
+
if entity_yaml["references"].any? do |ref|
|
|
75
|
+
ref["uri"] == ext_ref["uri"] && ref["authority"] == ext_ref["authority"]
|
|
76
|
+
end
|
|
77
|
+
# Skip if reference already exists
|
|
78
|
+
puts "Reference already exists for entity ID: #{entity_id}"
|
|
79
|
+
else
|
|
80
|
+
# Add the reference
|
|
81
|
+
puts "Adding reference for entity ID: #{entity_id}, URI: #{ext_ref['uri']}, Authority: #{ext_ref['authority']}"
|
|
82
|
+
entity_yaml["references"] << ext_ref
|
|
83
|
+
end
|
|
84
|
+
end
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
# Write to YAML file
|
|
88
|
+
write_yaml_file(output_file, output_data)
|
|
89
|
+
|
|
90
|
+
puts "Added #{entity_references.values.size} QUDT references to #{entity_type}"
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
# Helper to write YAML file
|
|
94
|
+
def write_yaml_file(output_file, output_data)
|
|
95
|
+
# Ensure the output directory exists
|
|
96
|
+
output_dir = File.dirname(output_file)
|
|
97
|
+
FileUtils.mkdir_p(output_dir)
|
|
98
|
+
|
|
99
|
+
# Write to YAML file with proper formatting
|
|
100
|
+
yaml_content = output_data.to_yaml
|
|
101
|
+
|
|
102
|
+
# Preserve existing schema header or add default one
|
|
103
|
+
yaml_content = preserve_schema_header(output_file, yaml_content)
|
|
104
|
+
|
|
105
|
+
File.write(output_file, yaml_content)
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
# Preserve existing schema header or add default one
|
|
109
|
+
def preserve_schema_header(original_file, yaml_content)
|
|
110
|
+
schema_header = nil
|
|
111
|
+
|
|
112
|
+
# Extract existing schema header if file exists
|
|
113
|
+
if File.exist?(original_file)
|
|
114
|
+
original_content = File.read(original_file)
|
|
115
|
+
if (match = original_content.match(/^# yaml-language-server: \$schema=.+$/))
|
|
116
|
+
schema_header = match[0]
|
|
117
|
+
end
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
# Remove any existing schema header from new content to avoid duplication
|
|
121
|
+
yaml_content = yaml_content.gsub(
|
|
122
|
+
/^# yaml-language-server: \$schema=.+$\n/, ""
|
|
123
|
+
)
|
|
124
|
+
|
|
125
|
+
# Add preserved or default schema header
|
|
126
|
+
if schema_header
|
|
127
|
+
"#{schema_header}\n#{yaml_content}"
|
|
128
|
+
else
|
|
129
|
+
entity_type = File.basename(original_file, ".yaml")
|
|
130
|
+
"# yaml-language-server: $schema=schemas/#{entity_type}-schema.yaml\n#{yaml_content}"
|
|
131
|
+
end
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
# Get the original YAML file path
|
|
135
|
+
def get_original_yaml_file(_db_entities, output_file)
|
|
136
|
+
# The database path should be available from the update command
|
|
137
|
+
# We need to construct the path to the original YAML file
|
|
138
|
+
entity_type = File.basename(output_file, ".yaml")
|
|
139
|
+
|
|
140
|
+
# Try to find the original file in the database directory
|
|
141
|
+
# Look for it relative to where we expect it to be
|
|
142
|
+
# nil
|
|
143
|
+
|
|
144
|
+
# Try to get database directory from environment or assume it's the fixtures
|
|
145
|
+
database_dir = ENV["UNITSDB_DATABASE_PATH"] ||
|
|
146
|
+
File.join(File.dirname(__FILE__),
|
|
147
|
+
"../../../data")
|
|
148
|
+
|
|
149
|
+
original_yaml_file = File.join(database_dir, "#{entity_type}.yaml")
|
|
150
|
+
|
|
151
|
+
# If that doesn't exist, try to find it relative to the current working directory
|
|
152
|
+
unless File.exist?(original_yaml_file)
|
|
153
|
+
original_yaml_file = File.join("data",
|
|
154
|
+
"#{entity_type}.yaml")
|
|
155
|
+
end
|
|
156
|
+
|
|
157
|
+
# If still not found, create a fallback
|
|
158
|
+
unless File.exist?(original_yaml_file)
|
|
159
|
+
puts "Warning: Could not find original YAML file. Creating empty template."
|
|
160
|
+
original_yaml_file = output_file
|
|
161
|
+
FileUtils.mkdir_p(File.dirname(original_yaml_file))
|
|
162
|
+
File.write(original_yaml_file, { entity_type => [] }.to_yaml)
|
|
163
|
+
end
|
|
164
|
+
|
|
165
|
+
puts "Using original YAML file: #{original_yaml_file}"
|
|
166
|
+
original_yaml_file
|
|
167
|
+
end
|
|
168
|
+
|
|
169
|
+
# Get entity ID (either from identifiers array or directly)
|
|
170
|
+
def get_entity_id(entity)
|
|
171
|
+
if entity.respond_to?(:identifiers) && entity.identifiers && !entity.identifiers.empty?
|
|
172
|
+
entity.identifiers.first.id
|
|
173
|
+
elsif entity.respond_to?(:id)
|
|
174
|
+
entity.id
|
|
175
|
+
end
|
|
176
|
+
end
|
|
177
|
+
|
|
178
|
+
# Check if an entity has been manually verified (has a special flag)
|
|
179
|
+
def manually_verified?(entity)
|
|
180
|
+
return false unless entity.respond_to?(:references) && entity.references
|
|
181
|
+
|
|
182
|
+
entity.references.any? do |ref|
|
|
183
|
+
ref.authority == QUDT_AUTHORITY && ref.respond_to?(:verified) && ref.verified
|
|
184
|
+
end
|
|
185
|
+
end
|
|
186
|
+
end
|
|
187
|
+
end
|
|
188
|
+
end
|
|
189
|
+
end
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "thor"
|
|
4
|
+
|
|
5
|
+
module Unitsdb
|
|
6
|
+
module Commands
|
|
7
|
+
module Qudt
|
|
8
|
+
autoload :Check, "unitsdb/commands/qudt/check"
|
|
9
|
+
autoload :Formatter, "unitsdb/commands/qudt/formatter"
|
|
10
|
+
autoload :TtlParser, "unitsdb/commands/qudt/ttl_parser"
|
|
11
|
+
autoload :Update, "unitsdb/commands/qudt/update"
|
|
12
|
+
autoload :Updater, "unitsdb/commands/qudt/updater"
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
class QudtCommand < Thor
|
|
16
|
+
# Inherit trace option from parent CLI
|
|
17
|
+
class_option :trace, type: :boolean, default: false,
|
|
18
|
+
desc: "Show full backtrace on error"
|
|
19
|
+
|
|
20
|
+
desc "check", "Check QUDT references in UnitsDB"
|
|
21
|
+
option :entity_type, type: :string, aliases: "-e",
|
|
22
|
+
desc: "Entity type to check (units, quantities, dimensions, unit_systems). If not specified, all types are checked"
|
|
23
|
+
option :ttl_dir, type: :string, aliases: "-t",
|
|
24
|
+
desc: "Path to directory containing QUDT TTL files. If not specified, vocabularies will be downloaded from online sources"
|
|
25
|
+
option :output_updated_database, type: :string, aliases: "-o",
|
|
26
|
+
desc: "Directory path to write updated YAML files with added QUDT references"
|
|
27
|
+
option :direction, type: :string, default: "both", aliases: "-r",
|
|
28
|
+
desc: "Direction to check: 'to_qudt' (UnitsDB→QUDT), 'from_qudt' (QUDT→UnitsDB), or 'both'"
|
|
29
|
+
option :include_potential_matches, type: :boolean, default: false, aliases: "-p",
|
|
30
|
+
desc: "Include potential matches when updating references (default: false)"
|
|
31
|
+
option :database, type: :string, required: true, aliases: "-d",
|
|
32
|
+
desc: "Path to UnitsDB database (required)"
|
|
33
|
+
def check
|
|
34
|
+
run_command(Qudt::Check, options)
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
desc "update", "Update UnitsDB with QUDT references"
|
|
38
|
+
option :entity_type, type: :string, aliases: "-e",
|
|
39
|
+
desc: "Entity type to update (units, quantities, dimensions, unit_systems). If not specified, all types are updated"
|
|
40
|
+
option :ttl_dir, type: :string, aliases: "-t",
|
|
41
|
+
desc: "Path to directory containing QUDT TTL files. If not specified, vocabularies will be downloaded from online sources"
|
|
42
|
+
option :output_dir, type: :string, aliases: "-o",
|
|
43
|
+
desc: "Directory path to write updated YAML files (defaults to database path)"
|
|
44
|
+
option :include_potential_matches, type: :boolean, default: false, aliases: "-p",
|
|
45
|
+
desc: "Include potential matches when updating references (default: false)"
|
|
46
|
+
option :database, type: :string, required: true, aliases: "-d",
|
|
47
|
+
desc: "Path to UnitsDB database (required)"
|
|
48
|
+
def update
|
|
49
|
+
run_command(Qudt::Update, options)
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
private
|
|
53
|
+
|
|
54
|
+
def run_command(command_class, options)
|
|
55
|
+
command = command_class.new(options)
|
|
56
|
+
command.run
|
|
57
|
+
rescue Unitsdb::Errors::CLIRuntimeError => e
|
|
58
|
+
handle_cli_error(e)
|
|
59
|
+
rescue StandardError => e
|
|
60
|
+
handle_error(e)
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
def handle_cli_error(error)
|
|
64
|
+
if options[:trace]
|
|
65
|
+
raise error
|
|
66
|
+
else
|
|
67
|
+
warn "Error: #{error.message}"
|
|
68
|
+
exit 1
|
|
69
|
+
end
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
def handle_error(error)
|
|
73
|
+
if options[:trace]
|
|
74
|
+
raise error
|
|
75
|
+
else
|
|
76
|
+
warn "Error: #{error.message}"
|
|
77
|
+
exit 1
|
|
78
|
+
end
|
|
79
|
+
end
|
|
80
|
+
end
|
|
81
|
+
end
|
|
82
|
+
end
|
|
@@ -1,111 +1,75 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require_relative "base"
|
|
4
3
|
require "yaml"
|
|
5
4
|
require "zip"
|
|
6
5
|
require "fileutils"
|
|
7
6
|
|
|
8
7
|
module Unitsdb
|
|
9
8
|
module Commands
|
|
10
|
-
class Release < Base
|
|
9
|
+
class Release < ::Unitsdb::Commands::Base
|
|
11
10
|
def run
|
|
12
|
-
#
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
if missing_files.any?
|
|
17
|
-
puts "Error: The following required files are missing:"
|
|
18
|
-
missing_files.each { |f| puts " - #{f}" }
|
|
19
|
-
exit(1)
|
|
20
|
-
end
|
|
21
|
-
|
|
22
|
-
# Extract schema version from any file (they should all have the same version)
|
|
23
|
-
first_yaml = load_yaml(yaml_files.first)
|
|
24
|
-
schema_version = first_yaml["schema_version"]
|
|
25
|
-
|
|
26
|
-
unless schema_version
|
|
27
|
-
puts "Error: Could not determine schema version from #{yaml_files.first}"
|
|
28
|
-
exit(1)
|
|
29
|
-
end
|
|
30
|
-
|
|
31
|
-
# Verify all files have the same schema version
|
|
32
|
-
inconsistent_files = []
|
|
33
|
-
yaml_files.each do |file|
|
|
34
|
-
yaml = load_yaml(file)
|
|
35
|
-
next unless yaml["schema_version"] != schema_version
|
|
36
|
-
|
|
37
|
-
inconsistent_files << {
|
|
38
|
-
file: file,
|
|
39
|
-
version: yaml["schema_version"]
|
|
40
|
-
}
|
|
41
|
-
end
|
|
42
|
-
|
|
43
|
-
if inconsistent_files.any?
|
|
44
|
-
puts "Error: Inconsistent schema versions detected:"
|
|
45
|
-
puts " Expected version: #{schema_version}"
|
|
46
|
-
inconsistent_files.each do |info|
|
|
47
|
-
puts " - #{info[:file]}: #{info[:version]}"
|
|
48
|
-
end
|
|
49
|
-
exit(1)
|
|
50
|
-
end
|
|
11
|
+
# Load the database
|
|
12
|
+
db = load_database(@options[:database])
|
|
13
|
+
db.version = @options[:version]
|
|
51
14
|
|
|
52
15
|
# Create output directory if it doesn't exist
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
when "
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
16
|
+
FileUtils.mkdir_p(@options[:output_dir])
|
|
17
|
+
|
|
18
|
+
# Generate release files based on format option
|
|
19
|
+
format = (@options[:format] || "all").downcase
|
|
20
|
+
case format
|
|
21
|
+
when "yaml"
|
|
22
|
+
create_unified_yaml(db)
|
|
23
|
+
when "zip"
|
|
24
|
+
create_zip_archive(db)
|
|
25
|
+
when "all"
|
|
26
|
+
create_unified_yaml(db)
|
|
27
|
+
create_zip_archive(db)
|
|
28
|
+
else
|
|
29
|
+
raise Unitsdb::Errors::InvalidFormatError,
|
|
30
|
+
"Invalid format '#{@options[:format]}': must be 'yaml', 'zip', or 'all'"
|
|
67
31
|
end
|
|
68
32
|
|
|
69
|
-
puts "Release files created successfully in #{output_dir}"
|
|
33
|
+
puts "Release files created successfully in #{@options[:output_dir]}"
|
|
34
|
+
rescue Unitsdb::Errors::DatabaseError => e
|
|
35
|
+
raise Unitsdb::Errors::DatabaseLoadError,
|
|
36
|
+
"Failed to create release: #{e.message}"
|
|
70
37
|
end
|
|
71
38
|
|
|
72
39
|
private
|
|
73
40
|
|
|
74
|
-
def create_unified_yaml(
|
|
75
|
-
# Create a unified YAML
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
yaml = load_yaml(file)
|
|
81
|
-
# Get the collection key (units, scales, etc.) - it's the key that's not schema_version
|
|
82
|
-
collection_key = (yaml.keys - ["schema_version"]).first
|
|
83
|
-
if collection_key
|
|
84
|
-
# Add the collection to the unified structure
|
|
85
|
-
unified_yaml[collection_key] = yaml[collection_key]
|
|
86
|
-
end
|
|
87
|
-
end
|
|
88
|
-
|
|
89
|
-
# Write the unified YAML to a file
|
|
90
|
-
output_file = File.join(output_dir, "unitsdb-#{schema_version}.yaml")
|
|
91
|
-
File.write(output_file, unified_yaml.to_yaml)
|
|
92
|
-
puts "Created unified YAML file: #{output_file}"
|
|
41
|
+
def create_unified_yaml(db)
|
|
42
|
+
# Create a unified YAML file with all database components
|
|
43
|
+
output_path = File.join(@options[:output_dir],
|
|
44
|
+
"unitsdb-#{@options[:version]}.yaml")
|
|
45
|
+
File.write(output_path, db.to_yaml)
|
|
46
|
+
puts "Created unified YAML file: #{output_path}"
|
|
93
47
|
end
|
|
94
48
|
|
|
95
|
-
def create_zip_archive(
|
|
96
|
-
# Create a ZIP archive
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
49
|
+
def create_zip_archive(db)
|
|
50
|
+
# Create a ZIP archive with individual YAML files
|
|
51
|
+
output_path = File.join(@options[:output_dir],
|
|
52
|
+
"unitsdb-#{@options[:version]}.zip")
|
|
53
|
+
|
|
54
|
+
Zip::File.open(output_path, Zip::File::CREATE) do |zipfile|
|
|
55
|
+
{
|
|
56
|
+
dimensions: Unitsdb::Dimensions,
|
|
57
|
+
unit_systems: Unitsdb::UnitSystems,
|
|
58
|
+
units: Unitsdb::Units,
|
|
59
|
+
prefixes: Unitsdb::Prefixes,
|
|
60
|
+
quantities: Unitsdb::Quantities,
|
|
61
|
+
}.each_pair do |access_method, collection_klass|
|
|
62
|
+
db.send(access_method).tap do |data|
|
|
63
|
+
collection = collection_klass.new(access_method => data)
|
|
64
|
+
collection.version = @options[:version]
|
|
65
|
+
zipfile.get_output_stream("#{access_method}.yaml") do |f|
|
|
66
|
+
f.write(collection.to_yaml)
|
|
67
|
+
end
|
|
68
|
+
end
|
|
105
69
|
end
|
|
106
70
|
end
|
|
107
71
|
|
|
108
|
-
puts "Created ZIP archive: #{
|
|
72
|
+
puts "Created ZIP archive: #{output_path}"
|
|
109
73
|
end
|
|
110
74
|
end
|
|
111
75
|
end
|
|
@@ -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
|
|
@@ -33,8 +31,8 @@ module Unitsdb
|
|
|
33
31
|
puts entity.send("to_#{format.downcase}")
|
|
34
32
|
return
|
|
35
33
|
rescue NoMethodError
|
|
36
|
-
|
|
37
|
-
|
|
34
|
+
raise Unitsdb::Errors::InvalidFormatError,
|
|
35
|
+
"Unable to convert entity to #{format.upcase} format: output format not supported for this entity type"
|
|
38
36
|
end
|
|
39
37
|
end
|
|
40
38
|
|
|
@@ -64,11 +62,10 @@ module Unitsdb
|
|
|
64
62
|
print_entity_with_ids(entity)
|
|
65
63
|
end
|
|
66
64
|
rescue Unitsdb::Errors::DatabaseError => e
|
|
67
|
-
|
|
68
|
-
|
|
65
|
+
raise Unitsdb::Errors::DatabaseLoadError,
|
|
66
|
+
"Failed to load database: #{e.message}"
|
|
69
67
|
rescue StandardError => e
|
|
70
|
-
|
|
71
|
-
exit(1)
|
|
68
|
+
raise Unitsdb::Errors::CLIRuntimeError, "Search failed: #{e.message}"
|
|
72
69
|
end
|
|
73
70
|
end
|
|
74
71
|
|
|
@@ -93,7 +90,7 @@ module Unitsdb
|
|
|
93
90
|
else
|
|
94
91
|
puts " IDs:"
|
|
95
92
|
identifiers.each do |id|
|
|
96
|
-
puts " - #{id.id} (Type: #{id.type ||
|
|
93
|
+
puts " - #{id.id} (Type: #{id.type || 'N/A'})"
|
|
97
94
|
end
|
|
98
95
|
end
|
|
99
96
|
|
|
@@ -122,7 +119,7 @@ module Unitsdb
|
|
|
122
119
|
if entity.identifiers&.any?
|
|
123
120
|
puts " - Identifiers:"
|
|
124
121
|
entity.identifiers.each do |id|
|
|
125
|
-
puts " - #{id.id} (Type: #{id.type ||
|
|
122
|
+
puts " - #{id.id} (Type: #{id.type || 'N/A'})"
|
|
126
123
|
end
|
|
127
124
|
else
|
|
128
125
|
puts " - Identifiers: None"
|
|
@@ -132,7 +129,11 @@ module Unitsdb
|
|
|
132
129
|
case entity
|
|
133
130
|
when Unitsdb::Unit
|
|
134
131
|
puts " - Symbols:" if entity.respond_to?(:symbols) && entity.symbols&.any?
|
|
135
|
-
|
|
132
|
+
if entity.respond_to?(:symbols) && entity.symbols&.any?
|
|
133
|
+
entity.symbols.each do |s|
|
|
134
|
+
puts " - #{s}"
|
|
135
|
+
end
|
|
136
|
+
end
|
|
136
137
|
|
|
137
138
|
puts " - Definition: #{entity.definition}" if entity.respond_to?(:definition) && entity.definition
|
|
138
139
|
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "fileutils"
|
|
4
|
+
|
|
5
|
+
module Unitsdb
|
|
6
|
+
module Commands
|
|
7
|
+
module Ucum
|
|
8
|
+
class Check < Base
|
|
9
|
+
# Constants
|
|
10
|
+
ENTITY_TYPES = %w[units prefixes].freeze
|
|
11
|
+
|
|
12
|
+
def run
|
|
13
|
+
# Get options
|
|
14
|
+
entity_type = @options[:entity_type]&.downcase
|
|
15
|
+
direction = @options[:direction]&.downcase || "both"
|
|
16
|
+
output_dir = @options[:output_updated_database]
|
|
17
|
+
include_potential = @options[:include_potential_matches] || false
|
|
18
|
+
database_path = @options[:database]
|
|
19
|
+
ucum_file = @options[:ucum_file]
|
|
20
|
+
|
|
21
|
+
# Validate parameters
|
|
22
|
+
validate_parameters(direction, ucum_file)
|
|
23
|
+
|
|
24
|
+
# Use the path as-is without expansion
|
|
25
|
+
puts "Using database directory: #{database_path}"
|
|
26
|
+
|
|
27
|
+
@db = Unitsdb::Database.from_db(database_path)
|
|
28
|
+
|
|
29
|
+
puts "Using UCUM file: #{ucum_file}"
|
|
30
|
+
puts "Include potential matches: #{include_potential ? 'Yes' : 'No'}"
|
|
31
|
+
|
|
32
|
+
# Parse UCUM XML file
|
|
33
|
+
ucum_data = XmlParser.parse_ucum_file(ucum_file)
|
|
34
|
+
|
|
35
|
+
# Process entity types
|
|
36
|
+
process_entities(entity_type, ucum_data, direction, output_dir,
|
|
37
|
+
include_potential)
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
private
|
|
41
|
+
|
|
42
|
+
# Process all entity types or a specific one
|
|
43
|
+
def process_entities(entity_type, ucum_data, direction, output_dir,
|
|
44
|
+
include_potential)
|
|
45
|
+
if entity_type && ENTITY_TYPES.include?(entity_type)
|
|
46
|
+
process_entity_type(entity_type, ucum_data, direction, output_dir,
|
|
47
|
+
include_potential)
|
|
48
|
+
else
|
|
49
|
+
ENTITY_TYPES.each do |type|
|
|
50
|
+
process_entity_type(type, ucum_data, direction, output_dir,
|
|
51
|
+
include_potential)
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
# Process a specific entity type
|
|
57
|
+
def process_entity_type(entity_type, ucum_data, direction, output_dir,
|
|
58
|
+
include_potential = false)
|
|
59
|
+
puts "\n========== Processing #{entity_type.upcase} References ==========\n"
|
|
60
|
+
|
|
61
|
+
db_entities = @db.send(entity_type)
|
|
62
|
+
ucum_entities = XmlParser.get_entities_from_ucum(entity_type,
|
|
63
|
+
ucum_data)
|
|
64
|
+
|
|
65
|
+
puts "Found #{ucum_entities.size} #{entity_type} in UCUM"
|
|
66
|
+
puts "Found #{db_entities.size} #{entity_type} in database"
|
|
67
|
+
|
|
68
|
+
if %w[from_ucum
|
|
69
|
+
both].include?(direction)
|
|
70
|
+
check_from_ucum(entity_type, ucum_entities, db_entities, output_dir,
|
|
71
|
+
include_potential)
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
return unless %w[to_ucum both].include?(direction)
|
|
75
|
+
|
|
76
|
+
check_to_ucum(entity_type, ucum_entities, db_entities, output_dir,
|
|
77
|
+
include_potential)
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
# Validation helpers
|
|
81
|
+
def validate_parameters(direction, ucum_file)
|
|
82
|
+
unless %w[to_ucum from_ucum both].include?(direction)
|
|
83
|
+
raise Unitsdb::Errors::InvalidParameterError,
|
|
84
|
+
"Invalid direction '#{direction}': must be 'to_ucum', 'from_ucum', or 'both'"
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
return if File.exist?(ucum_file)
|
|
88
|
+
|
|
89
|
+
raise Unitsdb::Errors::FileNotFoundError,
|
|
90
|
+
"UCUM file not found: #{ucum_file}"
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
# Direction handler: UCUM → UnitsDB
|
|
94
|
+
def check_from_ucum(entity_type, ucum_entities, db_entities,
|
|
95
|
+
output_dir, include_potential = false)
|
|
96
|
+
Formatter.print_direction_header("UCUM → UnitsDB")
|
|
97
|
+
|
|
98
|
+
matches, missing_matches, unmatched_ucum = Matcher.match_ucum_to_db(
|
|
99
|
+
entity_type, ucum_entities, db_entities
|
|
100
|
+
)
|
|
101
|
+
|
|
102
|
+
# Print results
|
|
103
|
+
Formatter.display_ucum_results(entity_type, matches, missing_matches,
|
|
104
|
+
unmatched_ucum)
|
|
105
|
+
|
|
106
|
+
# Update references if needed
|
|
107
|
+
return unless output_dir && !missing_matches.empty?
|
|
108
|
+
|
|
109
|
+
output_file = File.join(output_dir, "#{entity_type}.yaml")
|
|
110
|
+
Updater.update_references(entity_type, missing_matches, db_entities,
|
|
111
|
+
output_file, include_potential)
|
|
112
|
+
puts "\nUpdated references written to #{output_file}"
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
# Direction handler: UnitsDB → UCUM
|
|
116
|
+
def check_to_ucum(entity_type, ucum_entities, db_entities, output_dir,
|
|
117
|
+
include_potential = false)
|
|
118
|
+
Formatter.print_direction_header("UnitsDB → UCUM")
|
|
119
|
+
|
|
120
|
+
matches, missing_refs, unmatched_db = Matcher.match_db_to_ucum(
|
|
121
|
+
entity_type, ucum_entities, db_entities
|
|
122
|
+
)
|
|
123
|
+
|
|
124
|
+
# Print results
|
|
125
|
+
Formatter.display_db_results(entity_type, matches, missing_refs,
|
|
126
|
+
unmatched_db)
|
|
127
|
+
|
|
128
|
+
# Update references if needed
|
|
129
|
+
return unless output_dir && !missing_refs.empty?
|
|
130
|
+
|
|
131
|
+
output_file = File.join(output_dir, "#{entity_type}.yaml")
|
|
132
|
+
Updater.update_references(entity_type, missing_refs, db_entities,
|
|
133
|
+
output_file, include_potential)
|
|
134
|
+
puts "\nUpdated references written to #{output_file}"
|
|
135
|
+
end
|
|
136
|
+
end
|
|
137
|
+
end
|
|
138
|
+
end
|
|
139
|
+
end
|