quantify 1.0.5 → 1.1.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.
@@ -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