unitsdb 1.0.0 → 2.0.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.
Files changed (80) hide show
  1. checksums.yaml +4 -4
  2. data/.gitmodules +3 -0
  3. data/.rspec +2 -1
  4. data/.rubocop_todo.yml +168 -15
  5. data/Gemfile +3 -2
  6. data/README.adoc +803 -1
  7. data/exe/unitsdb +7 -0
  8. data/lib/unitsdb/cli.rb +88 -0
  9. data/lib/unitsdb/commands/_modify.rb +22 -0
  10. data/lib/unitsdb/commands/base.rb +26 -0
  11. data/lib/unitsdb/commands/check_si.rb +124 -0
  12. data/lib/unitsdb/commands/get.rb +133 -0
  13. data/lib/unitsdb/commands/normalize.rb +81 -0
  14. data/lib/unitsdb/commands/release.rb +73 -0
  15. data/lib/unitsdb/commands/search.rb +219 -0
  16. data/lib/unitsdb/commands/si_formatter.rb +485 -0
  17. data/lib/unitsdb/commands/si_matcher.rb +470 -0
  18. data/lib/unitsdb/commands/si_ttl_parser.rb +100 -0
  19. data/lib/unitsdb/commands/si_updater.rb +212 -0
  20. data/lib/unitsdb/commands/ucum/check.rb +126 -0
  21. data/lib/unitsdb/commands/ucum/formatter.rb +141 -0
  22. data/lib/unitsdb/commands/ucum/matcher.rb +301 -0
  23. data/lib/unitsdb/commands/ucum/update.rb +84 -0
  24. data/lib/unitsdb/commands/ucum/updater.rb +98 -0
  25. data/lib/unitsdb/commands/ucum/xml_parser.rb +34 -0
  26. data/lib/unitsdb/commands/ucum.rb +43 -0
  27. data/lib/unitsdb/commands/validate/identifiers.rb +42 -0
  28. data/lib/unitsdb/commands/validate/references.rb +318 -0
  29. data/lib/unitsdb/commands/validate/si_references.rb +109 -0
  30. data/lib/unitsdb/commands/validate.rb +40 -0
  31. data/lib/unitsdb/database.rb +662 -0
  32. data/lib/unitsdb/dimension.rb +49 -0
  33. data/lib/unitsdb/dimension_details.rb +20 -0
  34. data/lib/unitsdb/dimension_reference.rb +8 -0
  35. data/lib/unitsdb/dimensions.rb +5 -10
  36. data/lib/unitsdb/errors.rb +13 -0
  37. data/lib/unitsdb/external_reference.rb +14 -0
  38. data/lib/unitsdb/identifier.rb +8 -0
  39. data/lib/unitsdb/localized_string.rb +17 -0
  40. data/lib/unitsdb/prefix.rb +30 -0
  41. data/lib/unitsdb/prefix_reference.rb +10 -0
  42. data/lib/unitsdb/prefixes.rb +5 -11
  43. data/lib/unitsdb/quantities.rb +5 -31
  44. data/lib/unitsdb/quantity.rb +21 -0
  45. data/lib/unitsdb/quantity_reference.rb +10 -0
  46. data/lib/unitsdb/root_unit_reference.rb +14 -0
  47. data/lib/unitsdb/scale.rb +17 -0
  48. data/lib/unitsdb/scale_properties.rb +12 -0
  49. data/lib/unitsdb/scale_reference.rb +10 -0
  50. data/lib/unitsdb/scales.rb +12 -0
  51. data/lib/unitsdb/si_derived_base.rb +19 -0
  52. data/lib/unitsdb/symbol_presentations.rb +3 -8
  53. data/lib/unitsdb/ucum.rb +198 -0
  54. data/lib/unitsdb/unit.rb +63 -0
  55. data/lib/unitsdb/unit_reference.rb +10 -0
  56. data/lib/unitsdb/unit_system.rb +15 -0
  57. data/lib/unitsdb/unit_system_reference.rb +10 -0
  58. data/lib/unitsdb/unit_systems.rb +5 -10
  59. data/lib/unitsdb/units.rb +5 -10
  60. data/lib/unitsdb/utils.rb +84 -0
  61. data/lib/unitsdb/version.rb +1 -1
  62. data/lib/unitsdb.rb +12 -2
  63. data/unitsdb.gemspec +6 -3
  64. metadata +124 -20
  65. data/lib/unitsdb/dimensions/dimension.rb +0 -59
  66. data/lib/unitsdb/dimensions/quantity.rb +0 -32
  67. data/lib/unitsdb/dimensions/symbol.rb +0 -26
  68. data/lib/unitsdb/prefixes/prefix.rb +0 -35
  69. data/lib/unitsdb/prefixes/symbol.rb +0 -17
  70. data/lib/unitsdb/quantities/quantity.rb +0 -37
  71. data/lib/unitsdb/quantities/unit_reference.rb +0 -15
  72. data/lib/unitsdb/unit_systems/unit_system.rb +0 -19
  73. data/lib/unitsdb/units/quantity_reference.rb +0 -17
  74. data/lib/unitsdb/units/root_unit.rb +0 -21
  75. data/lib/unitsdb/units/root_units.rb +0 -18
  76. data/lib/unitsdb/units/si_derived_base.rb +0 -26
  77. data/lib/unitsdb/units/symbol.rb +0 -19
  78. data/lib/unitsdb/units/system.rb +0 -17
  79. data/lib/unitsdb/units/unit.rb +0 -73
  80. data/lib/unitsdb/unitsdb.rb +0 -6
data/exe/unitsdb ADDED
@@ -0,0 +1,7 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ require "unitsdb"
5
+ require "unitsdb/cli"
6
+
7
+ Unitsdb::CLI.start(ARGV)
@@ -0,0 +1,88 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "thor"
4
+ require_relative "commands/base"
5
+ require_relative "commands/validate"
6
+ require_relative "commands/_modify"
7
+ require_relative "commands/ucum"
8
+ require "fileutils"
9
+
10
+ module Unitsdb
11
+ class CLI < Thor
12
+ # Fix Thor deprecation warning
13
+ def self.exit_on_failure?
14
+ true
15
+ end
16
+
17
+ desc "ucum SUBCOMMAND", "UCUM-related commands"
18
+ subcommand "ucum", Commands::UcumCommand
19
+
20
+ desc "_modify SUBCOMMAND", "Commands that modify the database"
21
+ subcommand "_modify", Commands::ModifyCommand
22
+
23
+ desc "validate SUBCOMMAND", "Validate database files for different conditions"
24
+ subcommand "validate", Commands::ValidateCommand
25
+
26
+ desc "search QUERY", "Search for entities containing the given text"
27
+ option :type, type: :string, aliases: "-t",
28
+ desc: "Entity type to search (units, prefixes, quantities, dimensions, unit_systems)"
29
+ option :id, type: :string, aliases: "-i",
30
+ desc: "Search for an entity with a specific identifier"
31
+ option :id_type, type: :string,
32
+ desc: "Filter get_by_id search by identifier type"
33
+ option :format, type: :string, default: "text",
34
+ desc: "Output format (text, json, yaml)"
35
+ option :database, type: :string, required: true, aliases: "-d",
36
+ desc: "Path to UnitsDB database (required)"
37
+
38
+ def search(query)
39
+ require_relative "commands/search"
40
+ Commands::Search.new(options).run(query)
41
+ end
42
+
43
+ desc "get ID", "Get detailed information about an entity by ID"
44
+ option :id_type, type: :string,
45
+ desc: "Filter by identifier type"
46
+ option :format, type: :string, default: "text",
47
+ desc: "Output format (text, json, yaml)"
48
+ option :database, type: :string, required: true, aliases: "-d",
49
+ desc: "Path to UnitsDB database (required)"
50
+ def get(id)
51
+ require_relative "commands/get"
52
+ Commands::Get.new(options).get(id)
53
+ end
54
+
55
+ desc "check_si", "Check and update SI digital framework references in UnitsDB"
56
+ option :entity_type, type: :string, aliases: "-e",
57
+ desc: "Entity type to check (units, quantities, or prefixes). If not specified, all types are checked"
58
+ option :ttl_dir, type: :string, required: true, aliases: "-t",
59
+ desc: "Path to the directory containing SI digital framework TTL files"
60
+ option :output_updated_database, type: :string, aliases: "-o",
61
+ desc: "Directory path to write updated YAML files with added SI references"
62
+ option :direction, type: :string, default: "both", aliases: "-r",
63
+ desc: "Direction to check: 'to_si' (UnitsDB→TTL), 'from_si' (TTL→UnitsDB), or 'both'"
64
+ option :include_potential_matches, type: :boolean, default: false, aliases: "-p",
65
+ desc: "Include potential matches when updating references (default: false)"
66
+ option :database, type: :string, required: true, aliases: "-d",
67
+ desc: "Path to UnitsDB database (required)"
68
+
69
+ def check_si
70
+ require_relative "commands/check_si"
71
+ Commands::CheckSi.new(options).run
72
+ end
73
+
74
+ desc "release", "Create release files (unified YAML and/or ZIP archive)"
75
+ option :format, type: :string, default: "all", aliases: "-f",
76
+ desc: "Output format: 'yaml' (single file), 'zip' (archive), or 'all' (both)"
77
+ option :output_dir, type: :string, default: ".", aliases: "-o",
78
+ desc: "Directory to output release files"
79
+ option :version, type: :string, aliases: "-v", required: true,
80
+ desc: "Version number for the release (x.y.z)"
81
+ option :database, type: :string, required: true, aliases: "-d",
82
+ desc: "Path to UnitsDB database (required)"
83
+ def release
84
+ require_relative "commands/release"
85
+ Commands::Release.new(options).run
86
+ end
87
+ end
88
+ 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: "nist",
10
+ desc: "Sort units by: 'short' (name), 'nist' (ID, default), '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,26 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "yaml"
4
+ require "fileutils"
5
+
6
+ module Unitsdb
7
+ module Commands
8
+ class Base
9
+ def initialize(options = {})
10
+ @options = options
11
+ puts "Database directory path: #{@options[:database]}" if @options[:database]
12
+ end
13
+
14
+ protected
15
+
16
+ def load_database(path = nil)
17
+ path ||= @options[:database]
18
+ raise Unitsdb::Errors::DatabaseError, "Database path not specified" unless path
19
+
20
+ Unitsdb::Database.from_db(path)
21
+ rescue StandardError => e
22
+ raise Unitsdb::Errors::DatabaseError, "Failed to load database: #{e.message}"
23
+ end
24
+ end
25
+ end
26
+ 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 = YAML.safe_load(File.read(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,73 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "yaml"
4
+ require "zip"
5
+ require "fileutils"
6
+
7
+ module Unitsdb
8
+ module Commands
9
+ class Release < ::Unitsdb::Commands::Base
10
+ def run
11
+ # Load the database
12
+ db = load_database(@options[:database])
13
+ db.version = @options[:version]
14
+
15
+ # Create output directory if it doesn't exist
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
+ puts "Invalid format option: #{@options[:format]}"
30
+ puts "Valid options are: 'yaml', 'zip', or 'all'"
31
+ exit(1)
32
+ end
33
+
34
+ puts "Release files created successfully in #{@options[:output_dir]}"
35
+ rescue Unitsdb::Errors::DatabaseError => e
36
+ puts "Error: #{e.message}"
37
+ exit(1)
38
+ end
39
+
40
+ private
41
+
42
+ def create_unified_yaml(db)
43
+ # Create a unified YAML file with all database components
44
+ output_path = File.join(@options[:output_dir], "unitsdb-#{@options[:version]}.yaml")
45
+ File.write(output_path, db.to_yaml)
46
+ puts "Created unified YAML file: #{output_path}"
47
+ end
48
+
49
+ def create_zip_archive(db)
50
+ # Create a ZIP archive with individual YAML files
51
+ output_path = File.join(@options[:output_dir], "unitsdb-#{@options[:version]}.zip")
52
+
53
+ Zip::File.open(output_path, Zip::File::CREATE) do |zipfile|
54
+ {
55
+ dimensions: Unitsdb::Dimensions,
56
+ unit_systems: Unitsdb::UnitSystems,
57
+ units: Unitsdb::Units,
58
+ prefixes: Unitsdb::Prefixes,
59
+ quantities: Unitsdb::Quantities
60
+ }.each_pair do |access_method, collection_klass|
61
+ db.send(access_method).tap do |data|
62
+ collection = collection_klass.new(access_method => data)
63
+ collection.version = @options[:version]
64
+ zipfile.get_output_stream("#{access_method}.yaml") { |f| f.write(collection.to_yaml) }
65
+ end
66
+ end
67
+ end
68
+
69
+ puts "Created ZIP archive: #{output_path}"
70
+ end
71
+ end
72
+ end
73
+ end