measured 0.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.
@@ -0,0 +1,67 @@
1
+ class Measured::Measurable
2
+ include Comparable
3
+ include Measured::Arithmetic
4
+
5
+ attr_reader :unit, :value
6
+
7
+ def initialize(value, unit)
8
+ raise Measured::UnitError, "Unit #{ unit } does not exits." unless self.class.conversion.unit_or_alias?(unit)
9
+
10
+ @value = value
11
+ @value = BigDecimal(@value) unless @value.is_a?(BigDecimal)
12
+
13
+ @unit = self.class.conversion.to_unit_name(unit)
14
+ end
15
+
16
+ def convert_to(new_unit)
17
+ new_unit_name = self.class.conversion.to_unit_name(new_unit)
18
+ value = self.class.conversion.convert(@value, from: @unit, to: new_unit_name)
19
+
20
+ self.class.new(value, new_unit)
21
+ end
22
+
23
+ def convert_to!(new_unit)
24
+ converted = convert_to(new_unit)
25
+
26
+ @value = converted.value
27
+ @unit = converted.unit
28
+
29
+ self
30
+ end
31
+
32
+ def to_s
33
+ [value.to_f.to_s.gsub(/\.0\Z/, ""), unit].join(" ")
34
+ end
35
+
36
+ def inspect
37
+ "#<#{ self.class }: #{ value } #{ unit }>"
38
+ end
39
+
40
+ def <=>(other)
41
+ if other.is_a?(self.class) && unit == other.unit
42
+ value <=> other.value
43
+ end
44
+ end
45
+
46
+ def ==(other)
47
+ !!(other.is_a?(self.class) && unit == other.unit && value == other.value)
48
+ end
49
+
50
+ alias_method :eql?, :==
51
+
52
+ class << self
53
+
54
+ def conversion
55
+ @conversion ||= Measured::Conversion.new
56
+ end
57
+
58
+ def units
59
+ conversion.unit_names
60
+ end
61
+
62
+ def units_with_aliases
63
+ conversion.unit_names_with_aliases
64
+ end
65
+
66
+ end
67
+ end
@@ -0,0 +1,59 @@
1
+ class Measured::Unit
2
+ include Comparable
3
+
4
+ def initialize(name, aliases: [], value: nil)
5
+ @name = name.to_s
6
+ @names = ([@name] + aliases.map{|n| n.to_s }).sort
7
+
8
+ @conversion_amount, @conversion_unit = parse_value(value) if value
9
+ end
10
+
11
+ attr_reader :name, :names, :conversion_amount, :conversion_unit
12
+
13
+ def to_s
14
+ if conversion_string
15
+ "#{ @name } (#{ conversion_string })"
16
+ else
17
+ @name
18
+ end
19
+ end
20
+
21
+ def inspect
22
+ "#<Measured::Unit: #{ @name } (#{ @names.join(", ") }) #{ conversion_string }>"
23
+ end
24
+
25
+ def <=>(other)
26
+ if self.class == other.class
27
+ if other.names != @names
28
+ other.names <=> @names
29
+ else
30
+ other.conversion_amount <=> @conversion_amount
31
+ end
32
+ else
33
+ @name <=> other
34
+ end
35
+ end
36
+
37
+ def inverse_conversion_amount
38
+ if conversion_amount.is_a?(Rational)
39
+ Rational(conversion_amount.denominator, conversion_amount.numerator)
40
+ else
41
+ BigDecimal(1) / conversion_amount
42
+ end
43
+ end
44
+
45
+ private
46
+
47
+ def conversion_string
48
+ "#{ conversion_amount } #{ conversion_unit }" if @conversion_amount || @conversion_unit
49
+ end
50
+
51
+ def parse_value(tokens)
52
+ tokens = tokens.split(" ") if tokens.is_a?(String)
53
+ raise Measured::UnitError, "Cannot parse 'number unit' or [number, unit] formatted tokens from #{ tokens }." unless tokens.size == 2
54
+
55
+ tokens[0] = BigDecimal(tokens[0]) unless tokens[0].is_a?(BigDecimal) || tokens[0].is_a?(Rational)
56
+
57
+ tokens
58
+ end
59
+ end
@@ -0,0 +1,26 @@
1
+ class Measured::Length < Measured::Measurable
2
+
3
+ conversion.set_base :m,
4
+ aliases: [:meter, :metre, :meters, :metres]
5
+
6
+ conversion.add :cm,
7
+ aliases: [:centimeter, :centimetre, :centimeters, :centimetres],
8
+ value: "0.01 m"
9
+
10
+ conversion.add :mm,
11
+ aliases: [:millimeter, :millimetre, :millimeters, :millimetres],
12
+ value: "0.001 m"
13
+
14
+ conversion.add :in,
15
+ aliases: [:inch, :inches],
16
+ value: "0.0254 m"
17
+
18
+ conversion.add :ft,
19
+ aliases: [:foot, :feet],
20
+ value: "0.3048 m"
21
+
22
+ conversion.add :yd,
23
+ aliases: [:yard, :yards],
24
+ value: "0.9144 m"
25
+
26
+ end
@@ -0,0 +1,18 @@
1
+ class Measured::Weight < Measured::Measurable
2
+
3
+ conversion.set_base :g,
4
+ aliases: [:gram, :grams]
5
+
6
+ conversion.add :kg,
7
+ aliases: [:kilogram, :kilograms],
8
+ value: "1000 g"
9
+
10
+ conversion.add :lb,
11
+ aliases: [:lbs, :pound, :pounds],
12
+ value: [Rational(45359237,1e8), "kg"]
13
+
14
+ conversion.add :oz,
15
+ aliases: [:ounce, :ounces],
16
+ value: [Rational(1,16), "lb"]
17
+
18
+ end
@@ -0,0 +1,3 @@
1
+ module Measured
2
+ VERSION = "0.0.1"
3
+ end
data/lib/measured.rb ADDED
@@ -0,0 +1,4 @@
1
+ require "measured/base"
2
+
3
+ require "measured/units/length"
4
+ require "measured/units/weight"
data/measured.gemspec ADDED
@@ -0,0 +1,28 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'measured/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "measured"
8
+ spec.version = Measured::VERSION
9
+ spec.authors = ["Kevin McPhillips"]
10
+ spec.email = ["github@kevinmcphillips.ca"]
11
+ spec.summary = %q{Encapsulate measurements with their units in Ruby}
12
+ spec.description = %q{Wrapper objects which encapsulate measurments and their associated units in Ruby.}
13
+ spec.homepage = ""
14
+ spec.license = "MIT"
15
+
16
+ spec.files = `git ls-files -z`.split("\x0")
17
+ spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
18
+ spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
19
+ spec.require_paths = ["lib"]
20
+
21
+ spec.add_runtime_dependency "activesupport", ">= 4.0"
22
+
23
+ spec.add_development_dependency "bundler", "~> 1.7"
24
+ spec.add_development_dependency "rake", "~> 10.0"
25
+ spec.add_development_dependency "minitest", "~> 5.5.1"
26
+ spec.add_development_dependency "mocha", "~> 1.1.0"
27
+ spec.add_development_dependency "pry"
28
+ end
@@ -0,0 +1,133 @@
1
+ require "test_helper"
2
+
3
+ class Measured::ArithmeticTest < ActiveSupport::TestCase
4
+ setup do
5
+ @two = Magic.new(2, :magic_missile)
6
+ @three = Magic.new(3, :magic_missile)
7
+ @four = Magic.new(4, :magic_missile)
8
+ end
9
+
10
+ test "#+ should add together same units" do
11
+ assert_equal Magic.new(5, :magic_missile), @two + @three
12
+ assert_equal Magic.new(5, :magic_missile), @three + @two
13
+ end
14
+
15
+ test "#+ should add a number to the value" do
16
+ assert_equal Magic.new(5, :magic_missile), @two + 3
17
+ assert_equal Magic.new(5, :magic_missile), 2 + @three
18
+ end
19
+
20
+ test "#+ should raise if different unit system" do
21
+ assert_raises TypeError do
22
+ OtherFakeSystem.new(1, :other_fake_base) + @two
23
+ end
24
+
25
+ assert_raises TypeError do
26
+ @two + OtherFakeSystem.new(1, :other_fake_base)
27
+ end
28
+ end
29
+
30
+ test "#+ should raise if adding something nonsense" do
31
+ assert_raises TypeError do
32
+ @two + "thing"
33
+ end
34
+
35
+ assert_raises TypeError do
36
+ "thing" + @two
37
+ end
38
+ end
39
+
40
+ test "#- should subtract same units" do
41
+ assert_equal Magic.new(-1, :magic_missile), @two - @three
42
+ assert_equal Magic.new(1, :magic_missile), @three - @two
43
+ end
44
+
45
+ test "#- should subtract a number from the value" do
46
+ assert_equal Magic.new(-1, :magic_missile), @two - 3
47
+ assert_equal Magic.new(1, :magic_missile), 2 - @three
48
+ end
49
+
50
+ test "#- should raise if different unit system" do
51
+ assert_raises TypeError do
52
+ OtherFakeSystem.new(1, :other_fake_base) - @two
53
+ end
54
+
55
+ assert_raises TypeError do
56
+ @two - OtherFakeSystem.new(1, :other_fake_base)
57
+ end
58
+ end
59
+
60
+ test "#- should raise if subtracting something nonsense" do
61
+ assert_raises TypeError do
62
+ @two - "thing"
63
+ end
64
+
65
+ assert_raises NoMethodError do
66
+ "thing" - @two
67
+ end
68
+ end
69
+
70
+ test "#* should multiply together same units" do
71
+ assert_equal Magic.new(6, :magic_missile), @two * @three
72
+ assert_equal Magic.new(6, :magic_missile), @three * @two
73
+ end
74
+
75
+ test "#* should multiply a number to the value" do
76
+ assert_equal Magic.new(6, :magic_missile), @two * 3
77
+ assert_equal Magic.new(6, :magic_missile), 2 * @three
78
+ end
79
+
80
+ test "#* should raise if different unit system" do
81
+ assert_raises TypeError do
82
+ OtherFakeSystem.new(1, :other_fake_base) * @two
83
+ end
84
+
85
+ assert_raises TypeError do
86
+ @two * OtherFakeSystem.new(1, :other_fake_base)
87
+ end
88
+ end
89
+
90
+ test "#* should raise if multiplying something nonsense" do
91
+ assert_raises TypeError do
92
+ @two * "thing"
93
+ end
94
+
95
+ assert_raises TypeError do
96
+ "thing" * @two
97
+ end
98
+ end
99
+
100
+ test "#/ should divide together same units" do
101
+ assert_equal Magic.new("0.5", :magic_missile), @two / @four
102
+ assert_equal Magic.new(2, :magic_missile), @four / @two
103
+ end
104
+
105
+ test "#/ should divide a number to the value" do
106
+ assert_equal Magic.new("0.5", :magic_missile), @two / 4
107
+ assert_equal Magic.new(2, :magic_missile), 2 / @four
108
+ end
109
+
110
+ test "#/ should raise if different unit system" do
111
+ assert_raises TypeError do
112
+ OtherFakeSystem.new(1, :other_fake_base) / @two
113
+ end
114
+
115
+ assert_raises TypeError do
116
+ @two / OtherFakeSystem.new(1, :other_fake_base)
117
+ end
118
+ end
119
+
120
+ test "#/ should raise if dividing something nonsense" do
121
+ assert_raises TypeError do
122
+ @two / "thing"
123
+ end
124
+
125
+ assert_raises NoMethodError do
126
+ "thing" / @two
127
+ end
128
+ end
129
+
130
+ test "#-@ returns the negative version" do
131
+ assert_equal Magic.new(-2, :magic_missile), -@two
132
+ end
133
+ end
@@ -0,0 +1,19 @@
1
+ require "test_helper"
2
+
3
+ class Measured::ConversionTableTest < ActiveSupport::TestCase
4
+ setup do
5
+ @unit = Measured::Unit.new(:test)
6
+ end
7
+
8
+ test "#initialize accepts a list of units and a base unit" do
9
+ Measured::ConversionTable.new([@unit])
10
+ end
11
+
12
+ test "#to_h should return a hash for the simple case" do
13
+ expected = {
14
+ "test" => {"test" => BigDecimal("1")}
15
+ }
16
+
17
+ assert_equal expected, Measured::ConversionTable.new([@unit]).to_h
18
+ end
19
+ end
@@ -0,0 +1,206 @@
1
+ require "test_helper"
2
+
3
+ class Measured::ConversionTest < ActiveSupport::TestCase
4
+ setup do
5
+ @conversion = Measured::Conversion.new
6
+ end
7
+
8
+ test "#base sets the base unit" do
9
+ @conversion.set_base :m, aliases: [:metre]
10
+ assert_equal ["m", "metre"], @conversion.base_unit.names
11
+ end
12
+
13
+ test "#base doesn't allow a second base to be added" do
14
+ @conversion.set_base :m, aliases: [:metre]
15
+
16
+ assert_raises Measured::UnitError do
17
+ @conversion.set_base :in
18
+ end
19
+ end
20
+
21
+ test "#add adds a new unit" do
22
+ @conversion.set_base :m
23
+ @conversion.add :in, aliases: [:inch], value: "0.0254 meter"
24
+
25
+ assert_equal 2, @conversion.units.count
26
+ end
27
+
28
+ test "#add cannot add duplicate unit names" do
29
+ @conversion.set_base :m
30
+ @conversion.add :in, aliases: [:inch], value: "0.0254 meter"
31
+
32
+ assert_raises Measured::UnitError do
33
+ @conversion.add :in, aliases: [:thing], value: "123 m"
34
+ end
35
+
36
+ assert_raises Measured::UnitError do
37
+ @conversion.add :inch, value: "123 m"
38
+ end
39
+ end
40
+
41
+ test "#add does not allow you to add a unit before the base" do
42
+ assert_raises Measured::UnitError do
43
+ @conversion.add :in, aliases: [:inch], value: "0.0254 meter"
44
+ end
45
+ end
46
+
47
+ test "#unit_names_with_aliases lists all allowed unit names" do
48
+ @conversion.set_base :m
49
+ @conversion.add :in, aliases: [:inch], value: "0.0254 meter"
50
+ @conversion.add :ft, aliases: [:feet, :foot], value: "0.3048 meter"
51
+
52
+ assert_equal ["feet", "foot", "ft", "in", "inch", "m"], @conversion.unit_names_with_aliases
53
+ end
54
+
55
+ test "#unit_names lists all base unit names without aliases" do
56
+ @conversion.set_base :m
57
+ @conversion.add :in, aliases: [:inch], value: "0.0254 meter"
58
+ @conversion.add :ft, aliases: [:feet, :foot], value: "0.3048 meter"
59
+
60
+ assert_equal ["ft", "in", "m"], @conversion.unit_names
61
+ end
62
+
63
+ test "#unit? checks if the unit is part of the units and aliases" do
64
+ @conversion.set_base :m
65
+ @conversion.add :inch, aliases: [:in], value: "0.0254 meter"
66
+
67
+ assert @conversion.unit?(:inch)
68
+ assert @conversion.unit?("m")
69
+ refute @conversion.unit?("in")
70
+ refute @conversion.unit?(:yard)
71
+ end
72
+
73
+ test "#unit_or_alias? checks if the unit is part of the units but not aliases" do
74
+ @conversion.set_base :m
75
+ @conversion.add :inch, aliases: [:in], value: "0.0254 meter"
76
+
77
+ assert @conversion.unit_or_alias?(:inch)
78
+ assert @conversion.unit_or_alias?("m")
79
+ assert @conversion.unit_or_alias?("in")
80
+ refute @conversion.unit_or_alias?(:yard)
81
+ end
82
+
83
+ test "#to_unit_name converts a unit name to its base unit" do
84
+ assert_equal "fireball", Magic.conversion.to_unit_name("fire")
85
+ end
86
+
87
+ test "#to_unit_name does not care about string or symbol" do
88
+ assert_equal "fireball", Magic.conversion.to_unit_name(:fire)
89
+ end
90
+
91
+ test "#to_unit_name passes through if already base unit name" do
92
+ assert_equal "fireball", Magic.conversion.to_unit_name("fireball")
93
+ end
94
+
95
+ test "#to_unit_name raises if not found" do
96
+ assert_raises Measured::UnitError do
97
+ Magic.conversion.to_unit_name("thunder")
98
+ end
99
+ end
100
+
101
+ test "#convert raises if either unit is not found" do
102
+ assert_raises Measured::UnitError do
103
+ Magic.conversion.convert(1, from: "fire", to: "doesnt_exist")
104
+ end
105
+
106
+ assert_raises Measured::UnitError do
107
+ Magic.conversion.convert(1, from: "doesnt_exist", to: "fire")
108
+ end
109
+ end
110
+
111
+ test "#convert converts betwen two known units" do
112
+ @conversion.set_base :m
113
+ @conversion.add :cm, value: "0.01 m"
114
+
115
+ assert_equal BigDecimal("10"), @conversion.convert(BigDecimal("1000"), from: "cm", to: "m")
116
+ assert_equal BigDecimal("250"), @conversion.convert(BigDecimal("2.5"), from: "m", to: "cm")
117
+ end
118
+
119
+ test "#convert handles the same unit" do
120
+ @conversion.set_base :m
121
+ @conversion.add :cm, value: "0.01 m"
122
+
123
+ assert_equal BigDecimal("2"), @conversion.convert(BigDecimal("2"), from: "cm", to: "cm")
124
+ end
125
+
126
+ test "#conversion_table returns expected nested hashes with BigDecimal conversion factors in a tiny data set" do
127
+ @conversion.set_base :m
128
+ @conversion.add :cm, value: "0.01 m"
129
+
130
+ expected = {
131
+ "m" => {
132
+ "m" => BigDecimal("1"),
133
+ "cm" => BigDecimal("100")
134
+ },
135
+ "cm" => {
136
+ "cm" => BigDecimal("1"),
137
+ "m" => BigDecimal("0.01")
138
+ }
139
+ }
140
+
141
+ assert_equal expected, @conversion.conversion_table
142
+ end
143
+
144
+ test "#conversion_table returns expected nested hashes with BigDecimal conversion factors" do
145
+ @conversion.set_base :m
146
+ @conversion.add :cm, value: "0.01 m"
147
+ @conversion.add :mm, value: "0.001 m"
148
+
149
+ expected = {
150
+ "m" => {
151
+ "m" => BigDecimal("1"),
152
+ "cm" => BigDecimal("100"),
153
+ "mm" => BigDecimal("1000")
154
+ },
155
+ "cm" => {
156
+ "cm" => BigDecimal("1"),
157
+ "m" => BigDecimal("0.01"),
158
+ "mm" => BigDecimal("10")
159
+ },
160
+ "mm" => {
161
+ "mm" => BigDecimal("1"),
162
+ "m" => BigDecimal("0.001"),
163
+ "cm" => BigDecimal("0.1")
164
+ }
165
+ }
166
+
167
+ assert_equal expected, @conversion.conversion_table
168
+ end
169
+
170
+ test "#conversion_table returns expected nested hashes with BigDecimal conversion factors in an indrect path" do
171
+ @conversion.set_base :mm
172
+ @conversion.add :cm, value: "10 mm"
173
+ @conversion.add :dm, value: "10 cm"
174
+ @conversion.add :m, value: "10 dm"
175
+
176
+ expected = {
177
+ "m" => {
178
+ "m" => BigDecimal("1"),
179
+ "cm" => BigDecimal("100"),
180
+ "dm" => BigDecimal("10"),
181
+ "mm" => BigDecimal("1000")
182
+ },
183
+ "cm" => {
184
+ "cm" => BigDecimal("1"),
185
+ "dm" => BigDecimal("0.1"),
186
+ "m" => BigDecimal("0.01"),
187
+ "mm" => BigDecimal("10")
188
+ },
189
+ "dm" => {
190
+ "dm" => BigDecimal("1"),
191
+ "cm" => BigDecimal("10"),
192
+ "m" => BigDecimal("0.1"),
193
+ "mm" => BigDecimal("100")
194
+ },
195
+ "mm" => {
196
+ "mm" => BigDecimal("1"),
197
+ "m" => BigDecimal("0.001"),
198
+ "dm" => BigDecimal("0.01"),
199
+ "cm" => BigDecimal("0.1")
200
+ }
201
+ }
202
+
203
+ assert_equal expected, @conversion.conversion_table
204
+ end
205
+
206
+ end