unitfy 1.0.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.
- data/.gitignore +3 -0
- data/.rspec +4 -0
- data/Gemfile +18 -0
- data/Gemfile.lock +89 -0
- data/Guardfile +15 -0
- data/MIT-LICENSE +20 -0
- data/README +0 -0
- data/Rakefile +3 -0
- data/lib/unity.rb +20 -0
- data/lib/unity/arithmetic.rb +64 -0
- data/lib/unity/comparison.rb +33 -0
- data/lib/unity/conversion.rb +46 -0
- data/lib/unity/dimension.rb +37 -0
- data/lib/unity/dimension/integer.rb +34 -0
- data/lib/unity/dimension/vector.rb +43 -0
- data/lib/unity/fraction.rb +118 -0
- data/lib/unity/lookup.rb +102 -0
- data/lib/unity/lookup/definitions.rb +50 -0
- data/lib/unity/lookup/derived_unit.rb +24 -0
- data/lib/unity/lookup/property.rb +21 -0
- data/lib/unity/lookup/simple_unit.rb +26 -0
- data/lib/unity/quantity.rb +20 -0
- data/lib/unity/version.rb +3 -0
- data/spec/arithmetic_spec.rb +6 -0
- data/spec/comparison_spec.rb +6 -0
- data/spec/conversion_spec.rb +8 -0
- data/spec/dimension/integer_spec.rb +7 -0
- data/spec/dimension/vector_spec.rb +7 -0
- data/spec/dimension_spec.rb +7 -0
- data/spec/fabricators/unit_fabricator.rb +14 -0
- data/spec/fraction_spec.rb +7 -0
- data/spec/lookup/derived_unit_spec.rb +44 -0
- data/spec/lookup/property_spec.rb +18 -0
- data/spec/lookup/simple_unit_spec.rb +41 -0
- data/spec/lookup_spec.rb +135 -0
- data/spec/quantity_spec.rb +16 -0
- data/spec/spec_helper.rb +28 -0
- data/spec/support/load_debugger.rb +7 -0
- data/spec/support/shared_examples/units/arithmetic.rb +127 -0
- data/spec/support/shared_examples/units/comparison.rb +69 -0
- data/spec/support/shared_examples/units/conversion.rb +49 -0
- data/spec/support/shared_examples/units/dimension/integer.rb +41 -0
- data/spec/support/shared_examples/units/dimension/vector.rb +10 -0
- data/spec/support/shared_examples/units/fractions.rb +131 -0
- data/unitfy.gemspec +24 -0
- metadata +133 -0
@@ -0,0 +1,118 @@
|
|
1
|
+
# -*- encoding : utf-8 -*-
|
2
|
+
module Unity
|
3
|
+
class InvalidFormat < RuntimeError
|
4
|
+
end
|
5
|
+
|
6
|
+
module Fraction
|
7
|
+
extend ::ActiveSupport::Concern
|
8
|
+
|
9
|
+
included do
|
10
|
+
|
11
|
+
end
|
12
|
+
|
13
|
+
module ClassMethods
|
14
|
+
|
15
|
+
end
|
16
|
+
|
17
|
+
def initialize *args
|
18
|
+
self.value ||= 0.0
|
19
|
+
self.numerator ||= []
|
20
|
+
self.denominator ||= []
|
21
|
+
end
|
22
|
+
|
23
|
+
|
24
|
+
def expression=string
|
25
|
+
unless string.blank?
|
26
|
+
num_denom = string.split('/')
|
27
|
+
raise(InvalidFormat, string) if num_denom.length > 2
|
28
|
+
self.numerator = num_denom[0].split('*').map(&:strip)
|
29
|
+
self.denominator = (num_denom[1].nil? ? [] : num_denom[1].split('*')).map(&:strip)
|
30
|
+
self.value = extract_value!(:numerator) / extract_value!(:denominator)
|
31
|
+
reduce
|
32
|
+
else
|
33
|
+
self.value = 0.0
|
34
|
+
self.numerator = []
|
35
|
+
self.denominator = []
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
def expression
|
40
|
+
numertor_string = ([value] + (numerator || [])).join('*')
|
41
|
+
denominator.empty? ? numertor_string : "#{numertor_string}/#{denominator.join('*')}"
|
42
|
+
end
|
43
|
+
|
44
|
+
def unit
|
45
|
+
numertor_string = (numerator.blank? && !denominator.blank?) ? '1.0' : (numerator || []).join('*')
|
46
|
+
denominator.blank? ? numertor_string : "#{numertor_string}/#{denominator.join('*')}"
|
47
|
+
end
|
48
|
+
|
49
|
+
def expanded_numerator
|
50
|
+
expand_unit_array numerator
|
51
|
+
end
|
52
|
+
|
53
|
+
def expanded_denominator
|
54
|
+
expand_unit_array denominator
|
55
|
+
end
|
56
|
+
|
57
|
+
def inverse
|
58
|
+
new_value = value == 0.0 ? 0.0 : 1.0 / value
|
59
|
+
self.class.new :value => new_value, :numerator => denominator, :denominator => numerator
|
60
|
+
end
|
61
|
+
|
62
|
+
def reduce
|
63
|
+
expand
|
64
|
+
|
65
|
+
self.denominator.delete_if do |unit|
|
66
|
+
if nindex = self.numerator.index(unit)
|
67
|
+
self.numerator.delete_at(nindex)
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
self.numerator = reduce_unit_array(self.numerator)
|
72
|
+
self.denominator = reduce_unit_array(self.denominator)
|
73
|
+
|
74
|
+
end
|
75
|
+
|
76
|
+
private
|
77
|
+
|
78
|
+
def expand
|
79
|
+
self.numerator = expanded_numerator
|
80
|
+
self.denominator = expanded_denominator
|
81
|
+
end
|
82
|
+
|
83
|
+
def seperate! array
|
84
|
+
[].tap{|rejected| array.delete_if { |v| yield(v) && rejected << v }}
|
85
|
+
end
|
86
|
+
|
87
|
+
def extract_value! method
|
88
|
+
number_regex = /\A[-+]?\d*\.?\d+([eE][-+]?\d+)?\Z/
|
89
|
+
seperate!(self.send(method)){|x| number_regex =~ x }.map(&:to_f).
|
90
|
+
inject(1.0){|product, number| product*number }
|
91
|
+
end
|
92
|
+
|
93
|
+
def reduce_unit_array array
|
94
|
+
old_array = array.clone
|
95
|
+
[].tap do |new_array|
|
96
|
+
while !old_array.empty?
|
97
|
+
unit = old_array[0]
|
98
|
+
count = old_array.count(unit)
|
99
|
+
old_array.delete(unit)
|
100
|
+
count = (count == 1) ? '' : "^#{count}"
|
101
|
+
new_array.push("#{unit}#{count}")
|
102
|
+
end
|
103
|
+
end
|
104
|
+
end
|
105
|
+
|
106
|
+
def expand_unit_array array
|
107
|
+
array.collect do |unit|
|
108
|
+
if /\A([^\^\s]+)(\^(\d+))?\Z/ =~ unit
|
109
|
+
unit = Regexp.last_match(1)
|
110
|
+
power = Regexp.last_match(3).blank? ? 1 : Regexp.last_match(3).to_i
|
111
|
+
Array.new(power, unit)
|
112
|
+
else
|
113
|
+
raise InvalidFormat, "Invalid format for Unit '#{unit}'"
|
114
|
+
end
|
115
|
+
end.flatten.compact
|
116
|
+
end
|
117
|
+
end
|
118
|
+
end
|
data/lib/unity/lookup.rb
ADDED
@@ -0,0 +1,102 @@
|
|
1
|
+
# -*- encoding : utf-8 -*-
|
2
|
+
require 'active_model'
|
3
|
+
require 'unity/lookup/simple_unit'
|
4
|
+
require 'unity/lookup/derived_unit'
|
5
|
+
require 'unity/lookup/property'
|
6
|
+
require 'securerandom'
|
7
|
+
|
8
|
+
module Unity
|
9
|
+
class Lookup
|
10
|
+
|
11
|
+
class Invalid < RuntimeError
|
12
|
+
end
|
13
|
+
|
14
|
+
class Duplicate < RuntimeError
|
15
|
+
end
|
16
|
+
|
17
|
+
class Undefined < RuntimeError
|
18
|
+
end
|
19
|
+
|
20
|
+
|
21
|
+
class << self
|
22
|
+
def add object
|
23
|
+
raise Invalid, object.errors.inspect unless object.valid?
|
24
|
+
raise Duplicate, "The Unit #{object.name} already exists" unless @@unit_names[object.name.to_sym].nil?
|
25
|
+
key = store_object object
|
26
|
+
@@unit_names[object.name.to_sym] = key
|
27
|
+
@@unit_ints[object.dimension_int] ||= []
|
28
|
+
@@unit_ints[object.dimension_int] << key
|
29
|
+
end
|
30
|
+
|
31
|
+
def find! name
|
32
|
+
key = @@unit_names[name.to_sym].tap {|k| raise Undefined, "Unit #{name} is undefined" unless k }
|
33
|
+
get_object key
|
34
|
+
end
|
35
|
+
|
36
|
+
def compatible_units int_or_sym_or_string
|
37
|
+
int =
|
38
|
+
if int_or_sym_or_string.is_a? Integer
|
39
|
+
int_or_sym_or_string
|
40
|
+
else
|
41
|
+
tmp = find_property(int_or_sym_or_string)
|
42
|
+
tmp.nil? ? nil : tmp.dimension_int
|
43
|
+
end
|
44
|
+
(@@unit_ints[int] || []).map do |key|
|
45
|
+
get_object key
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
def find_property int_or_sym_or_string
|
50
|
+
key =
|
51
|
+
if int_or_sym_or_string.is_a? Integer
|
52
|
+
@@property_ints[int_or_sym_or_string]
|
53
|
+
else
|
54
|
+
@@property_names[int_or_sym_or_string.to_sym]
|
55
|
+
end.tap {|k| return nil unless k }
|
56
|
+
get_object key
|
57
|
+
end
|
58
|
+
|
59
|
+
def add_property object
|
60
|
+
raise Invalid, object.errors.inspect unless object.valid?
|
61
|
+
existing = @@property_ints[object.dimension_int]
|
62
|
+
raise Duplicate, "The property for dimension int #{object.dimension_int} is already defined as #{get_object(existing).name}" unless existing.nil?
|
63
|
+
key = store_object object
|
64
|
+
@@property_names[object.name.to_sym] = key
|
65
|
+
@@property_ints[object.dimension_int]= key
|
66
|
+
end
|
67
|
+
|
68
|
+
def property_names
|
69
|
+
@@property_names.keys.map(&:to_sym)
|
70
|
+
end
|
71
|
+
|
72
|
+
def clear!
|
73
|
+
@@object_cache = {}
|
74
|
+
@@unit_names = Hash.new
|
75
|
+
@@unit_ints = Hash.new
|
76
|
+
@@property_ints = Hash.new
|
77
|
+
@@property_names= Hash.new
|
78
|
+
end
|
79
|
+
|
80
|
+
def reload!
|
81
|
+
load "#{File.dirname(__FILE__)}/lookup/definitions.rb"
|
82
|
+
end
|
83
|
+
|
84
|
+
private
|
85
|
+
|
86
|
+
def store_object object
|
87
|
+
SecureRandom.uuid.tap do |key|
|
88
|
+
@@object_cache[key] = object
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
def get_object key
|
93
|
+
@@object_cache[key]
|
94
|
+
end
|
95
|
+
|
96
|
+
end
|
97
|
+
|
98
|
+
clear!
|
99
|
+
reload!
|
100
|
+
|
101
|
+
end
|
102
|
+
end
|
@@ -0,0 +1,50 @@
|
|
1
|
+
# -*- encoding : utf-8 -*-
|
2
|
+
|
3
|
+
module Unity
|
4
|
+
|
5
|
+
Lookup.clear!
|
6
|
+
|
7
|
+
#SIMPLE UNITS
|
8
|
+
|
9
|
+
#TIME
|
10
|
+
Lookup.add SimpleUnit.new(:name => "s", :si_factor => 1, :dimension => :time)
|
11
|
+
Lookup.add SimpleUnit.new(:name => "h", :si_factor => 3600, :dimension => :time)
|
12
|
+
|
13
|
+
Lookup.add_property Property.new(:name => :time, :expression => 'h')
|
14
|
+
|
15
|
+
#LENGTH
|
16
|
+
Lookup.add SimpleUnit.new(:name => "m", :si_factor => 1, :dimension => :length)
|
17
|
+
Lookup.add SimpleUnit.new(:name => "km", :si_factor => 1000, :dimension => :length)
|
18
|
+
|
19
|
+
Lookup.add_property Property.new(:name => :distance, :expression => 'km')
|
20
|
+
|
21
|
+
|
22
|
+
#MASS
|
23
|
+
Lookup.add SimpleUnit.new(:name => "g", :si_factor => 1, :dimension => :mass)
|
24
|
+
Lookup.add SimpleUnit.new(:name => "kg", :si_factor => 1000, :dimension => :mass)
|
25
|
+
Lookup.add SimpleUnit.new(:name => "tonne", :si_factor => 1000000, :dimension => :mass)
|
26
|
+
|
27
|
+
Lookup.add_property Property.new(:name => :mass, :expression => 'tonne')
|
28
|
+
|
29
|
+
|
30
|
+
#DERIVED UNITS
|
31
|
+
|
32
|
+
#ENERGY
|
33
|
+
Lookup.add DerivedUnit.new(:name => "J", :expression => 'kg*m^2/s^2')
|
34
|
+
Lookup.add DerivedUnit.new(:name => "kWh", :expression => '3.6e6*J')
|
35
|
+
|
36
|
+
Lookup.add_property Property.new(:name => :energy, :expression => 'kWh')
|
37
|
+
|
38
|
+
#AREA
|
39
|
+
|
40
|
+
Lookup.add DerivedUnit.new(:name => "hectare", :expression => '10000*m^2')
|
41
|
+
Lookup.add DerivedUnit.new(:name => "acre", :expression => '4046.85642*m^2')
|
42
|
+
|
43
|
+
Lookup.add_property Property.new(:name => :area, :expression => 'hectare')
|
44
|
+
|
45
|
+
end
|
46
|
+
|
47
|
+
|
48
|
+
|
49
|
+
|
50
|
+
|
@@ -0,0 +1,24 @@
|
|
1
|
+
# -*- encoding : utf-8 -*-
|
2
|
+
module Unity
|
3
|
+
class DerivedUnit
|
4
|
+
|
5
|
+
include ActiveModel::Validations
|
6
|
+
include Dimension::Vector
|
7
|
+
include Conversion
|
8
|
+
|
9
|
+
attr_accessor :name, :numerator, :denominator, :value
|
10
|
+
|
11
|
+
alias :to_s :name
|
12
|
+
validates_presence_of :name
|
13
|
+
validates_format_of :name, :with => /\A[^\s*\/\^]+\Z/, :message => "cannot contain *, / or ^"
|
14
|
+
|
15
|
+
def initialize(attributes = {})
|
16
|
+
attributes.each do |name, value|
|
17
|
+
send("#{name}=", value)
|
18
|
+
end
|
19
|
+
super
|
20
|
+
end
|
21
|
+
|
22
|
+
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
# -*- encoding : utf-8 -*-
|
2
|
+
module Unity
|
3
|
+
class Property
|
4
|
+
|
5
|
+
include ActiveModel::Validations
|
6
|
+
include Dimension::Vector
|
7
|
+
include Conversion
|
8
|
+
|
9
|
+
attr_accessor :name, :numerator, :denominator, :value
|
10
|
+
|
11
|
+
validates_presence_of :name
|
12
|
+
|
13
|
+
def initialize(attributes = {})
|
14
|
+
attributes.each do |name, value|
|
15
|
+
send("#{name}=", value)
|
16
|
+
end
|
17
|
+
super
|
18
|
+
end
|
19
|
+
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
# -*- encoding : utf-8 -*-
|
2
|
+
module Unity
|
3
|
+
class SimpleUnit
|
4
|
+
|
5
|
+
include ActiveModel::Validations
|
6
|
+
include Dimension::Integer
|
7
|
+
|
8
|
+
attr_accessor :dimension, :name, :si_factor
|
9
|
+
alias :to_s :name
|
10
|
+
|
11
|
+
validates_inclusion_of :dimension, :in => Dimension::LIST
|
12
|
+
validates_presence_of :si_factor, :dimension, :name
|
13
|
+
validates_format_of :name, :with => /\A[^\s*\/\^]+\Z/, :message => "cannot contain *, / or ^"
|
14
|
+
|
15
|
+
def dimension_vector
|
16
|
+
@dimension_vector ||= Dimension::LIST.map{|d| d == dimension ? 1 : 0 }
|
17
|
+
end
|
18
|
+
|
19
|
+
def initialize(attributes = {})
|
20
|
+
attributes.each do |name, value|
|
21
|
+
send("#{name}=", value)
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
# -*- encoding : utf-8 -*-
|
2
|
+
module Unity
|
3
|
+
class Quantity
|
4
|
+
|
5
|
+
include Dimension::Vector
|
6
|
+
include Conversion
|
7
|
+
include Arithmetic
|
8
|
+
|
9
|
+
def initialize(attributes = {})
|
10
|
+
attributes.each do |name, value|
|
11
|
+
send("#{name}=", value)
|
12
|
+
end
|
13
|
+
super
|
14
|
+
end
|
15
|
+
|
16
|
+
attr_accessor :name, :numerator, :denominator, :cached_expression, :value
|
17
|
+
alias :to_s :expression
|
18
|
+
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
# -*- encoding : utf-8 -*-
|
2
|
+
require 'unity/lookup/simple_unit'
|
3
|
+
require 'unity/lookup/derived_unit'
|
4
|
+
|
5
|
+
Fabricator(:simple_unit, :class_name => "Unity::SimpleUnit") do
|
6
|
+
si_factor 1000.0
|
7
|
+
name 'km'
|
8
|
+
dimension :length
|
9
|
+
end
|
10
|
+
|
11
|
+
Fabricator(:derived_unit, :class_name => "Unity::DerivedUnit") do
|
12
|
+
name 'kWh'
|
13
|
+
end
|
14
|
+
|