dimensional 1.1.1 → 2.0.0
Sign up to get free protection for your applications and to get access to all the features.
- data/CHANGELOG +2 -1
- data/TODO +4 -1
- data/lib/dimensional.rb +1 -0
- data/lib/dimensional/locale.rb +42 -0
- data/lib/dimensional/metric.rb +33 -25
- data/lib/dimensional/system.rb +6 -4
- data/lib/dimensional/unit.rb +1 -1
- data/lib/dimensional/version.rb +1 -1
- data/test/demo.rb +36 -20
- data/test/dimensional_test.rb +13 -11
- data/test/locale_test.rb +64 -0
- data/test/metric_test.rb +22 -25
- data/test/system_test.rb +2 -2
- metadata +5 -2
data/CHANGELOG
CHANGED
@@ -3,4 +3,5 @@
|
|
3
3
|
0.1.2 Cleanup tests to work with gem check -t.
|
4
4
|
1.0.0 Production Release 1.
|
5
5
|
1.0.1 Use Rationals for default values and avoid the dreaded integer division = 0 problem.
|
6
|
-
1.1.1 Add Metric.load constructor, improve docs and enhance scanner.
|
6
|
+
1.1.1 Add Metric.load constructor, improve docs and enhance scanner.
|
7
|
+
2.0.0 Introduce Dimensional::Locale
|
data/TODO
CHANGED
data/lib/dimensional.rb
CHANGED
@@ -0,0 +1,42 @@
|
|
1
|
+
module Dimensional
|
2
|
+
# A Locale is a prioritized list of Systems per Metric.
|
3
|
+
# Locales solve the problem of parsing ambiguous units such as US gallons and Imperial gallons. They also allow
|
4
|
+
# unit systems to be prioritized for specific metrics. For example, in the domain of aeronautics, the "international
|
5
|
+
# nautical mile" is used universally for distances and "knots" are used for speed. For these metrics, an "International"
|
6
|
+
# system should have the highest priority when parsing ambiguous units and when formatting output.
|
7
|
+
class Locale
|
8
|
+
class << self
|
9
|
+
# The default locale, which is used when auto-creating locales.
|
10
|
+
def default
|
11
|
+
@default ||= self.new(:DEFAULT)
|
12
|
+
end
|
13
|
+
|
14
|
+
# Create a new locale and define a constant for it
|
15
|
+
def register(name, ss = Array.new)
|
16
|
+
l = new(name, ss)
|
17
|
+
const_set(l.to_s.to_sym, l)
|
18
|
+
end
|
19
|
+
|
20
|
+
# Create a new locale (using systems from the default locale) and define a constant for it
|
21
|
+
def const_missing(symbol)
|
22
|
+
register(symbol, default.systems.dup) # Copy default's systems for independence
|
23
|
+
end
|
24
|
+
|
25
|
+
def reset!
|
26
|
+
@default = nil
|
27
|
+
constants.each {|d| remove_const(d)}
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
attr_accessor :systems
|
32
|
+
# Locales have a prioritized list of systems and a name.
|
33
|
+
def initialize(name, ss = Array.new)
|
34
|
+
@name = name
|
35
|
+
@systems = ss
|
36
|
+
end
|
37
|
+
|
38
|
+
def to_s
|
39
|
+
@name.to_s
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
data/lib/dimensional/metric.rb
CHANGED
@@ -8,21 +8,25 @@ module Dimensional
|
|
8
8
|
# A Measure string is composed of a number followed by a unit separated by optional whitespace.
|
9
9
|
# A unit (optional) is composed of a non-digit character followed by zero or more word characters and terminated by some stuff.
|
10
10
|
# Scientific notation is not currently supported.
|
11
|
+
# TODO: Move this to a locale
|
11
12
|
NUMERIC_REGEXP = /((?=\d|\.\d)\d*(?:\.\d*)?)\s*(\D.*?)?\s*(?=\d|$)/
|
12
13
|
|
13
14
|
class << self
|
14
|
-
attr_accessor :dimension, :base, :default
|
15
|
+
attr_accessor :dimension, :base, :default, :universal_systems
|
15
16
|
|
16
|
-
# The units
|
17
|
+
# The units of this metric, grouped by system.
|
17
18
|
def units
|
18
|
-
@units ||= Unit.select{|u| u.dimension == dimension}.
|
19
|
+
@units ||= Hash.new([]).merge(Unit.select{|u| u.dimension == dimension}.group_by{|u| u.system})
|
19
20
|
end
|
20
21
|
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
22
|
+
def systems(locale)
|
23
|
+
locale.systems.dup.unshift(*(universal_systems || [])).uniq
|
24
|
+
end
|
25
|
+
|
26
|
+
# Find the unit matching the given string, preferring units in the given locale
|
27
|
+
def find_unit(str, locale = Locale.default)
|
28
|
+
us = systems(locale).inject([]){|us, system| us + units[system].sort_by{|u| configuration[u][:preference]}.reverse}
|
29
|
+
us.detect{|u| configuration[u][:detector].match(str.to_s)}
|
26
30
|
end
|
27
31
|
|
28
32
|
def configuration
|
@@ -35,19 +39,16 @@ module Dimensional
|
|
35
39
|
@dimension ||= unit.dimension
|
36
40
|
@base ||= unit
|
37
41
|
@default ||= unit
|
38
|
-
@units = nil
|
39
42
|
raise "Unit #{unit} is not compatible with dimension #{dimension || '<nil>'}." unless unit.dimension == dimension
|
40
43
|
configuration[unit] = {:detector => unit.detector, :format => unit.format, :preference => unit.preference * 1.01}.merge(options)
|
41
44
|
end
|
42
45
|
|
43
|
-
# Parse a string into a Metric instance. Providing a
|
46
|
+
# Parse a string into a Metric instance. Providing a locale will help resolve ambiguities.
|
44
47
|
# Unrecognized strings return nil.
|
45
|
-
def parse(str,
|
46
|
-
system = System[system] unless system.kind_of?(System)
|
48
|
+
def parse(str, locale = Locale.default)
|
47
49
|
elements = str.to_s.scan(NUMERIC_REGEXP).map do |(v, us)|
|
48
|
-
unit = us.nil? ? default : find_unit(us,
|
50
|
+
unit = us.nil? ? default : find_unit(us, locale)
|
49
51
|
raise ArgumentError, "Unit cannot be determined (#{us})" unless unit
|
50
|
-
system = unit.system # Set the system to restrict subsequent filtering
|
51
52
|
value = Integer(v) rescue Float(v)
|
52
53
|
new(value, unit)
|
53
54
|
end
|
@@ -60,12 +61,16 @@ module Dimensional
|
|
60
61
|
end
|
61
62
|
end
|
62
63
|
|
63
|
-
# Sort units by "best" fit for the desired order of magnitude. Preference values offset OOM differences.
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
64
|
+
# Sort units by "best" fit for the desired order of magnitude. Preference values offset OOM differences. There is
|
65
|
+
# a bias in favor of negative OOM differences (humans like 6" more than 0.5ft).
|
66
|
+
def best_fit(target_oom, system)
|
67
|
+
us = units[system]
|
68
|
+
us = us.sort_by do |u|
|
69
|
+
oom_delta = Math.log10(u.factor) - target_oom
|
70
|
+
avoid_fraction_bonus = (oom_delta > 0.0 ? 0 : 1.5)
|
71
|
+
(configuration[u][:preference] - oom_delta.abs) + avoid_fraction_bonus
|
68
72
|
end
|
73
|
+
us.last
|
69
74
|
end
|
70
75
|
|
71
76
|
# Create a new instance with the given value (assumed to be in the base unit) and convert it to the preferred unit.
|
@@ -75,7 +80,7 @@ module Dimensional
|
|
75
80
|
end
|
76
81
|
|
77
82
|
attr_reader :unit
|
78
|
-
def initialize(value, unit = self.class.default)
|
83
|
+
def initialize(value, unit = self.class.default || self.class.base)
|
79
84
|
raise ArgumentError, "No default unit set" unless unit
|
80
85
|
@unit = unit
|
81
86
|
super(value)
|
@@ -87,20 +92,23 @@ module Dimensional
|
|
87
92
|
self.class.new(new_value, new_unit)
|
88
93
|
end
|
89
94
|
|
90
|
-
# Convert
|
95
|
+
# Convert into the "most appropriate" unit in the given system. A similar order-of-magnitude for the result is preferred.
|
91
96
|
def change_system(system)
|
92
97
|
system = System[system] unless system.kind_of?(System)
|
93
98
|
target_oom = Math.log10(self.unit.factor)
|
94
|
-
bu = self.class.best_fit(target_oom
|
99
|
+
bu = self.class.best_fit(target_oom, system)
|
95
100
|
convert(bu)
|
96
101
|
end
|
97
102
|
|
98
|
-
# Convert
|
99
|
-
|
103
|
+
# Convert into the best unit for the given Locale. The first system of the given locale with units is elected the preferred system,
|
104
|
+
# and within the preferred system, preference is given to units yielding a metric whose order of magnitude is close to zero.
|
105
|
+
def localize(locale = Locale.default)
|
100
106
|
target_oom = Math.log10(self) + Math.log10(self.unit.factor)
|
101
|
-
|
107
|
+
preferred_system = self.class.systems(locale).detect{ |s| self.class.units[s].any? }
|
108
|
+
bu = self.class.best_fit(target_oom, preferred_system)
|
102
109
|
convert(bu)
|
103
110
|
end
|
111
|
+
alias preferred localize
|
104
112
|
|
105
113
|
# Return a new metric expressed in the base unit
|
106
114
|
def base
|
data/lib/dimensional/system.rb
CHANGED
@@ -15,13 +15,15 @@ module Dimensional
|
|
15
15
|
const_set(s.abbreviation, s) rescue nil # Not all symbols strings are valid constant names
|
16
16
|
s
|
17
17
|
end
|
18
|
-
|
18
|
+
|
19
19
|
# Lookup the system by name or abbreviation
|
20
20
|
def self.[](sym)
|
21
|
-
|
22
|
-
@abbreviation_registry[sym] || @registry[sym]
|
21
|
+
sym = sym && sym.to_sym
|
22
|
+
s = @abbreviation_registry[sym] || @registry[sym]
|
23
|
+
raise "Unknown system #{sym.to_s}" unless s
|
24
|
+
s
|
23
25
|
end
|
24
|
-
|
26
|
+
|
25
27
|
# Purge all systems from storage.
|
26
28
|
def self.reset!
|
27
29
|
constants.each {|d| remove_const(d)}
|
data/lib/dimensional/unit.rb
CHANGED
@@ -52,7 +52,7 @@ module Dimensional
|
|
52
52
|
@reference_units = options[:reference_units] || {}
|
53
53
|
@abbreviation = options[:abbreviation]
|
54
54
|
@detector = options[:detector] || /\A#{[name, abbreviation].compact.join('|')}\Z/
|
55
|
-
@format = options[:format] ||
|
55
|
+
@format = options[:format] || "%s %U"
|
56
56
|
@preference = options[:preference] || 0
|
57
57
|
validate
|
58
58
|
end
|
data/lib/dimensional/version.rb
CHANGED
data/test/demo.rb
CHANGED
@@ -17,6 +17,7 @@ Dimension.register('Volume', 'V', {Dimension::L => 3})
|
|
17
17
|
Dimension.register('Velocity', 'Vel', {Dimension::L => 1, Dimension::T => -1})
|
18
18
|
Dimension.register('Acceleration', 'Acc', {Dimension::L => 1, Dimension::T => -2})
|
19
19
|
Dimension.register('Force', 'F', {Dimension::M => 1, Dimension::L => 1, Dimension::T => -2})
|
20
|
+
Dimension.register('Pressure', 'Press', {Dimension::M => 1, Dimension::L => -1, Dimension::T => -2})
|
20
21
|
Dimension.register('Torque', 'τ', {Dimension::M => 1, Dimension::L => 2, Dimension::T => -2}) # equivalent to Energy
|
21
22
|
Dimension.register('Energy', 'E', {Dimension::M => 1, Dimension::L => 2, Dimension::T => -2}) # a.k.a. work
|
22
23
|
Dimension.register('Power', 'P', {Dimension::M => 1, Dimension::L => 2, Dimension::T => -3})
|
@@ -24,23 +25,27 @@ Dimension.register('Voltage', 'emf', {Dimension::M => 1, Dimension::L => 2, Dime
|
|
24
25
|
|
25
26
|
# Define common Systems of Measurement
|
26
27
|
System.register('SI - International System (kg, tonne, m)', 'SI')
|
28
|
+
System.register('Universal', 'U') # International standards not included in SI
|
27
29
|
System.register('US Customary (lbs, ton, ft)', 'US') # http://en.wikipedia.org/wiki/United_States_customary_units
|
28
30
|
System.register('US Customary Troy (oz)', 'USt') # http://en.wikipedia.org/wiki/United_States_customary_units
|
29
31
|
System.register('British Imperial (lbs, ton, ft)', 'Imp') # http://en.wikipedia.org/wiki/Imperial_units
|
30
32
|
System.register('British Admiralty', 'BA')
|
31
33
|
System.register('Foot-Pound-Second', 'FPS') #http://en.wikipedia.org/wiki/Foot-pound-second_system
|
34
|
+
System.register('Furlong/Firkin/Fortnight', 'FFF') # http://en.wikipedia.org/wiki/FFF_System
|
32
35
|
|
33
36
|
Configurator.start do
|
34
37
|
dimension(:L) do
|
35
38
|
system(:SI) do
|
36
39
|
base('meter', 'm', :detector => /\A(met(er|re)s?|m)\Z/) do
|
37
|
-
derive('decimeter', 'dm', Rational(1, 10), :detector => /\A(decimet(er|re)s?|dm)\Z/, :preference =>
|
40
|
+
derive('decimeter', 'dm', Rational(1, 10), :detector => /\A(decimet(er|re)s?|dm)\Z/, :preference => -3)
|
38
41
|
derive('centimeter', 'cm', Rational(1, 100), :detector => /\A(centimet(er|re)s?|cm)\Z/)
|
39
|
-
derive('decameter', 'dam', 10, :detector => /\A(de(c|k)amet(er|re)s?|dam)\Z
|
42
|
+
derive('decameter', 'dam', 10, :detector => /\A(de(c|k)amet(er|re)s?|dam)\Z/, :preference => -3)
|
40
43
|
derive('kilometer', 'km', 1000, :detector => /\A(kilomet(er|re)s?|km)\Z/)
|
41
|
-
derive('nautical mile', 'M', 1852, :detector => /\A(nautical miles?|NMs?|nmis?)\Z/)
|
42
44
|
end
|
43
45
|
end
|
46
|
+
system(:U) do
|
47
|
+
reference('nautical mile', 'M', Dimensional::Unit[:L, :SI, 'meter'], 1852, :detector => /\A(nautical miles?|nm|nmi|M)\Z/)
|
48
|
+
end
|
44
49
|
system(:US) do # As of 1 July 1959 (http://en.wikipedia.org/wiki/United_States_customary_units#Units_of_length)
|
45
50
|
reference('yard', 'yd', Unit[:L, :SI, 'meter'], 0.9144, :detector => /\A(yards?|yds?)\Z/) do
|
46
51
|
derive('foot', 'ft', Rational(1,3), :detector => /\A(foot|feet|ft|')\Z/, :format => "%p'") do
|
@@ -84,16 +89,14 @@ Configurator.start do
|
|
84
89
|
dimension(:M) do
|
85
90
|
system(:SI) do
|
86
91
|
base('kilogram', 'kg', :detector => /\A(kilograms?|kg)\Z/) do
|
87
|
-
derive('tonne', 't', 1000, :detector => /\A(tonnes?)\Z/)
|
88
|
-
end
|
92
|
+
derive('tonne', 't', 1000, :detector => /\A(tonnes?)\Z/) # metric ton
|
89
93
|
derive('gram', 'g', Rational(1, 1000), :detector => /\A(grams?|g)\Z/)
|
90
94
|
end
|
91
95
|
end
|
92
96
|
system(:US) do # Common units for mass and, occasionally, force/weight (http://en.wikipedia.org/wiki/United_States_customary_units#Units_of_mass)
|
93
97
|
reference('pound', 'lb', Unit[:M, :SI, 'gram'], 453.59237, :detector => /\A(pounds?|lbs?|#)\Z/) do # avoirdupois
|
94
98
|
derive('hundredweight', 'cwt', 100, :detector => /\A(hundredweights?|cwt)\Z/) do
|
95
|
-
derive('ton', 't', 20, :detector => /\A(tons?|t)\Z/)
|
96
|
-
end
|
99
|
+
derive('ton', 't', 20, :detector => /\A(tons?|t)\Z/) # short ton
|
97
100
|
end
|
98
101
|
derive('ounce', 'oz', Rational(1, 16), :detector => /\A(ounces?|ozs?)\Z/)
|
99
102
|
derive('grain', 'gr', Rational(1, 7000), :detector => /\A(grains?|gr)\Z/) do
|
@@ -138,13 +141,14 @@ Configurator.start do
|
|
138
141
|
end
|
139
142
|
end
|
140
143
|
combine('square foot', 'ft2', {Unit[:L, :US, :ft] => 2}, :detector => /ft2/)
|
144
|
+
combine('square inch', 'in2', {Unit[:L, :US, :in] => 2}, :detector => /in2/)
|
141
145
|
end
|
142
146
|
end
|
143
147
|
|
144
148
|
dimension(:V) do
|
145
149
|
system(:SI) do
|
146
|
-
combine('cubic meter', 'm3', {Unit[:L, :SI, :meter] => 3}, :detector => /\A(cubic met(er|re)s?|m3)\Z/, :preference =>
|
147
|
-
derive('cubic decimeter', 'dm3', Rational(1, 1000), :detector => /\A(cubic decimet(er|re)s?|dm3)\Z/, :preference =>
|
150
|
+
combine('cubic meter', 'm3', {Unit[:L, :SI, :meter] => 3}, :detector => /\A(cubic met(er|re)s?|m3)\Z/, :preference => -2) do
|
151
|
+
derive('cubic decimeter', 'dm3', Rational(1, 1000), :detector => /\A(cubic decimet(er|re)s?|dm3)\Z/, :preference => -3) do
|
148
152
|
self.alias('liter', 'l', :detector => /\A(lit(er|re)s?|l|L)\Z/) do
|
149
153
|
derive('milliliter', 'ml', Rational(1, 1000), :detector => /\A(millilit(er|re)s?|ml|mL)\Z/)
|
150
154
|
end
|
@@ -157,7 +161,7 @@ Configurator.start do
|
|
157
161
|
derive('cup', 'cp', 2, :detector => /\A(cups?|cps?)\Z/) do
|
158
162
|
derive('pint', 'pt', 2, :detector => /\A(pints?|pts?)\Z/) do
|
159
163
|
derive('quart', 'qt', 2, :detector => /\A(quarts?|qts?)\Z/) do
|
160
|
-
derive('gallon', 'gal', 4, :detector => /\A(gallons?|
|
164
|
+
derive('gallon', 'gal', 4, :detector => /\A(gallons?|gals?)\Z/)
|
161
165
|
end
|
162
166
|
end
|
163
167
|
end
|
@@ -166,7 +170,7 @@ Configurator.start do
|
|
166
170
|
end
|
167
171
|
system(:US) do
|
168
172
|
combine('cubic inch', 'in3', {Unit[:L, :US, :inch] => 3}, :detector => /\A(cubic inch(es)?|in3)\Z/ ) do
|
169
|
-
derive('gallon', '
|
173
|
+
derive('gallon', 'gal', 231, :detector => /\A(gallons?|gals?)\Z/) do
|
170
174
|
derive('quart', 'qt', Rational(1,4), :detector => /\A(quarts?|qts?)\Z/) do
|
171
175
|
derive('pint', 'pt', Rational(1,2), :detector => /\A(pints?|pts?)\Z/) do
|
172
176
|
derive('cup', nil, Rational(1,2), :detector => /\Acups?\Z/) do
|
@@ -191,7 +195,9 @@ Configurator.start do
|
|
191
195
|
derive('minute', 'm', 60, :detector => /\A(minutes?|m)\Z/) do
|
192
196
|
derive('hour', 'h', 60, :detector => /\A(hours?|h)\Z/) do
|
193
197
|
derive('day', 'd', 24, :detector => /\A(days?)\Z/) do
|
194
|
-
derive('week', 'w', 7, :detector => /\A(weeks?|wks?)\Z/)
|
198
|
+
derive('week', 'w', 7, :detector => /\A(weeks?|wks?)\Z/) do
|
199
|
+
derive('fortnight', nil, 2, :detector => /\A(fortnights?)\Z/)
|
200
|
+
end
|
195
201
|
derive('year', 'yr', 365 + Rational(1, 4), :detector => /\A(years?|yrs?)\Z/)
|
196
202
|
end
|
197
203
|
end
|
@@ -218,8 +224,8 @@ Configurator.start do
|
|
218
224
|
system(:US) do
|
219
225
|
combine('mile per hour', 'mph', {Unit[:L, :US, :mile] => 1, Unit[:T, :SI, :hour] => -1}, :detector => /\A(mile per hour|mph)\Z/)
|
220
226
|
end
|
221
|
-
system(:
|
222
|
-
combine('knot', 'kt', {Unit[:L, :
|
227
|
+
system(:U) do
|
228
|
+
combine('knot', 'kt', {Unit[:L, :U, :M] => 1, Unit[:T, :SI, :hour] => -1}, :detector => /\A(knots?|kn|kts?)\Z/)
|
223
229
|
end
|
224
230
|
end
|
225
231
|
|
@@ -278,12 +284,20 @@ Configurator.start do
|
|
278
284
|
|
279
285
|
dimension(:Voltage) do
|
280
286
|
system(:SI) do
|
281
|
-
combine('Volt', 'V',{Unit[:P, :SI, :W] => 1, Unit[:I, :SI, :A] => -1}, :detector => /\A(volts?|V)\Z/) do
|
287
|
+
combine('Volt', 'V', {Unit[:P, :SI, :W] => 1, Unit[:I, :SI, :A] => -1}, :detector => /\A(volts?|V)\Z/) do
|
282
288
|
derive('millivolt', 'mV', Rational(1, 1000))
|
283
289
|
derive('kilovolt', 'kV', 1000)
|
284
290
|
end
|
285
291
|
end
|
286
292
|
end
|
293
|
+
dimension(:Pressure) do
|
294
|
+
system(:SI) do
|
295
|
+
combine('pascal', 'Pa', {Unit[:F, :SI, :N] => 1, Unit[:A, :SI, :m2] => -1}, :detector => /\A(pascals?|Pa)\Z/)
|
296
|
+
end
|
297
|
+
system(:US) do
|
298
|
+
combine('pound per square inch', 'psi', {Unit[:F, :US, :lbf] => 1, Unit[:A, :US, :in2] => -1}, :detector => /\A(psi)\Z/)
|
299
|
+
end
|
300
|
+
end
|
287
301
|
|
288
302
|
dimension(nil) do
|
289
303
|
system(:SI) do
|
@@ -301,6 +315,10 @@ Configurator.start do
|
|
301
315
|
end
|
302
316
|
end
|
303
317
|
|
318
|
+
Locale.default.systems = [System::SI, System::U, System::US, System::Imp]
|
319
|
+
Locale::US.systems = [System::US, System::USt, System::U, System::SI, System::Imp]
|
320
|
+
Locale::GB.systems = [System::Imp, System::SI, System::U, System::US]
|
321
|
+
|
304
322
|
class Length < Dimensional::Metric
|
305
323
|
self.dimension = Dimensional::Dimension::L
|
306
324
|
self.base = Unit[:L, :SI, :m]
|
@@ -310,17 +328,15 @@ end
|
|
310
328
|
class Autonomy < Dimensional::Metric
|
311
329
|
self.dimension = Dimension::L
|
312
330
|
self.base = Unit[:L, :SI, :m]
|
313
|
-
self.default = Unit[:L, :
|
314
|
-
|
315
|
-
configure(Unit[:L, :US, :mi], {:preference => 3.5})
|
316
|
-
configure(Unit[:L, :Imp, :mi], {:preference => 3.5})
|
331
|
+
self.default = Unit[:L, :U, :M]
|
332
|
+
self.universal_systems = [System::U]
|
317
333
|
end
|
318
334
|
|
319
335
|
class EngineDisplacement < Dimensional::Metric
|
320
336
|
self.dimension = Dimensional::Dimension::V
|
321
337
|
self.base = Unit[:V, :SI, :l]
|
322
338
|
self.default = base
|
323
|
-
configure(Unit[:V, :US, :in3], {:preference =>
|
339
|
+
configure(Unit[:V, :US, :in3], {:preference => 3})
|
324
340
|
end
|
325
341
|
|
326
342
|
class MechanicalPower < Dimensional::Metric
|
data/test/dimensional_test.rb
CHANGED
@@ -16,6 +16,7 @@ class DimensionalTest < Test::Unit::TestCase
|
|
16
16
|
Dimension.reset!
|
17
17
|
System.reset!
|
18
18
|
Unit.reset!
|
19
|
+
Locale.reset!
|
19
20
|
end
|
20
21
|
|
21
22
|
def test_a_lot
|
@@ -29,10 +30,10 @@ class DimensionalTest < Test::Unit::TestCase
|
|
29
30
|
end
|
30
31
|
|
31
32
|
def test_engine_displacement
|
32
|
-
assert m = EngineDisplacement.
|
33
|
+
assert m = EngineDisplacement.load(5.7)
|
33
34
|
assert_equal 5.7, m
|
34
35
|
assert_same Unit[:V, :SI, 'liter'], m.unit
|
35
|
-
m = m.
|
36
|
+
m = m.localize(Locale::US)
|
36
37
|
assert_in_delta 347, m, 1
|
37
38
|
assert_same Unit[:V, :US, 'in3'], m.unit
|
38
39
|
end
|
@@ -41,7 +42,7 @@ class DimensionalTest < Test::Unit::TestCase
|
|
41
42
|
assert m = MechanicalPower.parse('430hp')
|
42
43
|
assert_equal 430, m
|
43
44
|
assert_same Unit[:P, :US, :hp], m.unit
|
44
|
-
m = m.
|
45
|
+
m = m.localize(Locale::FI)
|
45
46
|
assert_in_delta 320, m, 1
|
46
47
|
assert_same Unit[:P, :SI, :kW], m.unit
|
47
48
|
end
|
@@ -53,19 +54,20 @@ class DimensionalTest < Test::Unit::TestCase
|
|
53
54
|
self.default = Unit[:Vel, :SI, :"km/h"]
|
54
55
|
self.base = default
|
55
56
|
end
|
56
|
-
assert m = speed.parse('20 knots')
|
57
|
+
assert m = speed.parse('20 knots', Locale::DK)
|
57
58
|
assert_equal 20, m
|
58
|
-
assert_same Unit[:Vel, :
|
59
|
-
m = m.
|
59
|
+
assert_same Unit[:Vel, :U, 'knot'], m.unit
|
60
|
+
m = m.localize(Locale::US)
|
60
61
|
assert_in_delta 23, m, 0.1
|
61
62
|
assert_same Unit[:Vel, :US, 'mph'], m.unit
|
62
63
|
end
|
63
64
|
|
65
|
+
# All Locale's should have an explicit system ordering for the Autonomy metric that places the Universal system first
|
64
66
|
def test_autonomy
|
65
|
-
assert m = Autonomy.
|
66
|
-
assert_same Unit[:L, :
|
67
|
-
|
68
|
-
|
69
|
-
assert_same Unit[:L, :
|
67
|
+
assert m = Autonomy.load(200000)
|
68
|
+
assert_same Unit[:L, :U, :M], m.unit
|
69
|
+
assert_in_delta 108, m, 0.1
|
70
|
+
m = m.localize(Locale::US)
|
71
|
+
assert_same Unit[:L, :U, :M], m.unit
|
70
72
|
end
|
71
73
|
end
|
data/test/locale_test.rb
ADDED
@@ -0,0 +1,64 @@
|
|
1
|
+
require "helper"
|
2
|
+
|
3
|
+
class LocaleTest < Test::Unit::TestCase
|
4
|
+
include Dimensional
|
5
|
+
|
6
|
+
def setup
|
7
|
+
System.register('British Imperial', 'Imp')
|
8
|
+
System.register('United States Customary', 'US')
|
9
|
+
System.register('International System of Units', 'SI')
|
10
|
+
System.register('International Standards', 'Std')
|
11
|
+
end
|
12
|
+
|
13
|
+
def teardown
|
14
|
+
System.reset!
|
15
|
+
Locale.reset!
|
16
|
+
end
|
17
|
+
|
18
|
+
def test_default_locale
|
19
|
+
assert_kind_of Locale, d = Locale.default
|
20
|
+
assert_kind_of Array, d.systems
|
21
|
+
assert d.systems.empty?
|
22
|
+
end
|
23
|
+
|
24
|
+
def test_create
|
25
|
+
default_systems = [System::US, System::SI, System::Std]
|
26
|
+
assert us = Locale.new(:US, default_systems)
|
27
|
+
assert_equal default_systems, us.systems
|
28
|
+
assert_equal 'US', us.to_s
|
29
|
+
end
|
30
|
+
|
31
|
+
def test_register
|
32
|
+
assert_kind_of Locale, us = Locale.register(:US)
|
33
|
+
assert_same us, Locale::US
|
34
|
+
assert_equal 'US', us.to_s
|
35
|
+
end
|
36
|
+
|
37
|
+
def test_auto_constant_defaults
|
38
|
+
default_default_systems = [System::SI, System::Std, System::US, System::Imp]
|
39
|
+
Locale.default.systems = default_default_systems
|
40
|
+
assert_kind_of Locale, ca = Locale::CA
|
41
|
+
assert_equal default_default_systems, ca.systems
|
42
|
+
end
|
43
|
+
|
44
|
+
def test_auto_constant_defaults_for_all_metrics
|
45
|
+
default_default_systems = [System::SI, System::Std, System::US, System::Imp]
|
46
|
+
us_default_systems = [System::Std, System::US, System::SI, System::Imp]
|
47
|
+
Locale.default.systems = default_default_systems
|
48
|
+
assert_kind_of Locale, us = Locale::US
|
49
|
+
us.systems = us_default_systems
|
50
|
+
assert_equal us_default_systems, us.systems
|
51
|
+
end
|
52
|
+
|
53
|
+
def test_auto_constant_defaults_are_independent
|
54
|
+
default_default_systems = [System::SI, System::Std, System::US, System::Imp]
|
55
|
+
default_ca_systems = [System::SI, System::Std, System::Imp, System::US]
|
56
|
+
default_us_systems = [System::US, System::Std, System::SI, System::Imp]
|
57
|
+
Locale.default.systems = default_default_systems
|
58
|
+
Locale::CA.systems = default_ca_systems
|
59
|
+
Locale::US.systems.replace(default_us_systems)
|
60
|
+
assert_equal default_default_systems, Locale::BE.systems
|
61
|
+
assert_equal default_us_systems, Locale::US.systems
|
62
|
+
assert_equal default_ca_systems, Locale::CA.systems
|
63
|
+
end
|
64
|
+
end
|
data/test/metric_test.rb
CHANGED
@@ -35,19 +35,16 @@ class MetricTest < Test::Unit::TestCase
|
|
35
35
|
# Dimensionless Units
|
36
36
|
@each = Unit.register('each', System::US, nil, {:abbreviation => 'ea'})
|
37
37
|
@dozen = Unit.register('dozen', System::US, nil, {:reference_units => {@each => 0}, :reference_factor => 12, :abbreviation => 'dz'})
|
38
|
+
Locale.default.systems = [System::SI, System::US, System::BA]
|
39
|
+
Locale::US.systems = [System::US, System::SI, System::BA]
|
40
|
+
Locale::BA.systems = [System::BA]
|
38
41
|
end
|
39
42
|
|
40
43
|
def teardown
|
41
44
|
Dimension.reset!
|
42
45
|
System.reset!
|
43
46
|
Unit.reset!
|
44
|
-
|
45
|
-
|
46
|
-
def test_associated_units
|
47
|
-
beam = Class.new(Metric)
|
48
|
-
beam.dimension = Dimension::L
|
49
|
-
assert beam.units.include?(@cable)
|
50
|
-
assert !beam.units.include?(@pound_force)
|
47
|
+
Locale.reset!
|
51
48
|
end
|
52
49
|
|
53
50
|
def test_register_conflicting_unit
|
@@ -90,8 +87,8 @@ class MetricTest < Test::Unit::TestCase
|
|
90
87
|
def test_find_unit
|
91
88
|
depth = Class.new(Metric)
|
92
89
|
depth.dimension = Dimension::L
|
93
|
-
assert_same @foot_ba, depth.find_unit('foot',
|
94
|
-
assert_same @foot_us, depth.find_unit('foot',
|
90
|
+
assert_same @foot_ba, depth.find_unit('foot', Locale::BA)
|
91
|
+
assert_same @foot_us, depth.find_unit('foot', Locale::US)
|
95
92
|
end
|
96
93
|
|
97
94
|
def test_scanner
|
@@ -109,7 +106,7 @@ class MetricTest < Test::Unit::TestCase
|
|
109
106
|
def test_parse
|
110
107
|
depth = Class.new(Metric)
|
111
108
|
depth.dimension = Dimension::L
|
112
|
-
assert m = depth.parse("15ft",
|
109
|
+
assert m = depth.parse("15ft", Locale::BA)
|
113
110
|
assert_same @foot_ba, m.unit
|
114
111
|
assert_equal 15, m
|
115
112
|
end
|
@@ -117,7 +114,7 @@ class MetricTest < Test::Unit::TestCase
|
|
117
114
|
def test_parse_with_whitespace
|
118
115
|
depth = Class.new(Metric)
|
119
116
|
depth.dimension = Dimension::L
|
120
|
-
m = depth.parse("15 ft",
|
117
|
+
m = depth.parse("15 ft", Locale::BA)
|
121
118
|
assert_same @foot_ba, m.unit
|
122
119
|
assert_equal 15, m
|
123
120
|
end
|
@@ -125,7 +122,7 @@ class MetricTest < Test::Unit::TestCase
|
|
125
122
|
def test_parse_with_multiword_unit
|
126
123
|
range = Class.new(Metric)
|
127
124
|
range.dimension = Dimension::L
|
128
|
-
m = range.parse("15 nautical miles"
|
125
|
+
m = range.parse("15 nautical miles")
|
129
126
|
assert_same @international_nautical_mile, m.unit
|
130
127
|
assert_equal 15, m
|
131
128
|
end
|
@@ -133,7 +130,7 @@ class MetricTest < Test::Unit::TestCase
|
|
133
130
|
def test_parse_compound
|
134
131
|
depth = Class.new(Metric)
|
135
132
|
depth.dimension = Dimension::L
|
136
|
-
d = depth.parse("15ft11in",
|
133
|
+
d = depth.parse("15ft11in", Locale::US)
|
137
134
|
assert_in_delta(15 + Rational(11, 12), d, 0.000001)
|
138
135
|
assert_same @foot_us, d.unit
|
139
136
|
end
|
@@ -141,7 +138,7 @@ class MetricTest < Test::Unit::TestCase
|
|
141
138
|
def test_parse_compound_with_whitespace
|
142
139
|
depth = Class.new(Metric)
|
143
140
|
depth.dimension = Dimension::L
|
144
|
-
d = depth.parse("1 ft 11 in",
|
141
|
+
d = depth.parse("1 ft 11 in", Locale::US)
|
145
142
|
assert_same d.unit, @foot_us
|
146
143
|
assert_in_delta(1 + Rational(11, 12).to_f, d, 0.000001)
|
147
144
|
assert_same @foot_us, d.unit
|
@@ -151,7 +148,7 @@ class MetricTest < Test::Unit::TestCase
|
|
151
148
|
depth = Class.new(Metric)
|
152
149
|
depth.dimension = Dimension::L
|
153
150
|
assert_raises ArgumentError do
|
154
|
-
depth.parse("1 foot 11cm",
|
151
|
+
depth.parse("1 foot 11cm", Locale::US)
|
155
152
|
end
|
156
153
|
end
|
157
154
|
|
@@ -159,7 +156,7 @@ class MetricTest < Test::Unit::TestCase
|
|
159
156
|
depth = Class.new(Metric)
|
160
157
|
depth.dimension = Dimension::L
|
161
158
|
depth.default = @meter
|
162
|
-
assert_instance_of depth, m = depth.parse("10",
|
159
|
+
assert_instance_of depth, m = depth.parse("10", Locale::US)
|
163
160
|
assert_equal @meter, m.unit
|
164
161
|
end
|
165
162
|
|
@@ -176,7 +173,7 @@ class MetricTest < Test::Unit::TestCase
|
|
176
173
|
def test_to_f
|
177
174
|
depth = Class.new(Metric)
|
178
175
|
depth.dimension = Dimension::L
|
179
|
-
d = depth.parse("1.85m",
|
176
|
+
d = depth.parse("1.85m", Locale::SI)
|
180
177
|
assert_instance_of Float, d.to_f
|
181
178
|
end
|
182
179
|
|
@@ -232,7 +229,7 @@ class MetricTest < Test::Unit::TestCase
|
|
232
229
|
range.dimension = Dimension::L
|
233
230
|
m0 = range.new(1, @foot_us)
|
234
231
|
assert m1 = m0.change_system(:SI)
|
235
|
-
assert_same @
|
232
|
+
assert_same @centimeter, m1.unit
|
236
233
|
end
|
237
234
|
|
238
235
|
def test_change_system_mile
|
@@ -250,7 +247,7 @@ class MetricTest < Test::Unit::TestCase
|
|
250
247
|
self.dimension = Dimension::L
|
251
248
|
end
|
252
249
|
m0 = range.new(100000, @meter)
|
253
|
-
assert m1 = m0.preferred
|
250
|
+
assert m1 = m0.preferred(Locale::FR)
|
254
251
|
assert_same @kilometer, m1.unit
|
255
252
|
end
|
256
253
|
|
@@ -278,26 +275,26 @@ class MetricTest < Test::Unit::TestCase
|
|
278
275
|
def test_stringify_with_abbreviation
|
279
276
|
range = Class.new(Metric)
|
280
277
|
range.dimension = Dimension::L
|
281
|
-
assert_equal "1.
|
278
|
+
assert_equal "1.85 nm", range.parse('1.85 miles', Locale::BA).to_s
|
282
279
|
end
|
283
280
|
|
284
281
|
def test_parse_gibberish_as_nil
|
285
282
|
beam = Class.new(Metric)
|
286
283
|
beam.dimension = Dimension::L
|
287
|
-
assert_nil beam.parse("gibberish",
|
284
|
+
assert_nil beam.parse("gibberish", Locale::US)
|
288
285
|
end
|
289
286
|
|
290
287
|
def test_format_output
|
291
288
|
depth = Class.new(Metric)
|
292
289
|
depth.dimension = Dimension::L
|
293
|
-
m = depth.parse("15ft3in",
|
290
|
+
m = depth.parse("15ft3in", Locale::BA)
|
294
291
|
assert_equal "15.25 (ft)", m.strfmeasure("%4.2f (%U)")
|
295
292
|
end
|
296
293
|
|
297
294
|
def test_format_output_with_multiple_substitutions
|
298
295
|
depth = Class.new(Metric)
|
299
296
|
depth.dimension = Dimension::L
|
300
|
-
m = depth.parse("15ft4in",
|
297
|
+
m = depth.parse("15ft4in", Locale::BA)
|
301
298
|
assert_equal "15.33 (ft)\t%\t<15.3333333ft>", m.strfmeasure("%4.2f (%U)\t%%\t<%10.7f%U>")
|
302
299
|
end
|
303
300
|
|
@@ -305,7 +302,7 @@ class MetricTest < Test::Unit::TestCase
|
|
305
302
|
distance = Class.new(Metric)
|
306
303
|
distance.dimension = Dimension::L
|
307
304
|
distance.configure(@nautical_mile, :precision => -2)
|
308
|
-
assert_equal "1.8600nm", distance.parse('1.8565454 nm',
|
309
|
-
assert_equal "1.86", distance.parse('1.8565454 nm',
|
305
|
+
assert_equal "1.8600nm", distance.parse('1.8565454 nm', Locale::BA).strfmeasure("%.4f%U")
|
306
|
+
assert_equal "1.86", distance.parse('1.8565454 nm', Locale::BA).strfmeasure("%s")
|
310
307
|
end
|
311
308
|
end
|
data/test/system_test.rb
CHANGED
@@ -31,8 +31,8 @@ class SystemTest < Test::Unit::TestCase
|
|
31
31
|
assert_same s, System::BA
|
32
32
|
end
|
33
33
|
|
34
|
-
def
|
35
|
-
|
34
|
+
def test_raise_exception_when_system_not_found
|
35
|
+
assert_raises RuntimeError do
|
36
36
|
System['British Admiralty']
|
37
37
|
end
|
38
38
|
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: dimensional
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version:
|
4
|
+
version: 2.0.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Chris Hapgood
|
@@ -9,7 +9,7 @@ autorequire:
|
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
11
|
|
12
|
-
date: 2010-02-
|
12
|
+
date: 2010-02-10 00:00:00 -05:00
|
13
13
|
default_executable:
|
14
14
|
dependencies: []
|
15
15
|
|
@@ -31,6 +31,7 @@ files:
|
|
31
31
|
- lib/dimensional.rb
|
32
32
|
- lib/dimensional/configurator.rb
|
33
33
|
- lib/dimensional/dimension.rb
|
34
|
+
- lib/dimensional/locale.rb
|
34
35
|
- lib/dimensional/metric.rb
|
35
36
|
- lib/dimensional/system.rb
|
36
37
|
- lib/dimensional/unit.rb
|
@@ -40,6 +41,7 @@ files:
|
|
40
41
|
- test/dimension_test.rb
|
41
42
|
- test/dimensional_test.rb
|
42
43
|
- test/helper.rb
|
44
|
+
- test/locale_test.rb
|
43
45
|
- test/metric_test.rb
|
44
46
|
- test/system_test.rb
|
45
47
|
- test/unit_test.rb
|
@@ -77,6 +79,7 @@ test_files:
|
|
77
79
|
- test/configurator_test.rb
|
78
80
|
- test/dimension_test.rb
|
79
81
|
- test/dimensional_test.rb
|
82
|
+
- test/locale_test.rb
|
80
83
|
- test/metric_test.rb
|
81
84
|
- test/system_test.rb
|
82
85
|
- test/unit_test.rb
|