diff --git a/Gemfile b/Gemfile index de82ade..1a7a0f2 100644 --- a/Gemfile +++ b/Gemfile @@ -2,6 +2,3 @@ source "https://rubygems.org" # Specify your gem's dependencies in unitsml.gemspec gemspec - -gem "rake", "~> 12.0" -gem "rspec", "~> 3.0" diff --git a/Gemfile.lock b/Gemfile.lock index 950f62e..279f334 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -6,7 +6,20 @@ PATH GEM remote: https://rubygems.org/ specs: + ast (2.4.2) + byebug (11.1.3) + coderay (1.1.3) diff-lcs (1.4.4) + docile (1.3.5) + method_source (0.9.2) + parallel (1.20.1) + parser (3.0.0.0) + ast (~> 2.4.1) + powerpack (0.1.3) + pry (0.12.2) + coderay (~> 1.1.0) + method_source (~> 0.9.0) + rainbow (3.0.0) rake (12.3.3) rspec (3.10.0) rspec-core (~> 3.10.0) @@ -21,13 +34,32 @@ GEM diff-lcs (>= 1.2.0, < 2.0) rspec-support (~> 3.10.0) rspec-support (3.10.2) + rubocop (0.54.0) + parallel (~> 1.10) + parser (>= 2.5) + powerpack (~> 0.1) + rainbow (>= 2.2.2, < 4.0) + ruby-progressbar (~> 1.7) + unicode-display_width (~> 1.0, >= 1.0.1) + ruby-progressbar (1.11.0) + simplecov (0.21.2) + docile (~> 1.1) + simplecov-html (~> 0.11) + simplecov_json_formatter (~> 0.1) + simplecov-html (0.12.3) + simplecov_json_formatter (0.1.2) + unicode-display_width (1.7.0) PLATFORMS ruby DEPENDENCIES + byebug + pry (~> 0.12.2) rake (~> 12.0) - rspec (~> 3.0) + rspec (~> 3.6) + rubocop (= 0.54.0) + simplecov (~> 0.15) unitsml! BUNDLED WITH diff --git a/lib/unitsml.rb b/lib/unitsml.rb index fa246a7..2707500 100644 --- a/lib/unitsml.rb +++ b/lib/unitsml.rb @@ -1,8 +1,19 @@ require "unitsml/version" +require "unitsml/dimension" +require "unitsml/quantity" require "unitsml/unit" +require "unitsml/unit_system" module Unitsml def find_unit(ascii:) Unitsml::Unit.find_unit(ascii: ascii) end + + def find_dimension(ascii:) + Unitsml::Dimension.find_unit(ascii: ascii) + end + + def find_unit_system(ascii:) + Unitsml::UnitSystem.find_unit_system(ascii: ascii) + end end diff --git a/lib/unitsml/dimension.rb b/lib/unitsml/dimension.rb new file mode 100644 index 0000000..1d9355b --- /dev/null +++ b/lib/unitsml/dimension.rb @@ -0,0 +1,86 @@ +module Unitsml + class Dimension + UNITS_DB_DIMENSIONS_PATH = File + .expand_path("../../vendor/unitsdb/dimensions.yaml", + File.dirname(__FILE__)) + .freeze + + attr_reader :id, + :length, + :mass, + :time, + :electric_current, + :thermodynamic_temperature, + :amount_of_substance, + :luminous_intensity, + :plane_angle, + :dimensionless + + class << self + def find_dimension(ascii:) + symbols.find { |unit| unit.id == ascii.to_s } + end + + def symbols + @symbols ||= YAML + .load(File.read(UNITS_DB_DIMENSIONS_PATH)) + .map { |(id, attrs)| Unitsml::Dimension.new(id, attrs) } + end + + def from_yaml(yaml_path) + @symbols = YAML + .load(File.read(yaml_path)) + .map { |(id, attrs)| Unitsml::Dimension.new(id, attrs) } + end + end + + def initialize(id, hash) + begin + @id = id + @dimensionless = hash["dimensionless"] + hash["length"] and @length = hash["length"]["powerNumerator"].to_i + hash["mass"] and @mass = hash["mass"]["powerNumerator"].to_i + hash["time"] and @time = hash["time"]["powerNumerator"].to_i + hash["electric_current"] and @electric_current = hash["electric_current"]["powerNumerator"].to_i + hash["thermodynamic_temperature"] and + @thermodynamic_temperature = hash["thermodynamic_temperature"]["powerNumerator"].to_i + hash["amount_of_substance"] and @amount_of_substance = hash["amount_of_substance"]["powerNumerator"].to_i + hash["luminous_intensity"] and @luminous_intensity = hash["luminous_intensity"]["powerNumerator"].to_i + hash["plane_angle"] and @plane_angle = hash["plane_angle"]["powerNumerator"].to_i + rescue + raise StandardError.new "Parse fail on Dimension #{id}: #{hash}" + end + end + + def keys + ret = [] + @length and ret << "Length" + @mass and ret << "Mass" + @time and ret << "Time" + @electric_current and ret << "ElectricCurrent" + @thermodynamic_temperature and ret << "ThermodynamicTemperature" + @amount_of_substance and ret << "AmountOfSubstance" + @luminous_intensity and ret << "LuminousIntensity" + @plane_angle and ret << "PlaneAngle" + ret + end + + def exponent(key) + case key + when "Length" then @length + when "Mass" then @mass + when "Time" then @time + when "ElectricCurrent" then @electric_current + when "ThermodynamicTemperature" then @thermodynamic_temperature + when "AmountOfSubstance" then @amount_of_substance + when "LuminousIntensity" then @luminous_intensity + when "PlaneAngle" then @plane_angle + end + end + + def vector + "#{@length}:#{@mass}:#{@time}:#{@electric_current}:#{@thermodynamic_temperature}:#{@amount_of_substance}:"\ + "#{@luminous_intensity}:#{@plane_angle}" + end + end +end diff --git a/lib/unitsml/quantity.rb b/lib/unitsml/quantity.rb new file mode 100644 index 0000000..4135853 --- /dev/null +++ b/lib/unitsml/quantity.rb @@ -0,0 +1,60 @@ +require "unitsml/unit" + +module Unitsml + class Quantity + UNITS_DB_QUANTITIES_PATH = File + .expand_path("../../vendor/unitsdb/quantities.yaml", + File.dirname(__FILE__)) + .freeze + + attr_reader :id, :dimension, :type, :names, :units + + class << self + def find_quantity(ascii:) + symbols.find { |unit| unit.id == ascii.to_s } + end + + def symbols + @symbols ||= YAML + .load(File.read(UNITS_DB_QUANTITIES_PATH)) + .map { |(id, attrs)| Unitsml::Quantity.new(id, attrs) } + end + + def from_yaml(yaml_path) + @symbols = YAML + .load(File.read(yaml_path)) + .map { |(id, attrs)| Unitsml::Quantity.new(id, attrs) } + end + end + + def initialize(id, hash) + begin + @id = id + @dimension = hash["dimension_url"].sub(/^#/, "") + @type = hash["quantity_type"] + hash["quantity_name"] and @names = hash["quantity_name"] + @units = serialize_units(hash["unit_reference"]) + # rescue + # raise StandardError.new "Parse fail on Quantity #{id}: #{hash}" + end + end + + def serialize_units(unit_references) + return unless unit_references + + unit_references + .map do |attrs| + id = attrs["url"].sub(/^#/, "") + Unitsml::Unit.symbols.find { |unit| unit.id == id } + end + end + + def name + @names&.first + end + + def unit + @units&.first + end + end +end \ No newline at end of file diff --git a/lib/unitsml/unit.rb b/lib/unitsml/unit.rb index 8b96a6b..22795db 100644 --- a/lib/unitsml/unit.rb +++ b/lib/unitsml/unit.rb @@ -1,8 +1,9 @@ require "yaml" +require "unitsml/unit_symbol" module Unitsml class Unit - UNITS_DB_PATH = File + UNITS_DB_UNITS_PATH = File .expand_path("../../vendor/unitsdb/units.yaml", File.dirname(__FILE__)) .freeze @@ -21,32 +22,42 @@ class Unit :prefixed class << self - def find_unit(ascii: ascii) - id, attrs = symbols.find { |(id, attribute)| id == ascii.to_s } - Unitsml::Unit.new(id, attrs) if id + def find_unit(ascii:) + symbols.find { |unit| unit.symbols_hash.keys.include?(ascii.to_s) } end def symbols - @symbols ||= YAML.load(File.read(UNITS_DB_PATH)) + @symbols ||= YAML + .load(File.read(UNITS_DB_UNITS_PATH)) + .map { |(id, attrs)| Unitsml::Unit.new(id, attrs) } + end + + def from_yaml(yaml_path) + @symbols = YAML + .load(File.read(yaml_path)) + .map { |(id, attrs)| Unitsml::Unit.new(id, attrs) } end end - def initialize(id, hash) + def initialize(id, attrs) begin @id = id @short = short - @dimension = hash["dimension_url"].sub(/^#/, "") - hash["short"] && !hash["short"].empty? and @short = hash["short"] - @unit_system = hash["unit_system"] - @names = hash["unit_name"] - @symbols_hash = hash["unit_symbols"]&.each_with_object({}) { |h, m| m[h["id"]] = h } || {} - @symbols = hash["unit_symbols"] - hash["root_units"] and hash["root_units"]["enumerated_root_units"] and - @root = hash["root_units"]["enumerated_root_units"] - hash["quantity_reference"] and @quantities = hash["quantity_reference"].map { |x| x["url"].sub(/^#/, "") } - hash["si_derived_bases"] and @si_derived_bases = hash["si_derived_bases"] + @dimension = attrs["dimension_url"].sub(/^#/, "") + attrs["short"] && !attrs["short"].empty? and @short = attrs["short"] + @unit_system = attrs["unit_system"] + @names = attrs["unit_name"] + @symbols_hash = attrs["unit_symbols"] + &.each_with_object({}) do |sym_attrs, res| + res[sym_attrs["id"]] = Unitsml::UnitSymbol.new(id, sym_attrs) + end || {} + @symbols = attrs["unit_symbols"]&.map { |sym_attrs| Unitsml::UnitSymbol.new(sym_attrs["id"], sym_attrs) } + attrs["root_units"] and attrs["root_units"]["enumerated_root_units"] and + @root = attrs["root_units"]["enumerated_root_units"] + attrs["quantity_reference"] and @quantities = attrs["quantity_reference"].map { |x| x["url"].sub(/^#/, "") } + attrs["si_derived_bases"] and @si_derived_bases = attrs["si_derived_bases"] rescue - raise StandardError.new "Parse fail on Unit #{id}: #{hash}" + raise StandardError.new "Parse fail on Unit #{id}: #{attrs}" end end @@ -69,5 +80,9 @@ def symbolid def symbolids @symbols ? @symbols.map { |s| s["id"] } : [ @short ] end + + def to_latex + @symbols.map(&:latex).reduce(:+) if @symbols + end end end diff --git a/lib/unitsml/unit_symbol.rb b/lib/unitsml/unit_symbol.rb new file mode 100644 index 0000000..27b9fdf --- /dev/null +++ b/lib/unitsml/unit_symbol.rb @@ -0,0 +1,21 @@ +require "yaml" + +module Unitsml + class UnitSymbol + attr_reader :id, + :ascii, + :html, + :mathml, + :latex, + :unicode + + def initialize(id, hash) + @id = hash['id'] + @ascii = hash['ascii'] + @html = hash['html'] + @mathml = hash['mathml'] + @latex = hash['latex'] + @unicode = hash['unicode'] + end + end +end diff --git a/lib/unitsml/unit_system.rb b/lib/unitsml/unit_system.rb new file mode 100644 index 0000000..040a28a --- /dev/null +++ b/lib/unitsml/unit_system.rb @@ -0,0 +1,36 @@ +module Unitsml + class UnitSystem + UNITS_DB_UNIT_SYSTEMS_PATH = File + .expand_path("../../vendor/unitsdb/unit_systems.yaml", + File.dirname(__FILE__)) + .freeze + + attr_reader :id, + :name, + :acceptable + + class << self + def find_unit_system(ascii:) + symbols.find { |unit| unit.id == ascii.to_s } + end + + def symbols + @symbols ||= YAML + .load(File.read(UNITS_DB_UNIT_SYSTEMS_PATH)) + .map { |attrs| Unitsml::UnitSystem.new(attrs["id"], attrs) } + end + + def from_yaml(yaml_path) + @symbols = YAML + .load(File.read(yaml_path)) + .map { |attrs| Unitsml::UnitSystem.new(attrs["id"], attrs) } + end + end + + def initialize(id, attrs) + @id = attrs["id"] + @name = attrs["name"] + @acceptable = attrs["acceptable"] + end + end +end diff --git a/spec/fixtures/dimensions_test.yaml b/spec/fixtures/dimensions_test.yaml new file mode 100644 index 0000000..00b1d43 --- /dev/null +++ b/spec/fixtures/dimensions_test.yaml @@ -0,0 +1,15 @@ +--- +NISTd1: + length: + powerNumerator: 1 + symbol: L + +NISTd2: + mass: + powerNumerator: 1 + symbol: M + +NISTd3: + time: + powerNumerator: 1 + symbol: T diff --git a/spec/fixtures/unit_systems_test.yaml b/spec/fixtures/unit_systems_test.yaml new file mode 100644 index 0000000..01a4988 --- /dev/null +++ b/spec/fixtures/unit_systems_test.yaml @@ -0,0 +1,7 @@ +--- +- id: test1 + name: SI + acceptable: true +- id: test2 + name: SI + acceptable: true \ No newline at end of file diff --git a/spec/fixtures/units_test.yaml b/spec/fixtures/units_test.yaml new file mode 100644 index 0000000..20e0b1b --- /dev/null +++ b/spec/fixtures/units_test.yaml @@ -0,0 +1,76 @@ +--- +"test1": + dimension_url: "#NISTd1" + short: meter + root: true + unit_system: + type: "SI_base" + name: "SI" + unit_name: + - "meter" + unit_symbols: + - id: "m" + ascii: "m" + html: "m" + mathml: "m" + latex: \ensuremath{\mathrm{m}} + unicode: "m" + root_units: + enumerated_root_units: + - unit: "meter" + power_denominator: 1 + power_numerator: 1 + quantity_reference: + - name: "length" + url: "#NISTq1" + - name: "diameter" + url: "#NISTq100" + - name: "length of path" + url: "#NISTq101" + - name: "cartesian coordinates" + url: "#NISTq102" + - name: "position vector" + url: "#NISTq103" + - name: "displacement" + url: "#NISTq104" + - name: "radius of curvature" + url: "#NISTq105" + - name: "wavelength" + url: "#NISTq114" + - name: "distance" + url: "#NISTq48" + - name: "breadth" + url: "#NISTq95" + - name: "height" + url: "#NISTq96" + - name: "thickness" + url: "#NISTq97" + - name: "radius" + url: "#NISTq98" + - name: "radial distance" + url: "#NISTq99" + +"test2": + dimension_url: "#NISTd7" + short: candela + root: true + unit_system: + type: "SI_base" + name: "SI" + unit_name: + - "candela" + unit_symbols: + - id: "cd" + ascii: "cd" + html: "cd" + mathml: "cd" + latex: \ensuremath{\mathrm{cd}} + unicode: "cd" + root_units: + enumerated_root_units: + - unit: "candela" + power_denominator: 1 + power_numerator: 1 + quantity_reference: + - name: "luminous intensity" + url: "#NISTq7" \ No newline at end of file diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index 1ed808f..49f45bb 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -1,5 +1,6 @@ require "bundler/setup" require "unitsml" +require "byebug" RSpec.configure do |config| # Enable flags like --only-failures and --next-failure @@ -12,3 +13,7 @@ c.syntax = :expect end end + +def fixtures_path(path) + File.join(File.expand_path("./fixtures", __dir__), path) +end \ No newline at end of file diff --git a/spec/unitsml/dimension_spec.rb b/spec/unitsml/dimension_spec.rb new file mode 100644 index 0000000..62f86ee --- /dev/null +++ b/spec/unitsml/dimension_spec.rb @@ -0,0 +1,35 @@ +require "spec_helper" + +RSpec.describe Unitsml::Dimension do + describe ".find_dimension" do + subject(:find_dimension) { described_class.find_dimension(ascii: dimension_symbol) } + + context 'when existing symbol' do + let(:dimension_symbol) { :NISTd1 } + + it 'returns instance of Unitsml::Dimension' do + expect(find_dimension).to(be_instance_of(Unitsml::Dimension)) + expect(find_dimension.id).to(eq("NISTd1")) + end + end + + context 'when non existing symbol' do + let(:dimension_symbol) { :random_name } + + it 'returns nil' do + expect(find_dimension).to(be_nil) + end + end + end + + describe ".from_yaml" do + subject(:from_yaml) { described_class.from_yaml(yaml_path) } + + let(:yaml_path) { fixtures_path('dimensions_test.yaml') } + + it "loads symbols files and uses it for lookup" do + expect { from_yaml }.to change { described_class.symbols.length }.to(3) + expect(described_class.symbols.map(&:id)).to(eq(%w[NISTd1 NISTd2 NISTd3])) + end + end +end \ No newline at end of file diff --git a/spec/unitsml/quantity_spec.rb b/spec/unitsml/quantity_spec.rb new file mode 100644 index 0000000..ac31672 --- /dev/null +++ b/spec/unitsml/quantity_spec.rb @@ -0,0 +1,36 @@ +require "spec_helper" + +RSpec.describe Unitsml::Quantity do + describe ".find_quantity" do + subject(:find_quantity) { described_class.find_quantity(ascii: unit_symbol) } + + context 'when existing symbol' do + let(:unit_symbol) { "NISTq48" } + + it 'returns instance of Unitsml::Unit' do + expect(find_quantity).to(be_instance_of(described_class)) + expect(find_quantity.id).to(eq(unit_symbol)) + expect(find_quantity.units.first).to(be_instance_of(Unitsml::Unit)) + end + end + + context 'when non existing symbol' do + let(:unit_symbol) { :random_name } + + it 'returns nil' do + expect(find_quantity).to(be_nil) + end + end + end + + describe ".from_yaml" do + subject(:from_yaml) { described_class.from_yaml(yaml_path) } + + let(:yaml_path) { fixtures_path('units_test.yaml') } + + it "loads symbols files and uses it for lookup" do + expect { from_yaml }.to change { described_class.symbols.length }.to(2) + expect(described_class.symbols.map(&:id)).to(eq(%w[test1 test2])) + end + end +end \ No newline at end of file diff --git a/spec/unitsml/unit_spec.rb b/spec/unitsml/unit_spec.rb index 59df5cc..ba17c0d 100644 --- a/spec/unitsml/unit_spec.rb +++ b/spec/unitsml/unit_spec.rb @@ -5,11 +5,13 @@ subject(:find_unit) { described_class.find_unit(ascii: unit_symbol) } context 'when existing symbol' do - let(:unit_symbol) { :NISTu1 } + let(:unit_symbol) { "mL" } it 'returns instance of Unitsml::Unit' do expect(find_unit).to(be_instance_of(Unitsml::Unit)) - expect(find_unit.short).to(eq("meter")) + expect(find_unit.short).to(eq("milliliter")) + expect(find_unit.symbols.first).to(be_instance_of(Unitsml::UnitSymbol)) + expect(find_unit.to_latex).to(eq("\\ensuremath{\\mathrm{mL}}\\ensuremath{\\mathrm{ml}}")) end end @@ -21,4 +23,15 @@ end end end + + describe ".from_yaml" do + subject(:from_yaml) { described_class.from_yaml(yaml_path) } + + let(:yaml_path) { fixtures_path('units_test.yaml') } + + it "loads symbols files and uses it for lookup" do + expect { from_yaml }.to change { described_class.symbols.length }.to(2) + expect(described_class.symbols.map(&:id)).to(eq(%w[test1 test2])) + end + end end \ No newline at end of file diff --git a/spec/unitsml/unit_system_spec.rb b/spec/unitsml/unit_system_spec.rb new file mode 100644 index 0000000..437b668 --- /dev/null +++ b/spec/unitsml/unit_system_spec.rb @@ -0,0 +1,35 @@ +require "spec_helper" + +RSpec.describe Unitsml::UnitSystem do + describe ".find_unit_system_system" do + subject(:find_unit_system) { described_class.find_unit_system(ascii: unit_symbol) } + + context 'when existing symbol' do + let(:unit_symbol) { "SI_base" } + + it 'returns instance of Unitsml::UnitSystem' do + expect(find_unit_system).to(be_instance_of(described_class)) + expect(find_unit_system.name).to(eq("SI")) + end + end + + context 'when non existing symbol' do + let(:unit_symbol) { :random_name } + + it 'returns nil' do + expect(find_unit_system).to(be_nil) + end + end + end + + describe ".from_yaml" do + subject(:from_yaml) { described_class.from_yaml(yaml_path) } + + let(:yaml_path) { fixtures_path("unit_systems_test.yaml") } + + it "loads symbols files and uses it for lookup" do + expect { from_yaml }.to change { described_class.symbols.length }.to(2) + expect(described_class.symbols.map(&:id)).to(eq(%w[test1 test2])) + end + end +end \ No newline at end of file diff --git a/spec/unitsml_spec.rb b/spec/unitsml_spec.rb index ff927b3..77fb592 100644 --- a/spec/unitsml_spec.rb +++ b/spec/unitsml_spec.rb @@ -2,8 +2,4 @@ it "has a version number" do expect(Unitsml::VERSION).not_to be nil end - - it "does something useful" do - expect(false).to eq(true) - end end diff --git a/unitsml.gemspec b/unitsml.gemspec index e5e026e..6bb602f 100644 --- a/unitsml.gemspec +++ b/unitsml.gemspec @@ -24,4 +24,11 @@ Gem::Specification.new do |spec| spec.bindir = "exe" spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) } spec.require_paths = ["lib"] + + spec.add_development_dependency "byebug" + spec.add_development_dependency "pry", "~> 0.12.2" + spec.add_development_dependency "rake", "~> 12.0" + spec.add_development_dependency "rspec", "~> 3.6" + spec.add_development_dependency "rubocop", "= 0.54.0" + spec.add_development_dependency "simplecov", "~> 0.15" end