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 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
@@ -1 +1,4 @@
1
- 1. Fix parsing units like "6m3" for six cubic meters.
1
+ 1. Fix parsing units like "6m3" for six cubic meters.
2
+ 2. Allow locale-specific formatters and detectors.
3
+ 3. Default format per dimension/system/metric.
4
+ 4. Auto generation of metric prefix units.
@@ -1,5 +1,6 @@
1
1
  require 'dimensional/dimension'
2
2
  require 'dimensional/system'
3
+ require 'dimensional/locale'
3
4
  require 'dimensional/unit'
4
5
  require 'dimensional/metric'
5
6
  require 'dimensional/version'
@@ -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
@@ -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 applicable to this metric in priority order (highest priority first)
17
+ # The units of this metric, grouped by system.
17
18
  def units
18
- @units ||= Unit.select{|u| u.dimension == dimension}.sort_by{|u| configuration[u][:preference]}.reverse
19
+ @units ||= Hash.new([]).merge(Unit.select{|u| u.dimension == dimension}.group_by{|u| u.system})
19
20
  end
20
21
 
21
- # Find the unit matching the given string, preferring units in the given system
22
- def find_unit(str, system = nil)
23
- system = System[system] unless system.kind_of?(System)
24
- us = self.units.select{|u| configuration[u][:detector].match(str.to_s)}
25
- us.detect{|u| u.system == system} || us.first
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 unit system (or associated symbol) will prefer the units from that system.
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, system = nil)
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, system)
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
- def best_fit(target_oom)
65
- units.sort_by do |u|
66
- oom_delta = (Math.log10(u.factor) - target_oom).abs
67
- configuration[u][:preference] - oom_delta
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 this metric to the "most appropriate" unit in the given system. A similar order-of-magnitude for the result is preferred.
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).select{|u| u.system == system}.last
99
+ bu = self.class.best_fit(target_oom, system)
95
100
  convert(bu)
96
101
  end
97
102
 
98
- # Convert this metric to the "most appropriate" unit in the current system. A resulting order of magnitude close to zero is preferred.
99
- def preferred
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
- bu = self.class.best_fit(target_oom).select{|u| u.system == unit.system}.last
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
@@ -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
- return nil unless sym = sym && sym.to_sym
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)}
@@ -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] || dimension.nil? ? "%s %U" : "%s%U"
55
+ @format = options[:format] || "%s %U"
56
56
  @preference = options[:preference] || 0
57
57
  validate
58
58
  end
@@ -1,3 +1,3 @@
1
1
  module Dimensional
2
- VERSION = "1.1.1"
2
+ VERSION = "2.0.0"
3
3
  end
@@ -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 => 0.5)
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/) do # metric ton
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/) do # short ton
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 => 0.75) do
147
- derive('cubic decimeter', 'dm3', Rational(1, 1000), :detector => /\A(cubic decimet(er|re)s?|dm3)\Z/, :preference => 0.5) do
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?|gal)\Z/)
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', 'g', 231, :detector => /\A(gallons?|gal)\Z/) do
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(:BA) do
222
- combine('knot', 'kt', {Unit[:L, :BA, :nm] => 1, Unit[:T, :SI, :hour] => -1}, :detector => /\A(knots?|kts?)\Z/)
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, :BA, :nm]
314
- configure(Unit[:L, :SI, :km], {:preference => 2})
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 => 2})
339
+ configure(Unit[:V, :US, :in3], {:preference => 3})
324
340
  end
325
341
 
326
342
  class MechanicalPower < Dimensional::Metric
@@ -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.parse('5.7l')
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.change_system(:US)
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.change_system(:SI)
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, :BA, 'knot'], m.unit
59
- m = m.change_system(:US)
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.new(200000, Unit[:L, :SI, :meter])
66
- assert_same Unit[:L, :SI, :meter], m.unit
67
- m = m.change_system(:US)
68
- assert_in_delta 124.3, m, 0.1
69
- assert_same Unit[:L, :US, :mi], m.unit
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
@@ -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
@@ -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
- end
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', :BA)
94
- assert_same @foot_us, depth.find_unit('foot', :US)
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", :BA)
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", :BA)
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", :SI)
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", :US)
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", :US)
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", :L)
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", :US)
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", :SI)
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 @meter, m1.unit
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.85nm", range.parse('1.85 miles', :BA).to_s
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", :L)
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", :BA)
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", :BA)
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', :BA).strfmeasure("%.4f%U")
309
- assert_equal "1.86", distance.parse('1.8565454 nm', :BA).strfmeasure("%s")
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
@@ -31,8 +31,8 @@ class SystemTest < Test::Unit::TestCase
31
31
  assert_same s, System::BA
32
32
  end
33
33
 
34
- def test_not_raise_exception_when_system_not_found
35
- assert_nothing_raised do
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: 1.1.1
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-08 00:00:00 -05:00
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