unite 1.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 (46) hide show
  1. data/.gitignore +3 -0
  2. data/.rspec +4 -0
  3. data/Gemfile +18 -0
  4. data/Gemfile.lock +89 -0
  5. data/Guardfile +15 -0
  6. data/MIT-LICENSE +20 -0
  7. data/README +0 -0
  8. data/Rakefile +3 -0
  9. data/lib/unity/arithmetic.rb +64 -0
  10. data/lib/unity/comparison.rb +33 -0
  11. data/lib/unity/conversion.rb +46 -0
  12. data/lib/unity/dimension/integer.rb +34 -0
  13. data/lib/unity/dimension/vector.rb +43 -0
  14. data/lib/unity/dimension.rb +37 -0
  15. data/lib/unity/fraction.rb +118 -0
  16. data/lib/unity/lookup/definitions.rb +50 -0
  17. data/lib/unity/lookup/derived_unit.rb +24 -0
  18. data/lib/unity/lookup/property.rb +21 -0
  19. data/lib/unity/lookup/simple_unit.rb +26 -0
  20. data/lib/unity/lookup.rb +102 -0
  21. data/lib/unity/quantity.rb +20 -0
  22. data/lib/unity/version.rb +3 -0
  23. data/lib/unity.rb +20 -0
  24. data/spec/arithmetic_spec.rb +6 -0
  25. data/spec/comparison_spec.rb +6 -0
  26. data/spec/conversion_spec.rb +8 -0
  27. data/spec/dimension/integer_spec.rb +7 -0
  28. data/spec/dimension/vector_spec.rb +7 -0
  29. data/spec/dimension_spec.rb +7 -0
  30. data/spec/fabricators/unit_fabricator.rb +14 -0
  31. data/spec/fraction_spec.rb +7 -0
  32. data/spec/lookup/derived_unit_spec.rb +44 -0
  33. data/spec/lookup/property_spec.rb +18 -0
  34. data/spec/lookup/simple_unit_spec.rb +41 -0
  35. data/spec/lookup_spec.rb +135 -0
  36. data/spec/quantity_spec.rb +16 -0
  37. data/spec/spec_helper.rb +28 -0
  38. data/spec/support/load_debugger.rb +7 -0
  39. data/spec/support/shared_examples/units/arithmetic.rb +127 -0
  40. data/spec/support/shared_examples/units/comparison.rb +69 -0
  41. data/spec/support/shared_examples/units/conversion.rb +49 -0
  42. data/spec/support/shared_examples/units/dimension/integer.rb +41 -0
  43. data/spec/support/shared_examples/units/dimension/vector.rb +10 -0
  44. data/spec/support/shared_examples/units/fractions.rb +131 -0
  45. data/unite.gemspec +24 -0
  46. metadata +133 -0
@@ -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,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,3 @@
1
+ module Unity
2
+ VERSION = "1.0.1"
3
+ end
data/lib/unity.rb ADDED
@@ -0,0 +1,20 @@
1
+ require "unity/version"
2
+
3
+ module Unity
4
+
5
+ end
6
+
7
+
8
+ require 'active_support'
9
+
10
+ # Dir["#{Rails.root.to_s}/lib/units/**/*.rb"].each {|file| require file }
11
+ require 'unity/fraction'
12
+ require 'unity/dimension'
13
+ require 'unity/comparison'
14
+ require 'unity/conversion'
15
+ require 'unity/arithmetic'
16
+
17
+
18
+ require 'unity/lookup'
19
+
20
+ require "unity/quantity"
@@ -0,0 +1,6 @@
1
+ # -*- encoding : utf-8 -*-
2
+ require 'spec_helper'
3
+
4
+ describe Unity::Arithmetic do
5
+
6
+ end
@@ -0,0 +1,6 @@
1
+ # -*- encoding : utf-8 -*-
2
+ require 'spec_helper'
3
+
4
+ describe Unity::Comparison do
5
+
6
+ end
@@ -0,0 +1,8 @@
1
+ # -*- encoding : utf-8 -*-
2
+ require 'spec_helper'
3
+
4
+ describe Unity::Conversion do
5
+
6
+ end
7
+
8
+
@@ -0,0 +1,7 @@
1
+ # -*- encoding : utf-8 -*-
2
+ require 'spec_helper'
3
+
4
+ describe Unity::Dimension::Integer do
5
+
6
+ end
7
+
@@ -0,0 +1,7 @@
1
+ # -*- encoding : utf-8 -*-
2
+ require 'spec_helper'
3
+
4
+ describe Unity::Dimension::Vector do
5
+
6
+ end
7
+
@@ -0,0 +1,7 @@
1
+ # -*- encoding : utf-8 -*-
2
+ require 'spec_helper'
3
+
4
+ describe Unity::Dimension do
5
+
6
+
7
+ 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
+
@@ -0,0 +1,7 @@
1
+ # -*- encoding : utf-8 -*-
2
+ require 'spec_helper'
3
+
4
+ describe Unity::Fraction do
5
+
6
+
7
+ end
@@ -0,0 +1,44 @@
1
+ # -*- encoding : utf-8 -*-
2
+ require 'spec_helper'
3
+ require 'unity/lookup/derived_unit.rb'
4
+
5
+ module Unity
6
+ describe DerivedUnit do
7
+ it { should validate_presence_of(:name)}
8
+
9
+ %w{ F J Hz N }.each do |unit|
10
+ it "should allow valid unit #{unit}" do
11
+ Fabricate.build(:derived_unit, :name => unit).should be_valid
12
+ end
13
+ end
14
+
15
+ %w{km^3 m/g km*tonne}.each do |unit|
16
+ it "should not allow invalid unit #{unit}" do
17
+ Fabricate.build(:derived_unit, :name => unit).should_not be_valid
18
+ end
19
+ end
20
+
21
+ describe 'based off of simple units' do
22
+ subject { DerivedUnit.new(:name => "J", :expression => 'kg*m^2/s^2') }
23
+
24
+ its(:dimension_vector) { should == [2, -2, 0, 1, 0, 0, 0, 0] }
25
+ its(:property_name) { should == :energy }
26
+ end
27
+
28
+ describe 'based off of other derived unit' do
29
+
30
+ subject { DerivedUnit.new(:name => "kWh", :expression => '3.6e5*J') }
31
+
32
+ its(:dimension_vector) { should == [2, -2, 0, 1, 0, 0, 0, 0] }
33
+ its(:property_name) { should == :energy }
34
+
35
+ end
36
+
37
+ include_examples "comparable unit"
38
+ include_examples "dimension integer"
39
+ include_examples "convertable value"
40
+ include_examples "dimension vectors"
41
+ include_examples "unit fractions"
42
+
43
+ end
44
+ end
@@ -0,0 +1,18 @@
1
+ # -*- encoding : utf-8 -*-
2
+ require 'spec_helper'
3
+ require 'unity/lookup/property.rb'
4
+
5
+ module Unity
6
+ describe Property do
7
+ it { should validate_presence_of(:name)}
8
+
9
+
10
+ include_examples "comparable unit"
11
+ include_examples "dimension integer"
12
+ include_examples "convertable value"
13
+ include_examples "dimension vectors"
14
+ include_examples "unit fractions"
15
+
16
+ end
17
+ end
18
+
@@ -0,0 +1,41 @@
1
+ # -*- encoding : utf-8 -*-
2
+ require 'spec_helper'
3
+ require 'unity/lookup/simple_unit.rb'
4
+
5
+ module Unity
6
+ describe SimpleUnit do
7
+ it { should ensure_inclusion_of(:dimension).in_array(Dimension::LIST) }
8
+ it { should validate_presence_of(:si_factor)}
9
+ it { should validate_presence_of(:name)}
10
+ it { should validate_presence_of(:dimension)}
11
+
12
+ %w{ $ £ € m }.each do |unit|
13
+ it "should allow valid unit #{unit}" do
14
+ Fabricate.build(:simple_unit, :name => unit).should be_valid
15
+ end
16
+ end
17
+
18
+ %w{km^3 m/g km*tonne}.each do |unit|
19
+ it "should not allow invalid unit #{unit}" do
20
+ Fabricate.build(:simple_unit, :name => unit).should_not be_valid
21
+ end
22
+ end
23
+
24
+ describe "instance methods" do
25
+ let(:dimension) { :length }
26
+ subject { Fabricate.build :simple_unit, :name => "m", :si_factor => 1, :dimension => :length }
27
+
28
+ its(:dimension_vector) { should == Dimension::LIST.map{|d| d == dimension ? 1 : 0}}
29
+ its(:property_name) { should == :distance }
30
+
31
+ end
32
+
33
+ describe "class methods" do
34
+ subject { SimpleUnit }
35
+
36
+ end
37
+
38
+ include_examples "dimension integer"
39
+
40
+ end
41
+ end
@@ -0,0 +1,135 @@
1
+ # -*- encoding : utf-8 -*-
2
+ require 'spec_helper'
3
+
4
+ module Unity
5
+ describe Lookup do
6
+ subject { Lookup }
7
+
8
+
9
+ describe "adding and finding" do
10
+
11
+ def unit_stub stubs_hash
12
+ stub({:valid? => true, :errors => {} , :dimension_int => 30}.merge(stubs_hash))
13
+ end
14
+
15
+ def property_stub stubs_hash
16
+ stub({:name => :quasar, :expression => 'qu', :valid? => true, :errors => {} , :dimension_int => 30}.merge(stubs_hash))
17
+ end
18
+
19
+
20
+ after do
21
+ subject.reload!
22
+ end
23
+
24
+ describe "units" do
25
+ let(:name) { 'scott' }
26
+ let(:unit_object) { unit_stub(:valid? => valid, :name => name) }
27
+ context "with invalid object" do
28
+ let(:valid) { false }
29
+
30
+ it "should raise an error" do
31
+ lambda { subject.add(unit_object) }.should raise_exception(Lookup::Invalid)
32
+ end
33
+
34
+ end
35
+
36
+ context "with valid object" do
37
+ let(:valid) { true }
38
+ before do
39
+ subject.add(unit_object)
40
+ end
41
+
42
+ it 'should be able to find the unit by name' do
43
+ subject.find!(name).should == unit_object
44
+ end
45
+
46
+
47
+ it "should raise not found if unit is undefined" do
48
+ lambda { subject.find!(name+"undefined") }.should raise_exception(Lookup::Undefined)
49
+ end
50
+
51
+ context "with non uniq name" do
52
+ let(:duplicate_name) { name }
53
+ let(:duplicate_unit_object) { unit_stub(:name => duplicate_name) }
54
+
55
+ it "should raise an error" do
56
+ lambda { subject.add(duplicate_unit_object) }.should raise_exception(Lookup::Duplicate)
57
+ end
58
+ end
59
+
60
+ describe "compatible units" do
61
+ let(:compatible_unit_object) { unit_stub(:name => "unit_name1", :dimension_int => unit_object.dimension_int) }
62
+ let(:incompatible_unit_object) { unit_stub(:name => "unit_name2", :dimension_int => (unit_object.dimension_int + 10)) }
63
+
64
+ before do
65
+ subject.add(compatible_unit_object)
66
+ subject.add(incompatible_unit_object)
67
+ end
68
+
69
+ describe "by integer" do
70
+ it "should find compatible units" do
71
+ subject.compatible_units(unit_object.dimension_int).should =~ [compatible_unit_object, unit_object]
72
+ end
73
+ end
74
+
75
+ describe "by property" do
76
+ let(:property_object) { property_stub(:dimension_int => unit_object.dimension_int) }
77
+ before do
78
+ subject.add_property(property_object)
79
+ end
80
+
81
+ it "should find compatible units" do
82
+ subject.compatible_units(property_object.name).should =~ [compatible_unit_object, unit_object]
83
+ end
84
+ end
85
+
86
+ end
87
+
88
+
89
+ end
90
+
91
+ describe "property" do
92
+ let(:propert_object) { property_stub(:name => :quasar, :expression => 'qu', :valid? => valid)}
93
+
94
+ context 'with invalid object' do
95
+ let(:valid) { false }
96
+ it "should raise an error" do
97
+ lambda { subject.add_property(propert_object) }.should raise_exception(Lookup::Invalid)
98
+ end
99
+ end
100
+
101
+ context 'with valid object' do
102
+ let(:valid) { true }
103
+ before do
104
+ subject.add_property(propert_object)
105
+ end
106
+
107
+ it "should give a list of properties" do
108
+ subject.property_names.should include(propert_object.name)
109
+ end
110
+
111
+ it "should return nil if not found" do
112
+ subject.find_property("undefined#{propert_object.name}").should == nil
113
+ end
114
+
115
+ it "should raise exception if already defined" do
116
+ lambda {subject.add_property(propert_object)}.should raise_exception Lookup::Duplicate
117
+ end
118
+
119
+ it 'should be able to find by dimension id' do
120
+ subject.find_property(propert_object.dimension_int).should == propert_object
121
+ end
122
+
123
+ it 'should be able to find by name' do
124
+ subject.find_property(propert_object.name).should == propert_object
125
+ end
126
+
127
+
128
+ end
129
+ end
130
+
131
+ end
132
+
133
+ end
134
+ end
135
+ end
@@ -0,0 +1,16 @@
1
+ # -*- encoding : utf-8 -*-
2
+ require 'spec_helper'
3
+
4
+ module Unity
5
+ describe Quantity do
6
+
7
+ subject { Quantity.new :expression => expression }
8
+
9
+ include_examples "unit arithmetic"
10
+ include_examples "comparable unit"
11
+ include_examples "dimension integer"
12
+ include_examples "convertable value"
13
+ include_examples "dimension vectors"
14
+ include_examples "unit fractions"
15
+ end
16
+ end
@@ -0,0 +1,28 @@
1
+ # -*- encoding : utf-8 -*-
2
+ # This file is copied to spec/ when you run 'rails generate rspec:install'
3
+
4
+ $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
5
+ $LOAD_PATH.unshift(File.dirname(__FILE__))
6
+
7
+ require 'rspec'
8
+ require 'shoulda/matchers/active_model'
9
+ require 'mocha'
10
+ require 'unity'
11
+ require 'fabrication'
12
+
13
+ # Requires supporting files with custom matchers and macros, etc,
14
+ # in ./support/ and its subdirectories.
15
+ Dir["#{File.dirname(__FILE__)}/support/**/*.rb"].each {|f| require f}
16
+
17
+ RSpec.configure do |config|
18
+
19
+ config.mock_with :mocha
20
+
21
+ config.before(:each) do
22
+ end
23
+
24
+ config.include(Shoulda::Matchers::ActiveModel)
25
+
26
+ config.filter_run :focus => true
27
+ config.run_all_when_everything_filtered = true
28
+ end
@@ -0,0 +1,7 @@
1
+ # -*- encoding : utf-8 -*-
2
+ begin
3
+ require 'ruby-debug'
4
+ puts "** USING THE RUBY DEBUGGER **"
5
+ rescue LoadError
6
+ puts "!!! RUBY DEBUGGER NOT INSTALLED !!!"
7
+ end
@@ -0,0 +1,127 @@
1
+ # -*- encoding : utf-8 -*-
2
+ shared_examples "unit arithmetic" do
3
+
4
+ shared_examples "addition operation" do
5
+
6
+ context "addition result" do
7
+ subject { object1 + object2 }
8
+ let(:addition_value) { addition_result.first }
9
+ let(:addition_unit) { addition_result.last }
10
+
11
+ its(:value) { should == addition_value }
12
+ its(:unit) { should == addition_unit }
13
+ end
14
+
15
+ end
16
+ shared_examples "subtraction operation" do
17
+
18
+ context "subtraction result" do
19
+ subject { object1 - object2 }
20
+ let(:subtraction_value) { subtraction_result.first }
21
+ let(:subtraction_unit) { subtraction_result.last }
22
+
23
+ its(:value) { should == subtraction_value }
24
+ its(:unit) { should == subtraction_unit }
25
+ end
26
+
27
+ end
28
+ shared_examples "multiplication operation" do
29
+
30
+ context "multiplication result" do
31
+ subject { object1 * object2 }
32
+ let(:multiplication_value) { multiplication_result.first }
33
+ let(:multiplication_unit) { multiplication_result.last }
34
+
35
+ its(:value) { should == multiplication_value }
36
+ its(:unit) { should == multiplication_unit }
37
+ end
38
+
39
+ end
40
+ shared_examples "division operation" do
41
+
42
+ context "division result" do
43
+ subject { object1 / object2 }
44
+ let(:division_value) { division_result.first }
45
+ let(:division_unit) { division_result.last }
46
+
47
+ its(:value) { should == division_value }
48
+ its(:unit) { should == division_unit }
49
+ end
50
+
51
+ end
52
+
53
+ describe "arithmetic" do
54
+ describe 'with no units' do
55
+ let(:expression1) { 100.0 }
56
+ let(:expression2) { 10.0 }
57
+
58
+ let(:addition_result) { [110.0, ''] }
59
+ let(:subtraction_result) { [90.0, ''] }
60
+ let(:division_result) { [10.0, ''] }
61
+ let(:multiplication_result) { [1000.0, ''] }
62
+
63
+ context "with an numeric as the first object" do
64
+ let(:object1) { expression1.to_f }
65
+ let(:object2) { described_class.new :expression => expression2.to_s }
66
+
67
+ include_examples "addition operation"
68
+ include_examples "subtraction operation"
69
+ include_examples "multiplication operation"
70
+ include_examples "division operation"
71
+ end
72
+
73
+
74
+ context "with an numeric as the second object" do
75
+ let(:object1) { described_class.new :expression => expression1.to_s }
76
+ let(:object2) { expression2.to_f }
77
+
78
+ include_examples "addition operation"
79
+ include_examples "subtraction operation"
80
+ include_examples "multiplication operation"
81
+ include_examples "division operation"
82
+ end
83
+
84
+ end
85
+
86
+ context "with units" do
87
+ let(:object1) { described_class.new :expression => expression1 }
88
+ let(:object2) { described_class.new :expression => expression2 }
89
+
90
+ context "that are compatible" do
91
+ let(:expression1) { "10*km" }
92
+ let(:expression2) { "1000*m" }
93
+
94
+ let(:addition_result) { [11.0, 'km'] }
95
+ let(:subtraction_result) { [9.0, 'km'] }
96
+ let(:division_result) { [10.0, ''] }
97
+ let(:multiplication_result) { [10.0, 'km^2'] }
98
+
99
+ include_examples "addition operation"
100
+ include_examples "subtraction operation"
101
+ include_examples "multiplication operation"
102
+ include_examples "division operation"
103
+ end
104
+
105
+ context "that are incompatible " do
106
+ let(:expression1) { "10*kg" }
107
+ let(:expression2) { "10*m" }
108
+
109
+ let(:division_result) { [1.0, 'kg/m'] }
110
+ let(:multiplication_result) { [100.0, 'kg*m'] }
111
+
112
+ include_examples "multiplication operation"
113
+ include_examples "division operation"
114
+
115
+ it "should raise error for subtraction" do
116
+ lambda { object1 - object2 }.should raise_exception Unity::IncompatibleError
117
+ end
118
+ it "should raise error for addition" do
119
+ lambda { object1 + object2 }.should raise_exception Unity::IncompatibleError
120
+ end
121
+
122
+ end
123
+
124
+ end
125
+ end
126
+ end
127
+