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
data/lib/unitsdb/cli.rb
ADDED
@@ -0,0 +1,81 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "thor"
|
4
|
+
require_relative "commands/validate"
|
5
|
+
require_relative "commands/_modify"
|
6
|
+
require "fileutils"
|
7
|
+
|
8
|
+
module Unitsdb
|
9
|
+
class CLI < Thor
|
10
|
+
# Fix Thor deprecation warning
|
11
|
+
def self.exit_on_failure?
|
12
|
+
true
|
13
|
+
end
|
14
|
+
|
15
|
+
desc "_modify SUBCOMMAND", "Commands that modify the database"
|
16
|
+
subcommand "_modify", Commands::ModifyCommand
|
17
|
+
|
18
|
+
desc "validate SUBCOMMAND", "Validate database files for different conditions"
|
19
|
+
subcommand "validate", Commands::ValidateCommand
|
20
|
+
|
21
|
+
desc "search QUERY", "Search for entities containing the given text"
|
22
|
+
option :type, type: :string, aliases: "-t",
|
23
|
+
desc: "Entity type to search (units, prefixes, quantities, dimensions, unit_systems)"
|
24
|
+
option :id, type: :string, aliases: "-i",
|
25
|
+
desc: "Search for an entity with a specific identifier"
|
26
|
+
option :id_type, type: :string,
|
27
|
+
desc: "Filter get_by_id search by identifier type"
|
28
|
+
option :format, type: :string, default: "text",
|
29
|
+
desc: "Output format (text, json, yaml)"
|
30
|
+
option :database, type: :string, required: true, aliases: "-d",
|
31
|
+
desc: "Path to UnitsDB database (required)"
|
32
|
+
|
33
|
+
def search(query)
|
34
|
+
require_relative "commands/search"
|
35
|
+
Commands::Search.new(options).run(query)
|
36
|
+
end
|
37
|
+
|
38
|
+
desc "get ID", "Get detailed information about an entity by ID"
|
39
|
+
option :id_type, type: :string,
|
40
|
+
desc: "Filter by identifier type"
|
41
|
+
option :format, type: :string, default: "text",
|
42
|
+
desc: "Output format (text, json, yaml)"
|
43
|
+
option :database, type: :string, required: true, aliases: "-d",
|
44
|
+
desc: "Path to UnitsDB database (required)"
|
45
|
+
def get(id)
|
46
|
+
require_relative "commands/get"
|
47
|
+
Commands::Get.new(options).get(id)
|
48
|
+
end
|
49
|
+
|
50
|
+
desc "check_si", "Check and update SI digital framework references in UnitsDB"
|
51
|
+
option :entity_type, type: :string, aliases: "-e",
|
52
|
+
desc: "Entity type to check (units, quantities, or prefixes). If not specified, all types are checked"
|
53
|
+
option :ttl_dir, type: :string, required: true, aliases: "-t",
|
54
|
+
desc: "Path to the directory containing SI digital framework TTL files"
|
55
|
+
option :output_updated_database, type: :string, aliases: "-o",
|
56
|
+
desc: "Directory path to write updated YAML files with added SI references"
|
57
|
+
option :direction, type: :string, default: "both", aliases: "-r",
|
58
|
+
desc: "Direction to check: 'to_si' (UnitsDB→TTL), 'from_si' (TTL→UnitsDB), or 'both'"
|
59
|
+
option :include_potential_matches, type: :boolean, default: false, aliases: "-p",
|
60
|
+
desc: "Include potential matches when updating references (default: false)"
|
61
|
+
option :database, type: :string, required: true, aliases: "-d",
|
62
|
+
desc: "Path to UnitsDB database (required)"
|
63
|
+
|
64
|
+
def check_si
|
65
|
+
require_relative "commands/check_si"
|
66
|
+
Commands::CheckSi.new(options).run
|
67
|
+
end
|
68
|
+
|
69
|
+
desc "release", "Create release files (unified YAML and/or ZIP archive)"
|
70
|
+
option :format, type: :string, default: "all", aliases: "-f",
|
71
|
+
desc: "Output format: 'yaml' (single file), 'zip' (archive), or 'all' (both)"
|
72
|
+
option :output_dir, type: :string, default: ".", aliases: "-o",
|
73
|
+
desc: "Directory to output release files"
|
74
|
+
option :database, type: :string, required: true, aliases: "-d",
|
75
|
+
desc: "Path to UnitsDB database (required)"
|
76
|
+
def release
|
77
|
+
require_relative "commands/release"
|
78
|
+
Commands::Release.new(options).run
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "thor"
|
4
|
+
|
5
|
+
module Unitsdb
|
6
|
+
module Commands
|
7
|
+
class ModifyCommand < Thor
|
8
|
+
desc "normalize [INPUT] [OUTPUT]", "Normalize a YAML file or all YAML files with --all"
|
9
|
+
method_option :sort, type: :string, default: "short",
|
10
|
+
desc: "Sort units by: 'short' (name), 'nist' (ID), 'unitsml' (ID), or 'none'"
|
11
|
+
method_option :database, type: :string, required: true, aliases: "-d",
|
12
|
+
desc: "Path to UnitsDB database (required)"
|
13
|
+
method_option :all, type: :boolean, default: false, aliases: "-a",
|
14
|
+
desc: "Process all YAML files in the repository"
|
15
|
+
|
16
|
+
def normalize(input = nil, output = nil)
|
17
|
+
require_relative "normalize"
|
18
|
+
Normalize.new(options).run(input, output)
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,52 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "thor"
|
4
|
+
require "yaml"
|
5
|
+
require_relative "../utils"
|
6
|
+
require_relative "../database"
|
7
|
+
|
8
|
+
module Unitsdb
|
9
|
+
module Commands
|
10
|
+
class Base
|
11
|
+
def initialize(options = {})
|
12
|
+
@options = options
|
13
|
+
end
|
14
|
+
|
15
|
+
protected
|
16
|
+
|
17
|
+
def yaml_files(input = nil, opts = nil)
|
18
|
+
options_to_use = opts || @options
|
19
|
+
|
20
|
+
if options_to_use[:all]
|
21
|
+
Unitsdb::Utils::DEFAULT_YAML_FILES.map { |f| File.join(options_to_use[:database], f) }
|
22
|
+
elsif input
|
23
|
+
[input]
|
24
|
+
else
|
25
|
+
puts "Error: INPUT file is required when not using --all"
|
26
|
+
exit(1)
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
def load_database(database_path = nil)
|
31
|
+
path = database_path || @options[:database]
|
32
|
+
|
33
|
+
raise Unitsdb::Errors::DatabaseError, "Database path must be specified using the --database option" if path.nil?
|
34
|
+
|
35
|
+
Unitsdb::Database.from_db(path)
|
36
|
+
rescue Unitsdb::Errors::DatabaseError => e
|
37
|
+
puts "Error: #{e.message}"
|
38
|
+
exit(1)
|
39
|
+
end
|
40
|
+
|
41
|
+
def load_yaml(file_path)
|
42
|
+
return nil unless File.exist?(file_path)
|
43
|
+
|
44
|
+
YAML.safe_load(File.read(file_path))
|
45
|
+
end
|
46
|
+
|
47
|
+
def file_type(file_path)
|
48
|
+
File.basename(file_path, ".yaml")
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
@@ -0,0 +1,124 @@
|
|
1
|
+
# frozen_string_literal: true
|
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
|
+
module Unitsdb
|
12
|
+
module Commands
|
13
|
+
class CheckSi < Base
|
14
|
+
# Constants
|
15
|
+
ENTITY_TYPES = %w[units quantities prefixes].freeze
|
16
|
+
|
17
|
+
def run
|
18
|
+
# Get options
|
19
|
+
entity_type = @options[:entity_type]&.downcase
|
20
|
+
direction = @options[:direction]&.downcase || "both"
|
21
|
+
output_dir = @options[:output_updated_database]
|
22
|
+
include_potential = @options[:include_potential_matches] || false
|
23
|
+
database_path = @options[:database]
|
24
|
+
ttl_dir = @options[:ttl_dir]
|
25
|
+
|
26
|
+
# Validate parameters
|
27
|
+
validate_parameters(direction, ttl_dir)
|
28
|
+
|
29
|
+
# Use the path as-is without expansion
|
30
|
+
puts "Using database directory: #{database_path}"
|
31
|
+
|
32
|
+
@db = Unitsdb::Database.from_db(database_path)
|
33
|
+
|
34
|
+
puts "Using TTL directory: #{ttl_dir}"
|
35
|
+
puts "Include potential matches: #{include_potential ? "Yes" : "No"}"
|
36
|
+
|
37
|
+
# Parse TTL files
|
38
|
+
graph = SiTtlParser.parse_ttl_files(ttl_dir)
|
39
|
+
|
40
|
+
# Process entity types
|
41
|
+
process_entities(entity_type, graph, direction, output_dir, include_potential)
|
42
|
+
end
|
43
|
+
|
44
|
+
private
|
45
|
+
|
46
|
+
# Process all entity types or a specific one
|
47
|
+
def process_entities(entity_type, graph, direction, output_dir, include_potential)
|
48
|
+
if entity_type && ENTITY_TYPES.include?(entity_type)
|
49
|
+
process_entity_type(entity_type, graph, direction, output_dir, include_potential)
|
50
|
+
else
|
51
|
+
ENTITY_TYPES.each do |type|
|
52
|
+
process_entity_type(type, graph, direction, output_dir, include_potential)
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
# Process a specific entity type
|
58
|
+
def process_entity_type(entity_type, graph, direction, output_dir, include_potential = false)
|
59
|
+
puts "\n========== Processing #{entity_type.upcase} References ==========\n"
|
60
|
+
|
61
|
+
db_entities = @db.send(entity_type)
|
62
|
+
ttl_entities = SiTtlParser.extract_entities_from_ttl(entity_type, graph)
|
63
|
+
|
64
|
+
puts "Found #{ttl_entities.size} #{entity_type} in SI digital framework"
|
65
|
+
puts "Found #{db_entities.size} #{entity_type} in database"
|
66
|
+
|
67
|
+
check_from_si(entity_type, ttl_entities, db_entities, output_dir, include_potential) if %w[from_si
|
68
|
+
both].include?(direction)
|
69
|
+
|
70
|
+
return unless %w[to_si both].include?(direction)
|
71
|
+
|
72
|
+
check_to_si(entity_type, ttl_entities, db_entities, output_dir, include_potential)
|
73
|
+
end
|
74
|
+
|
75
|
+
# Validation helpers
|
76
|
+
def validate_parameters(direction, ttl_dir)
|
77
|
+
unless %w[to_si from_si both].include?(direction)
|
78
|
+
puts "Invalid direction: #{direction}. Must be one of: to_si, from_si, both"
|
79
|
+
exit(1)
|
80
|
+
end
|
81
|
+
|
82
|
+
return if Dir.exist?(ttl_dir)
|
83
|
+
|
84
|
+
puts "TTL directory not found: #{ttl_dir}"
|
85
|
+
exit(1)
|
86
|
+
end
|
87
|
+
|
88
|
+
# Direction handler: TTL → DB
|
89
|
+
def check_from_si(entity_type, ttl_entities, db_entities, output_dir, include_potential = false)
|
90
|
+
SiFormatter.print_direction_header("SI → UnitsDB")
|
91
|
+
|
92
|
+
matches, missing_matches, unmatched_ttl = SiMatcher.match_ttl_to_db(entity_type, ttl_entities, db_entities)
|
93
|
+
|
94
|
+
# Print results
|
95
|
+
SiFormatter.display_si_results(entity_type, matches, missing_matches, unmatched_ttl)
|
96
|
+
|
97
|
+
# Update references if needed
|
98
|
+
return unless output_dir && !missing_matches.empty?
|
99
|
+
|
100
|
+
output_file = File.join(output_dir, "#{entity_type}.yaml")
|
101
|
+
SiUpdater.update_references(entity_type, missing_matches, db_entities, output_file, include_potential,
|
102
|
+
database_path)
|
103
|
+
puts "\nUpdated references written to #{output_file}"
|
104
|
+
end
|
105
|
+
|
106
|
+
# Direction handler: DB → TTL
|
107
|
+
def check_to_si(entity_type, ttl_entities, db_entities, output_dir, include_potential = false)
|
108
|
+
SiFormatter.print_direction_header("UnitsDB → SI")
|
109
|
+
|
110
|
+
matches, missing_refs, unmatched_db = SiMatcher.match_db_to_ttl(entity_type, ttl_entities, db_entities)
|
111
|
+
|
112
|
+
# Print results
|
113
|
+
SiFormatter.display_db_results(entity_type, matches, missing_refs, unmatched_db)
|
114
|
+
|
115
|
+
# Update references if needed
|
116
|
+
return unless output_dir && !missing_refs.empty?
|
117
|
+
|
118
|
+
output_file = File.join(output_dir, "#{entity_type}.yaml")
|
119
|
+
SiUpdater.update_db_references(entity_type, missing_refs, output_file, include_potential, @options[:database])
|
120
|
+
puts "\nUpdated references written to #{output_file}"
|
121
|
+
end
|
122
|
+
end
|
123
|
+
end
|
124
|
+
end
|
@@ -0,0 +1,133 @@
|
|
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 Get < Base
|
10
|
+
def get(id)
|
11
|
+
# Database path is guaranteed by Thor's global option
|
12
|
+
id_type = @options[:id_type]
|
13
|
+
format = @options[:format] || "text"
|
14
|
+
|
15
|
+
begin
|
16
|
+
database = load_database(@options[:database])
|
17
|
+
|
18
|
+
# Search by ID
|
19
|
+
entity = database.get_by_id(id: id, type: id_type)
|
20
|
+
|
21
|
+
unless entity
|
22
|
+
puts "No entity found with ID: '#{id}'"
|
23
|
+
return
|
24
|
+
end
|
25
|
+
|
26
|
+
# Output based on format
|
27
|
+
if %w[json yaml].include?(format.downcase)
|
28
|
+
begin
|
29
|
+
puts entity.send("to_#{format.downcase}")
|
30
|
+
return
|
31
|
+
rescue NoMethodError
|
32
|
+
puts "Error: Unable to convert entity to #{format} format"
|
33
|
+
exit(1)
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
# Default text output
|
38
|
+
print_entity_details(entity)
|
39
|
+
rescue Unitsdb::Errors::DatabaseError => e
|
40
|
+
puts "Error: #{e.message}"
|
41
|
+
exit(1)
|
42
|
+
rescue StandardError => e
|
43
|
+
puts "Error searching database: #{e.message}"
|
44
|
+
exit(1)
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
private
|
49
|
+
|
50
|
+
def print_entity_details(entity)
|
51
|
+
# Determine entity type
|
52
|
+
entity_type = get_entity_type(entity)
|
53
|
+
|
54
|
+
# Get name
|
55
|
+
name = get_entity_name(entity)
|
56
|
+
|
57
|
+
puts "Entity details:"
|
58
|
+
puts " - Type: #{entity_type}"
|
59
|
+
puts " - Name: #{name}"
|
60
|
+
|
61
|
+
# Print description if available
|
62
|
+
puts " - Description: #{entity.short}" if entity.respond_to?(:short) && entity.short && entity.short != name
|
63
|
+
|
64
|
+
# Print all identifiers
|
65
|
+
if entity.identifiers&.any?
|
66
|
+
puts " - Identifiers:"
|
67
|
+
entity.identifiers.each do |id|
|
68
|
+
puts " - #{id.id} (Type: #{id.type || "N/A"})"
|
69
|
+
end
|
70
|
+
else
|
71
|
+
puts " - Identifiers: None"
|
72
|
+
end
|
73
|
+
|
74
|
+
# Print additional properties based on entity type
|
75
|
+
case entity
|
76
|
+
when Unitsdb::Unit
|
77
|
+
puts " - Symbols:" if entity.respond_to?(:symbols) && entity.symbols&.any?
|
78
|
+
entity.symbols.each { |s| puts " - #{s}" } if entity.respond_to?(:symbols) && entity.symbols&.any?
|
79
|
+
|
80
|
+
puts " - Definition: #{entity.definition}" if entity.respond_to?(:definition) && entity.definition
|
81
|
+
|
82
|
+
if entity.respond_to?(:dimensions) && entity.dimensions&.any?
|
83
|
+
puts " - Dimensions:"
|
84
|
+
entity.dimensions.each { |d| puts " - #{d}" }
|
85
|
+
end
|
86
|
+
when Unitsdb::Quantity
|
87
|
+
puts " - Dimensions: #{entity.dimension}" if entity.respond_to?(:dimension) && entity.dimension
|
88
|
+
when Unitsdb::Prefix
|
89
|
+
puts " - Value: #{entity.value}" if entity.respond_to?(:value) && entity.value
|
90
|
+
puts " - Symbol: #{entity.symbol}" if entity.respond_to?(:symbol) && entity.symbol
|
91
|
+
when Unitsdb::Dimension
|
92
|
+
# Any dimension-specific properties
|
93
|
+
when Unitsdb::UnitSystem
|
94
|
+
puts " - Organization: #{entity.organization}" if entity.respond_to?(:organization) && entity.organization
|
95
|
+
end
|
96
|
+
|
97
|
+
# Print references if available
|
98
|
+
return unless entity.respond_to?(:references) && entity.references&.any?
|
99
|
+
|
100
|
+
puts " - References:"
|
101
|
+
entity.references.each do |ref|
|
102
|
+
puts " - #{ref.type}: #{ref.uri}"
|
103
|
+
end
|
104
|
+
end
|
105
|
+
|
106
|
+
def get_entity_type(entity)
|
107
|
+
case entity
|
108
|
+
when Unitsdb::Unit
|
109
|
+
"Unit"
|
110
|
+
when Unitsdb::Prefix
|
111
|
+
"Prefix"
|
112
|
+
when Unitsdb::Quantity
|
113
|
+
"Quantity"
|
114
|
+
when Unitsdb::Dimension
|
115
|
+
"Dimension"
|
116
|
+
when Unitsdb::UnitSystem
|
117
|
+
"UnitSystem"
|
118
|
+
else
|
119
|
+
"Unknown"
|
120
|
+
end
|
121
|
+
end
|
122
|
+
|
123
|
+
def get_entity_name(entity)
|
124
|
+
# Using early returns is still preferable for simple conditions
|
125
|
+
return entity.names.first if entity.respond_to?(:names) && entity.names&.any?
|
126
|
+
return entity.name if entity.respond_to?(:name) && entity.name
|
127
|
+
return entity.short if entity.respond_to?(:short) && entity.short
|
128
|
+
|
129
|
+
"N/A" # Default if no name found
|
130
|
+
end
|
131
|
+
end
|
132
|
+
end
|
133
|
+
end
|
@@ -0,0 +1,81 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative "base"
|
4
|
+
require "yaml"
|
5
|
+
|
6
|
+
module Unitsdb
|
7
|
+
module Commands
|
8
|
+
class Normalize < Base
|
9
|
+
def run(input = nil, output = nil)
|
10
|
+
unless @options[:all] || (input && output)
|
11
|
+
puts "Error: INPUT and OUTPUT are required when not using --all"
|
12
|
+
exit(1)
|
13
|
+
end
|
14
|
+
|
15
|
+
if @options[:all]
|
16
|
+
Unitsdb::Utils::DEFAULT_YAML_FILES.each do |file|
|
17
|
+
path = File.join(@options[:database], file)
|
18
|
+
next unless File.exist?(path)
|
19
|
+
|
20
|
+
normalize_file(path, path)
|
21
|
+
puts "Normalized #{path}"
|
22
|
+
end
|
23
|
+
puts "All YAML files normalized successfully!"
|
24
|
+
end
|
25
|
+
|
26
|
+
return unless input && output
|
27
|
+
|
28
|
+
normalize_file(input, output)
|
29
|
+
puts "Normalized YAML written to #{output}"
|
30
|
+
end
|
31
|
+
|
32
|
+
private
|
33
|
+
|
34
|
+
def normalize_file(input, output)
|
35
|
+
# Load the original YAML to work with
|
36
|
+
yaml = load_yaml(input)
|
37
|
+
|
38
|
+
# For schema 2.0.0, we need to handle the schema_version and the main collection key
|
39
|
+
if yaml.key?("schema_version") && yaml["schema_version"] == "2.0.0"
|
40
|
+
# Get the collection key (units, scales, etc.)
|
41
|
+
collection_key = (yaml.keys - ["schema_version"]).first
|
42
|
+
|
43
|
+
# Sort the collection items if requested
|
44
|
+
if @options[:sort] && @options[:sort] != "none" && collection_key
|
45
|
+
# Sort the collection items based on the sort option
|
46
|
+
case @options[:sort]
|
47
|
+
when "nist", "unitsml"
|
48
|
+
# Sort by ID (nist or unitsml)
|
49
|
+
id_type = @options[:sort]
|
50
|
+
yaml[collection_key] = sort_by_id_type(yaml[collection_key], id_type)
|
51
|
+
else # default to "short"
|
52
|
+
# Use the existing sort_yaml_keys method for default sorting
|
53
|
+
yaml[collection_key] = Unitsdb::Utils.sort_yaml_keys(yaml[collection_key])
|
54
|
+
end
|
55
|
+
end
|
56
|
+
elsif @options[:sort] && @options[:sort] != "none"
|
57
|
+
# For any other format, just sort all keys
|
58
|
+
yaml = Unitsdb::Utils.sort_yaml_keys(yaml)
|
59
|
+
end
|
60
|
+
|
61
|
+
# Write the normalized output
|
62
|
+
File.write(output, yaml.to_yaml)
|
63
|
+
end
|
64
|
+
|
65
|
+
# Sort collection items by a specific ID type (nist or unitsml)
|
66
|
+
def sort_by_id_type(collection, id_type)
|
67
|
+
return collection unless collection.is_a?(Array)
|
68
|
+
|
69
|
+
collection.sort_by do |item|
|
70
|
+
# Find the identifier of the specified type
|
71
|
+
identifier = if item.key?("identifiers") && item["identifiers"].is_a?(Array)
|
72
|
+
item["identifiers"].find { |id| id["type"] == id_type }
|
73
|
+
end
|
74
|
+
|
75
|
+
# Use the ID if found, otherwise use a placeholder to sort to the end
|
76
|
+
identifier ? identifier["id"].to_s : "zzzzz"
|
77
|
+
end.map { |item| Unitsdb::Utils.sort_yaml_keys(item) }
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
@@ -0,0 +1,112 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative "base"
|
4
|
+
require "yaml"
|
5
|
+
require "zip"
|
6
|
+
require "fileutils"
|
7
|
+
|
8
|
+
module Unitsdb
|
9
|
+
module Commands
|
10
|
+
class Release < Base
|
11
|
+
def run
|
12
|
+
# Verify all required files exist
|
13
|
+
yaml_files = Unitsdb::Utils::DEFAULT_YAML_FILES.map { |f| File.join(@options[:database], f) }
|
14
|
+
missing_files = yaml_files.reject { |f| File.exist?(f) }
|
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
|
51
|
+
|
52
|
+
# Create output directory if it doesn't exist
|
53
|
+
output_dir = @options[:output_dir] || "."
|
54
|
+
FileUtils.mkdir_p(output_dir) unless Dir.exist?(output_dir)
|
55
|
+
|
56
|
+
# Generate outputs based on format option
|
57
|
+
format = @options[:format] || "all"
|
58
|
+
|
59
|
+
case format.downcase
|
60
|
+
when "yaml", "all"
|
61
|
+
create_unified_yaml(yaml_files, schema_version, output_dir)
|
62
|
+
end
|
63
|
+
|
64
|
+
case format.downcase
|
65
|
+
when "zip", "all"
|
66
|
+
create_zip_archive(yaml_files, schema_version, output_dir)
|
67
|
+
end
|
68
|
+
|
69
|
+
puts "Release files created successfully in #{output_dir}"
|
70
|
+
end
|
71
|
+
|
72
|
+
private
|
73
|
+
|
74
|
+
def create_unified_yaml(yaml_files, schema_version, output_dir)
|
75
|
+
# Create a unified YAML structure
|
76
|
+
unified_yaml = { "schema_version" => schema_version }
|
77
|
+
|
78
|
+
# Load each YAML file and add its collection to the unified structure
|
79
|
+
yaml_files.each do |file|
|
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}"
|
93
|
+
end
|
94
|
+
|
95
|
+
def create_zip_archive(yaml_files, schema_version, output_dir)
|
96
|
+
# Create a ZIP archive containing all YAML files
|
97
|
+
output_file = File.join(output_dir, "unitsdb-#{schema_version}.zip")
|
98
|
+
|
99
|
+
Zip::File.open(output_file, Zip::File::CREATE) do |zipfile|
|
100
|
+
yaml_files.each do |file|
|
101
|
+
# Get the filename without the path
|
102
|
+
filename = File.basename(file)
|
103
|
+
# Add the file to the ZIP archive
|
104
|
+
zipfile.add(filename, file)
|
105
|
+
end
|
106
|
+
end
|
107
|
+
|
108
|
+
puts "Created ZIP archive: #{output_file}"
|
109
|
+
end
|
110
|
+
end
|
111
|
+
end
|
112
|
+
end
|