quantify 1.0.5 → 1.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -32,19 +32,19 @@ module Quantify
32
32
 
33
33
  # Absolute index as names always contain 'per' before denominator units
34
34
  def name
35
- @unit.name.to_power(@index.abs)
35
+ name_to_power(@unit.name, @index.abs)
36
36
  end
37
37
 
38
38
  def pluralized_name
39
- @unit.pluralized_name.to_power(@index.abs)
39
+ name_to_power(@unit.pluralized_name, @index.abs)
40
40
  end
41
41
 
42
42
  def symbol
43
- @unit.symbol.to_s + ( @index.nil? or @index == 1 ? "" : "^#{@index}" )
43
+ @unit.symbol.to_s + ( @index.nil? || @index == 1 ? "" : formatted_index )
44
44
  end
45
45
 
46
46
  def label
47
- @unit.label + (@index == 1 ? "" : "^#{@index}")
47
+ @unit.label + (@index == 1 ? "" : formatted_index)
48
48
  end
49
49
 
50
50
  # Reciprocalized version of label, i.e. sign changed. This is used to make
@@ -52,7 +52,7 @@ module Quantify
52
52
  # i.e. where no '/' appears in the label
53
53
  #
54
54
  def reciprocalized_label
55
- @unit.label + (@index == -1 ? "" : "^#{@index * -1}")
55
+ @unit.label + (@index == -1 ? "" : formatted_index(@index * -1))
56
56
  end
57
57
 
58
58
  def factor
@@ -70,22 +70,62 @@ module Quantify
70
70
  # The following methods refer only to the unit of the CompoundBaseUnit
71
71
  # object, rather than the unit *together with its index*
72
72
 
73
- Unit_Methods = [ :base_quantity_si_unit, :base_unit, :benchmark_unit, :si_unit,
74
- :non_si_unit, :prefixed_unit, :derived_unit ]
75
-
76
- Unit_Methods.each do |method|
77
- method = "is_#{method.to_s}?"
78
- define_method(method) do
79
- @unit.send method.to_sym
80
- end
73
+ def is_base_quantity_si_unit?
74
+ @unit.is_base_quantity_si_unit?
75
+ end
76
+
77
+ def is_base_unit?
78
+ @unit.is_base_unit?
79
+ end
80
+
81
+ def is_benchmark_unit?
82
+ @unit.is_benchmark_unit?
83
+ end
84
+
85
+ def is_si_unit?
86
+ @unit.is_si_unit?
87
+ end
88
+
89
+ def is_non_si_unit?
90
+ @unit.is_non_si_unit?
91
+ end
92
+
93
+ def is_prefixed_unit?
94
+ @unit.is_prefixed_unit?
81
95
  end
82
96
 
97
+ def is_derived_unit?
98
+ @unit.is_derived_unit?
99
+ end
100
+
83
101
  def measures
84
102
  @unit.measures
85
103
  end
86
104
 
87
105
  def initialize_copy(source)
88
- instance_variable_set("@unit", unit.clone)
106
+ instance_variable_set("@unit", @unit.clone)
107
+ end
108
+
109
+ private
110
+
111
+ # Returns a string representation of the unit index, formatted according to
112
+ # the global superscript configuration
113
+ #
114
+ def formatted_index(index=nil)
115
+ index = "^#{index.nil? ? @index : index}"
116
+ Quantify.use_superscript_characters? ? index.with_superscript_characters : index
117
+ end
118
+
119
+ def name_to_power(string,index)
120
+ name = string.clone
121
+ case index
122
+ when 1 then name
123
+ when 2 then "square #{name}"
124
+ when 3 then "cubic #{name}"
125
+ else
126
+ ordinal = ActiveSupport::Inflector.ordinalize index
127
+ name << " to the #{ordinal} power"
128
+ end
89
129
  end
90
130
  end
91
131
  end
@@ -41,7 +41,7 @@ module Quantify
41
41
  next if new_base.unit.is_dimensionless?
42
42
 
43
43
  new_base.index = base_units.select do |other_base|
44
- new_base.unit == other_base.unit
44
+ new_base.unit.is_equivalent_to? other_base.unit
45
45
  end.inject(new_base.index) do |index,other_base|
46
46
  base_units.delete other_base
47
47
  index += other_base.index
@@ -57,8 +57,8 @@ module Quantify
57
57
  #
58
58
  # This is a class method which takes an arbitrary array of base units as an
59
59
  # argument. This means that consolidation can be performed on either all
60
- # base units or just a subset - the numerator or denominator units.
61
- #''
60
+ # base units or just a subset - e.g. the numerator or denominator units.
61
+ #
62
62
  # The units to use for particular physical dimension can be specified
63
63
  # following the inital argument. If no unit is specified for a physical
64
64
  # quantity which is represented in the array of base units, then the first
@@ -93,8 +93,8 @@ module Quantify
93
93
  @base_units << unit
94
94
  elsif unit.is_a? Unit::Base
95
95
  @base_units << CompoundBaseUnit.new(unit)
96
- elsif unit.is_a? Array and unit.first.is_a? Unit::Base and
97
- not unit.first.is_a? Compound and unit.size == 2
96
+ elsif unit.is_a?(Array) && unit.first.is_a?(Unit::Base) &&
97
+ !unit.first.is_a?(Compound) && unit.size == 2
98
98
  @base_units << CompoundBaseUnit.new(unit.first,unit.last)
99
99
  else
100
100
  raise Exceptions::InvalidArgumentError, "#{unit} does not represent a valid base unit"
@@ -165,10 +165,10 @@ module Quantify
165
165
  raise Exceptions::InvalidArgumentError, "Cannot cancel by a compound unit" if unit.is_a? Unit::Compound
166
166
  unit = Unit.for unit unless unit.is_a? Unit::Base
167
167
 
168
- numerator_unit = numerator_units.find { |base| unit == base.unit }
169
- denominator_unit = denominator_units.find { |base| unit == base.unit }
168
+ numerator_unit = numerator_units.find { |base| unit.is_equivalent_to? base.unit }
169
+ denominator_unit = denominator_units.find { |base| unit.is_equivalent_to? base.unit }
170
170
 
171
- if numerator_unit and denominator_unit
171
+ if numerator_unit && denominator_unit
172
172
  cancel_value = [numerator_unit.index,denominator_unit.index].min.abs
173
173
  numerator_unit.index -= cancel_value
174
174
  denominator_unit.index += cancel_value
@@ -177,6 +177,20 @@ module Quantify
177
177
  consolidate_numerator_and_denominator_units!
178
178
  end
179
179
 
180
+ # Make the base units of self use consistent units for each physical quantity
181
+ # represented. For example, lb/kg => kg/kg.
182
+ #
183
+ # By default, units are rationalized within the the numerator and denominator
184
+ # respectively. That is, different units representing the same physical
185
+ # quantity may appear across the numerator and denominator, but not within
186
+ # each. To fully rationalize the base units of self, pass in the symbol
187
+ # :full as a first argument. Otherwise :partial is passed as the default.
188
+ #
189
+ # The units to use for particular physical dimension can be specified
190
+ # following the inital argument. If no unit is specified for a physical
191
+ # quantity which is represented in the array of base units, then the first
192
+ # unit found for that physical quantity is used as the canonical one.
193
+ #
180
194
  def rationalize_base_units!(scope=:partial,*units)
181
195
  if scope == :full
182
196
  Compound.rationalize_base_units(@base_units,*units)
@@ -187,16 +201,26 @@ module Quantify
187
201
  consolidate_numerator_and_denominator_units!
188
202
  end
189
203
 
204
+ # Return a known unit which is equivalent to self in terms of its physical
205
+ # quantity (dimensions), factor and scaling attributes (i.e. representing the
206
+ # precise same physical unit but perhaps with different identifiers), e.g.
207
+ #
208
+ # ((Unit.kg*(Unit.m**"))/(Unit.s**2)).equivalent_known_unit.name
209
+ #
210
+ # #=> "joule"
211
+ #
190
212
  def equivalent_known_unit
191
213
  Unit.units.find do |unit|
192
- self == unit and
193
- not unit.is_compound_unit?
214
+ self.is_equivalent_to?(unit) && !unit.is_compound_unit?
194
215
  end
195
216
  end
196
217
 
197
- def or_equivalent &block
218
+ # Returns an equivalent known unit (via #equivalent_known_unit) if it exists.
219
+ # Otherwise, returns false.
220
+ #
221
+ def or_equivalent
198
222
  equivalent_unit = equivalent_known_unit
199
- if equivalent_unit and equivalent_unit.acts_as_equivalent_unit
223
+ if equivalent_unit && equivalent_unit.acts_as_equivalent_unit
200
224
  return equivalent_unit
201
225
  else
202
226
  return self
@@ -206,11 +230,11 @@ module Quantify
206
230
  protected
207
231
 
208
232
  def initialize_attributes
209
- @dimensions = derive_dimensions
210
- @name = derive_name
211
- @symbol = derive_symbol
212
- @factor = derive_factor
213
- @label = derive_label
233
+ self.dimensions = derive_dimensions
234
+ self.name = derive_name
235
+ self.symbol = derive_symbol
236
+ self.factor = derive_factor
237
+ self.label = derive_label
214
238
  end
215
239
 
216
240
  # Partially consolidate base units, i.e. numerator and denomiator are
@@ -304,7 +328,7 @@ module Quantify
304
328
  format = ( unit_label.empty? ? :label : :reciprocalized_label )
305
329
  unit_label << "/" unless unit_label.empty?
306
330
  denominator_units.inject(unit_label) do |label,base|
307
- label << "·" unless unit_label.empty? or unit_label =~ /\/\z/
331
+ label << "·" unless unit_label.empty? || unit_label =~ /\/\z/
308
332
  label << base.send(format)
309
333
  end
310
334
  end
@@ -12,7 +12,7 @@ module Quantify
12
12
  #
13
13
  def initialize(options=nil)
14
14
  @scaling = 0.0
15
- if options.is_a? Hash and options[:scaling]
15
+ if options.is_a?(Hash) && options[:scaling]
16
16
  @scaling = options.delete(:scaling).to_f
17
17
  end
18
18
  super(options)
@@ -10,16 +10,16 @@ module Quantify
10
10
  end
11
11
  end
12
12
 
13
- def self.configure &block
14
- self.class_eval &block if block
13
+ def self.configure(&block)
14
+ self.class_eval(&block) if block
15
15
  end
16
16
 
17
17
  attr_reader :name, :symbol, :factor
18
18
 
19
19
  def initialize(options)
20
- @symbol = options[:symbol].standardize
20
+ @symbol = options[:symbol].remove_underscores
21
21
  @factor = options[:factor].to_f
22
- @name = options[:name].standardize.downcase
22
+ @name = options[:name].remove_underscores.downcase
23
23
  end
24
24
 
25
25
  def is_si_prefix?
@@ -27,7 +27,7 @@ module Quantify
27
27
  end
28
28
 
29
29
  def is_non_si_prefix?
30
- self.is_a? NonSI
30
+ self.is_a?(NonSI)
31
31
  end
32
32
 
33
33
  def label
@@ -14,7 +14,7 @@ module Quantify
14
14
  def self.unload(*unloaded_prefixes)
15
15
  [unloaded_prefixes].flatten.each do |unloaded_prefix|
16
16
  unloaded_prefix = Prefix.for(unloaded_prefix)
17
- @prefixes.delete_if { |unit| unit.label == unloaded_prefix.label }
17
+ @prefixes.delete_if { |prefix| prefix.label == unloaded_prefix.label }
18
18
  end
19
19
  end
20
20
 
@@ -24,10 +24,10 @@ module Quantify
24
24
 
25
25
  def self.for(name_or_symbol,collection=nil)
26
26
  return name_or_symbol.clone if name_or_symbol.is_a? Quantify::Unit::Prefix::Base
27
- if name_or_symbol.is_a? String or name_or_symbol.is_a? Symbol
27
+ if name_or_symbol.is_a?(String) || name_or_symbol.is_a?(Symbol)
28
28
  if prefix = (collection.nil? ? @prefixes : collection).find do |prefix|
29
- prefix.name == name_or_symbol.standardize.downcase or
30
- prefix.symbol == name_or_symbol.standardize
29
+ prefix.name == name_or_symbol.remove_underscores.downcase ||
30
+ prefix.symbol == name_or_symbol.remove_underscores
31
31
  end
32
32
  return prefix.clone
33
33
  else
@@ -25,8 +25,8 @@ module Quantify
25
25
  attr_reader :units
26
26
  end
27
27
 
28
- def self.configure &block
29
- self.class_eval &block if block
28
+ def self.configure(&block)
29
+ self.class_eval(&block) if block
30
30
  end
31
31
 
32
32
  # Instance variable containing system of known units
@@ -40,8 +40,7 @@ module Quantify
40
40
  # Remove a unit from the system of known units
41
41
  def self.unload(*unloaded_units)
42
42
  [unloaded_units].flatten.each do |unloaded_unit|
43
- unloaded_unit = Unit.for(unloaded_unit)
44
- @units.delete_if { |unit| unit.label == unloaded_unit.label }
43
+ @units.delete(Unit.for(unloaded_unit))
45
44
  end
46
45
  end
47
46
 
@@ -90,10 +89,10 @@ module Quantify
90
89
  #
91
90
  def self.for(name_symbol_label_or_object)
92
91
  return name_symbol_label_or_object.clone if name_symbol_label_or_object.is_a? Unit::Base
93
- return nil if name_symbol_label_or_object.nil? or
94
- ( name_symbol_label_or_object.is_a?(String) and name_symbol_label_or_object.empty? )
92
+ return nil if name_symbol_label_or_object.nil? ||
93
+ ( name_symbol_label_or_object.is_a?(String) && name_symbol_label_or_object.empty? )
95
94
  name_symbol_or_label = name_symbol_label_or_object
96
- unless name_symbol_or_label.is_a? String or name_symbol_or_label.is_a? Symbol
95
+ unless name_symbol_or_label.is_a?(String) || name_symbol_or_label.is_a?(Symbol)
97
96
  raise Exceptions::InvalidArgumentError, "Argument must be a Symbol or String"
98
97
  end
99
98
  if unit = Unit.match(name_symbol_or_label)
@@ -109,7 +108,7 @@ module Quantify
109
108
  # Parse complex strings into unit.
110
109
  #
111
110
  def self.parse(string)
112
- string = string.standardize
111
+ string = string.remove_underscores.without_superscript_characters
113
112
  if string.scan(/(\/|per)/).size > 1
114
113
  raise Exceptions::InvalidArgumentError, "Malformed unit: multiple uses of '/' or 'per'"
115
114
  end
@@ -118,7 +117,7 @@ module Quantify
118
117
  numerator, per, denominator = string.split(/(\/|per)/)
119
118
  units += Unit.parse_numerator_units(numerator)
120
119
  units += Unit.parse_denominator_units(denominator) unless denominator.nil?
121
- if units.size == 1 and units.first.index == 1
120
+ if units.size == 1 && units.first.index == 1
122
121
  return units.first.unit
123
122
  else
124
123
  return Unit::Compound.new(*units)
@@ -141,40 +140,51 @@ module Quantify
141
140
 
142
141
  def self.match_known_unit(attribute, string_or_symbol)
143
142
  string_or_symbol = Unit.format_unit_attribute(attribute, string_or_symbol)
144
- unit = @units.find { |unit| unit.send(attribute) == string_or_symbol }
145
- return unit.clone rescue nil
143
+ @units.find do |unit|
144
+ unit_attribute = unit.send(attribute)
145
+ if attribute == :name
146
+ unit_attribute.downcase == string_or_symbol
147
+ else
148
+ unit_attribute == string_or_symbol
149
+ end
150
+ end.clone rescue nil
146
151
  end
147
152
 
148
153
  def self.match_prefixed_variant(attribute, string_or_symbol)
149
154
  string_or_symbol = Unit.format_unit_attribute(attribute, string_or_symbol)
150
- if string_or_symbol =~ /\A(#{Unit::Prefix.si_prefixes.map(&attribute).join("|")})(#{Unit.si_non_prefixed_units.map(&attribute).join("|")})\z/ or
151
- string_or_symbol =~ /\A(#{Unit::Prefix.non_si_prefixes.map(&attribute).join("|")})(#{Unit.non_si_non_prefixed_units.map(&attribute).join("|")})\z/
155
+ if string_or_symbol =~ /\A(#{units_for_regex(Unit::Prefix,:si_prefixes,attribute)})(#{units_for_regex(Unit,:si_non_prefixed_units,attribute)})\z/ ||
156
+ string_or_symbol =~ /\A(#{units_for_regex(Unit::Prefix,:non_si_prefixes,attribute)})(#{units_for_regex(Unit,:non_si_non_prefixed_units,attribute)})\z/
152
157
  return Unit.for($2).with_prefix($1).clone
153
158
  end
154
159
  return nil
155
160
  end
156
161
 
157
- # Standardizes query strings or symbols into canonical form for unit names,
162
+ # standardize query strings or symbols into canonical form for unit names,
158
163
  # symbols and labels
159
164
  #
160
165
  def self.format_unit_attribute(attribute, string_or_symbol)
161
166
  string_or_symbol = case attribute
162
- when :symbol then string_or_symbol.standardize
163
- when :name then string_or_symbol.standardize.singularize.downcase
167
+ when :symbol then string_or_symbol.remove_underscores
168
+ when :name then string_or_symbol.remove_underscores.singularize.downcase
164
169
  else string_or_symbol.to_s
165
170
  end
171
+ Quantify.use_superscript_characters? ?
172
+ string_or_symbol.with_superscript_characters : string_or_symbol.without_superscript_characters
166
173
  end
167
174
 
168
175
  def self.parse_unit_and_index(string)
169
176
  string.scan(/([^0-9\^]+)\^?([\d\.-]*)?/i)
170
- index = ($2.nil? or $2.empty? ? 1 : $2.to_i)
177
+ index = ($2.nil? || $2.empty? ? 1 : $2.to_i)
171
178
  CompoundBaseUnit.new($1.to_s, index)
172
179
  end
173
180
 
174
181
  def self.parse_numerator_units(string)
175
182
  # If no middot then names parsed by whitespace
176
- # Need to consider multi word unit names
177
- num_units = ( string =~ /·/ ? string.split("·") : string.split(" ") )
183
+ if string =~ /·/
184
+ num_units = string.split("·")
185
+ else
186
+ num_units = Unit.escape_multi_word_units(string).split(" ")
187
+ end
178
188
  num_units.map! do |substring|
179
189
  Unit.parse_unit_and_index(substring)
180
190
  end
@@ -199,26 +209,47 @@ module Quantify
199
209
  # given importance in #match (and #for) methods regexen
200
210
  #
201
211
  def self.si_non_prefixed_units
202
- @units.select {|unit| unit.is_si_unit? and not unit.is_prefixed_unit? }
212
+ @units.select {|unit| unit.is_si_unit? && !unit.is_prefixed_unit? }
203
213
  end
204
214
 
205
215
  # This can be replicated by method missing approach, but explicit method provided
206
216
  # given importance in #match (and #for) methods regexen
207
217
  #
208
218
  def self.non_si_non_prefixed_units
209
- @units.select {|unit| unit.is_non_si_unit? and not unit.is_prefixed_unit? }
219
+ @units.select {|unit| unit.is_non_si_unit? && !unit.is_prefixed_unit? }
210
220
  end
211
221
 
222
+ # Return a list of unit names which are multi-word
212
223
  def self.multi_word_unit_names
213
- @units.map(&:name).compact.select {|name| name.word_count > 1 }
224
+ @units.map {|unit| unit.name if unit.name.word_count > 1 }.compact
214
225
  end
215
226
 
227
+ # Return a list of pluralised unit names which are multi-word
216
228
  def self.multi_word_unit_pluralized_names
217
- multi_word_unit_names.map(&:pluralize)
229
+ multi_word_unit_names.map {|name| name.pluralize }
218
230
  end
219
231
 
232
+ # Return a list of unit symbols which are multi-word
220
233
  def self.multi_word_unit_symbols
221
- @units.map(&:symbol).compact.select {|symbol| symbol.word_count > 1 }
234
+ @units.map {|unit| unit.symbol if unit.symbol.word_count > 1 }.compact
235
+ end
236
+
237
+ # Underscore any parts of string which represent multi-word unit identifiers
238
+ # so that units can be parsed on whitespace
239
+ #
240
+ def self.escape_multi_word_units(string)
241
+ (multi_word_unit_symbols + multi_word_unit_pluralized_names + multi_word_unit_names).each do |id|
242
+ string.gsub!(id, id.gsub(" ","_")) if /#{id}/.match(string)
243
+ end
244
+ string
245
+ end
246
+
247
+ # Returns a list of "|" separated unit identifiers for use as regex
248
+ # alternatives. Lists are constructed by calling
249
+ def self.units_for_regex(klass,method,attribute)
250
+ list = klass.send(method).map { |item| item.send(attribute) }
251
+ list.map! { |item| item.downcase } if attribute == :name
252
+ list.join("|")
222
253
  end
223
254
 
224
255
  end