unitwise 0.3.2 → 0.4.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: ebcd2779e6a54ca23cc3cd644f3720bd9de8846d
4
- data.tar.gz: 4bc1072fe92630814f05e9402e5cc5ac8983ce94
3
+ metadata.gz: 2cb7a8a61e366794106f2f13a5b18bce3ca3be61
4
+ data.tar.gz: 6c430a9fb789f8aafb7911ba6832056bd47af100
5
5
  SHA512:
6
- metadata.gz: 5de61cb9d586961307d03e8c3db35ba3d851d6cc8dcdfd8944c2c6d4b6e9e6833abe82c0fe498d1924bd3a23927c980a7c0fa252ba5e931d848afd10fa278672
7
- data.tar.gz: 9b01d1fc8f63294e72bd20060f411c8a54d5ffffe7cb872b504728aa0ea8e4c189f2df696a50bb392b291c1582629c852e7dcbeab2634429b61f3883f4f95f46
6
+ metadata.gz: c683c3397398221d6d80903b7cbb5b393868f132c9a73e67579f0c8f18f03817ce39b7956b6afdd9f8823876777579d07bd3e3e5e9f298fa9fcdd304fb228fa0
7
+ data.tar.gz: ad2a38986b46e8e5ad356dc92ff6076e11d4c1fb2bba01000f40fd89a4b9f36902e95a497dc6666f8fbb6f4a7f40f6f2664eaaba619b75f7f56bad9783f47ae4
data/README.md CHANGED
@@ -1,4 +1,4 @@
1
- # Unitwise
1
+ # [Unitwise](//github.com/joshwlewis/unitwise)
2
2
 
3
3
  [![Gem Version](https://badge.fury.io/rb/unitwise.png)](http://badge.fury.io/rb/unitwise)
4
4
  [![Build Status](https://travis-ci.org/joshwlewis/unitwise.png)](https://travis-ci.org/joshwlewis/unitwise)
@@ -7,33 +7,76 @@
7
7
  [![Code Climate](https://codeclimate.com/github/joshwlewis/unitwise.png)](https://codeclimate.com/github/joshwlewis/unitwise)
8
8
 
9
9
 
10
- Unitwise is a library for performing mathematical operations and conversions on all units defined by the [Unified Code for Units of Measure(UCUM)](http://unitsofmeasure.org/).
10
+ Unitwise is a Ruby library for unit measurement conversion and math.
11
11
 
12
- Unitwise supports a vast number of units. At the time of writing, it supports 95 metric units, 199 non-metric units, and 24 unit prefixes. That's approximately 2,500 basic units, but these can also be combined through multiplication and/or division to create an infinite number of possibilities.
12
+ For an over the top example, consider a car (2800 lb) completing the quarter
13
+ mile in 10 seconds (with uniform acceleration).
14
+
15
+ ```ruby
16
+ distance = 0.25.mile # => #<Unitwise::Measurement value=0.25 unit=mile>
17
+ time = 10.second # => #<Unitwise::Measurement value=10 unit=second>
18
+ mass = 2800.pound # => #<Unitwise::Measurement value=2800 unit=pound>
19
+
20
+ acceleration = 2 * distance / time ** 2
21
+ # => #<Unitwise::Measurement value=0.005 unit=[mi_us]/s2>
22
+
23
+ force = (mass * acceleration).to_lbf
24
+ # => #<Unitwise::Measurement value=2297.5084316991147 unit=lbf>
25
+
26
+ power = (force * distance / time).to_horsepower
27
+ # => #<Unitwise::Measurement value=551.4031264140402 unit=horsepower>
28
+
29
+ speed = ((2 * acceleration * distance) ** 0.5).convert_to("mile/hour")
30
+ # => #<Unitwise::Measurement value=180.0 unit=mile/hour>
31
+ ```
32
+
33
+ ## Rationale
34
+
35
+ Unitwise is based on the [Unified Code for Units of Measure(UCUM)](http://unitsofmeasure.org/),
36
+ which aims to maintain a cross-platform list of units and their conversions.
37
+ This gives Unitwise a few key advantages:
38
+
39
+ - An enormous list of units. At the time of writing, there are 96 metric units,
40
+ 211 non-metric units, and 24 unit prefixes. Whatever unit/units you need, they
41
+ are here.
42
+
43
+ - An accurate and up to date set of units. The units, prefixes, and conversions
44
+ are maintained by UCUM, and are imported into this library with a rake task.
45
+
46
+ One of the objectives of Unitwise was that it should comprehend any combination
47
+ of units. For instance it needed to understand that a unit of
48
+ 'kilogram.(meter/second)2' was equivalent to 'kilogram.meter.(meter/second2)'.
49
+ This resulted in two unique features:
50
+
51
+ - An expression grammer built with a PEG parser. This makes expression
52
+ parsing more efficient and allows nestable parentheses. For example, this is possible: '(kilogram.(meter/second)2)2'
53
+
54
+ - Smart compatibility detection. Each unit is reduced down to its most elementary
55
+ atoms to determine compatibility with another unit. For example, it knows that
56
+ 'meter/second2' should be considered compatible with 'kilogram.foot.minute-2/pound'.
13
57
 
14
58
  ## Usage
15
59
 
16
60
  ### Initialization:
17
61
 
18
- Instantiate measurements with `Unitwise()`
62
+ Measurements can be instantiated with `Unitwise()`.
19
63
 
20
64
  ```ruby
21
65
  require 'unitwise'
22
66
 
23
- Unitwise(2.3, 'kilogram') # => <Unitwise::Measurement 2.3 kilogram>
24
- Unitwise('pound') # => <Unitwise::Measurement 1 pound>
67
+ Unitwise(2.3, 'kilogram') # => #<Unitwise::Measurement value=2.3 unit=kilogram>
68
+ Unitwise('pound') # => #<Unitwise::Measurement value=1 unit=pound>
25
69
  ```
26
70
 
27
- or require the core extensions for some syntactic sugar.
71
+ Unitwise doesn't mess with the Ruby core library by default. So if you want
72
+ the syntactic sugar shown throughout this document, you'll need to require the
73
+ core extensions.
28
74
 
29
75
  ```ruby
30
76
  require 'unitwise/ext'
31
77
 
32
- 1.convert('liter')
33
- # => <Unitwise::Measurement 1 liter>
34
-
35
- 4.teaspoon
36
- # => <Unitwise::Measurement 4 teaspoon>
78
+ 1.convert_to('liter') # => #<Unitwise::Measurement value=1 unit=liter>
79
+ 4.teaspoon # => #<Unitwise::Measurement value=4 unit=teaspoon>
37
80
  ```
38
81
 
39
82
  ### Conversion
@@ -42,22 +85,17 @@ Unitwise is able to convert any unit within the UCUM spec to any other
42
85
  compatible unit.
43
86
 
44
87
  ```ruby
45
- distance = Unitwise(5, 'kilometer')
46
- # => <Unitwise::Measurement 5 kilometer>
47
-
48
- distance.convert('mile')
49
- # => <Unitwise::Measurement 3.106849747474748 mile>
88
+ 5.kilometer.convert_to('mile')
89
+ # => #<Unitwise::Measurement value=3.106849747474748 unit=mile>
50
90
  ```
51
91
 
52
- The prettier version of `convert(unit)` is just calling the unit as a method
53
- name:
92
+ The prettier version of `convert_to(unit)` is appending the unit code, name, etc.
93
+ to a `to_` message.
94
+ name.
54
95
 
55
96
  ```ruby
56
- distance = 26.2.mile
57
- # => <Unitwise::Measurement 26.2 mile>
58
-
59
- distance.kilometer
60
- # => <Unitwise::Measurement 42.164897129794255 kilometer>
97
+ Unitwise(26.2, 'mile').to_kilometer
98
+ # => #<Unitwise::Measurement value=42.164897129794255 unit=kilometer>
61
99
  ```
62
100
 
63
101
  ### Comparison
@@ -66,11 +104,10 @@ It also has the ability to compare measurements with the same or different units
66
104
 
67
105
  ```ruby
68
106
  12.inch == 1.foot # => true
69
-
70
107
  1.meter > 1.yard # => true
71
108
  ```
72
109
 
73
- Again, you have to compare compatible units. For example, comparing two
110
+ Again, you have to compare compatible units. For example, comparing two
74
111
  temperatures will work, comparing a mass to a length would fail.
75
112
 
76
113
  ### SI abbreviations
@@ -78,8 +115,8 @@ temperatures will work, comparing a mass to a length would fail.
78
115
  You can use shorthand for SI units.
79
116
 
80
117
  ```ruby
81
- 1.m # => <Unitwise::Measurement 1 meter>
82
- 1.ml #=> <Unitwise::Measurement 1 milliliter>
118
+ 1000.m == 1.km # => true
119
+ 1.ml == 0.001.l # => true
83
120
  ```
84
121
 
85
122
  ### Complex Units
@@ -89,17 +126,17 @@ them -- they can still be converted, compared, or operated on.
89
126
 
90
127
  ```ruby
91
128
  speed = Unitwise(60, 'mile/hour')
92
- # => <Unitwise::Measurement 60 mile/hour>
129
+ # => #<Unitwise::Measurement value=60 unit=mile/hour>
93
130
 
94
- speed.convert('m/s')
95
- # => <Unitwise::Measurement 26.822453644907288 m/s>
131
+ speed.convert_to('m/s')
132
+ # => #<Unitwise::Measurement value=26.822453644907288 unit=m/s>
96
133
  ```
97
134
 
98
135
  Exponents and parenthesis are supported as well.
99
136
 
100
137
  ```ruby
101
- Unitwise(1000, 'kg.s-1.(m/s)2').watt
102
- # => <Unitwise::Measurement 1000 watt>
138
+ Unitwise(1000, 'kg.s-1.(m/s)2').to_kilowatt
139
+ # => #<Unitwise::Measurement value=1.0 unit=kilowatt>
103
140
  ```
104
141
 
105
142
  ### Math
@@ -108,39 +145,30 @@ You can add or subtract compatible measurements.
108
145
 
109
146
  ```ruby
110
147
  2.meter + 3.inch - 1.yard
111
- # => <Unitwise::Measurement 1.1618 meter>
148
+ # => #<Unitwise::Measurement value=1.1618 unit=meter>
112
149
  ```
113
150
 
114
151
  You can multiply or divide measurements and numbers.
115
152
 
116
153
  ```ruby
117
154
  110.volt * 2
118
- # => <Unitwise::Measurement 220 volt>
155
+ # => #<Unitwise::Measurement value=220 unit=volt>
119
156
  ```
120
157
 
121
- You can multiply or divide measurements with measurements. Here is a fun example
122
- from Physics 101
158
+ You can multiply or divide measurements with measurements.
123
159
 
124
160
  ```ruby
125
- m = 20.kg # => <Unitwise::Measurement 20 kg>
126
-
127
- a = 10.m / 1.s2 # => <Unitwise::Measurement 10 m/s2>
161
+ 20.milligram / 1.liter
162
+ # => #<Unitwise::Measurement value=20 unit=mg/l>
128
163
 
129
- f = m * a # => <Unitwise::Measurement 50 kg.m/s2>
130
-
131
- f.newton # => <Unitwise::Measurement 50 newton>
132
164
  ```
133
165
 
134
- ### Unit Compatibility
135
-
136
- Unitwise is fairly intelligent about unit compatibility. It boils each unit down
137
- to it's basic composition to determine if they are compatible. For instance,
138
- energy (say a Joule, which can be expressed as kg*m2/s2) would have the
139
- components mass<sup>1</sup>, length<sup>2</sup>, and
140
- time<sup>-2</sup>. Any unit that could be reduced to this same composition
141
- would be considered compatible.
166
+ Exponentiation is also supported.
142
167
 
143
- I've extracted this datatype into it's own gem ([SignedMultiset](//github.com/joshwlewis/signed_multiset)) if you find this construct interesting.
168
+ ```ruby
169
+ (10.cm ** 3).to_liter
170
+ # => #<Unitwise::Measurement value=1.0 unit=liter>
171
+ ```
144
172
 
145
173
  ### Unit Names and Atom Codes
146
174
 
@@ -157,30 +185,28 @@ Just as an example, you can see here that there are actually a few versions of i
157
185
  and foot:
158
186
 
159
187
  ```ruby
160
- 1.convert('[ft_i]') == 1.convert('[ft_us]') # => false
188
+ 1.convert_to('[ft_i]') == 1.convert_to('[ft_us]') # => false
161
189
 
162
- 3.convert('[in_br]') == 3.convert('[in_i]') # => false
190
+ 3.convert_to('[in_br]') == 3.convert_to('[in_i]') # => false
163
191
  ```
164
192
 
165
193
  ### Available Units
166
194
 
167
- If you are looking for a particular unit (or 'atom' in the UCUM spec), chances
168
- are that it is in here. There is a rudimentary search function for both
169
- `Unitwise::Atom` and `Unitwise::Prefix`.
195
+ If you are looking for a particular unit, you can search with a string or
196
+ Regexp.
170
197
 
171
198
  ```ruby
172
- Unitwise::Atom.search('fathom')
173
- # => [ ... ]
174
- Unitwise::Prefix.search('milli')
199
+ Unitwise.search('fathom')
175
200
  # => [ ... ]
201
+
176
202
  ```
177
203
 
178
- You can also get the official list from the UCUM website in XML format at
179
- [unitsofmeasure.org/ucum-essence.xml](http://unitsofmeasure.org/ucum-essence.xml)
180
- or a YAML version within this repo
204
+ You can also get the official list from the UCUM website in XML format at
205
+ [unitsofmeasure.org/ucum-essence.xml](http://unitsofmeasure.org/ucum-essence.xml)
206
+ or a YAML version within this repo
181
207
  [github.com/joshwlewis/unitwise/tree/master/data](//github.com/joshwlewis/unitwise/tree/master/data).
182
208
 
183
- ### Supported Ruby Versions
209
+ ## Supported Ruby Versions
184
210
 
185
211
  This library aims to support and is tested against the following Ruby
186
212
  implementations:
data/lib/unitwise/atom.rb CHANGED
@@ -41,26 +41,29 @@ module Unitwise
41
41
  # prefixes.
42
42
  # @return [true, false]
43
43
  # @api public
44
- def metric?
45
- base? ? true : !!metric
44
+ def metric
45
+ base? ? true : !!@metric
46
46
  end
47
+ alias_method :metric?, :metric
47
48
 
48
49
  # Determine if a unit is special. Special atoms are not defined on a
49
50
  # traditional ratio scale.
50
51
  # @return [true, false]
51
52
  # @api public
52
- def special?
53
- !!special
53
+ def special
54
+ !!@special
54
55
  end
56
+ alias_method :special?, :special
55
57
 
56
58
  # Determine if a unit is arbitrary. Arbitrary atoms are not of any specific
57
59
  # dimension and have no general meaning, therefore cannot be compared with
58
60
  # any other unit.
59
61
  # @return [true, false]
60
62
  # @api public
61
- def arbitrary?
62
- !!arbitrary
63
+ def arbitrary
64
+ !!@arbitrary
63
65
  end
66
+ alias_method :arbitrary?, :arbitrary
64
67
 
65
68
  # Determine how far away a unit is from a base unit.
66
69
  # @return [Integer]
data/lib/unitwise/base.rb CHANGED
@@ -7,11 +7,7 @@ module Unitwise
7
7
  @all ||= data.map{|d| self.new d }
8
8
  end
9
9
 
10
- def self.find(term)
11
- all.find { |i| i.search_strings.any? { |string| string == term } }
12
- end
13
-
14
- def self.find_by(method, string)
10
+ def self.find(string, method=:primary_code)
15
11
  self.all.find do |i|
16
12
  key = i.send(method)
17
13
  if key.respond_to?(:each)
@@ -22,12 +18,6 @@ module Unitwise
22
18
  end
23
19
  end
24
20
 
25
- def self.search(term)
26
- self.all.select do |i|
27
- i.search_strings.any? { |string| string =~ /#{term}/i }
28
- end
29
- end
30
-
31
21
  def names=(names)
32
22
  @names = Array(names)
33
23
  end
@@ -38,10 +28,6 @@ module Unitwise
38
28
  end
39
29
  end
40
30
 
41
- def search_strings
42
- [primary_code, secondary_code, names, slugs, symbol].flatten.compact
43
- end
44
-
45
31
  def to_s
46
32
  primary_code
47
33
  end
@@ -0,0 +1,76 @@
1
+ module Unitwise
2
+ # A compound is a combination of an atom and a prefix.
3
+ # This class is isolated from the rest of the code base and primarily exists
4
+ # for the convenience of listing and searching possible combinations
5
+ # of atoms and prefixes.
6
+ class Compound < Liner.new(:atom, :prefix)
7
+ # List all possible compounds
8
+ # @return [Array]
9
+ # @api public
10
+ def self.all
11
+ @all || build
12
+ end
13
+
14
+ # Builds up the list of possible compounds. Only required if you are
15
+ # defining your own atoms/prefixes.
16
+ # @return [Array]
17
+ # @api public
18
+ def self.build
19
+ compounds = Atom.all.map { |a| new(a) }
20
+ Atom.all.select(&:metric).each do |a|
21
+ Prefix.all.each do |p|
22
+ compounds << new(a, p)
23
+ end
24
+ end
25
+ @all = compounds
26
+ end
27
+
28
+ # Search for compounds with a search term
29
+ # @param [String, Regexp] What to search for
30
+ # @return [Array]
31
+ # @api public
32
+ def self.search(term)
33
+ all.select do |compound|
34
+ compound.search_strings.any? { |string| Regexp.new(term).match(string) }
35
+ end
36
+ end
37
+
38
+ [:primary_code, :secondary_code, :symbol].each do |attr|
39
+ define_method attr do
40
+ instance_variable_get("@#{attr}") ||
41
+ instance_variable_set("@#{attr}",
42
+ prefix ? "#{prefix.send attr}#{atom.send attr}" : atom.send(attr))
43
+ end
44
+ end
45
+
46
+ [:names, :slugs].each do |attr|
47
+ define_method attr do
48
+ instance_variable_get("@#{attr}") ||
49
+ instance_variable_set("@#{attr}",
50
+ if prefix
51
+ prefix.send(attr).zip(atom.send(attr)).map{ |set| set.join('') }
52
+ else
53
+ atom.send(attr)
54
+ end)
55
+ end
56
+ end
57
+
58
+ def atom=(value)
59
+ value.is_a?(Atom) ? super(value) : super(Atom.find value)
60
+ end
61
+
62
+ def prefix=(value)
63
+ value.is_a?(Prefix) ? super(value) : super(Prefix.find value)
64
+ end
65
+
66
+ def search_strings
67
+ @search_strings ||= [primary_code, secondary_code, symbol, names, slugs].flatten.uniq
68
+ end
69
+
70
+ def attribute_string
71
+ [:atom, :prefix, :primary_code, :secondary_code, :symbol, :names, :slugs].map do |attr|
72
+ "#{attr}='#{send attr}'"
73
+ end.join(', ')
74
+ end
75
+ end
76
+ end
@@ -5,8 +5,8 @@ module Unitwise
5
5
  rule(integer: simple(:i)) { i.to_i }
6
6
  rule(fixnum: simple(:f)) { f.to_f }
7
7
 
8
- rule(prefix_code: simple(:c)) { |ctx| Prefix.find_by(ctx[:key], ctx[:c]) }
9
- rule(atom_code: simple(:c)) { |ctx| Atom.find_by(ctx[:key], ctx[:c]) }
8
+ rule(prefix_code: simple(:c)) { |ctx| Prefix.find(ctx[:c], ctx[:key]) }
9
+ rule(atom_code: simple(:c)) { |ctx| Atom.find(ctx[:c], ctx[:key]) }
10
10
  rule(term: subtree(:h)) { Term.new(h) }
11
11
 
12
12
  rule(operator: simple(:o), right: simple(:r)) do
@@ -1,11 +1,29 @@
1
1
  class Numeric
2
- def convert(unit)
2
+ # Converts numeric to a measurement
3
+ # @param unit [Unitwise::Unit, String] The unit to use in the measurement
4
+ # @return [Unitwise::Measurement]
5
+ # @example
6
+ # 26.2.convert_to('mile') # => #<Unitwise::Measurement 1 mile>
7
+ # @api public
8
+ def convert_to(unit)
3
9
  Unitwise::Measurement.new(self, unit)
4
10
  end
5
11
 
12
+ # Converts numeric to a measurement by the method name
13
+ # @exmple
14
+ # 26.2.mile # => #<Unitwise::Measurement 26.2 mile>
15
+ # 100.to_foot # => #<Unitwise::Measurement 100 foot>
16
+ # @api semipublic
6
17
  def method_missing(meth, *args, &block)
7
- convert(meth)
8
- rescue Unitwise::ExpressionError
9
- super(meth, *args)
18
+ if args.empty? && !block_given?
19
+ unit = (match = /\Ato_(\w+)\Z/.match(meth)) ? match[1] : meth
20
+ begin
21
+ convert_to(unit)
22
+ rescue Unitwise::ExpressionError
23
+ super(meth, *args, &block)
24
+ end
25
+ else
26
+ super(meth, *args, &block)
27
+ end
10
28
  end
11
29
  end
@@ -23,10 +23,10 @@ module Unitwise
23
23
  # @param other_unit [String, Measurement::Unit] Either a string expression
24
24
  # or a Measurement::Unit
25
25
  # @example
26
- # measurement1.convert('foot')
27
- # measurement2.convert('kilogram')
26
+ # measurement1.convert_to('foot')
27
+ # measurement2.convert_to('kilogram')
28
28
  # @api public
29
- def convert(other_unit)
29
+ def convert_to(other_unit)
30
30
  other_unit = Unit.new(other_unit)
31
31
  if similar_to?(other_unit)
32
32
  new(converted_value(other_unit), other_unit)
@@ -126,13 +126,17 @@ module Unitwise
126
126
  Rational(value)
127
127
  end
128
128
 
129
- # Will attempt to convert to a unit by the method name.
129
+ # Will attempt to convert to a unit by method name.
130
130
  # @example
131
- # measurement.foot # => <Unitwise::Measurement 4 foot>
131
+ # measurement.to_foot # => <Unitwise::Measurement 4 foot>
132
132
  # @api semipublic
133
133
  def method_missing(meth, *args, &block)
134
- if Unitwise::Expression.decompose(meth)
135
- self.convert(meth)
134
+ if args.empty? && !block_given? && (match = /\Ato_(\w+)\Z/.match(meth))
135
+ begin
136
+ convert_to(match[1])
137
+ rescue ExpressionError
138
+ super(meth, *args, &block)
139
+ end
136
140
  else
137
141
  super(meth, *args, &block)
138
142
  end
@@ -168,7 +172,7 @@ module Unitwise
168
172
  # @api private
169
173
  def combine(operator, other)
170
174
  if similar_to?(other)
171
- new(value.send(operator, other.convert(unit).value), unit)
175
+ new(value.send(operator, other.convert_to(unit).value), unit)
172
176
  end
173
177
  end
174
178
 
@@ -179,7 +183,7 @@ module Unitwise
179
183
  new(value.send(operator, other), unit)
180
184
  elsif other.respond_to?(:composition)
181
185
  if similar_to?(other)
182
- converted = other.convert(unit)
186
+ converted = other.convert_to(unit)
183
187
  new(value.send(operator, converted.value), unit.send(operator, converted.unit))
184
188
  else
185
189
  new(value.send(operator, other.value), unit.send(operator, other.unit))
@@ -14,13 +14,6 @@ module Unitwise
14
14
  @unit.is_a?(Unit) ? @unit : Unit.new(@unit)
15
15
  end
16
16
 
17
- # Duplicate this instance
18
- # @return [Unitwise::Unit]
19
- # @api public
20
- def dup
21
- self.class.new(value, unit)
22
- end
23
-
24
17
  # List the atoms associated with this scale's unit.
25
18
  # @return [Array]
26
19
  # @api public
@@ -28,7 +21,7 @@ module Unitwise
28
21
  unit.atoms
29
22
  end
30
23
 
31
- # List the atoms associated with this scale's unit.
24
+ # List the terms associated with this scale's unit.
32
25
  # @return [Array]
33
26
  # @api public
34
27
  def terms
@@ -74,18 +67,14 @@ module Unitwise
74
67
  unit.depth + 1
75
68
  end
76
69
 
77
- # Is this the deepest level scale in the scale chain?
78
- # @return [true, false]
79
- # @api public
80
- def terminal?
81
- depth <= 3
82
- end
83
-
84
70
  # Convert to a simple string representing the scale.
85
71
  # @api public
86
72
  def to_s
87
73
  "#{value} #{unit}"
88
74
  end
89
75
 
76
+ def inspect
77
+ "#<#{self.class} value=#{value} unit=#{unit}>"
78
+ end
90
79
  end
91
80
  end
data/lib/unitwise/unit.rb CHANGED
@@ -34,28 +34,16 @@ module Unitwise
34
34
  terms.first.functional(x, forward)
35
35
  end
36
36
 
37
- def dup
38
- self.class.new(expression)
39
- end
40
-
41
37
  def depth
42
38
  terms.map(&:depth).max + 1
43
39
  end
44
40
 
45
- def terminal?
46
- depth <= 3
47
- end
48
-
49
41
  def root_terms
50
42
  terms.flat_map(&:root_terms)
51
43
  end
52
44
 
53
45
  def scalar
54
- if terms.empty?
55
- 1
56
- else
57
- terms.map(&:scalar).inject(&:*)
58
- end
46
+ terms.map(&:scalar).inject(&:*)
59
47
  end
60
48
 
61
49
  def *(other)
@@ -1,3 +1,3 @@
1
1
  module Unitwise
2
- VERSION = "0.3.2"
2
+ VERSION = "0.4.0"
3
3
  end
data/lib/unitwise.rb CHANGED
@@ -11,11 +11,22 @@ require 'unitwise/atom'
11
11
  require 'unitwise/prefix'
12
12
  require 'unitwise/term'
13
13
  require 'unitwise/unit'
14
+ require 'unitwise/compound'
14
15
  require 'unitwise/errors'
15
16
 
16
- # Unitwise is a library for performing mathematical operations and conversions
17
+ # Unitwise is a library for performing mathematical operations and conversions
17
18
  # on all units defined by the [Unified Code for Units of Measure(UCUM).
18
19
  module Unitwise
20
+
21
+ # Search for available compounds. This is just a helper method for
22
+ # convenience
23
+ # @param term [String, Regexp]
24
+ # @return [Array]
25
+ # @api public
26
+ def self.search(term)
27
+ Compound.search(term)
28
+ end
29
+
19
30
  # The system path for the installed gem
20
31
  # @api private
21
32
  def self.path
@@ -44,4 +55,3 @@ def Unitwise(first, last=nil)
44
55
  Unitwise::Measurement.new(1, first)
45
56
  end
46
57
  end
47
-
@@ -36,16 +36,6 @@ module ScaleTests
36
36
  end
37
37
  end
38
38
 
39
- describe "#dup" do
40
- it "must return a new instance" do
41
- subject.must_respond_to(:dup)
42
- subject.dup.must_be_instance_of(described_class)
43
- subject.dup.value.must_equal subject.value
44
- subject.dup.unit.to_s.must_equal subject.unit.to_s
45
- subject.dup.object_id.wont_equal subject.dup.object_id
46
- end
47
- end
48
-
49
39
  describe "#terms" do
50
40
  it "must return an array of terms" do
51
41
  subject.terms.must_be_kind_of(Enumerable)
@@ -90,12 +80,6 @@ module ScaleTests
90
80
  end
91
81
  end
92
82
 
93
- describe "#terminal?" do
94
- it "must return true for the last of kind in the chain" do
95
- subject.terminal?.must_equal false
96
- k.terminal?.must_equal true
97
- end
98
- end
99
83
  end
100
84
  end
101
85
  end
@@ -23,14 +23,6 @@ describe Unitwise::Atom do
23
23
  end
24
24
  end
25
25
 
26
- describe "::search" do
27
- it "must search for atoms" do
28
- result = subject.search("inch")
29
- result.count.must_equal 10
30
- result.must_include subject.find("inch")
31
- end
32
- end
33
-
34
26
  let(:second) { Unitwise::Atom.find("s") }
35
27
  let(:yard) { Unitwise::Atom.find("[yd_i]")}
36
28
  let(:pi) { Unitwise::Atom.find("[pi]")}
@@ -0,0 +1,43 @@
1
+ require 'test_helper'
2
+
3
+ describe Unitwise::Compound do
4
+ describe :class_methods do
5
+ describe :all do
6
+ subject { Unitwise::Compound.all }
7
+ it { subject.must_be_kind_of Enumerable }
8
+ it { subject.first.must_be_instance_of Unitwise::Compound }
9
+ end
10
+ describe :search do
11
+ it "should return a list of units" do
12
+ search = Unitwise::Compound.search('foo')
13
+ search.must_be_kind_of Enumerable
14
+ search.first.must_be_instance_of Unitwise::Compound
15
+ end
16
+ end
17
+ end
18
+
19
+ describe :instance_methods do
20
+ let(:prefixed) { Unitwise::Compound.new('m','k') }
21
+ let(:unprefixed) { Unitwise::Compound.new("[in_i]") }
22
+ it "should have an atom" do
23
+ [prefixed, unprefixed].each { |ex| ex.atom.must_be_instance_of Unitwise::Atom }
24
+ end
25
+ it "should have a prefix when appropriate" do
26
+ prefixed.prefix.must_be_instance_of Unitwise::Prefix
27
+ unprefixed.prefix.must_equal nil
28
+ end
29
+ it "should concatenate common methods" do
30
+ prefixed.primary_code.must_equal 'km'
31
+ unprefixed.secondary_code.must_equal '[IN_I]'
32
+ prefixed.names.must_equal ['kilometer']
33
+ unprefixed.slugs.must_equal ['inch']
34
+ end
35
+ it "should have an array of strings for searching" do
36
+ prefixed.search_strings.must_be_kind_of(Array)
37
+ end
38
+ it "should look ok for inspection" do
39
+ prefixed.inspect.must_include 'km'
40
+ unprefixed.inspect.must_include '[IN_I]'
41
+ end
42
+ end
43
+ end
@@ -4,42 +4,47 @@ describe Numeric do
4
4
 
5
5
  describe "#convert" do
6
6
  it "must work for Integer" do
7
- measurement = 22.convert("kg")
7
+ measurement = 22.convert_to("kg")
8
8
  measurement.must_be_instance_of(Unitwise::Measurement)
9
9
  measurement.value.must_equal 22
10
10
  end
11
11
  it "must work for Fixnum" do
12
- measurement = 24.25.convert("[ft_i]")
12
+ measurement = 24.25.convert_to("[ft_i]")
13
13
  measurement.must_be_instance_of(Unitwise::Measurement)
14
14
  measurement.value.must_equal 24.25
15
15
  end
16
16
  it "must work for Float" do
17
- measurement = (22.0/7).convert("[mi_i]")
17
+ measurement = (22.0/7).convert_to("[mi_i]")
18
18
  measurement.must_be_instance_of(Unitwise::Measurement)
19
19
  measurement.value.must_equal 3.142857142857143
20
20
  end
21
21
  it "must work for Rational" do
22
- measurement = Rational(22/7).convert("N/m2")
22
+ measurement = Rational(22/7).convert_to("N/m2")
23
23
  measurement.must_be_instance_of(Unitwise::Measurement)
24
24
  measurement.value.must_equal Rational(22/7)
25
25
  end
26
26
  end
27
27
 
28
28
  describe "#method_missing" do
29
- it "must match mm" do
29
+ it "must match 'mm'" do
30
30
  mm = 2.5.mm
31
31
  mm.must_be_instance_of(Unitwise::Measurement)
32
32
  mm.value.must_equal 2.5
33
33
  end
34
- it "must match foot" do
35
- ft = 4.foot
36
- ft.must_be_instance_of(Unitwise::Measurement)
37
- ft.value.must_equal 4
34
+ it "must match 'to_mm'" do
35
+ mm = 2.5.to_mm
36
+ mm.must_be_instance_of(Unitwise::Measurement)
37
+ mm.value.must_equal 2.5
38
38
  end
39
+
39
40
  it "must not match 'foo'" do
40
41
  ->{ 1.foo }.must_raise NoMethodError
41
42
  end
42
43
 
44
+ it "must not match 'to_foo'" do
45
+ ->{ 1.to_foo }.must_raise NoMethodError
46
+ end
47
+
43
48
  end
44
49
 
45
50
 
@@ -11,27 +11,27 @@ describe Unitwise::Measurement do
11
11
  end
12
12
  end
13
13
 
14
- describe "#convert" do
14
+ describe "#convert_to" do
15
15
  it "must convert to a similar unit code" do
16
- mph.convert('km/h').value.must_equal 96.56063999999999
16
+ mph.convert_to('km/h').value.must_equal 96.56063999999999
17
17
  end
18
18
  it "must raise an error if the units aren't similar" do
19
- ->{ mph.convert('N') }.must_raise Unitwise::ConversionError
19
+ ->{ mph.convert_to('N') }.must_raise Unitwise::ConversionError
20
20
  end
21
21
  it "must convert special units to their base units" do
22
- cel.convert('K').value.must_equal 295.15
22
+ cel.convert_to('K').value.must_equal 295.15
23
23
  end
24
24
  it "must convert base units to special units" do
25
- k.convert('Cel').value.must_equal 100
25
+ k.convert_to('Cel').value.must_equal 100
26
26
  end
27
27
  it "must convert special units to special units" do
28
- f.convert('Cel').value.must_equal 37
28
+ f.convert_to('Cel').value.must_equal 37
29
29
  end
30
30
  it "must convert special units to non-special units" do
31
- cel.convert("[degR]").value.must_equal 531.27
31
+ cel.convert_to("[degR]").value.must_equal 531.27
32
32
  end
33
33
  it "must convert derived units to special units" do
34
- r.convert("Cel").value.round.must_equal 0
34
+ r.convert_to("Cel").value.round.must_equal 0
35
35
  end
36
36
  end
37
37
 
@@ -127,14 +127,14 @@ describe Unitwise::Measurement do
127
127
 
128
128
  describe "#method_missing" do
129
129
  let(:meter) { Unitwise::Measurement.new(1, 'm')}
130
- it "must convert 'mm'" do
131
- convert = meter.mm
130
+ it "must convert 'to_mm'" do
131
+ convert = meter.to_mm
132
132
  convert.must_be_instance_of Unitwise::Measurement
133
133
  convert.value.must_equal 1000
134
134
  end
135
135
 
136
- it "must convert 'foot'" do
137
- convert = meter.foot
136
+ it "must convert 'to_foot'" do
137
+ convert = meter.to_foot
138
138
  convert.must_be_instance_of Unitwise::Measurement
139
139
  convert.value.must_equal 3.280839895013123
140
140
  end
@@ -143,6 +143,10 @@ describe Unitwise::Measurement do
143
143
  ->{ meter.foo }.must_raise NoMethodError
144
144
  end
145
145
 
146
+ it "must not convert 'to_foo'" do
147
+ ->{ meter.to_foo }.must_raise NoMethodError
148
+ end
149
+
146
150
  end
147
151
 
148
152
  describe "#to_f" do
@@ -22,12 +22,4 @@ describe Unitwise::Prefix do
22
22
  end
23
23
  end
24
24
 
25
- describe "#search" do
26
- it "should search for a prefix" do
27
- result = subject.search("mi")
28
- result.count.must_equal 3
29
- result.must_include subject.find("milli")
30
- end
31
- end
32
-
33
25
  end
@@ -9,7 +9,15 @@ describe Unitwise do
9
9
  Unitwise('kg').must_be_instance_of Unitwise::Measurement
10
10
  end
11
11
  end
12
+
13
+ describe 'search' do
14
+ it "must return results" do
15
+ Unitwise.search('foo').must_be_instance_of(Array)
16
+ end
17
+ end
18
+
12
19
  it "should have a path" do
13
20
  Unitwise.path.must_match(/unitwise$/)
14
21
  end
15
- end
22
+
23
+ end
data/unitwise.gemspec CHANGED
@@ -17,13 +17,13 @@ Gem::Specification.new do |gem|
17
17
  gem.test_files = gem.files.grep(%r{^test/})
18
18
  gem.require_paths = ["lib"]
19
19
 
20
- gem.add_dependency "liner", "~> 0.2.1"
21
- gem.add_dependency "signed_multiset", "~> 0.2.0"
22
- gem.add_dependency "parslet", "~> 1.5.0"
20
+ gem.add_dependency "liner", "~> 0.2"
21
+ gem.add_dependency "signed_multiset", "~> 0.2"
22
+ gem.add_dependency "parslet", "~> 1.5"
23
23
 
24
- gem.add_development_dependency "minitest"
25
- gem.add_development_dependency "rake"
26
- gem.add_development_dependency "nori"
27
- gem.add_development_dependency "nokogiri"
28
- gem.add_development_dependency "coveralls"
24
+ gem.add_development_dependency "minitest", ">= 5.0"
25
+ gem.add_development_dependency "rake", ">= 10.0"
26
+ gem.add_development_dependency "nori", "~> 2.3"
27
+ gem.add_development_dependency "nokogiri", "~> 1.6"
28
+ gem.add_development_dependency "coveralls", "~> 0.6"
29
29
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: unitwise
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.3.2
4
+ version: 0.4.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Josh Lewis
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2014-03-01 00:00:00.000000000 Z
11
+ date: 2014-03-06 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: liner
@@ -16,112 +16,112 @@ dependencies:
16
16
  requirements:
17
17
  - - "~>"
18
18
  - !ruby/object:Gem::Version
19
- version: 0.2.1
19
+ version: '0.2'
20
20
  type: :runtime
21
21
  prerelease: false
22
22
  version_requirements: !ruby/object:Gem::Requirement
23
23
  requirements:
24
24
  - - "~>"
25
25
  - !ruby/object:Gem::Version
26
- version: 0.2.1
26
+ version: '0.2'
27
27
  - !ruby/object:Gem::Dependency
28
28
  name: signed_multiset
29
29
  requirement: !ruby/object:Gem::Requirement
30
30
  requirements:
31
31
  - - "~>"
32
32
  - !ruby/object:Gem::Version
33
- version: 0.2.0
33
+ version: '0.2'
34
34
  type: :runtime
35
35
  prerelease: false
36
36
  version_requirements: !ruby/object:Gem::Requirement
37
37
  requirements:
38
38
  - - "~>"
39
39
  - !ruby/object:Gem::Version
40
- version: 0.2.0
40
+ version: '0.2'
41
41
  - !ruby/object:Gem::Dependency
42
42
  name: parslet
43
43
  requirement: !ruby/object:Gem::Requirement
44
44
  requirements:
45
45
  - - "~>"
46
46
  - !ruby/object:Gem::Version
47
- version: 1.5.0
47
+ version: '1.5'
48
48
  type: :runtime
49
49
  prerelease: false
50
50
  version_requirements: !ruby/object:Gem::Requirement
51
51
  requirements:
52
52
  - - "~>"
53
53
  - !ruby/object:Gem::Version
54
- version: 1.5.0
54
+ version: '1.5'
55
55
  - !ruby/object:Gem::Dependency
56
56
  name: minitest
57
57
  requirement: !ruby/object:Gem::Requirement
58
58
  requirements:
59
59
  - - ">="
60
60
  - !ruby/object:Gem::Version
61
- version: '0'
61
+ version: '5.0'
62
62
  type: :development
63
63
  prerelease: false
64
64
  version_requirements: !ruby/object:Gem::Requirement
65
65
  requirements:
66
66
  - - ">="
67
67
  - !ruby/object:Gem::Version
68
- version: '0'
68
+ version: '5.0'
69
69
  - !ruby/object:Gem::Dependency
70
70
  name: rake
71
71
  requirement: !ruby/object:Gem::Requirement
72
72
  requirements:
73
73
  - - ">="
74
74
  - !ruby/object:Gem::Version
75
- version: '0'
75
+ version: '10.0'
76
76
  type: :development
77
77
  prerelease: false
78
78
  version_requirements: !ruby/object:Gem::Requirement
79
79
  requirements:
80
80
  - - ">="
81
81
  - !ruby/object:Gem::Version
82
- version: '0'
82
+ version: '10.0'
83
83
  - !ruby/object:Gem::Dependency
84
84
  name: nori
85
85
  requirement: !ruby/object:Gem::Requirement
86
86
  requirements:
87
- - - ">="
87
+ - - "~>"
88
88
  - !ruby/object:Gem::Version
89
- version: '0'
89
+ version: '2.3'
90
90
  type: :development
91
91
  prerelease: false
92
92
  version_requirements: !ruby/object:Gem::Requirement
93
93
  requirements:
94
- - - ">="
94
+ - - "~>"
95
95
  - !ruby/object:Gem::Version
96
- version: '0'
96
+ version: '2.3'
97
97
  - !ruby/object:Gem::Dependency
98
98
  name: nokogiri
99
99
  requirement: !ruby/object:Gem::Requirement
100
100
  requirements:
101
- - - ">="
101
+ - - "~>"
102
102
  - !ruby/object:Gem::Version
103
- version: '0'
103
+ version: '1.6'
104
104
  type: :development
105
105
  prerelease: false
106
106
  version_requirements: !ruby/object:Gem::Requirement
107
107
  requirements:
108
- - - ">="
108
+ - - "~>"
109
109
  - !ruby/object:Gem::Version
110
- version: '0'
110
+ version: '1.6'
111
111
  - !ruby/object:Gem::Dependency
112
112
  name: coveralls
113
113
  requirement: !ruby/object:Gem::Requirement
114
114
  requirements:
115
- - - ">="
115
+ - - "~>"
116
116
  - !ruby/object:Gem::Version
117
- version: '0'
117
+ version: '0.6'
118
118
  type: :development
119
119
  prerelease: false
120
120
  version_requirements: !ruby/object:Gem::Requirement
121
121
  requirements:
122
- - - ">="
122
+ - - "~>"
123
123
  - !ruby/object:Gem::Version
124
- version: '0'
124
+ version: '0.6'
125
125
  description: Ruby implementation of the Unified Code for Units of Measure (UCUM)
126
126
  email:
127
127
  - josh.w.lewis@gmail.com
@@ -143,6 +143,7 @@ files:
143
143
  - lib/unitwise/atom.rb
144
144
  - lib/unitwise/base.rb
145
145
  - lib/unitwise/composable.rb
146
+ - lib/unitwise/compound.rb
146
147
  - lib/unitwise/errors.rb
147
148
  - lib/unitwise/expression.rb
148
149
  - lib/unitwise/expression/composer.rb
@@ -172,6 +173,7 @@ files:
172
173
  - test/test_helper.rb
173
174
  - test/unitwise/atom_test.rb
174
175
  - test/unitwise/base_test.rb
176
+ - test/unitwise/compound_test.rb
175
177
  - test/unitwise/expression/decomposer_test.rb
176
178
  - test/unitwise/expression/matcher_test.rb
177
179
  - test/unitwise/expression/parser_test.rb
@@ -214,6 +216,7 @@ test_files:
214
216
  - test/test_helper.rb
215
217
  - test/unitwise/atom_test.rb
216
218
  - test/unitwise/base_test.rb
219
+ - test/unitwise/compound_test.rb
217
220
  - test/unitwise/expression/decomposer_test.rb
218
221
  - test/unitwise/expression/matcher_test.rb
219
222
  - test/unitwise/expression/parser_test.rb
@@ -225,3 +228,4 @@ test_files:
225
228
  - test/unitwise/term_test.rb
226
229
  - test/unitwise/unit_test.rb
227
230
  - test/unitwise_test.rb
231
+ has_rdoc: