dimensional 1.1.1 → 2.0.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/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