unitfy 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|
+
|