unitsdb 2.1.0 → 2.1.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.
@@ -0,0 +1,98 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "yaml"
4
+ require "fileutils"
5
+
6
+ module Unitsdb
7
+ module Commands
8
+ module Ucum
9
+ # Updater for adding UCUM references to UnitsDB entities
10
+ module Updater
11
+ UCUM_AUTHORITY = "ucum"
12
+
13
+ module_function
14
+
15
+ # Update references in UnitsDB entities with UCUM references
16
+ def update_references(entity_type, matches, db_entities, output_file, include_potential = false)
17
+ puts "Updating UCUM references for #{entity_type}..."
18
+
19
+ # Create a map of entity IDs to their UCUM references
20
+ entity_references = {}
21
+
22
+ # Process each match
23
+ matches.each do |match|
24
+ db_entity = match[:db_entity]
25
+ ucum_entity = match[:ucum_entity]
26
+
27
+ # Skip potential matches unless specified
28
+ next if match[:potential] && !include_potential
29
+
30
+ # Get entity ID
31
+ entity_id = get_entity_id(db_entity)
32
+ next unless entity_id
33
+
34
+ # Initialize references for this entity
35
+ entity_references[entity_id] = ExternalReference.new(
36
+ uri: ucum_entity.identifier,
37
+ type: "informative",
38
+ authority: UCUM_AUTHORITY
39
+ )
40
+ end
41
+
42
+ # Update the YAML content
43
+ db_entities.send(entity_type).each do |entity|
44
+ # Find entity by ID
45
+ entity_id = if entity.identifiers
46
+ begin
47
+ entity.identifiers.first.id
48
+ rescue StandardError
49
+ nil
50
+ end
51
+ end
52
+
53
+ next unless entity_id && entity_references.key?(entity_id)
54
+
55
+ # Initialize references array if it doesn't exist
56
+ entity.references ||= []
57
+
58
+ # Add new references
59
+ if (ext_ref = entity_references[entity_id])
60
+ if entity.references.detect { |ref| ref.uri == ext_ref.uri && ref.authority == ext_ref.authority }
61
+ # Skip if reference already exists
62
+ puts "Reference already exists for entity ID: #{entity_id}"
63
+ else
64
+ # Add the reference
65
+ puts "Adding reference for entity ID: #{entity_id}, URI: #{ext_ref.uri}, Authority: #{ext_ref.authority}"
66
+ entity.references << ext_ref
67
+ end
68
+ end
69
+ end
70
+
71
+ # Write to YAML file
72
+ write_yaml_file(output_file, db_entities)
73
+
74
+ puts "Added #{entity_references.values.flatten.size} UCUM references to #{entity_type}"
75
+ end
76
+
77
+ # Helper to write YAML file
78
+ def write_yaml_file(output_file, output_data)
79
+ # Ensure the output directory exists
80
+ output_dir = File.dirname(output_file)
81
+ FileUtils.mkdir_p(output_dir) unless Dir.exist?(output_dir)
82
+
83
+ # Write to YAML file
84
+ File.write(output_file, output_data.to_yaml)
85
+ end
86
+
87
+ # Get entity ID (either from identifiers array or directly)
88
+ def get_entity_id(entity)
89
+ if entity.respond_to?(:identifiers) && entity.identifiers && !entity.identifiers.empty?
90
+ entity.identifiers.first.id
91
+ elsif entity.respond_to?(:id)
92
+ entity.id
93
+ end
94
+ end
95
+ end
96
+ end
97
+ end
98
+ end
@@ -0,0 +1,34 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "../../ucum"
4
+
5
+ module Unitsdb
6
+ module Commands
7
+ module Ucum
8
+ # Parser for UCUM XML files
9
+ module XmlParser
10
+ module_function
11
+
12
+ # Parse UCUM XML file and return parsed data
13
+ def parse_ucum_file(file_path)
14
+ puts "Parsing UCUM XML file: #{file_path}..."
15
+ content = File.read(file_path)
16
+ Unitsdb::UcumFile.from_xml(content)
17
+ end
18
+
19
+ # Get entities from parsed UCUM data based on entity type
20
+ def get_entities_from_ucum(entity_type, ucum_data)
21
+ case entity_type
22
+ when "prefixes"
23
+ ucum_data.prefixes
24
+ when "units"
25
+ # Combine base-units and units into a single array
26
+ ucum_data.base_units + ucum_data.units
27
+ else
28
+ []
29
+ end
30
+ end
31
+ end
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,43 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "thor"
4
+
5
+ module Unitsdb
6
+ module Commands
7
+ class UcumCommand < Thor
8
+ desc "check", "Check UCUM references in UnitsDB"
9
+ option :entity_type, type: :string, aliases: "-e",
10
+ desc: "Entity type to check (units, prefixes). If not specified, all types are checked"
11
+ option :ucum_file, type: :string, required: true, aliases: "-u",
12
+ desc: "Path to the UCUM essence XML file"
13
+ option :output_updated_database, type: :string, aliases: "-o",
14
+ desc: "Directory path to write updated YAML files with added UCUM references"
15
+ option :direction, type: :string, default: "both", aliases: "-r",
16
+ desc: "Direction to check: 'to_ucum' (UnitsDB→UCUM), 'from_ucum' (UCUM→UnitsDB), or 'both'"
17
+ option :include_potential_matches, type: :boolean, default: false, aliases: "-p",
18
+ desc: "Include potential matches when updating references (default: false)"
19
+ option :database, type: :string, required: true, aliases: "-d",
20
+ desc: "Path to UnitsDB database (required)"
21
+ def check
22
+ require_relative "ucum/check"
23
+ Ucum::Check.new(options).run
24
+ end
25
+
26
+ desc "update", "Update UnitsDB with UCUM references"
27
+ option :entity_type, type: :string, aliases: "-e",
28
+ desc: "Entity type to update (units, prefixes). If not specified, all types are updated"
29
+ option :ucum_file, type: :string, required: true, aliases: "-u",
30
+ desc: "Path to the UCUM essence XML file"
31
+ option :output_dir, type: :string, aliases: "-o",
32
+ desc: "Directory path to write updated YAML files (defaults to database path)"
33
+ option :include_potential_matches, type: :boolean, default: false, aliases: "-p",
34
+ desc: "Include potential matches when updating references (default: false)"
35
+ option :database, type: :string, required: true, aliases: "-d",
36
+ desc: "Path to UnitsDB database (required)"
37
+ def update
38
+ require_relative "ucum/update"
39
+ Ucum::Update.new(options).run
40
+ end
41
+ end
42
+ end
43
+ end
@@ -1,9 +1,11 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require_relative "../base"
4
+
3
5
  module Unitsdb
4
6
  module Commands
5
7
  module Validate
6
- class Identifiers < Base
8
+ class Identifiers < Unitsdb::Commands::Base
7
9
  def run
8
10
  db = load_database
9
11
  all_dups = db.validate_uniqueness
@@ -1,9 +1,11 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require_relative "../base"
4
+
3
5
  module Unitsdb
4
6
  module Commands
5
7
  module Validate
6
- class References < Base
8
+ class References < Unitsdb::Commands::Base
7
9
  def run
8
10
  # Load the database
9
11
  db = load_database(@options[:database])
@@ -103,12 +103,6 @@ module Unitsdb
103
103
  puts "Please fix the duplicates by either removing the reference from all but one entity,"
104
104
  puts "or by updating the references to use different URIs appropriate for each entity."
105
105
  end
106
-
107
- def load_database(path)
108
- Unitsdb::Database.from_db(path)
109
- rescue StandardError => e
110
- raise Unitsdb::Errors::DatabaseError, "Failed to load database: #{e.message}"
111
- end
112
106
  end
113
107
  end
114
108
  end
@@ -13,7 +13,7 @@ module Unitsdb
13
13
  def references
14
14
  require_relative "validate/references"
15
15
 
16
- Commands::References.new(options).run
16
+ Commands::Validate::References.new(options).run
17
17
  end
18
18
 
19
19
  desc "identifiers", "Check for uniqueness of identifier fields"
@@ -23,7 +23,7 @@ module Unitsdb
23
23
  def identifiers
24
24
  require_relative "validate/identifiers"
25
25
 
26
- Commands::Identifiers.new(options).run
26
+ Commands::Validate::Identifiers.new(options).run
27
27
  end
28
28
 
29
29
  desc "si_references", "Validate that each SI digital framework reference is unique per entity type"
@@ -33,7 +33,7 @@ module Unitsdb
33
33
  def si_references
34
34
  require_relative "validate/si_references"
35
35
 
36
- Validate::SiReferences.new(options).run
36
+ Commands::Validate::SiReferences.new(options).run
37
37
  end
38
38
  end
39
39
  end
@@ -12,6 +12,7 @@ module Unitsdb
12
12
  # model Config.model_for(:units)
13
13
 
14
14
  attribute :schema_version, :string
15
+ attribute :version, :string
15
16
  attribute :units, Unit, collection: true
16
17
  attribute :prefixes, Prefix, collection: true
17
18
  attribute :quantities, Quantity, collection: true
@@ -7,6 +7,7 @@ module Unitsdb
7
7
  # model Config.model_for(:dimensions)
8
8
 
9
9
  attribute :schema_version, :string
10
+ attribute :version, :string
10
11
  attribute :dimensions, Dimension, collection: true
11
12
  end
12
13
  end
@@ -17,6 +17,7 @@ module Unitsdb
17
17
  # model Config.model_for(:prefixes)
18
18
 
19
19
  attribute :schema_version, :string
20
+ attribute :version, :string
20
21
  attribute :prefixes, Prefix, collection: true
21
22
  end
22
23
  end
@@ -6,6 +6,7 @@ module Unitsdb
6
6
  class Quantities < Lutaml::Model::Serializable
7
7
  # model Config.model_for(:quantities)
8
8
  attribute :schema_version, :string
9
+ attribute :version, :string
9
10
  attribute :quantities, Quantity, collection: true
10
11
  end
11
12
  end
@@ -6,6 +6,7 @@ module Unitsdb
6
6
  class Scales < Lutaml::Model::Serializable
7
7
  # model Config.model_for(:Scale)
8
8
  attribute :schema_version, :string
9
+ attribute :version, :string
9
10
  attribute :scales, Scale, collection: true
10
11
  end
11
12
  end
@@ -0,0 +1,198 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "lutaml/model"
4
+
5
+ module Unitsdb
6
+ # <base-unit Code="s" CODE="S" dim="T">
7
+ # <name>second</name>
8
+ # <printSymbol>s</printSymbol>
9
+ # <property>time</property>
10
+ # </base-unit>
11
+ class UcumBaseUnit < Lutaml::Model::Serializable
12
+ attribute :code_sensitive, :string
13
+ attribute :code, :string
14
+ attribute :dimension, :string
15
+ attribute :name, :string
16
+ attribute :print_symbol, :string, raw: true
17
+ attribute :property, :string
18
+
19
+ xml do
20
+ root "base-unit"
21
+ map_attribute "Code", to: :code_sensitive
22
+ map_attribute "CODE", to: :code
23
+ map_attribute "dim", to: :dimension
24
+ map_element "name", to: :name
25
+ map_element "printSymbol", to: :print_symbol
26
+ map_element "property", to: :property
27
+ end
28
+
29
+ def identifier
30
+ "ucum:base-unit:code:#{code_sensitive}"
31
+ end
32
+ end
33
+
34
+ # <prefix Code="Y" CODE="YA">
35
+ # <name>yotta</name>
36
+ # <printSymbol>Y</printSymbol>
37
+ # <value value="1e24">1 &#215; 10<sup>24</sup>
38
+ # </value>
39
+ # </prefix>
40
+
41
+ class UcumPrefixValue < Lutaml::Model::Serializable
42
+ attribute :value, :string
43
+ attribute :content, :string
44
+
45
+ xml do
46
+ root "value"
47
+ map_attribute "value", to: :value
48
+ map_content to: :content
49
+ end
50
+ end
51
+
52
+ class UcumPrefix < Lutaml::Model::Serializable
53
+ attribute :code_sensitive, :string
54
+ attribute :code, :string
55
+ attribute :name, :string
56
+ attribute :print_symbol, :string, raw: true
57
+ attribute :value, UcumPrefixValue
58
+
59
+ xml do
60
+ root "prefix"
61
+ map_attribute "Code", to: :code_sensitive
62
+ map_attribute "CODE", to: :code
63
+ map_element "name", to: :name
64
+ map_element "printSymbol", to: :print_symbol
65
+ map_element "value", to: :value
66
+ end
67
+
68
+ def identifier
69
+ "ucum:prefix:code:#{code_sensitive}"
70
+ end
71
+ end
72
+
73
+ # <unit Code="10*" CODE="10*" isMetric="no" class="dimless">
74
+ # <name>the number ten for arbitrary powers</name>
75
+ # <printSymbol>10</printSymbol>
76
+ # <property>number</property>
77
+ # <value Unit="1" UNIT="1" value="10">10</value>
78
+ # </unit>
79
+
80
+ # <unit Code="gon" CODE="GON" isMetric="no" class="iso1000">
81
+ # <name>gon</name>
82
+ # <name>grade</name>
83
+ # <printSymbol>
84
+ # <sup>g</sup>
85
+ # </printSymbol>
86
+ # <property>plane angle</property>
87
+ # <value Unit="deg" UNIT="DEG" value="0.9">0.9</value>
88
+ # </unit>
89
+
90
+ # <unit Code="[D'ag'U]" CODE="[D'AG'U]" isMetric="no" isArbitrary="yes"
91
+ # class="chemical">
92
+ # <name>D-antigen unit</name>
93
+ # <printSymbol/>
94
+ # <property>procedure defined amount of a poliomyelitis d-antigen substance</property>
95
+ # <value Unit="1" UNIT="1" value="1">1</value>
96
+ # </unit>
97
+
98
+ # <unit Code="Cel" CODE="CEL" isMetric="yes" isSpecial="yes" class="si">
99
+ # <name>degree Celsius</name>
100
+ # <printSymbol>&#176;C</printSymbol>
101
+ # <property>temperature</property>
102
+ # <value Unit="cel(1 K)" UNIT="CEL(1 K)">
103
+ # <function name="Cel" value="1" Unit="K"/>
104
+ # </value>
105
+ # </unit>
106
+
107
+ class UcumUnitValueFunction < Lutaml::Model::Serializable
108
+ attribute :name, :string
109
+ attribute :value, :string
110
+ attribute :unit_sensitive, :string
111
+
112
+ xml do
113
+ root "function"
114
+ map_attribute "name", to: :name
115
+ map_attribute "value", to: :value
116
+ map_attribute "Unit", to: :unit_sensitive
117
+ end
118
+ end
119
+
120
+ class UcumUnitValue < Lutaml::Model::Serializable
121
+ attribute :unit_sensitive, :string
122
+ attribute :unit, :string
123
+ attribute :value, :string
124
+ attribute :function, UcumUnitValueFunction
125
+ attribute :content, :string
126
+
127
+ xml do
128
+ root "value"
129
+ map_attribute "Unit", to: :unit_sensitive
130
+ map_attribute "UNIT", to: :unit
131
+ map_attribute "value", to: :value
132
+ map_element "function", to: :function
133
+ map_content to: :content
134
+ end
135
+ end
136
+
137
+ class UcumUnit < Lutaml::Model::Serializable
138
+ attribute :code_sensitive, :string
139
+ attribute :code, :string
140
+ attribute :is_metric, :string
141
+ attribute :is_arbitrary, :string
142
+ attribute :is_special, :string
143
+ attribute :klass, :string
144
+ attribute :name, :string, collection: true
145
+ attribute :print_symbol, :string, raw: true
146
+ attribute :property, :string
147
+ attribute :value, UcumUnitValue
148
+
149
+ xml do
150
+ root "unit"
151
+ map_attribute "Code", to: :code_sensitive
152
+ map_attribute "CODE", to: :code
153
+ map_attribute "isMetric", to: :is_metric
154
+ map_attribute "isArbitrary", to: :is_arbitrary
155
+ map_attribute "isSpecial", to: :is_special
156
+ map_attribute "class", to: :klass
157
+
158
+ map_element "name", to: :name
159
+ map_element "printSymbol", to: :print_symbol
160
+ map_element "property", to: :property
161
+ map_element "value", to: :value
162
+ end
163
+
164
+ def identifier
165
+ # Use empty string if klass is not present
166
+ k = klass || ""
167
+ "ucum:unit:#{k}:code:#{code_sensitive}"
168
+ end
169
+ end
170
+
171
+ # This is the root element of the UCUM XML "ucum-essence.xml" file.
172
+ #
173
+ # <root xmlns="http://unitsofmeasure.org/ucum-essence" version="2.2" revision="N/A"
174
+ # revision-date="2024-06-17">
175
+
176
+ class UcumFile < Lutaml::Model::Serializable
177
+ attribute :revision, :string
178
+ attribute :version, :string
179
+ attribute :revision_date, :date
180
+ attribute :prefixes, UcumPrefix, collection: true
181
+ attribute :base_units, UcumBaseUnit, collection: true
182
+ attribute :units, UcumUnit, collection: true
183
+
184
+ xml do
185
+ root "root"
186
+ namespace "http://unitsofmeasure.org/ucum-essence"
187
+ map_attribute "version", to: :version
188
+ map_attribute "revision", to: :revision
189
+ map_attribute "revision-date", to: :revision_date
190
+
191
+ map_element "prefix", to: :prefixes
192
+ map_element "base-unit", to: :base_units
193
+ map_element "unit", to: :units
194
+ end
195
+
196
+ # No adapter registration needed
197
+ end
198
+ end
@@ -7,6 +7,7 @@ module Unitsdb
7
7
  # model Config.model_for(:unit_systems)
8
8
 
9
9
  attribute :schema_version, :string
10
+ attribute :version, :string
10
11
  attribute :unit_systems, UnitSystem, collection: true
11
12
  end
12
13
  end
data/lib/unitsdb/units.rb CHANGED
@@ -7,6 +7,7 @@ module Unitsdb
7
7
  # model Config.model_for(:units)
8
8
 
9
9
  attribute :schema_version, :string
10
+ attribute :version, :string
10
11
  attribute :units, Unit, collection: true
11
12
  end
12
13
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Unitsdb
4
- VERSION = "2.1.0"
4
+ VERSION = "2.1.1"
5
5
  end
data/unitsdb.gemspec CHANGED
@@ -19,8 +19,6 @@ Gem::Specification.new do |spec|
19
19
  spec.metadata["source_code_uri"] = spec.homepage
20
20
  spec.metadata["changelog_uri"] = "https://github.com/unitsml/unitsdb-ruby/releases"
21
21
 
22
- spec.required_ruby_version = Gem::Requirement.new(">= 3.0.0")
23
-
24
22
  # Specify which files should be added to the gem when it is released.
25
23
  # The `git ls-files -z` loads the files in the RubyGem that have been added into git.
26
24
  spec.files = Dir.chdir(File.expand_path(__dir__)) do
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: unitsdb
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.1.0
4
+ version: 2.1.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Ribose Inc.
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2025-05-20 00:00:00.000000000 Z
11
+ date: 2025-07-11 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: lutaml-model
@@ -132,6 +132,13 @@ files:
132
132
  - lib/unitsdb/commands/si_matcher.rb
133
133
  - lib/unitsdb/commands/si_ttl_parser.rb
134
134
  - lib/unitsdb/commands/si_updater.rb
135
+ - lib/unitsdb/commands/ucum.rb
136
+ - lib/unitsdb/commands/ucum/check.rb
137
+ - lib/unitsdb/commands/ucum/formatter.rb
138
+ - lib/unitsdb/commands/ucum/matcher.rb
139
+ - lib/unitsdb/commands/ucum/update.rb
140
+ - lib/unitsdb/commands/ucum/updater.rb
141
+ - lib/unitsdb/commands/ucum/xml_parser.rb
135
142
  - lib/unitsdb/commands/validate.rb
136
143
  - lib/unitsdb/commands/validate/identifiers.rb
137
144
  - lib/unitsdb/commands/validate/references.rb
@@ -159,6 +166,7 @@ files:
159
166
  - lib/unitsdb/scales.rb
160
167
  - lib/unitsdb/si_derived_base.rb
161
168
  - lib/unitsdb/symbol_presentations.rb
169
+ - lib/unitsdb/ucum.rb
162
170
  - lib/unitsdb/unit.rb
163
171
  - lib/unitsdb/unit_reference.rb
164
172
  - lib/unitsdb/unit_system.rb