unit_measurements_us_complete 5.17.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.
- checksums.yaml +7 -0
- data/.github/workflows/main.yml +31 -0
- data/.github/workflows/pages.yml +31 -0
- data/.gitignore +12 -0
- data/.rspec +3 -0
- data/.yardopts +2 -0
- data/CHANGELOG.md +645 -0
- data/Gemfile +7 -0
- data/Gemfile.lock +56 -0
- data/LICENSE.md +21 -0
- data/README.md +511 -0
- data/Rakefile +10 -0
- data/lib/unit_measurements/arithmetic.rb +225 -0
- data/lib/unit_measurements/base.rb +177 -0
- data/lib/unit_measurements/cache.rb +173 -0
- data/lib/unit_measurements/comparison.rb +64 -0
- data/lib/unit_measurements/configuration.rb +64 -0
- data/lib/unit_measurements/conversion.rb +92 -0
- data/lib/unit_measurements/errors/blank_quantity_error.rb +21 -0
- data/lib/unit_measurements/errors/blank_unit_error.rb +21 -0
- data/lib/unit_measurements/errors/missing_primitive_unit_error.rb +24 -0
- data/lib/unit_measurements/errors/parse_error.rb +37 -0
- data/lib/unit_measurements/errors/primitive_unit_already_set_error.rb +22 -0
- data/lib/unit_measurements/errors/unit_already_defined_error.rb +37 -0
- data/lib/unit_measurements/errors/unit_error.rb +36 -0
- data/lib/unit_measurements/extras/conversion_methods.rb +87 -0
- data/lib/unit_measurements/extras/numeric_methods.rb +85 -0
- data/lib/unit_measurements/formatter.rb +58 -0
- data/lib/unit_measurements/math.rb +120 -0
- data/lib/unit_measurements/measurement.rb +539 -0
- data/lib/unit_measurements/normalizer.rb +155 -0
- data/lib/unit_measurements/parser.rb +220 -0
- data/lib/unit_measurements/unit.rb +243 -0
- data/lib/unit_measurements/unit_group.rb +297 -0
- data/lib/unit_measurements/unit_group_builder.rb +224 -0
- data/lib/unit_measurements/unit_groups/acceleration.rb +36 -0
- data/lib/unit_measurements/unit_groups/all.rb +56 -0
- data/lib/unit_measurements/unit_groups/amount_of_substance.rb +13 -0
- data/lib/unit_measurements/unit_groups/angular_acceleration.rb +14 -0
- data/lib/unit_measurements/unit_groups/angular_velocity.rb +25 -0
- data/lib/unit_measurements/unit_groups/area.rb +49 -0
- data/lib/unit_measurements/unit_groups/catalytic_activity.rb +13 -0
- data/lib/unit_measurements/unit_groups/density.rb +33 -0
- data/lib/unit_measurements/unit_groups/dynamic_viscosity.rb +22 -0
- data/lib/unit_measurements/unit_groups/electric_charge.rb +20 -0
- data/lib/unit_measurements/unit_groups/electric_conductance.rb +15 -0
- data/lib/unit_measurements/unit_groups/electric_current.rb +19 -0
- data/lib/unit_measurements/unit_groups/electric_dipole_moment.rb +13 -0
- data/lib/unit_measurements/unit_groups/electric_potential.rb +17 -0
- data/lib/unit_measurements/unit_groups/electric_quadrupole_moment.rb +14 -0
- data/lib/unit_measurements/unit_groups/electrical_capacitance.rb +15 -0
- data/lib/unit_measurements/unit_groups/electrical_elastance.rb +13 -0
- data/lib/unit_measurements/unit_groups/electrical_inductance.rb +15 -0
- data/lib/unit_measurements/unit_groups/electrical_resistance.rb +16 -0
- data/lib/unit_measurements/unit_groups/energy.rb +58 -0
- data/lib/unit_measurements/unit_groups/force.rb +47 -0
- data/lib/unit_measurements/unit_groups/frequency.rb +16 -0
- data/lib/unit_measurements/unit_groups/illuminance.rb +18 -0
- data/lib/unit_measurements/unit_groups/information_entropy.rb +15 -0
- data/lib/unit_measurements/unit_groups/kinetic_viscosity.rb +18 -0
- data/lib/unit_measurements/unit_groups/length.rb +67 -0
- data/lib/unit_measurements/unit_groups/luminance.rb +21 -0
- data/lib/unit_measurements/unit_groups/luminous_flux.rb +11 -0
- data/lib/unit_measurements/unit_groups/luminous_intensity.rb +13 -0
- data/lib/unit_measurements/unit_groups/magnetic_field.rb +13 -0
- data/lib/unit_measurements/unit_groups/magnetic_flux.rb +15 -0
- data/lib/unit_measurements/unit_groups/magnetic_induction.rb +13 -0
- data/lib/unit_measurements/unit_groups/magnetomotive_force.rb +13 -0
- data/lib/unit_measurements/unit_groups/mass_flow_rate.rb +49 -0
- data/lib/unit_measurements/unit_groups/plane_angle.rb +30 -0
- data/lib/unit_measurements/unit_groups/power.rb +54 -0
- data/lib/unit_measurements/unit_groups/pressure.rb +60 -0
- data/lib/unit_measurements/unit_groups/quantity.rb +14 -0
- data/lib/unit_measurements/unit_groups/radiation_absorbed_dose.rb +14 -0
- data/lib/unit_measurements/unit_groups/radiation_equivalent_dose.rb +13 -0
- data/lib/unit_measurements/unit_groups/radiation_exposure.rb +15 -0
- data/lib/unit_measurements/unit_groups/radioactivity.rb +14 -0
- data/lib/unit_measurements/unit_groups/solid_angle.rb +18 -0
- data/lib/unit_measurements/unit_groups/sound_level.rb +13 -0
- data/lib/unit_measurements/unit_groups/temperature.rb +19 -0
- data/lib/unit_measurements/unit_groups/time.rb +29 -0
- data/lib/unit_measurements/unit_groups/torque.rb +40 -0
- data/lib/unit_measurements/unit_groups/velocity.rb +37 -0
- data/lib/unit_measurements/unit_groups/volume.rb +67 -0
- data/lib/unit_measurements/unit_groups/volumetric_flow_rate.rb +35 -0
- data/lib/unit_measurements/unit_groups/weight.rb +55 -0
- data/lib/unit_measurements/version.rb +8 -0
- data/lib/unit_measurements.rb +7 -0
- data/unit_measurements.gemspec +43 -0
- data/units.md +843 -0
- metadata +216 -0
@@ -0,0 +1,539 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
# -*- frozen_string_literal: true -*-
|
3
|
+
# -*- warn_indent: true -*-
|
4
|
+
|
5
|
+
module UnitMeasurements
|
6
|
+
# @abstract
|
7
|
+
# The +UnitMeasurements::Measurement+ is the abstract class and serves as superclass
|
8
|
+
# for all the unit groups. It includes several modules that provide mathematical
|
9
|
+
# operations, comparison, conversion, formatting, and other functionalities.
|
10
|
+
#
|
11
|
+
# This class provides a comprehensive set of methods and functionality for working
|
12
|
+
# with measurements in different units. It includes robust error handling and
|
13
|
+
# supports conversion between units. Additionally, it ensures that measurements
|
14
|
+
# are consistently represented.
|
15
|
+
#
|
16
|
+
# You should not directly initialize a +Measurement+ instance. Instead, create
|
17
|
+
# specialized measurement types by subclassing +Measurement+ and providing
|
18
|
+
# specific units and conversions through the +build+ method defined in the
|
19
|
+
# +UnitMeasurements+ module.
|
20
|
+
#
|
21
|
+
# @see Arithmetic
|
22
|
+
# @see Comparison
|
23
|
+
# @see Conversion
|
24
|
+
# @see Formatter
|
25
|
+
# @see Math
|
26
|
+
# @see NumericMethods
|
27
|
+
# @author {Harshal V. Ladhe}[https://shivam091.github.io/]
|
28
|
+
# @since 1.0.0
|
29
|
+
class Measurement
|
30
|
+
include Arithmetic
|
31
|
+
include Comparison
|
32
|
+
include Conversion
|
33
|
+
include Formatter
|
34
|
+
include Math
|
35
|
+
|
36
|
+
extend NumericMethods
|
37
|
+
extend ConversionMethods
|
38
|
+
|
39
|
+
# Regular expression to match conversion strings.
|
40
|
+
#
|
41
|
+
# @author {Harshal V. Ladhe}[https://shivam091.github.io/]
|
42
|
+
# @since 1.0.0
|
43
|
+
CONVERSION_STRING_REGEXP = /(.+?)\s?(?:\s+(?:in|to|as)\s+(.+)|\z)/i.freeze
|
44
|
+
|
45
|
+
# Quantity of the measurement.
|
46
|
+
#
|
47
|
+
# @return [Numeric] Quantity of the measurement.
|
48
|
+
#
|
49
|
+
# @author {Harshal V. Ladhe}[https://shivam091.github.io/]
|
50
|
+
# @since 1.0.0
|
51
|
+
attr_reader :quantity
|
52
|
+
|
53
|
+
# The unit associated with the measurement.
|
54
|
+
#
|
55
|
+
# @return [Unit] The +unit+ instance associated with the measurement.
|
56
|
+
#
|
57
|
+
# @author {Harshal V. Ladhe}[https://shivam091.github.io/]
|
58
|
+
# @since 1.0.0
|
59
|
+
attr_reader :unit
|
60
|
+
|
61
|
+
# Initializes a new instance of +Measurement+ with a specified +quantity+
|
62
|
+
# and +unit+.
|
63
|
+
#
|
64
|
+
# This method is intended to be overridden by subclasses and serves as a
|
65
|
+
# placeholder for common initialization logic. It raises an error if called
|
66
|
+
# directly on the abstract +Measurement+ class.
|
67
|
+
#
|
68
|
+
# @example Initializing the measurement with scientific number and unit:
|
69
|
+
# UnitMeasurements::Length.new(BigDecimal(2), "km")
|
70
|
+
# => 2.0 km
|
71
|
+
#
|
72
|
+
# UnitMeasurements::Length.new("2e+2", "km")
|
73
|
+
# => 200.0 km
|
74
|
+
#
|
75
|
+
# UnitMeasurements::Length.new("2e²", "km")
|
76
|
+
# => 200.0 km
|
77
|
+
#
|
78
|
+
# UnitMeasurements::Length.new("2e⁻²", "km")
|
79
|
+
# => 0.02 km
|
80
|
+
#
|
81
|
+
# @example Initializing the measurement with complex number and unit:
|
82
|
+
# UnitMeasurements::Length.new(Complex(2, 3), "km")
|
83
|
+
# => 2+3i km
|
84
|
+
#
|
85
|
+
# UnitMeasurements::Length.new("2+3i", "km")
|
86
|
+
# => 2.0+3.0i km
|
87
|
+
#
|
88
|
+
# @example Initializing the measurement with rational or mixed rational number and unit:
|
89
|
+
# UnitMeasurements::Length.new(Rational(2, 3), "km")
|
90
|
+
# => 0.6666666666666666 km
|
91
|
+
#
|
92
|
+
# UnitMeasurements::Length.new(2/3r, "km")
|
93
|
+
# => 2/3 km
|
94
|
+
#
|
95
|
+
# UnitMeasurements::Length.new("2/3", "km")
|
96
|
+
# => 0.6666666666666666 km
|
97
|
+
#
|
98
|
+
# UnitMeasurements::Length.new("½", "km")
|
99
|
+
# => 0.5 km
|
100
|
+
#
|
101
|
+
# UnitMeasurements::Length.new("2 ½", "km")
|
102
|
+
# => 2.5 km
|
103
|
+
#
|
104
|
+
# @example Initializing the measurement with ratio and unit:
|
105
|
+
# UnitMeasurements::Length.new("1:2", "km")
|
106
|
+
# => 0.5 km
|
107
|
+
#
|
108
|
+
# @param [Numeric|String] quantity The quantity of the measurement.
|
109
|
+
# @param [String|Unit] unit The unit of the measurement.
|
110
|
+
#
|
111
|
+
# @raise [BlankQuantityError] If +quantity+ is blank.
|
112
|
+
# @raise [BlankUnitError] If +unit+ is blank.
|
113
|
+
#
|
114
|
+
# @see BlankQuantityError
|
115
|
+
# @see BlankUnitError
|
116
|
+
# @author {Harshal V. Ladhe}[https://shivam091.github.io/]
|
117
|
+
# @since 1.0.0
|
118
|
+
def initialize(quantity, unit)
|
119
|
+
raise BlankQuantityError if quantity.blank?
|
120
|
+
raise BlankUnitError if unit.blank?
|
121
|
+
|
122
|
+
@quantity = convert_quantity(quantity)
|
123
|
+
@unit = unit_from_unit_or_name!(unit)
|
124
|
+
end
|
125
|
+
|
126
|
+
# Converts the measurement to a +target_unit+ and returns new instance of the
|
127
|
+
# measurement.
|
128
|
+
#
|
129
|
+
# When +use_cache+ value is true, conversion factor between units are checked
|
130
|
+
# in cache file of the unit group. If cached conversion factor is present in
|
131
|
+
# the cache file, it is used for conversion otherwise conversion factor is
|
132
|
+
# stored in the cache before converting the measurement to the +target_unit+.
|
133
|
+
#
|
134
|
+
# @example
|
135
|
+
# UnitMeasurements::Length.new(1, "m").convert_to("cm")
|
136
|
+
# => 100.0 cm
|
137
|
+
#
|
138
|
+
# UnitMeasurements::Length.new(1, "m").convert_to("mm", use_cache: true)
|
139
|
+
# => 1000.0 cm
|
140
|
+
#
|
141
|
+
# @param [String|Symbol] target_unit
|
142
|
+
# The target unit for conversion.
|
143
|
+
# @param [TrueClass|FalseClass] use_cache
|
144
|
+
# Indicates whether to use cached conversion factors.
|
145
|
+
#
|
146
|
+
# @return [Measurement]
|
147
|
+
# A new +Measurement+ instance with the converted +quantity+ and
|
148
|
+
# +target unit+.
|
149
|
+
#
|
150
|
+
# @author {Harshal V. Ladhe}[https://shivam091.github.io/]
|
151
|
+
# @since 1.0.0
|
152
|
+
def convert_to(target_unit, use_cache: false)
|
153
|
+
target_unit = unit_from_unit_or_name!(target_unit)
|
154
|
+
return self if target_unit == unit
|
155
|
+
|
156
|
+
conversion_factor = calculate_conversion_factor(target_unit, use_cache)
|
157
|
+
|
158
|
+
self.class.new((quantity * conversion_factor), target_unit)
|
159
|
+
end
|
160
|
+
alias_method :to, :convert_to
|
161
|
+
alias_method :in, :convert_to
|
162
|
+
alias_method :as, :convert_to
|
163
|
+
|
164
|
+
# Converts the measurement to a +target_unit+ and updates the current instance.
|
165
|
+
#
|
166
|
+
# @example
|
167
|
+
# UnitMeasurements::Length.new(1, "m").convert_to!("cm")
|
168
|
+
# => 100.0 cm
|
169
|
+
#
|
170
|
+
# UnitMeasurements::Length.new(1, "m").convert_to!("mm", use_cache: true)
|
171
|
+
# => 1000.0 mm
|
172
|
+
#
|
173
|
+
# @param [String|Symbol] target_unit
|
174
|
+
# The target unit for conversion.
|
175
|
+
# @param [TrueClass|FalseClass] use_cache
|
176
|
+
# Indicates whether to use cached conversion factors.
|
177
|
+
#
|
178
|
+
# @return [Measurement]
|
179
|
+
# The current +Measurement+ instance with updated +quantity+ and +unit+.
|
180
|
+
#
|
181
|
+
# @see #convert_to
|
182
|
+
# @author {Harshal V. Ladhe}[https://shivam091.github.io/]
|
183
|
+
# @since 1.0.0
|
184
|
+
def convert_to!(target_unit, use_cache: false)
|
185
|
+
measurement = convert_to(target_unit, use_cache: use_cache)
|
186
|
+
@quantity, @unit = measurement.quantity, measurement.unit
|
187
|
+
|
188
|
+
self
|
189
|
+
end
|
190
|
+
alias_method :to!, :convert_to!
|
191
|
+
alias_method :in!, :convert_to!
|
192
|
+
alias_method :as!, :convert_to!
|
193
|
+
|
194
|
+
# Converts the measurement to its primitive unit and returns a new instance
|
195
|
+
# of the +Measurement+.
|
196
|
+
#
|
197
|
+
# The method first retrieves the primitive unit of the unit group associated
|
198
|
+
# with the measurement. If the primitive unit is not set, it raises a
|
199
|
+
# +MissingPrimitiveUnitError+.
|
200
|
+
#
|
201
|
+
# @example
|
202
|
+
# UnitMeasurements::Length.new(1, "m").to_primitive
|
203
|
+
# => 1 m
|
204
|
+
#
|
205
|
+
# UnitMeasurements::Length.new(1, "cm").to_primitive
|
206
|
+
# => 0.01 m
|
207
|
+
#
|
208
|
+
# @param [TrueClass|FalseClass] use_cache
|
209
|
+
# Indicates whether to use cached conversion factors.
|
210
|
+
# @return [Measurement]
|
211
|
+
# A new +Measurement+ instance representing the measurement in its
|
212
|
+
# primitive unit.
|
213
|
+
#
|
214
|
+
# @raise [MissingPrimitiveUnitError]
|
215
|
+
# If the primitive unit is not set for the unit group associated with the
|
216
|
+
# measurement.
|
217
|
+
#
|
218
|
+
# @author {Harshal V. Ladhe}[https://shivam091.github.io/]
|
219
|
+
# @since 5.13.0
|
220
|
+
def to_primitive(use_cache: false)
|
221
|
+
primitive_unit = self.class.primitive
|
222
|
+
raise MissingPrimitiveUnitError if primitive_unit.nil?
|
223
|
+
|
224
|
+
convert_to(primitive_unit, use_cache: use_cache)
|
225
|
+
end
|
226
|
+
alias_method :in_primitive, :to_primitive
|
227
|
+
alias_method :as_primitive, :to_primitive
|
228
|
+
|
229
|
+
# Returns an object representation of the +Measurement+.
|
230
|
+
#
|
231
|
+
# @param [TrueClass|FalseClass] dump If +true+, returns the dump representation.
|
232
|
+
#
|
233
|
+
# @return [Object] An object representation of the +Measurement+.
|
234
|
+
#
|
235
|
+
# @author {Harshal V. Ladhe}[https://shivam091.github.io/]
|
236
|
+
# @since 1.0.0
|
237
|
+
def inspect(dump: false)
|
238
|
+
dump ? super() : to_s
|
239
|
+
end
|
240
|
+
|
241
|
+
# Returns a string representation of the +Measurement+.
|
242
|
+
#
|
243
|
+
# @return [String] A string representation of the +Measurement+.
|
244
|
+
#
|
245
|
+
# @author {Harshal V. Ladhe}[https://shivam091.github.io/]
|
246
|
+
# @since 1.0.0
|
247
|
+
def to_s
|
248
|
+
"#{quantity} #{unit}"
|
249
|
+
end
|
250
|
+
|
251
|
+
# Returns the +quantity+ of the +measurement+.
|
252
|
+
#
|
253
|
+
# @return [Numeric] Quantity of the measurement.
|
254
|
+
#
|
255
|
+
# @author {Harshal V. Ladhe}[https://shivam091.github.io/]
|
256
|
+
# @since 1.5.0
|
257
|
+
def quantity
|
258
|
+
case @quantity
|
259
|
+
when Rational
|
260
|
+
@quantity.denominator == 1 ? @quantity.numerator : @quantity
|
261
|
+
else
|
262
|
+
@quantity
|
263
|
+
end
|
264
|
+
end
|
265
|
+
|
266
|
+
class << self
|
267
|
+
extend Forwardable
|
268
|
+
|
269
|
+
# Methods delegated from the unit group.
|
270
|
+
def_delegators :unit_group, :primitive, :units, :cache_file, :unit_names,
|
271
|
+
:unit_with_name_and_aliases, :unit_names_with_aliases,
|
272
|
+
:unit_for, :unit_for!, :defined?, :unit_or_alias?, :[],
|
273
|
+
:units_for, :units_for!, :systems
|
274
|
+
|
275
|
+
# Parses an input string and returns a +Measurement+ instance depending on
|
276
|
+
# the input string. This method first normalizes the +input+ internally,
|
277
|
+
# using the +Normalizer+ before parsing it using the +Parser+.
|
278
|
+
#
|
279
|
+
# You can separate *source* and *target* units from each other in +input+
|
280
|
+
# using +to+, +in+, or +as+.
|
281
|
+
#
|
282
|
+
# If only the source unit is provided, it returns a new +Measurement+
|
283
|
+
# instance with the quantity in the source unit. If both source and target
|
284
|
+
# units are provided in the input string, it returns a new +Measurement+
|
285
|
+
# instance with the quantity converted to the target unit.
|
286
|
+
#
|
287
|
+
# @example Parsing string representing a complex number and source unit:
|
288
|
+
# UnitMeasurements::Length.parse("2+3i km")
|
289
|
+
# => 2.0+3.0i km
|
290
|
+
#
|
291
|
+
# @example Parsing string representing a complex number, source, and target units:
|
292
|
+
# UnitMeasurements::Length.parse("2+3i km in m")
|
293
|
+
# => 2000.0+3000.0i m
|
294
|
+
#
|
295
|
+
# @example Parsing string representing a rational or mixed rational number and source unit:
|
296
|
+
# UnitMeasurements::Length.parse("½ km")
|
297
|
+
# => 0.5 km
|
298
|
+
#
|
299
|
+
# UnitMeasurements::Length.parse("2/3 km")
|
300
|
+
# => 0.666666666666667 km
|
301
|
+
#
|
302
|
+
# UnitMeasurements::Length.parse("2 ½ km")
|
303
|
+
# => 2.5 km
|
304
|
+
#
|
305
|
+
# UnitMeasurements::Length.parse("2 1/2 km")
|
306
|
+
# => 2.5 km
|
307
|
+
#
|
308
|
+
# @example Parsing string representing a rational or mixed rational number, source, and target units:
|
309
|
+
# UnitMeasurements::Length.parse("½ km to m")
|
310
|
+
# => 500.0 km
|
311
|
+
#
|
312
|
+
# UnitMeasurements::Length.parse("2/3 km to m")
|
313
|
+
# => 666.666666666667 m
|
314
|
+
#
|
315
|
+
# UnitMeasurements::Length.parse("2 ½ km in m")
|
316
|
+
# => 2500.0 m
|
317
|
+
#
|
318
|
+
# UnitMeasurements::Length.parse("2 1/2 km as m")
|
319
|
+
# => 2500.0 m
|
320
|
+
#
|
321
|
+
# @example Parsing string representing a scientific number and source unit:
|
322
|
+
# UnitMeasurements::Length.parse("2e² km")
|
323
|
+
# => 200.0 km
|
324
|
+
#
|
325
|
+
# UnitMeasurements::Length.parse("2e+2 km")
|
326
|
+
# => 200.0 km
|
327
|
+
#
|
328
|
+
# UnitMeasurements::Length.parse("2e⁻² km")
|
329
|
+
# => 0.02 km
|
330
|
+
#
|
331
|
+
# @example Parsing string representing a scientific number, source, and target units:
|
332
|
+
#
|
333
|
+
# UnitMeasurements::Length.parse("2e+2 km to m")
|
334
|
+
# => 200000.0 m
|
335
|
+
#
|
336
|
+
# UnitMeasurements::Length.parse("2e⁻² km as m")
|
337
|
+
# => 20.0 m
|
338
|
+
#
|
339
|
+
# @example Parsing string representing a ratio and source unit:
|
340
|
+
# UnitMeasurements::Length.parse("1:2 km")
|
341
|
+
# => 0.5 km
|
342
|
+
#
|
343
|
+
# @example Parsing string representing a ratio, source, and target units:
|
344
|
+
# UnitMeasurements::Length.parse("1:2 km in m")
|
345
|
+
# => 500.0 m
|
346
|
+
#
|
347
|
+
# @param [String] input The input string to be parsed.
|
348
|
+
# @param [TrueClass|FalseClass] use_cache
|
349
|
+
# Indicates whether to use cached conversion factors.
|
350
|
+
#
|
351
|
+
# @return [Measurement] The +Measurement+ instance.
|
352
|
+
#
|
353
|
+
# @see Parser
|
354
|
+
# @see Normalizer
|
355
|
+
# @see CONVERSION_STRING_REGEXP
|
356
|
+
# @see ._parse
|
357
|
+
# @see #convert_to
|
358
|
+
# @author {Harshal V. Ladhe}[https://shivam091.github.io/]
|
359
|
+
# @since 1.0.0
|
360
|
+
def parse(input, use_cache: false)
|
361
|
+
input = Normalizer.normalize(input)
|
362
|
+
source, target = input.match(CONVERSION_STRING_REGEXP)&.captures
|
363
|
+
|
364
|
+
target ? _parse(source).convert_to(target, use_cache: use_cache) : _parse(source)
|
365
|
+
end
|
366
|
+
|
367
|
+
# Returns the +Cache+ instance for the unit group to store and retrieve
|
368
|
+
# conversion factors.
|
369
|
+
#
|
370
|
+
# @return [Cache] The +Cache+ instance.
|
371
|
+
#
|
372
|
+
# @example
|
373
|
+
# UnitMeasurements::Length.cached
|
374
|
+
# => #<UnitMeasurements::Cache:0x00007fe407249750>
|
375
|
+
#
|
376
|
+
# @see Cache
|
377
|
+
# @author {Harshal V. Ladhe}[https://shivam091.github.io/]
|
378
|
+
# @since 5.2.0
|
379
|
+
def cached
|
380
|
+
@cached ||= Cache.new(self)
|
381
|
+
end
|
382
|
+
|
383
|
+
# Clears the cached conversion factors of the unit group.
|
384
|
+
#
|
385
|
+
# @return [void]
|
386
|
+
#
|
387
|
+
# @example
|
388
|
+
# UnitMeasurements::Length.clear_cache
|
389
|
+
#
|
390
|
+
# @see Cache#clear_cache
|
391
|
+
#
|
392
|
+
# @author {Harshal V. Ladhe}[https://shivam091.github.io/]
|
393
|
+
# @since 5.2.0
|
394
|
+
def clear_cache
|
395
|
+
cached.clear_cache
|
396
|
+
end
|
397
|
+
|
398
|
+
# Calculates the ratio between two units.
|
399
|
+
#
|
400
|
+
# This method takes a source unit and a target unit, and returns the ratio
|
401
|
+
# between them as a string representation.
|
402
|
+
#
|
403
|
+
# @example Calculating the ratio between 'in' and 'ft':
|
404
|
+
# UnitMeasurements::Length.ratio("in", "ft")
|
405
|
+
# => "12.0 in/ft"
|
406
|
+
#
|
407
|
+
# UnitMeasurements::Length.ratio(UnitMeasurements::Length.unit_for("in"), "ft")
|
408
|
+
# => "12.0 in/ft"
|
409
|
+
#
|
410
|
+
# @param [Unit|String|Symbol] source_unit
|
411
|
+
# The source unit for the ratio calculation.
|
412
|
+
# @param [Unit|String|Symbol] target_unit
|
413
|
+
# The target unit for the ratio calculation.
|
414
|
+
#
|
415
|
+
# @return [String] The ratio between the source and target units.
|
416
|
+
#
|
417
|
+
# @raise [UnitError]
|
418
|
+
# If either the source unit or the target unit is not found in the unit group.
|
419
|
+
#
|
420
|
+
# @author {Harshal V. Ladhe}[https://shivam091.github.io/]
|
421
|
+
# @since 5.11.0
|
422
|
+
def ratio(source_unit, target_unit)
|
423
|
+
source_unit = source_unit.is_a?(Unit) ? source_unit : unit_for!(source_unit)
|
424
|
+
target_unit = target_unit.is_a?(Unit) ? target_unit : unit_for!(target_unit)
|
425
|
+
|
426
|
+
source_quantity = 1
|
427
|
+
target_quantity = new(source_quantity, target_unit).convert_to(source_unit).quantity
|
428
|
+
|
429
|
+
"#{target_quantity} #{source_unit}/#{target_unit}"
|
430
|
+
end
|
431
|
+
|
432
|
+
private
|
433
|
+
|
434
|
+
# @private
|
435
|
+
# The class attribute representing an instance of +UnitGroup+.
|
436
|
+
#
|
437
|
+
# @return [UnitGroup] An instance of +UnitGroup+.
|
438
|
+
#
|
439
|
+
# @raise [BaseError]
|
440
|
+
# If directly invoked on +Measurement+ rather than its subclasses.
|
441
|
+
#
|
442
|
+
# @see UnitGroup
|
443
|
+
# @author {Harshal V. Ladhe}[https://shivam091.github.io/]
|
444
|
+
# @since 1.0.0
|
445
|
+
def unit_group
|
446
|
+
raise BaseError, "`Measurement` does not have a `unit_group` instance. You cannot directly use `Measurement`. Instead, build a new unit group by calling `UnitMeasurements.build`."
|
447
|
+
end
|
448
|
+
|
449
|
+
# @private
|
450
|
+
# Parses the normalized string to return the +Measurement+ instance.
|
451
|
+
#
|
452
|
+
# @param [String] string String to be parsed.
|
453
|
+
#
|
454
|
+
# @return [Measurement] The +Measurement+ instance.
|
455
|
+
#
|
456
|
+
# @see Parser.parse
|
457
|
+
# @author {Harshal V. Ladhe}[https://shivam091.github.io/]
|
458
|
+
# @since 1.0.0
|
459
|
+
def _parse(string)
|
460
|
+
quantity, unit = Parser.parse(string)
|
461
|
+
|
462
|
+
new(quantity, unit)
|
463
|
+
end
|
464
|
+
end
|
465
|
+
|
466
|
+
private
|
467
|
+
|
468
|
+
# @private
|
469
|
+
# Converts the measurement quantity to a suitable format for internal use.
|
470
|
+
#
|
471
|
+
# @param [Numeric|String] quantity The quantity of the measurement.
|
472
|
+
#
|
473
|
+
# @return [Numeric] The converted quantity.
|
474
|
+
#
|
475
|
+
# @author {Harshal V. Ladhe}[https://shivam091.github.io/]
|
476
|
+
# @since 1.0.0
|
477
|
+
def convert_quantity(quantity)
|
478
|
+
case quantity
|
479
|
+
when Float
|
480
|
+
BigDecimal(quantity, Float::DIG)
|
481
|
+
when Integer
|
482
|
+
Rational(quantity)
|
483
|
+
when String
|
484
|
+
quantity = Normalizer.normalize(quantity)
|
485
|
+
quantity, _ = Parser.parse(quantity)
|
486
|
+
|
487
|
+
quantity
|
488
|
+
else
|
489
|
+
quantity
|
490
|
+
end
|
491
|
+
end
|
492
|
+
|
493
|
+
# @private
|
494
|
+
# Returns the +Unit+ instance associated with the +value+ provided.
|
495
|
+
#
|
496
|
+
# @param [String|Unit] value
|
497
|
+
# The value representing a unit name or +Unit+ instance.
|
498
|
+
#
|
499
|
+
# @return [Unit] The +Unit+ instance associated with +value+.
|
500
|
+
#
|
501
|
+
# @author {Harshal V. Ladhe}[https://shivam091.github.io/]
|
502
|
+
# @since 1.0.0
|
503
|
+
def unit_from_unit_or_name!(value)
|
504
|
+
value.is_a?(Unit) ? value : self.class.unit_for!(value)
|
505
|
+
end
|
506
|
+
|
507
|
+
# Calculates the conversion factor between the current unit and the target
|
508
|
+
# unit.
|
509
|
+
#
|
510
|
+
# If caching is enabled and a cached factor is available, it will be used.
|
511
|
+
# Otherwise, the conversion factor will be computed and, if caching is
|
512
|
+
# enabled, stored in the cache.
|
513
|
+
#
|
514
|
+
# @param [Unit] target_unit The target unit for conversion.
|
515
|
+
# @param [TrueClass|FalseClass] use_cache
|
516
|
+
# Indicates whether caching should be used.
|
517
|
+
#
|
518
|
+
# @return [Numeric] The conversion factor.
|
519
|
+
#
|
520
|
+
# @see Unit
|
521
|
+
# @see #convert_to
|
522
|
+
#
|
523
|
+
# @note If caching is enabled, the calculated conversion factor will be stored in the cache.
|
524
|
+
#
|
525
|
+
# @author {Harshal V. Ladhe}[https://shivam091.github.io/]
|
526
|
+
# @since 5.2.0
|
527
|
+
def calculate_conversion_factor(target_unit, use_cache)
|
528
|
+
use_cache = (UnitMeasurements.configuration.use_cache || use_cache)
|
529
|
+
|
530
|
+
if use_cache && (cached_factor = self.class.cached.get(unit.name, target_unit.name))
|
531
|
+
cached_factor
|
532
|
+
else
|
533
|
+
factor = unit.conversion_factor / target_unit.conversion_factor
|
534
|
+
self.class.cached.set(unit.name, target_unit.name, factor) if use_cache
|
535
|
+
factor
|
536
|
+
end
|
537
|
+
end
|
538
|
+
end
|
539
|
+
end
|
@@ -0,0 +1,155 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
# -*- frozen_stringing_literal: true -*-
|
3
|
+
# -*- warn_indent: true -*-
|
4
|
+
|
5
|
+
module UnitMeasurements
|
6
|
+
# The +UnitMeasurements::Normalizer+ class defines useful methods for handling
|
7
|
+
# input strings that may contain non-standard representations of numbers.
|
8
|
+
#
|
9
|
+
# It maps special symbols of exponents and fractions to their standard
|
10
|
+
# counterparts. The class ensures consistent handling of input strings within
|
11
|
+
# the library.
|
12
|
+
#
|
13
|
+
# @see Measurement
|
14
|
+
# @author {Harshal V. Ladhe}[https://shivam091.github.io/]
|
15
|
+
# @since 1.0.0
|
16
|
+
class Normalizer
|
17
|
+
# A mapping of superscript numbers, plus, and minus signs to their regular
|
18
|
+
# counterparts.
|
19
|
+
#
|
20
|
+
# @author {Harshal V. Ladhe}[https://shivam091.github.io/]
|
21
|
+
# @since 1.0.0
|
22
|
+
EXPONENTS_SYMBOLS = {
|
23
|
+
"⁰" => "0",
|
24
|
+
"¹" => "1",
|
25
|
+
"²" => "2",
|
26
|
+
"³" => "3",
|
27
|
+
"⁴" => "4",
|
28
|
+
"⁵" => "5",
|
29
|
+
"⁶" => "6",
|
30
|
+
"⁷" => "7",
|
31
|
+
"⁸" => "8",
|
32
|
+
"⁹" => "9",
|
33
|
+
"⁺" => "+",
|
34
|
+
"⁻" => "-",
|
35
|
+
}.freeze
|
36
|
+
|
37
|
+
# A mapping of special fraction symbols to their equivalent numeric fractions.
|
38
|
+
#
|
39
|
+
# @author {Harshal V. Ladhe}[https://shivam091.github.io/]
|
40
|
+
# @since 1.0.0
|
41
|
+
FRACTIONS_SYMBOLS = {
|
42
|
+
"¼" => "1/4",
|
43
|
+
"½" => "1/2",
|
44
|
+
"¾" => "3/4",
|
45
|
+
"⅓" => "1/3",
|
46
|
+
"⅔" => "2/3",
|
47
|
+
"⅕" => "1/5",
|
48
|
+
"⅖" => "2/5",
|
49
|
+
"⅗" => "3/5",
|
50
|
+
"⅘" => "4/5",
|
51
|
+
"⅙" => "1/6",
|
52
|
+
"⅚" => "5/6",
|
53
|
+
"⅐" => "1/7",
|
54
|
+
"⅛" => "1/8",
|
55
|
+
"⅜" => "3/8",
|
56
|
+
"⅝" => "5/8",
|
57
|
+
"⅞" => "7/8",
|
58
|
+
"⅑" => "1/9",
|
59
|
+
"⅒" => "1/10",
|
60
|
+
"↉" => "0/3",
|
61
|
+
}.freeze
|
62
|
+
|
63
|
+
# Matches a combination of digits, optional exponent notation, and optional plus or minus sign.
|
64
|
+
#
|
65
|
+
# @author {Harshal V. Ladhe}[https://shivam091.github.io/]
|
66
|
+
# @since 1.0.0
|
67
|
+
EXPONENT_REGEX = /
|
68
|
+
( # Start of the first capturing group
|
69
|
+
[\d]+ # One or more digits
|
70
|
+
[Ee]? # Match an optional 'E' or 'e' for scientific notation
|
71
|
+
[+-]? # Match an optional plus or minus sign
|
72
|
+
) # End of the first capturing group
|
73
|
+
( # Start of the second capturing group
|
74
|
+
#{EXPONENTS_SYMBOLS.keys.join("|")} # Match any of the exponent symbols defined in EXPONENTS_SYMBOLS
|
75
|
+
) # End of the second capturing group
|
76
|
+
/x.freeze
|
77
|
+
|
78
|
+
# Matches special fraction symbols.
|
79
|
+
#
|
80
|
+
# @author {Harshal V. Ladhe}[https://shivam091.github.io/]
|
81
|
+
# @since 1.0.0
|
82
|
+
FRACTION_REGEX = /
|
83
|
+
( # Start of the capturing group
|
84
|
+
#{FRACTIONS_SYMBOLS.keys.join("|")} # Match any of the fraction symbols defined in FRACTIONS_SYMBOLS
|
85
|
+
) # End of the capturing group
|
86
|
+
/x.freeze
|
87
|
+
|
88
|
+
# Matches a ratio in the format of +numerator:denominator+.
|
89
|
+
#
|
90
|
+
# @author {Harshal V. Ladhe}[https://shivam091.github.io/]
|
91
|
+
# @since 1.0.0
|
92
|
+
RATIO_REGEX = /
|
93
|
+
( # Start of the first capturing group
|
94
|
+
[\d]+ # One or more digits, representing the numerator
|
95
|
+
) # End of the first capturing group
|
96
|
+
: # Match a colon (:)
|
97
|
+
( # Start of the second capturing group
|
98
|
+
[\d]+ # One or more digits, representing the denominator
|
99
|
+
) # End of the second capturing group
|
100
|
+
/x.freeze
|
101
|
+
|
102
|
+
|
103
|
+
class << self
|
104
|
+
# Normalizes a string containing special symbols of exponents and fractions,
|
105
|
+
# and ratio.
|
106
|
+
#
|
107
|
+
# The +normalize+ method in the +UnitMeasurements::Normalizer+ module processes
|
108
|
+
# the input string to replace special symbols with their equivalent representations,
|
109
|
+
# such as converting superscript numbers to regular numbers, special fraction
|
110
|
+
# symbols to their numeric fractions, and ratios to fractional format, and
|
111
|
+
# remove leading and trailing whitespaces from the +string+.
|
112
|
+
#
|
113
|
+
# @example Normalizing special symbol of fraction:
|
114
|
+
# UnitMeasurements::Normalizer.normalize("¾")
|
115
|
+
# => "3/4"
|
116
|
+
#
|
117
|
+
# UnitMeasurements::Normalizer.normalize(" 1¾ ")
|
118
|
+
# => "1 3/4"
|
119
|
+
#
|
120
|
+
# @example Normalizing ratio:
|
121
|
+
# UnitMeasurements::Normalizer.normalize("1:2")
|
122
|
+
# => "1/2"
|
123
|
+
#
|
124
|
+
# @example Normalizing special symbol of exponents:
|
125
|
+
# UnitMeasurements::Normalizer.normalize("1e⁺²")
|
126
|
+
# => "1e+2"
|
127
|
+
#
|
128
|
+
# UnitMeasurements::Normalizer.normalize("1e⁻²")
|
129
|
+
# => "1e-2"
|
130
|
+
#
|
131
|
+
# @param [String] string The input string to be normalized.
|
132
|
+
#
|
133
|
+
# @return [String] The normalized version of the input string.
|
134
|
+
#
|
135
|
+
# @see Measurement
|
136
|
+
# @author {Harshal V. Ladhe}[https://shivam091.github.io/]
|
137
|
+
# @since 1.0.0
|
138
|
+
def normalize(string)
|
139
|
+
string.dup.tap do |str|
|
140
|
+
if str =~ Regexp.new(EXPONENT_REGEX)
|
141
|
+
EXPONENTS_SYMBOLS.each do |search, replace|
|
142
|
+
str.gsub!(search) { "#{replace}" }
|
143
|
+
end
|
144
|
+
end
|
145
|
+
|
146
|
+
str.gsub!(FRACTION_REGEX) { " #{FRACTIONS_SYMBOLS[$1]}" }
|
147
|
+
|
148
|
+
str.gsub!(RATIO_REGEX) { "#{$1.to_i}/#{$2.to_i}" }
|
149
|
+
|
150
|
+
str.strip!
|
151
|
+
end
|
152
|
+
end
|
153
|
+
end
|
154
|
+
end
|
155
|
+
end
|