vanunits 1.5.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/HISTORY +16 -0
- data/HOWITWORKS.rdoc +72 -0
- data/LICENSE +23 -0
- data/README.rdoc +68 -0
- data/lib/van/units.rb +116 -0
- data/lib/van/units/base.rb +991 -0
- data/lib/van/units/constants.rb +2 -0
- data/lib/van/units/constants/cgs.rb +122 -0
- data/lib/van/units/constants/math.rb +3 -0
- data/lib/van/units/constants/math/cgs.rb +125 -0
- data/lib/van/units/constants/math/mks.rb +126 -0
- data/lib/van/units/constants/math/natural.rb +33 -0
- data/lib/van/units/constants/mks.rb +122 -0
- data/lib/van/units/currency.rb +160 -0
- data/lib/van/units/data/binary/base.rb +4 -0
- data/lib/van/units/data/cex.rb +5 -0
- data/lib/van/units/data/currency-default.rb +5 -0
- data/lib/van/units/data/currency-standard.rb +2 -0
- data/lib/van/units/data/currency/base.rb +89 -0
- data/lib/van/units/data/iec.rb +5 -0
- data/lib/van/units/data/iec_binary/base.rb +6 -0
- data/lib/van/units/data/si.rb +8 -0
- data/lib/van/units/data/si/base.rb +11 -0
- data/lib/van/units/data/si/constants.rb +88 -0
- data/lib/van/units/data/si/derived.rb +33 -0
- data/lib/van/units/data/si/extra.rb +35 -0
- data/lib/van/units/data/si/misc.rb +10 -0
- data/lib/van/units/data/uk.rb +10 -0
- data/lib/van/units/data/uk/base.rb +25 -0
- data/lib/van/units/data/units-default.rb +12 -0
- data/lib/van/units/data/units-standard.rb +5 -0
- data/lib/van/units/data/us.rb +10 -0
- data/lib/van/units/data/us/base.rb +47 -0
- data/lib/van/units/data/xmethods.rb +5 -0
- data/lib/van/units/data/xmethods/cached.rb +84 -0
- data/lib/van/units/data/xmethods/mapping.rb +87 -0
- data/lib/van/units/loaders.rb +100 -0
- data/lib/van/units/units.rb +111 -0
- data/lib/van/units_currency.rb +12 -0
- data/meta/author +1 -0
- data/meta/collection +1 -0
- data/meta/contact +1 -0
- data/meta/created +1 -0
- data/meta/description +5 -0
- data/meta/homepage +1 -0
- data/meta/name +1 -0
- data/meta/summary +1 -0
- data/meta/title +1 -0
- data/meta/version +1 -0
- data/qed/conversion.rdoc +14 -0
- data/qed/equality.rdoc +150 -0
- data/qed/operations.rdoc +74 -0
- data/test/test_constants.rb +12 -0
- data/test/test_currency.rb +26 -0
- data/test/test_units.rb +205 -0
- metadata +123 -0
data/HISTORY
ADDED
@@ -0,0 +1,16 @@
|
|
1
|
+
= Release History
|
2
|
+
|
3
|
+
== 1.0.0 / 2010-02=22
|
4
|
+
|
5
|
+
VanUnits is the units system orginally underpinning Stick.
|
6
|
+
Stick has implemented a new expirmental unit system, so prior
|
7
|
+
system is being spun-off as VanUnits.
|
8
|
+
|
9
|
+
The components include are the base unit system, the currency
|
10
|
+
syste (which needs a need online backend to function), and
|
11
|
+
a set of scientific constants utilizing the unit system.
|
12
|
+
|
13
|
+
Changes:
|
14
|
+
|
15
|
+
* Happy Brithday
|
16
|
+
|
data/HOWITWORKS.rdoc
ADDED
@@ -0,0 +1,72 @@
|
|
1
|
+
= How Units.rb Works
|
2
|
+
|
3
|
+
== Basics
|
4
|
+
|
5
|
+
There are two types of units: base units, which are not expressed in
|
6
|
+
function of other units, and derived units which are expressed as a
|
7
|
+
function of other units. The base units are represented by the
|
8
|
+
Van::Units::BaseUnit class. Derived units are implemented by
|
9
|
+
Van::Units::Unit. Derived units are represented as the product of
|
10
|
+
powers of other units that can be base units or derived units. These
|
11
|
+
powers are kept in a Hash. BaseUnit is never exposed to the user; if
|
12
|
+
you need a base unit, is represented as a Unit that's the product of a
|
13
|
+
single BaseUnit to the power 1. To be able to work with units, it's
|
14
|
+
often necessary to normalize them in some way, i.e., express them in
|
15
|
+
function of base units only. This is done by the Unit#simplify method.
|
16
|
+
This normalization is not performed automatically; rather we have
|
17
|
+
chosen to have the user of units.rb initiate any conversion so he or
|
18
|
+
she can have more control over rounding errors and such. Units can be
|
19
|
+
multiplied, divided and exponentiated.
|
20
|
+
|
21
|
+
Units by itself are not very interesting unless they are combined with
|
22
|
+
some numeric value. This is what the Van::Units::Value class does.
|
23
|
+
It holds a Unit, and a value which can be integers, float,
|
24
|
+
BigDecimals, complex numbers, etc. Values can be multiplied, divided,
|
25
|
+
and added and subtracted if the units are compatible. This is checked
|
26
|
+
by normalizing the units and then transforming the Value with the
|
27
|
+
larger unit to the smaller unit. For instance, adding inches and feet
|
28
|
+
will result in inches because an inch is the smaller unit. Value
|
29
|
+
supports most other numeric operators.
|
30
|
+
|
31
|
+
== Converters
|
32
|
+
|
33
|
+
units.rb has the notion of converters. This notion has been introduced
|
34
|
+
because sometimes units have the same name but differ in value
|
35
|
+
depending on location (e.g., a hundredweight in the UK and the US),
|
36
|
+
they can have the same symbol though they are different, or in the
|
37
|
+
case of currency you might want to use different services with
|
38
|
+
possibly slightly different exchange rates. A unit belongs
|
39
|
+
unambiguously to a single converter. Units from different converters
|
40
|
+
can be used together, so it is possible to use US and UK
|
41
|
+
hundredweights in the same expression. What happens here is that a
|
42
|
+
unit is not only determined by a name but also by a converter. That's
|
43
|
+
all there is to it really.
|
44
|
+
|
45
|
+
There's always the notion of a current converter. When constructing a
|
46
|
+
unit, the current converter is used by default unless specified
|
47
|
+
otherwise. This current converter can be changed with
|
48
|
+
Van::Units.with_unit_converter which takes a block. The current
|
49
|
+
converter is stored in a thread-local variable for the duration of the
|
50
|
+
block and is taken from there. This gives the user means to specify
|
51
|
+
what unit systems to use in a more granular way. Converter can include
|
52
|
+
other converter. This allows a converter to extend another converter
|
53
|
+
and override some of the names. This is actually what
|
54
|
+
Van::Units.with_unit_converter does: internally it creates an
|
55
|
+
anonymous converter that includes both the previous "current
|
56
|
+
converter" and the new one which gets stacked on top and thus takes
|
57
|
+
precedence.
|
58
|
+
|
59
|
+
== Syntactic Sugar
|
60
|
+
|
61
|
+
Units are specified using symbols like :mile and :in, but units.rb
|
62
|
+
offers a lot of syntactic sugar to make it easier to use. After
|
63
|
+
including Van::Units, you can use mile and in directly, or even do
|
64
|
+
1.in and 2.miles. This is implemented through method_missing and
|
65
|
+
const_missing (for units that start with a capital letter).
|
66
|
+
|
67
|
+
== Loading Config Files
|
68
|
+
|
69
|
+
units.rb uses a DSL to specify converters and units. These DSLs are
|
70
|
+
conveniently used in the config files to preload a large number of
|
71
|
+
units.
|
72
|
+
|
data/LICENSE
ADDED
@@ -0,0 +1,23 @@
|
|
1
|
+
The MIT License
|
2
|
+
|
3
|
+
Copyright (c) 2009 Peter Vanbroekhoven & Thomas Sawyer
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
7
|
+
in the Software without restriction, including without limitation the rights
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
10
|
+
furnished to do so, subject to the following conditions:
|
11
|
+
|
12
|
+
The above copyright notice and this permission notice shall be included in
|
13
|
+
all copies or substantial portions of the Software.
|
14
|
+
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
21
|
+
THE SOFTWARE.
|
22
|
+
|
23
|
+
|
data/README.rdoc
ADDED
@@ -0,0 +1,68 @@
|
|
1
|
+
= VanUnits
|
2
|
+
|
3
|
+
* home: http://rubyworks.github.com/vanunits
|
4
|
+
|
5
|
+
== Description
|
6
|
+
|
7
|
+
VanUnits is a sophisticated SI units system. It also includes
|
8
|
+
currency units and a large set of scientific constants.
|
9
|
+
|
10
|
+
== Usage
|
11
|
+
|
12
|
+
Here are some examples of using the units system.
|
13
|
+
|
14
|
+
reqiure 'van/units'
|
15
|
+
|
16
|
+
include Van::Units
|
17
|
+
|
18
|
+
1.bit/s + 8.bytes/s
|
19
|
+
(1.bit/s).to(byte/s)
|
20
|
+
1.mile.to(feet)
|
21
|
+
1.acre.to(yd**2)
|
22
|
+
1.acre.to(sq_yd)
|
23
|
+
1.gallon.to(self.L)
|
24
|
+
1.lb.to(kg)
|
25
|
+
1.m.s.to(m.s)
|
26
|
+
1.sq_mi.to(km**2)
|
27
|
+
1.mile.to(km)
|
28
|
+
|
29
|
+
Van::Units is namespace for all unit related classes. Mixing this in has
|
30
|
+
the additional effect of making Units.with_unit_converter available
|
31
|
+
without the <code>Units.</code> prefix, as well as the shortcuts for
|
32
|
+
creating Units (see Van::Units#method_missing).
|
33
|
+
|
34
|
+
|
35
|
+
=== Constants
|
36
|
+
|
37
|
+
Also included are a large assortment of real world contants. These come in two
|
38
|
+
varieties, typeless and typed via units.rb. (PLEASE NOTE: The typed variety is not
|
39
|
+
yet complete).
|
40
|
+
|
41
|
+
Constants are also provided in both mks (m kg s) and in cgs (cm g s) format.
|
42
|
+
|
43
|
+
require 'van/units/constants/mks'
|
44
|
+
require 'van/units/constants/cgs'
|
45
|
+
|
46
|
+
include Van::Units::Constants
|
47
|
+
|
48
|
+
MKS::SPEED_OF_LIGHT #=> 2.99792458e8 m/s
|
49
|
+
CGS::SPEED_OF_LIGHT #=> 2.99792458e10 cm/s
|
50
|
+
|
51
|
+
Big thanks to Daniel Carrera and Brian Gough for their original work on Math::Constants
|
52
|
+
from which these numbers derive.
|
53
|
+
|
54
|
+
|
55
|
+
== Authors/Contributors
|
56
|
+
|
57
|
+
* Peter Vanbroekhoven
|
58
|
+
* Thomas Sawyer
|
59
|
+
* Daniel Carrera
|
60
|
+
* Brian Gough
|
61
|
+
|
62
|
+
|
63
|
+
== License
|
64
|
+
|
65
|
+
Copyright 2006, 2007 Peter Vanbroekhoven, Thomas Sawyer
|
66
|
+
|
67
|
+
Stick is distributed under the terms of the MIT license.
|
68
|
+
|
data/lib/van/units.rb
ADDED
@@ -0,0 +1,116 @@
|
|
1
|
+
require 'van/units/units'
|
2
|
+
|
3
|
+
module Van
|
4
|
+
module Units
|
5
|
+
|
6
|
+
# Load conversion units.
|
7
|
+
class Converter
|
8
|
+
require("units-standard")
|
9
|
+
end
|
10
|
+
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
|
15
|
+
# Checkrun
|
16
|
+
|
17
|
+
=begin check
|
18
|
+
|
19
|
+
class A
|
20
|
+
|
21
|
+
include Van::Units
|
22
|
+
|
23
|
+
def test
|
24
|
+
puts 1.bit/s + 8.bytes/s
|
25
|
+
|
26
|
+
puts((1.bit/s).to(byte/s))
|
27
|
+
|
28
|
+
puts 1.mile.to(feet)
|
29
|
+
|
30
|
+
puts 1.acre.to(yd**2)
|
31
|
+
|
32
|
+
puts 1.acre.to(sq_yd)
|
33
|
+
|
34
|
+
puts 1.gallon.to(L)
|
35
|
+
|
36
|
+
puts 1.lb.to(kg)
|
37
|
+
|
38
|
+
puts 1.m.s.to(m.s)
|
39
|
+
|
40
|
+
puts 1.sq_mi.to(km**2)
|
41
|
+
|
42
|
+
puts 1.mile.to(km)
|
43
|
+
|
44
|
+
#puts 1.usd.to(twd)
|
45
|
+
|
46
|
+
with_unit_converter(:uk) {
|
47
|
+
puts 1.cwt.to(lb)
|
48
|
+
}
|
49
|
+
|
50
|
+
with_unit_converter(:us) {
|
51
|
+
puts 1.cwt.to(lb)
|
52
|
+
}
|
53
|
+
|
54
|
+
puts 1.cwt(:uk).to(lb(:uk))
|
55
|
+
puts 1.cwt(:us).to(lb(:us))
|
56
|
+
|
57
|
+
puts Converter.current.lb
|
58
|
+
|
59
|
+
p Converter.registered_converters
|
60
|
+
|
61
|
+
#begin
|
62
|
+
# puts 1.try.to(usd)
|
63
|
+
#rescue TypeError
|
64
|
+
# p $!
|
65
|
+
#end
|
66
|
+
|
67
|
+
#puts 1.usd(:cex).to(twd(:cex))
|
68
|
+
|
69
|
+
puts 1.cwt(:uk).to(cwt(:us))
|
70
|
+
puts 1.cwt(:us).to(cwt(:uk))
|
71
|
+
|
72
|
+
with_unit_converter(:uk) {
|
73
|
+
puts 1.cwt(:uk).to(cwt(:us))
|
74
|
+
puts 1.cwt(:us).to(cwt(:uk))
|
75
|
+
}
|
76
|
+
|
77
|
+
p (1.m <=> 1.L)
|
78
|
+
p (1.m <=> 1.cm)
|
79
|
+
|
80
|
+
p((1.MB / s).to(kB / s))
|
81
|
+
|
82
|
+
with_unit_converter(:binary_iec_base) {
|
83
|
+
p((1.MB / s).to(kB / s))
|
84
|
+
}
|
85
|
+
|
86
|
+
p "m / s".to_unit
|
87
|
+
p "1 m / s".to_value
|
88
|
+
|
89
|
+
p "1 m / cm L".to_value.simplify
|
90
|
+
|
91
|
+
p "1 m / cm".to_value.to_f
|
92
|
+
|
93
|
+
p 1.m.to("cm")
|
94
|
+
|
95
|
+
p 1.m + "5cm"
|
96
|
+
|
97
|
+
p 1.m + 5.cm
|
98
|
+
|
99
|
+
p 5.cm + 1.m
|
100
|
+
|
101
|
+
p cm * m
|
102
|
+
|
103
|
+
p cm * "m"
|
104
|
+
|
105
|
+
p "-5mm".to_value
|
106
|
+
|
107
|
+
p "-5mm".to_value.abs
|
108
|
+
|
109
|
+
p ("5.0mm".to_value / 1).infinite?
|
110
|
+
end
|
111
|
+
end
|
112
|
+
|
113
|
+
A.new.test
|
114
|
+
|
115
|
+
=end
|
116
|
+
|
@@ -0,0 +1,991 @@
|
|
1
|
+
# Title:
|
2
|
+
#
|
3
|
+
# Units
|
4
|
+
#
|
5
|
+
# Summary:
|
6
|
+
#
|
7
|
+
# SI Units system, integrated into Ruby's method call system.
|
8
|
+
#
|
9
|
+
# Copyright:
|
10
|
+
#
|
11
|
+
# Copyright (c) 2005 Peter Vanbroekhoven, Thomas Sawyer
|
12
|
+
#
|
13
|
+
# License:
|
14
|
+
#
|
15
|
+
# Ruby License
|
16
|
+
#
|
17
|
+
# This module is free software. You may use, modify, and/or redistribute this
|
18
|
+
# software under the same terms as Ruby.
|
19
|
+
#
|
20
|
+
# This program is distributed in the hope that it will be useful, but WITHOUT
|
21
|
+
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
22
|
+
# FOR A PARTICULAR PURPOSE.
|
23
|
+
#
|
24
|
+
# Created:
|
25
|
+
#
|
26
|
+
# 2005.08.01
|
27
|
+
#
|
28
|
+
# Authors:
|
29
|
+
#
|
30
|
+
# - Peter Vanbroekhoven
|
31
|
+
# - Thomas Sawyer
|
32
|
+
|
33
|
+
require 'rbconfig'
|
34
|
+
require 'van/units/loaders'
|
35
|
+
|
36
|
+
class Exception
|
37
|
+
|
38
|
+
def clean_backtrace(regex)
|
39
|
+
regex = /^#{::Regexp.escape(__FILE__)}:\d+:in `#{::Regexp.escape(regex)}'$/ if regex.is_a? ::String
|
40
|
+
set_backtrace(backtrace.reject { |a| regex =~ a })
|
41
|
+
self
|
42
|
+
end
|
43
|
+
|
44
|
+
def self.with_clean_backtrace(regex)
|
45
|
+
begin
|
46
|
+
yield
|
47
|
+
rescue ::Exception
|
48
|
+
$!.clean_backtrace(regex).clean_backtrace("with_clean_backtrace") if not $DEBUG
|
49
|
+
raise
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
end
|
54
|
+
|
55
|
+
module Van
|
56
|
+
|
57
|
+
# The namespace for all unit related classes. Mixing this in has the additional effect
|
58
|
+
# of making Units.with_unit_converter available without the <code>Units.</code> prefix,
|
59
|
+
# as well as the shortcuts for creating Units (see Units#method_missing).
|
60
|
+
#
|
61
|
+
#--
|
62
|
+
# http://en.wikipedia.org/wiki/Celsius_scale
|
63
|
+
#
|
64
|
+
# degrees_Celsius vs Celsius_degrees
|
65
|
+
# Kelvin
|
66
|
+
#
|
67
|
+
# Kelvin Celsius Fahrenheit Rankine Delisle Newton Réaumur Rømer
|
68
|
+
#
|
69
|
+
# K C F R D N R R
|
70
|
+
#
|
71
|
+
# http://en.wikipedia.org/wiki/Conversion_of_units
|
72
|
+
#++
|
73
|
+
|
74
|
+
module Units
|
75
|
+
|
76
|
+
def method_missing(m, *args, &blk)
|
77
|
+
if args.length == 1
|
78
|
+
args[0] = (Units::Converter.converter(args[0]) rescue nil) if not args[0].is_a? Units::Converter
|
79
|
+
return Units::Unit.new({m => 1}, args[0]) if args[0] && args[0].registered?(m)
|
80
|
+
elsif (Units::Converter.current.registered?(m) rescue false)
|
81
|
+
raise ::ArgumentError, "Wrong number of arguments" if args.length != 0
|
82
|
+
return Units::Unit.new({m => 1}, Units::Converter.current)
|
83
|
+
end
|
84
|
+
::Exception.with_clean_backtrace("method_missing") {
|
85
|
+
super
|
86
|
+
}
|
87
|
+
end
|
88
|
+
|
89
|
+
def const_missing(c)
|
90
|
+
if (Units::Converter.current.registered?(c) rescue false)
|
91
|
+
return Units::Unit.new({c => 1}, Units::Converter.current)
|
92
|
+
else
|
93
|
+
::Exception.with_clean_backtrace("const_missing") {
|
94
|
+
super
|
95
|
+
}
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
def self.append_features(m)
|
100
|
+
m.send(:extend, Units)
|
101
|
+
super
|
102
|
+
end
|
103
|
+
|
104
|
+
# Executes the block with the current Converter changed to
|
105
|
+
# the given Converter. This allows to temporarily change the
|
106
|
+
# Converter used by default. A Converter object can be given,
|
107
|
+
# or the name of a registered Converter.
|
108
|
+
#
|
109
|
+
# Example:
|
110
|
+
#
|
111
|
+
# with_unit_converter(:uk) {
|
112
|
+
# puts 1.cwt.to(lb) # => 112.0 lb
|
113
|
+
# }
|
114
|
+
#
|
115
|
+
# with_unit_converter(:us) {
|
116
|
+
# puts 1.cwt.to(lb) # => 100.0 lb
|
117
|
+
# }
|
118
|
+
#
|
119
|
+
# See also Converter.current.
|
120
|
+
def with_unit_converter(name, &blk) # :yields:
|
121
|
+
Units::Converter.with_converter(name, &blk)
|
122
|
+
end
|
123
|
+
|
124
|
+
module_function :with_unit_converter
|
125
|
+
|
126
|
+
class Regexps
|
127
|
+
|
128
|
+
NUMBER_REGEXP = /(\-?\d+((?:\.\d+)?(?:[eE][-+]?\d+)?))/
|
129
|
+
SINGLE_UNIT_REGEXP = /([a-zA-Z_]+)(?::([a-zA-Z_]+))?(?:\s*\*\*\s*([+-]?\d+))?/
|
130
|
+
SINGLE_UNIT_NOC_REGEXP = /[a-zA-Z_]+(?::[a-zA-Z_]+)?(?:\s*\*\*\s*[+-]?\d+)?/ # Violates DRY principle
|
131
|
+
MULTIPLE_UNIT_REGEXP = /#{SINGLE_UNIT_NOC_REGEXP}(?:(?:\s+|\s*\*\s*)#{SINGLE_UNIT_NOC_REGEXP})*/
|
132
|
+
TOTAL_UNIT_REGEXP = /^\s*(?:1|(#{MULTIPLE_UNIT_REGEXP}))\s*(?:\/\s*(#{MULTIPLE_UNIT_REGEXP})\s*)?$/
|
133
|
+
TOTAL_UNIT_NOC_REGEXP = /\s*(?:1|(?:#{MULTIPLE_UNIT_REGEXP}))\s*(?:\/\s*(?:#{MULTIPLE_UNIT_REGEXP})\s*)?/ # Violates DRY principle
|
134
|
+
VALUE_REGEXP = /^\s*#{NUMBER_REGEXP}\s*\*?\s*(#{TOTAL_UNIT_NOC_REGEXP})$/
|
135
|
+
|
136
|
+
end
|
137
|
+
|
138
|
+
class BaseUnit
|
139
|
+
|
140
|
+
attr_reader :name, :converter
|
141
|
+
|
142
|
+
def initialize(name, converter = Units::Converter.current)
|
143
|
+
name = name.to_sym
|
144
|
+
raise ::ArgumentError, "unit #{name.to_s.dump} not registered with #{converter}" if not converter.registered? name
|
145
|
+
@name = name
|
146
|
+
@converter = converter
|
147
|
+
end
|
148
|
+
|
149
|
+
def conversion
|
150
|
+
@converter.send(:conversions, @name)
|
151
|
+
end
|
152
|
+
|
153
|
+
def ==(other)
|
154
|
+
other.is_a?(Units::BaseUnit) && other.name == @name && other.converter == @converter
|
155
|
+
end
|
156
|
+
|
157
|
+
alias eql? ==
|
158
|
+
|
159
|
+
def hash
|
160
|
+
@name.hash ^ @converter.hash
|
161
|
+
end
|
162
|
+
|
163
|
+
def to_s
|
164
|
+
if Units::Converter.current.includes?(converter)
|
165
|
+
@name.to_s
|
166
|
+
else
|
167
|
+
"#{@converter}:#{@name}"
|
168
|
+
end
|
169
|
+
end
|
170
|
+
|
171
|
+
alias inspect to_s
|
172
|
+
|
173
|
+
end
|
174
|
+
|
175
|
+
# This class represents a Unit. A Unit uses a given Converter with
|
176
|
+
# a number of registered units in which it can be expressed.
|
177
|
+
# A Unit is the product of the powers of other units. In principle, these
|
178
|
+
# need not be integer powers, but this may cause problems with rounding.
|
179
|
+
# The following code for example returns +false+:
|
180
|
+
# Unit.new(:m => 0.1) * Unit.new(:m => 0.2) == Unit.new(:m => 0.3)
|
181
|
+
#
|
182
|
+
# Units can be multiplied, divided, and raised to a given power.
|
183
|
+
# As an extra, 1 can be divided by a Unit.
|
184
|
+
#
|
185
|
+
# Examples:
|
186
|
+
#
|
187
|
+
# Unit.new(:mi => 1, :s => -1) ** 2 # => mi**2/s**2
|
188
|
+
# Unit.new(:mi => 1, :s => -1) * Unit.new(:s => 1, :usd => -1) # => mi/usd
|
189
|
+
# Unit.new(:mi => 1, :s => -1, Converter.converter(:uk)) *
|
190
|
+
# Unit.new(:s => 1, :usd => -1, Converter.converter(:us)) # => TypeError
|
191
|
+
# 1 / Unit.new(:mi => 1, :s => -1) # => s/mi
|
192
|
+
class Unit
|
193
|
+
|
194
|
+
attr_reader :units
|
195
|
+
|
196
|
+
# Creates a new (composite) Unit.
|
197
|
+
# It is passed a hash of the form <code>{:unit => exponent}</code>, and the
|
198
|
+
# Converter to use.
|
199
|
+
#
|
200
|
+
# Examples:
|
201
|
+
#
|
202
|
+
# Unit.new(:m => 1, :s => -1, Converter.converter(:uk)) # => m/s
|
203
|
+
# Unit.new(:mi => 1, :s => -2) # => mi/s**2
|
204
|
+
#
|
205
|
+
# See also Converter, Converter.converter
|
206
|
+
def initialize(units = {}, converter = nil)
|
207
|
+
conv = proc { converter ||= Units::Converter.current }
|
208
|
+
if units.is_a? ::String
|
209
|
+
@units = decode_string(units, conv)
|
210
|
+
else
|
211
|
+
@units = {}
|
212
|
+
units.each_pair do |k, v|
|
213
|
+
k = conv[].base_unit(k.to_sym) if not k.is_a? Units::BaseUnit
|
214
|
+
@units[k] = v
|
215
|
+
end
|
216
|
+
end
|
217
|
+
normalize_units
|
218
|
+
end
|
219
|
+
|
220
|
+
# Raises to the given power.
|
221
|
+
def **(p)
|
222
|
+
result = {}
|
223
|
+
@units.each_pair do |u, e|
|
224
|
+
result[u] = e * p
|
225
|
+
end
|
226
|
+
Units::Unit.new(result)
|
227
|
+
end
|
228
|
+
|
229
|
+
# Multiplies with the given Unit.
|
230
|
+
def *(other)
|
231
|
+
do_op(:*, :+, other)
|
232
|
+
end
|
233
|
+
|
234
|
+
# Divides by the given Unit.
|
235
|
+
def /(other)
|
236
|
+
do_op(:/, :-, other)
|
237
|
+
end
|
238
|
+
|
239
|
+
# Returns +true+ iff this Unit has all exponents equal to 0.
|
240
|
+
def unitless?
|
241
|
+
@units.empty?
|
242
|
+
end
|
243
|
+
|
244
|
+
def coerce(other) # :nodoc:
|
245
|
+
return [Units::Unit.new({}), self] if other == 1
|
246
|
+
Units::Converter.coerce_units(self, other)
|
247
|
+
end
|
248
|
+
|
249
|
+
def simplify
|
250
|
+
Units::Converter.simplify_unit(self)
|
251
|
+
end
|
252
|
+
|
253
|
+
# Returns +true+ iff the two units are equals, <i>i.e.,</i> iff they have
|
254
|
+
# the same exponent for all units, and they both use the same Converter.
|
255
|
+
def ==(other)
|
256
|
+
other.is_a?(Units::Unit) && other.units == units
|
257
|
+
end
|
258
|
+
|
259
|
+
alias eql? ==
|
260
|
+
|
261
|
+
def hash
|
262
|
+
@units.to_a.map { |e| e.hash }.hash
|
263
|
+
end
|
264
|
+
|
265
|
+
# Returns +true+ iff this Unit is compatible with the given
|
266
|
+
# Unit. This is less strict than equality because for example hours are
|
267
|
+
# compatible with seconds ("we can add them"), but hours are not seconds.
|
268
|
+
def compatible_with?(other)
|
269
|
+
conv1, conv2 = Units::Converter.coerce_units(self, other)
|
270
|
+
conv1.units == conv2.units
|
271
|
+
end
|
272
|
+
|
273
|
+
# Returns a human readable string representation.
|
274
|
+
def to_s
|
275
|
+
numerator = ""
|
276
|
+
denominator = ""
|
277
|
+
@units.each_pair do |u, e|
|
278
|
+
e_abs = e > 0 ? e : -e # This works with Complex too
|
279
|
+
(e > 0 ? numerator : denominator) << " #{u.to_s}#{"**#{e_abs}" if e_abs != 1}"
|
280
|
+
end
|
281
|
+
"#{numerator.lstrip}#{"/" + denominator.lstrip if not denominator.empty?}"
|
282
|
+
end
|
283
|
+
|
284
|
+
# Returns +self+.
|
285
|
+
def to_unit(converter = nil)
|
286
|
+
self
|
287
|
+
end
|
288
|
+
|
289
|
+
alias inspect to_s
|
290
|
+
|
291
|
+
def method_missing(m, *args, &blk)
|
292
|
+
if args.length == 1
|
293
|
+
args[0] = (Units::Converter.converter(args[0]) rescue nil) if not args[0].is_a? Units::Converter
|
294
|
+
return self * Units::Unit.new({m => 1}, args[0]) if args[0] && args[0].registered?(m)
|
295
|
+
elsif (Units::Converter.current.registered?(m) rescue false)
|
296
|
+
raise ::ArgumentError, "Wrong number of arguments" if args.length != 0
|
297
|
+
return self * Units::Unit.new({m => 1}, Units::Converter.current)
|
298
|
+
end
|
299
|
+
::Exception.with_clean_backtrace("method_missing") {
|
300
|
+
super
|
301
|
+
}
|
302
|
+
end
|
303
|
+
|
304
|
+
private
|
305
|
+
|
306
|
+
def decode_string(s, converter)
|
307
|
+
if Units::Regexps::TOTAL_UNIT_REGEXP =~ s
|
308
|
+
numerator, denominator = $1, $2
|
309
|
+
units = {}
|
310
|
+
decode_multiplicative_string(numerator, 1, converter, units) if numerator
|
311
|
+
decode_multiplicative_string(denominator, -1, converter, units) if denominator
|
312
|
+
units
|
313
|
+
elsif /^\s*$/ =~ s
|
314
|
+
{}
|
315
|
+
else
|
316
|
+
raise ::ArgumentError, "Illegal unit string #{s.dump}"
|
317
|
+
end
|
318
|
+
end
|
319
|
+
|
320
|
+
def decode_multiplicative_string(s, multiplier, converter, result)
|
321
|
+
s.scan(Units::Regexps::SINGLE_UNIT_REGEXP) do |conv, unit, exp|
|
322
|
+
if unit
|
323
|
+
conv = Units::Converter.converter(conv)
|
324
|
+
else
|
325
|
+
conv, unit = converter[], conv
|
326
|
+
end
|
327
|
+
exp ||= '1'
|
328
|
+
unit = conv.base_unit(unit)
|
329
|
+
result[unit] = (result[unit] || 0) + Integer(exp) * multiplier
|
330
|
+
end
|
331
|
+
end
|
332
|
+
|
333
|
+
def normalize_units
|
334
|
+
@units.keys.each do |unit|
|
335
|
+
@units.delete(unit) if @units[unit] == 0
|
336
|
+
end
|
337
|
+
end
|
338
|
+
|
339
|
+
def do_op(op, dual_op, other)
|
340
|
+
other = other.to_unit
|
341
|
+
raise TypeError, "cannot convert to Unit" unless Units::Unit === other
|
342
|
+
result = @units.dup
|
343
|
+
other.units.each_pair do |u, e|
|
344
|
+
result[u] = 0 if not result[u]
|
345
|
+
result[u] = result[u].send(dual_op, e)
|
346
|
+
end
|
347
|
+
Units::Unit.new(result)
|
348
|
+
end
|
349
|
+
|
350
|
+
end
|
351
|
+
|
352
|
+
# This class represents a Value with a numeric value and a Unit.
|
353
|
+
# The numeric value can be any Numeric, though it is not recommended
|
354
|
+
# to use Values.
|
355
|
+
#
|
356
|
+
# A Value can be added to, subtracted from and multiplied with another value,
|
357
|
+
# though only when both Values are using the same Converter. While multiplication
|
358
|
+
# is always possible, adding or subtracting values with incompatible units
|
359
|
+
# results in a TypeError. When two units are compatible but not the same,
|
360
|
+
# the Value with the larger of the units is converted to the smaller of the units.
|
361
|
+
# For example adding 100 seconds and 1 minute, the latter is converted to 60 seconds
|
362
|
+
# because a second is smaller than a minute. The result is 160 seconds.
|
363
|
+
class Value < Numeric
|
364
|
+
|
365
|
+
# The numeric value of this Value.
|
366
|
+
attr_reader :value
|
367
|
+
# The Unit of this value.
|
368
|
+
attr_reader :unit
|
369
|
+
|
370
|
+
include Comparable
|
371
|
+
|
372
|
+
class << self
|
373
|
+
|
374
|
+
alias old_new new
|
375
|
+
|
376
|
+
private :old_new
|
377
|
+
|
378
|
+
# Creates a new Value with the given numeric value and the given unit.
|
379
|
+
# Simply returns the given value if the given unit is unitless,
|
380
|
+
# <i>i.e.,</i> when <code>unit.unitless?</code> is true.
|
381
|
+
def new(value, *args)
|
382
|
+
res = new!(value, *args)
|
383
|
+
return res.value if res.unit.unitless?
|
384
|
+
res
|
385
|
+
end
|
386
|
+
|
387
|
+
def new!(value, *args)
|
388
|
+
if ::String === value
|
389
|
+
str = *args
|
390
|
+
converter = case args.length
|
391
|
+
when 0
|
392
|
+
when 1
|
393
|
+
conv = args[0]
|
394
|
+
else
|
395
|
+
raise ArgumentError, "wrong number of arguments"
|
396
|
+
end
|
397
|
+
value, unit = decode_string(value, converter)
|
398
|
+
else
|
399
|
+
if args.length == 1
|
400
|
+
unit = args[0]
|
401
|
+
else
|
402
|
+
raise ArgumentError, "wrong number of arguments"
|
403
|
+
end
|
404
|
+
end
|
405
|
+
unit = Unit.new(unit) unless unit.is_a?(Unit)
|
406
|
+
old_new(value, unit)
|
407
|
+
end
|
408
|
+
|
409
|
+
end
|
410
|
+
|
411
|
+
def initialize(value, unit) # :nodoc:
|
412
|
+
@value, @unit = value, unit
|
413
|
+
end
|
414
|
+
|
415
|
+
%w{ * / }.each do |op|
|
416
|
+
eval %{
|
417
|
+
def #{op}(other)
|
418
|
+
Units::Value.new(*do_multiplicative_op(:#{op}, other))
|
419
|
+
end
|
420
|
+
}
|
421
|
+
end
|
422
|
+
|
423
|
+
%w{ + - modulo remainder % }.each do |op|
|
424
|
+
eval %{
|
425
|
+
def #{op}(other)
|
426
|
+
Units::Value.new(*do_additive_op(:#{op}, other))
|
427
|
+
end
|
428
|
+
}
|
429
|
+
end
|
430
|
+
|
431
|
+
def divmod(other)
|
432
|
+
(q, r), unit = *do_additive_op(:divmod, other)
|
433
|
+
[q, Units::Value.new(r, unit)]
|
434
|
+
end
|
435
|
+
|
436
|
+
def div(other)
|
437
|
+
do_additive_op(:div, other)[0]
|
438
|
+
end
|
439
|
+
|
440
|
+
def -@ # :nodoc:
|
441
|
+
Value.new(-@value, @unit)
|
442
|
+
end
|
443
|
+
|
444
|
+
def +@ # :nodoc:
|
445
|
+
self
|
446
|
+
end
|
447
|
+
|
448
|
+
def **(other) # :nodoc:
|
449
|
+
Units::Value.new(@value ** other, @unit ** other)
|
450
|
+
end
|
451
|
+
|
452
|
+
def <=>(other) # :nodoc:
|
453
|
+
if other == 0
|
454
|
+
@value <=> 0
|
455
|
+
else
|
456
|
+
(self - other).value <=> 0
|
457
|
+
end
|
458
|
+
rescue TypeError
|
459
|
+
nil
|
460
|
+
end
|
461
|
+
|
462
|
+
alias eql? ==
|
463
|
+
|
464
|
+
def hash
|
465
|
+
@value.hash ^ @unit.hash
|
466
|
+
end
|
467
|
+
|
468
|
+
%w{ abs ceil floor next round succ truncate }.each do |op|
|
469
|
+
eval %{
|
470
|
+
def #{op}
|
471
|
+
Units::Value.new(@value.#{op}, @unit)
|
472
|
+
end
|
473
|
+
}
|
474
|
+
end
|
475
|
+
|
476
|
+
%w{ finite? infinite? integer? nan? nonzero? zero? }.each do |op|
|
477
|
+
eval %{
|
478
|
+
def #{op}
|
479
|
+
@value.#{op}
|
480
|
+
end
|
481
|
+
}
|
482
|
+
end
|
483
|
+
|
484
|
+
# Converts this Value to the given Unit.
|
485
|
+
# This only works if the Converters used by this Value's Unit
|
486
|
+
# and the given Unit are the same. It obviously fails if the
|
487
|
+
# Units are not compatible (can't add apples and oranges).
|
488
|
+
def to(to_unit, converter = nil)
|
489
|
+
raise ArgumentError, "Wrong number of arguments" if converter && !(::String === to_unit)
|
490
|
+
to_unit = to_unit.to_unit(converter)
|
491
|
+
raise TypeError, "cannot convert to Unit" unless Units::Unit === to_unit
|
492
|
+
conv1, conv2 = unit.coerce(to_unit)
|
493
|
+
raise TypeError, "incompatible units for operation" if conv1.units != conv2.units
|
494
|
+
mult = conv1.multiplier / conv2.multiplier
|
495
|
+
Units::Value.new(value * mult, to_unit)
|
496
|
+
end
|
497
|
+
|
498
|
+
def coerce(other) # :nodoc:
|
499
|
+
if ::Numeric === other
|
500
|
+
[Units::Value.new!(other, Units::Unit.new), self]
|
501
|
+
else
|
502
|
+
super
|
503
|
+
end
|
504
|
+
end
|
505
|
+
|
506
|
+
# Returns a human readable string representation.
|
507
|
+
def to_s
|
508
|
+
"#{@value} #{@unit}"
|
509
|
+
end
|
510
|
+
|
511
|
+
# Returns a float if this Value is unitless, and raises an
|
512
|
+
# exception otherwise.
|
513
|
+
def to_f
|
514
|
+
val = simplify
|
515
|
+
if Units::Value === val
|
516
|
+
raise TypeError, "Cannot convert to float"
|
517
|
+
else
|
518
|
+
val.to_f
|
519
|
+
end
|
520
|
+
end
|
521
|
+
|
522
|
+
# Returns an int if this Value is unitless, and raises an
|
523
|
+
# exception otherwise.
|
524
|
+
def to_i
|
525
|
+
val = simplify
|
526
|
+
if Units::Value === val
|
527
|
+
raise TypeError, "Cannot convert to integer"
|
528
|
+
else
|
529
|
+
val.to_i
|
530
|
+
end
|
531
|
+
end
|
532
|
+
|
533
|
+
# Returns an int if this Value is unitless, and raises an
|
534
|
+
# exception otherwise.
|
535
|
+
def to_int
|
536
|
+
val = simplify
|
537
|
+
if Units::Value === val
|
538
|
+
raise TypeError, "Cannot convert to integer"
|
539
|
+
else
|
540
|
+
val.to_int
|
541
|
+
end
|
542
|
+
end
|
543
|
+
|
544
|
+
# Forces simplification of the Unit part of this Value. Returns
|
545
|
+
# a new Value or a Float.
|
546
|
+
def simplify
|
547
|
+
mul, new_unit = *@unit.simplify
|
548
|
+
if new_unit.unitless?
|
549
|
+
@value * mul
|
550
|
+
else
|
551
|
+
Units::Value.new(@value * mul, new_unit)
|
552
|
+
end
|
553
|
+
end
|
554
|
+
|
555
|
+
# Returns +self+.
|
556
|
+
def to_value(converter = nil)
|
557
|
+
self
|
558
|
+
end
|
559
|
+
|
560
|
+
alias inspect to_s
|
561
|
+
|
562
|
+
def method_missing(m, *args, &blk)
|
563
|
+
if args.length == 1
|
564
|
+
args[0] = (Units::Converter.converter(args[0]) rescue nil) if not args[0].is_a? Units::Converter
|
565
|
+
return self * Units::Value.new(1, Units::Unit.new({m => 1}, args[0])) if args[0] && args[0].registered?(m)
|
566
|
+
elsif (Units::Converter.current.registered?(m) rescue false)
|
567
|
+
raise ::ArgumentError, "Wrong number of arguments" if args.length != 0
|
568
|
+
return self * Units::Value.new(1, Units::Unit.new({m => 1}, Units::Converter.current))
|
569
|
+
end
|
570
|
+
::Exception.with_clean_backtrace("method_missing") {
|
571
|
+
super
|
572
|
+
}
|
573
|
+
end
|
574
|
+
|
575
|
+
private
|
576
|
+
|
577
|
+
def self.decode_string(s, converter)
|
578
|
+
if m = Units::Regexps::VALUE_REGEXP.match(s)
|
579
|
+
value = m[2].empty? ? Integer(m[1]) : Float(m[1])
|
580
|
+
unit = Units::Unit.new(m[3], converter)
|
581
|
+
[value, unit]
|
582
|
+
else
|
583
|
+
raise ::ArgumentError, "Illegal value string #{s.dump}"
|
584
|
+
end
|
585
|
+
end
|
586
|
+
|
587
|
+
def do_additive_op(op, other)
|
588
|
+
other = other.to_value
|
589
|
+
raise TypeError, "cannot convert to Value" unless Units::Value === other
|
590
|
+
if other.is_a? Units::Value
|
591
|
+
conv1, conv2 = unit.coerce(other.unit)
|
592
|
+
raise TypeError, "incompatible units for #{op}" if conv1.units != conv2.units
|
593
|
+
mult = conv2.multiplier / conv1.multiplier
|
594
|
+
if mult > 1
|
595
|
+
[value.send(op, other.value * mult), unit]
|
596
|
+
else
|
597
|
+
mult = conv1.multiplier / conv2.multiplier
|
598
|
+
[(value * mult).send(op, other.value), other.unit]
|
599
|
+
end
|
600
|
+
else
|
601
|
+
raise TypeError, "incompatible operands for #{op}"
|
602
|
+
end
|
603
|
+
end
|
604
|
+
|
605
|
+
def do_multiplicative_op(op, other)
|
606
|
+
case other
|
607
|
+
when Units::Value
|
608
|
+
[value.send(op, other.value), unit.send(op, other.unit)]
|
609
|
+
when Units::Unit
|
610
|
+
[value, unit.send(op, other)]
|
611
|
+
when ::Numeric
|
612
|
+
[value.send(op, other), unit]
|
613
|
+
else
|
614
|
+
# TODO: How to make this work for Units as well?
|
615
|
+
# Problem : how check whether to_value failed without
|
616
|
+
# masking all exceptions?
|
617
|
+
other = other.to_value
|
618
|
+
raise TypeError, "cannot convert to Value" unless Units::Value === other
|
619
|
+
do_multiplicative_op(op, other)
|
620
|
+
end
|
621
|
+
end
|
622
|
+
|
623
|
+
end
|
624
|
+
|
625
|
+
# This class handles all conversions between units.
|
626
|
+
#
|
627
|
+
# There are two kinds of units; those that are not expressed as a function
|
628
|
+
# of other units --called base units-- and those that are expressed
|
629
|
+
# as a function of other units --called derived units. The latter kind is
|
630
|
+
# registered specifying how it depends on other units, while the former
|
631
|
+
# kind is not.
|
632
|
+
#
|
633
|
+
# This class also registers a list of Converters that are generally useable.
|
634
|
+
# The default Converter which is used when none is specified, can be retrieved
|
635
|
+
# with Converter.current. Converters can be registered with Converter.register.
|
636
|
+
#
|
637
|
+
# Converters can be loaded from YAML. This allows Converters to be specified in
|
638
|
+
# configuration files.
|
639
|
+
class Converter
|
640
|
+
|
641
|
+
Conversion = Struct.new(:units, :multiplier) # :nodoc:
|
642
|
+
|
643
|
+
class Conversion # :nodoc:
|
644
|
+
|
645
|
+
def **(p)
|
646
|
+
Conversion.new(units ** p, multiplier ** p)
|
647
|
+
end
|
648
|
+
|
649
|
+
def *(m)
|
650
|
+
Conversion.new(units * m.units, multiplier * m.multiplier)
|
651
|
+
end
|
652
|
+
|
653
|
+
def /(m)
|
654
|
+
Conversion.new(units / m.units, multiplier / m.multiplier)
|
655
|
+
end
|
656
|
+
|
657
|
+
end
|
658
|
+
|
659
|
+
ShiftedConversion = Struct.new(:delta_type, :offset)
|
660
|
+
|
661
|
+
# Returns the name of this Converter, or <tt>nil</tt> if the
|
662
|
+
# Converter is not registered.
|
663
|
+
attr_accessor :name
|
664
|
+
private :name=
|
665
|
+
|
666
|
+
# Creates a new Converter. If a block is given,
|
667
|
+
# it is executed in the newly created Converter's context.
|
668
|
+
def initialize(name)
|
669
|
+
@conversions = {}
|
670
|
+
@included = []
|
671
|
+
@name = name
|
672
|
+
end
|
673
|
+
|
674
|
+
# Included the given converter in the receiver, unless it
|
675
|
+
# was already included.
|
676
|
+
def include(conv)
|
677
|
+
conv = Units::Converter.converter(conv) if not conv.is_a?(Units::Converter)
|
678
|
+
raise "Circular include" if conv.includes?(self)
|
679
|
+
@included << conv if not includes? conv
|
680
|
+
self
|
681
|
+
end
|
682
|
+
|
683
|
+
# Returns whether the given converter was included in the
|
684
|
+
# receiver.
|
685
|
+
def includes?(conv)
|
686
|
+
conv = Units::Converter.converter(conv) if not conv.is_a?(Units::Converter)
|
687
|
+
return true if conv == self
|
688
|
+
@included.each do |c|
|
689
|
+
return true if conv == c || c.includes?(conv)
|
690
|
+
end
|
691
|
+
false
|
692
|
+
end
|
693
|
+
|
694
|
+
# Returns the list of all included converters. This list may
|
695
|
+
# contain duplicates in some cases.
|
696
|
+
def included_converters(result = [])
|
697
|
+
result << self
|
698
|
+
@included.reverse_each { |c| c.included_converters(result) }
|
699
|
+
result
|
700
|
+
end
|
701
|
+
|
702
|
+
# Checks whether the unit with the given name is registered.
|
703
|
+
# The name can be a symbol or a string.
|
704
|
+
def registered?(unit)
|
705
|
+
unit = unit.to_sym
|
706
|
+
return self if registered_here?(unit)
|
707
|
+
@included.reverse_each do |c|
|
708
|
+
if res = c.registered?(unit)
|
709
|
+
return res
|
710
|
+
end
|
711
|
+
end
|
712
|
+
nil
|
713
|
+
end
|
714
|
+
|
715
|
+
# Returns the base unit with this name
|
716
|
+
def base_unit(name)
|
717
|
+
if conv = registered?(name)
|
718
|
+
return Units::BaseUnit.new(name, conv)
|
719
|
+
end
|
720
|
+
raise "unit #{name.to_s.dump} not registered with #{self}"
|
721
|
+
end
|
722
|
+
|
723
|
+
# Returns the list of registered unit names as symbols.
|
724
|
+
def registered_units
|
725
|
+
@conversions.keys
|
726
|
+
end
|
727
|
+
|
728
|
+
#
|
729
|
+
def method_missing(m, *args, &blk)
|
730
|
+
if registered?(m)
|
731
|
+
raise ::ArgumentError, "Wrong number of arguments" if args.length != 0
|
732
|
+
return Units::Unit.new({m => 1}, self)
|
733
|
+
end
|
734
|
+
::Exception.with_clean_backtrace("method_missing") {
|
735
|
+
super
|
736
|
+
}
|
737
|
+
end
|
738
|
+
|
739
|
+
# Returns a human readable string representation of this Converter.
|
740
|
+
def to_s
|
741
|
+
(@name.to_s if @name) || "#<Converter:#{object_id.to_s(16)}>"
|
742
|
+
end
|
743
|
+
|
744
|
+
alias inspect to_s
|
745
|
+
|
746
|
+
private
|
747
|
+
|
748
|
+
# Registers a new Unit with the given name. The +data+ parameter
|
749
|
+
# is a Hash with some extra parameters (can be Strings or Symbols):
|
750
|
+
# +alias+:: Specifies possible aliases for the Unit.
|
751
|
+
# +abbrev+:: Specifies possible abbreviations or symbols for the Unit.
|
752
|
+
# The differences with aliases is that prefixes work differently;
|
753
|
+
# see +register_si_unit+, +register_binary_unit+ and
|
754
|
+
# +register_binary_iec_unit+.
|
755
|
+
# +equals+:: If present, specifies how the Unit depends on other units.
|
756
|
+
# The value for this key can either be a Hash with +unit+ mapping to
|
757
|
+
# a Unit and +multiplier+ mapping to a numeric multiplier, or
|
758
|
+
# a String containing the multiplier, followed by a space, followed by
|
759
|
+
# a representation of the Unit as returned by Unit#to_s.
|
760
|
+
#
|
761
|
+
# Examples:
|
762
|
+
#
|
763
|
+
# converter.register_unit(:pint, :alias => :pints, :abbrev => [:pt, :pts]))
|
764
|
+
# converter.register_unit(:quart, 'alias' => :quarts, :abbrev => ['qt', :qts], :equals => '2.0 pt'))
|
765
|
+
# converter.register_unit(:gallon, :alias => :gallons, :abbrev => :gal, 'equals' => {:unit => Unit.new('qt' => 1, converter), 'multiplier' => 4.0))
|
766
|
+
#
|
767
|
+
# Note that Symbols and Strings are generally exchangeable within this
|
768
|
+
# library (internally they are converted to Symbols). The number one reason
|
769
|
+
# for this is that String look better in YAML.
|
770
|
+
#
|
771
|
+
# See also +register_si_unit+, +register_binary_unit+, +register_binary_iec_unit+,
|
772
|
+
# +register_length_unit+, and +register_currency+ in currency.rb.
|
773
|
+
def register_unit(unit, abbrevs=[], aliases=[], &conversion)
|
774
|
+
unit = unit.to_sym
|
775
|
+
abbrevs = [abbrevs].flatten.map{ |a| a.to_sym }
|
776
|
+
aliases = [aliases].flatten.map{ |a| a.to_sym }
|
777
|
+
#aliases = ["#{unit}s".to_sym] if aliases.empty? # TRANS: hmm... not here?
|
778
|
+
#unit, aliases, abbrevs = extract_data(name, data, :to_sym)
|
779
|
+
#conversion = data[:equals]
|
780
|
+
# TRANS: this can be imporved now that conversion is a block?
|
781
|
+
conversion = conversion.call if conversion
|
782
|
+
conversion = decode_conversion(conversion) if conversion
|
783
|
+
conversion = self.class.convert_conversion(conversion[:unit].units, conversion[:multiplier]) if conversion
|
784
|
+
register_unit_internal(unit, conversion)
|
785
|
+
conversion = self.class.convert_conversion({base_unit(unit) => 1}, 1) if not conversion
|
786
|
+
(aliases + abbrevs).each do |u|
|
787
|
+
register_unit_internal(u, conversion)
|
788
|
+
end
|
789
|
+
end
|
790
|
+
|
791
|
+
def register_unit_internal(unit, conversion)
|
792
|
+
raise "unit #{unit.to_s.dump} already registered with #{self}" if registered_here? unit
|
793
|
+
@conversions[unit] = conversion || :none
|
794
|
+
end
|
795
|
+
|
796
|
+
def register_prefixed_unit(unit, prefixes, abbrevs=[], aliases=[], &conversion)
|
797
|
+
unit = unit.to_s
|
798
|
+
abbrevs = [abbrevs].flatten.map{ |a| a.to_s }
|
799
|
+
aliases = [aliases].flatten.map{ |a| a.to_s }
|
800
|
+
aliases = ["#{unit}s"] if aliases.empty?
|
801
|
+
#aliases, abbrevs = extract_data(unit, data, :to_s)
|
802
|
+
register_unit(unit, abbrevs, aliases, &conversion)
|
803
|
+
unit_sym = unit.to_sym
|
804
|
+
prefixes.each_pair do |pre,info|
|
805
|
+
abbrev = info[:abbrev]
|
806
|
+
multiplier = info[:multiplier] || 1
|
807
|
+
power = info[:power] || 1
|
808
|
+
register_unit(pre + unit) do
|
809
|
+
{:unit => Units::Unit.new({unit_sym => power}, self), :multiplier => multiplier}
|
810
|
+
end
|
811
|
+
aliases.each do |a|
|
812
|
+
register_unit(pre + a) do
|
813
|
+
{:unit => Units::Unit.new({unit_sym => power}, self), :multiplier => multiplier}
|
814
|
+
end
|
815
|
+
end
|
816
|
+
abbrevs.each do |a|
|
817
|
+
register_unit(abbrev + a) do
|
818
|
+
{:unit => Units::Unit.new({unit_sym => power}, self), :multiplier => multiplier}
|
819
|
+
end
|
820
|
+
end
|
821
|
+
end
|
822
|
+
end
|
823
|
+
|
824
|
+
def decode_conversion(data)
|
825
|
+
if not data.is_a? ::String
|
826
|
+
return {:unit => data[:unit] || data['unit'],
|
827
|
+
:multiplier => data[:multiplier] || data['multiplier']}
|
828
|
+
end
|
829
|
+
if /^\s*1\s*\// =~ data
|
830
|
+
{:unit => Units::Unit.new(data, self)}
|
831
|
+
elsif m = /^\s*#{Units::Regexps::NUMBER_REGEXP}(?:\s+(\S.*)$|\s*$)/.match(data)
|
832
|
+
unit = m[3] ? Units::Unit.new(m[3], self) : Units::Unit.new({}, self)
|
833
|
+
if m[1]
|
834
|
+
multiplier = m[2].empty? ? Integer(m[1]) : Float(m[1])
|
835
|
+
{:unit => unit, :multiplier => multiplier}
|
836
|
+
else
|
837
|
+
{:unit => unit}
|
838
|
+
end
|
839
|
+
else
|
840
|
+
{:unit => Units::Unit.new(data, self)}
|
841
|
+
end
|
842
|
+
end
|
843
|
+
|
844
|
+
# Checks whether the unit with the given name is registered.
|
845
|
+
# The name can be a symbol or a string.
|
846
|
+
def registered_here?(unit)
|
847
|
+
unit = unit.to_sym
|
848
|
+
conversions(unit) != nil
|
849
|
+
end
|
850
|
+
|
851
|
+
def conversions(unit)
|
852
|
+
@conversions[unit] #|| (unit == :'--base-currency--' ? :none : nil)
|
853
|
+
end
|
854
|
+
|
855
|
+
class << self
|
856
|
+
|
857
|
+
THREAD_REFERENCE = 'Units::converter'.to_sym
|
858
|
+
|
859
|
+
private :new
|
860
|
+
|
861
|
+
# Returns the current Converter in the current Thread.
|
862
|
+
# The default converter is the one returned by <code>converter(:default)</code>.
|
863
|
+
# See also Units#with_converter and Converter.converter.
|
864
|
+
def current
|
865
|
+
Thread.current[THREAD_REFERENCE] ||= converter(:default)
|
866
|
+
end
|
867
|
+
|
868
|
+
def with_converter(conv) # :nodoc:
|
869
|
+
conv = converter(conv) if not conv.is_a? Units::Converter
|
870
|
+
raise ::ArgumentError, "Converter expected" if not conv.is_a? Units::Converter
|
871
|
+
begin
|
872
|
+
old_conv = Thread.current[THREAD_REFERENCE]
|
873
|
+
if old_conv
|
874
|
+
new_conv = Converter.send(:new, nil)
|
875
|
+
new_conv.include(old_conv)
|
876
|
+
new_conv.include(conv)
|
877
|
+
else
|
878
|
+
new_conv = conv
|
879
|
+
end
|
880
|
+
Thread.current[THREAD_REFERENCE] = new_conv
|
881
|
+
yield
|
882
|
+
ensure
|
883
|
+
Thread.current[THREAD_REFERENCE] = old_conv
|
884
|
+
end
|
885
|
+
end
|
886
|
+
|
887
|
+
# Returns the converter with the given name.
|
888
|
+
# This name can be a Symbol or a String.
|
889
|
+
def converter(name, &blk)
|
890
|
+
if blk
|
891
|
+
(converters[name.to_sym] ||= new(name.to_sym)).instance_eval(&blk)
|
892
|
+
else
|
893
|
+
converters[name.to_sym] or raise ::ArgumentError, "No converter #{name.to_s.dump} found"
|
894
|
+
end
|
895
|
+
end
|
896
|
+
|
897
|
+
# # Registers the given Converter under the given name.
|
898
|
+
# # This name can be a Symbol or a String. A Converter
|
899
|
+
# # can be registered under one name only, and only one
|
900
|
+
# # Converter can be registered under a given name.
|
901
|
+
# def register(name, converter)
|
902
|
+
# name = name.to_sym
|
903
|
+
# return if converters[name] == converter
|
904
|
+
# raise ArgumentError, "This converter is already registered under anoher name" if converter.name
|
905
|
+
# raise ArgumentError, "A converter with this name already exists" if converters[name]
|
906
|
+
# converters[name] = converter
|
907
|
+
# converter.name ||= name
|
908
|
+
# end
|
909
|
+
|
910
|
+
# Returns the list of names of registered converters.
|
911
|
+
def registered_converters
|
912
|
+
converters.keys
|
913
|
+
end
|
914
|
+
|
915
|
+
def coerce_units(unit1, unit2) # :nodoc:
|
916
|
+
[convert_conversion(unit1.units), convert_conversion(unit2.units)]
|
917
|
+
end
|
918
|
+
|
919
|
+
def simplify_unit(unit) # :nodoc:
|
920
|
+
conv = convert_conversion(unit.units)
|
921
|
+
[conv[:multiplier], conv[:units]]
|
922
|
+
end
|
923
|
+
|
924
|
+
def convert_conversion(units, multiplier = nil)
|
925
|
+
multiplier ||= 1
|
926
|
+
base_units = {}
|
927
|
+
other_units = {}
|
928
|
+
units.each_pair do |u, e|
|
929
|
+
(u.conversion != :none ? other_units : base_units)[u] = e
|
930
|
+
end
|
931
|
+
result = Conversion.new(Units::Unit.new(base_units, self), multiplier)
|
932
|
+
other_units.each_pair do |u, e|
|
933
|
+
result *= (u.conversion ** e)
|
934
|
+
end
|
935
|
+
result
|
936
|
+
end
|
937
|
+
|
938
|
+
def converters
|
939
|
+
@converters ||= {}
|
940
|
+
end
|
941
|
+
|
942
|
+
end
|
943
|
+
|
944
|
+
end
|
945
|
+
|
946
|
+
# Contains some configuration related constants.
|
947
|
+
# TODO: Use plugin manager to find units.
|
948
|
+
module Config
|
949
|
+
# The directory in which the data files are searched for
|
950
|
+
#DATADIR = 'data/van/units' #DATADIR = File.join(::Config::CONFIG['DATADIR'], 'van', 'units')
|
951
|
+
#CONFIGDIR = 'lib/van/data' #DATADIR = File.join(::Config::CONFIG['DATADIR'], 'van', 'units')
|
952
|
+
SYSTEMDIR = File.dirname(__FILE__)
|
953
|
+
CONFIGDIR = File.join(SYSTEMDIR, 'data')
|
954
|
+
end
|
955
|
+
|
956
|
+
end
|
957
|
+
end
|
958
|
+
|
959
|
+
class Numeric
|
960
|
+
#
|
961
|
+
def method_missing(m, *args, &blk)
|
962
|
+
if args.length == 1
|
963
|
+
args[0] = (Van::Units::Converter.converter(args[0]) rescue nil) if not args[0].is_a?(Van::Units::Converter)
|
964
|
+
return Van::Units::Value.new(self, Van::Units::Unit.new({m => 1}, args[0])) if args[0] && args[0].registered?(m)
|
965
|
+
elsif Van::Units::Converter.current.registered?(m)
|
966
|
+
raise ::ArgumentError, "Wrong number of arguments" if args.length != 0
|
967
|
+
return Van::Units::Value.new(self, Van::Units::Unit.new({m => 1}, Van::Units::Converter.current))
|
968
|
+
end
|
969
|
+
::Exception.with_clean_backtrace("method_missing") {
|
970
|
+
super
|
971
|
+
}
|
972
|
+
end
|
973
|
+
|
974
|
+
#
|
975
|
+
def to_value(unit)
|
976
|
+
Van::Units::Value.new(self, unit)
|
977
|
+
end
|
978
|
+
end
|
979
|
+
|
980
|
+
|
981
|
+
class String
|
982
|
+
#
|
983
|
+
def to_unit(converter = nil)
|
984
|
+
Van::Units::Unit.new(self, converter)
|
985
|
+
end
|
986
|
+
|
987
|
+
#
|
988
|
+
def to_value(converter = nil)
|
989
|
+
Van::Units::Value.new(self, converter)
|
990
|
+
end
|
991
|
+
end
|