unit_measurements 4.8.0 → 4.10.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 +4 -4
- data/.github/workflows/main.yml +12 -11
- data/.github/workflows/pages.yml +31 -0
- data/CHANGELOG.md +19 -1
- data/Gemfile.lock +1 -1
- data/README.md +36 -69
- data/lib/unit_measurements/arithmetic.rb +91 -20
- data/lib/unit_measurements/base.rb +56 -0
- data/lib/unit_measurements/comparison.rb +45 -4
- data/lib/unit_measurements/conversion.rb +40 -10
- data/lib/unit_measurements/errors/parse_error.rb +20 -0
- data/lib/unit_measurements/errors/primitive_unit_already_set_error.rb +11 -0
- data/lib/unit_measurements/errors/unit_already_defined_error.rb +20 -0
- data/lib/unit_measurements/errors/unit_error.rb +19 -0
- data/lib/unit_measurements/formatter.rb +31 -9
- data/lib/unit_measurements/math.rb +51 -14
- data/lib/unit_measurements/measurement.rb +251 -8
- data/lib/unit_measurements/normalizer.rb +92 -3
- data/lib/unit_measurements/parser.rb +169 -7
- data/lib/unit_measurements/unit.rb +128 -1
- data/lib/unit_measurements/unit_group.rb +160 -11
- data/lib/unit_measurements/unit_group_builder.rb +133 -0
- data/lib/unit_measurements/unit_groups/all.rb +1 -6
- data/lib/unit_measurements/unit_groups/information_entropy.rb +15 -0
- data/lib/unit_measurements/version.rb +2 -1
- data/lib/unit_measurements.rb +16 -0
- data/unit_measurements.gemspec +1 -0
- data/units.md +13 -0
- metadata +5 -2
@@ -3,6 +3,28 @@
|
|
3
3
|
# -*- warn_indent: true -*-
|
4
4
|
|
5
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
|
+
# @author {Harshal V. Ladhe}[https://shivam091.github.io/]
|
27
|
+
# @since 1.0.0
|
6
28
|
class Measurement
|
7
29
|
include Arithmetic
|
8
30
|
include Comparison
|
@@ -10,10 +32,61 @@ module UnitMeasurements
|
|
10
32
|
include Formatter
|
11
33
|
include Math
|
12
34
|
|
35
|
+
# Regular expression to match conversion strings.
|
13
36
|
CONVERSION_STRING_REGEXP = /(.+?)\s?(?:\s+(?:in|to|as)\s+(.+)|\z)/i.freeze
|
14
37
|
|
15
|
-
|
38
|
+
# Quantity of the measurement.
|
39
|
+
attr_reader :quantity
|
16
40
|
|
41
|
+
# The unit associated with the measurement.
|
42
|
+
attr_reader :unit
|
43
|
+
|
44
|
+
# Initializes a new instance of +Measurement+ with a specified +quantity+
|
45
|
+
# and +unit+.
|
46
|
+
#
|
47
|
+
# This method is intended to be overridden by subclasses and serves as a
|
48
|
+
# placeholder for common initialization logic. It raises an error if called
|
49
|
+
# directly on the abstract +Measurement+ class.
|
50
|
+
#
|
51
|
+
# @example Initializing the measurement with scientific number and unit:
|
52
|
+
# UnitMeasurements::Length.new(BigDecimal(2), "km")
|
53
|
+
# => 2.0 km
|
54
|
+
#
|
55
|
+
# UnitMeasurements::Length.new("2e+2", "km")
|
56
|
+
# => 200.0 km
|
57
|
+
#
|
58
|
+
# @example Initializing the measurement with complex number and unit:
|
59
|
+
# UnitMeasurements::Length.new(Complex(2, 3), "km")
|
60
|
+
# => 2+3i km
|
61
|
+
#
|
62
|
+
# UnitMeasurements::Length.new("2+3i", "km")
|
63
|
+
# => 2.0+3.0i km
|
64
|
+
#
|
65
|
+
# @example Initializing the measurement with rational or mixed rational number and unit:
|
66
|
+
# UnitMeasurements::Length.new(Rational(2, 3), "km")
|
67
|
+
# => 0.6666666666666666 km
|
68
|
+
#
|
69
|
+
# UnitMeasurements::Length.new(2/3r, "km")
|
70
|
+
# => 2/3 km
|
71
|
+
#
|
72
|
+
# UnitMeasurements::Length.new("2/3", "km")
|
73
|
+
# => 0.6666666666666666 km
|
74
|
+
#
|
75
|
+
# UnitMeasurements::Length.new("½", "km")
|
76
|
+
# => 0.5 km
|
77
|
+
#
|
78
|
+
# @example Initializing the measurement with ratio and unit:
|
79
|
+
# UnitMeasurements::Length.new("1:2", "km")
|
80
|
+
# => 0.5 km
|
81
|
+
#
|
82
|
+
# @param [Numeric|String] quantity The quantity of the measurement.
|
83
|
+
# @param [String|Unit] unit The unit of the measurement.
|
84
|
+
#
|
85
|
+
# @raise [BaseError] If +quantity+ or +unit+ is blank.
|
86
|
+
#
|
87
|
+
# @see BaseError
|
88
|
+
# @author {Harshal V. Ladhe}[https://shivam091.github.io/]
|
89
|
+
# @since 1.0.0
|
17
90
|
def initialize(quantity, unit)
|
18
91
|
raise BaseError, "Quantity cannot be blank." if quantity.blank?
|
19
92
|
raise BaseError, "Unit cannot be blank." if unit.blank?
|
@@ -22,6 +95,20 @@ module UnitMeasurements
|
|
22
95
|
@unit = unit_from_unit_or_name!(unit)
|
23
96
|
end
|
24
97
|
|
98
|
+
# Converts the measurement to a +target_unit+.
|
99
|
+
#
|
100
|
+
# @example
|
101
|
+
# UnitMeasurements::Length.new(1, "m").convert_to("cm")
|
102
|
+
# => 100.0 cm
|
103
|
+
#
|
104
|
+
# @param [String|Symbol] target_unit The target unit for conversion.
|
105
|
+
#
|
106
|
+
# @return [Measurement]
|
107
|
+
# A new +Measurement+ instance with the converted +quantity+ and
|
108
|
+
# +target unit+.
|
109
|
+
#
|
110
|
+
# @author {Harshal V. Ladhe}[https://shivam091.github.io/]
|
111
|
+
# @since 1.0.0
|
25
112
|
def convert_to(target_unit)
|
26
113
|
target_unit = unit_from_unit_or_name!(target_unit)
|
27
114
|
|
@@ -31,24 +118,61 @@ module UnitMeasurements
|
|
31
118
|
|
32
119
|
self.class.new((quantity * conversion_factor), target_unit)
|
33
120
|
end
|
34
|
-
|
121
|
+
alias_method :to, :convert_to
|
122
|
+
alias_method :in, :convert_to
|
123
|
+
alias_method :as, :convert_to
|
35
124
|
|
125
|
+
# Converts the measurement to a +target_unit+ and updates the current instance.
|
126
|
+
#
|
127
|
+
# @example
|
128
|
+
# UnitMeasurements::Length.new(1, "m").convert_to!("cm")
|
129
|
+
# => 100.0 cm
|
130
|
+
#
|
131
|
+
# @param [String|Symbol] target_unit The target unit for conversion.
|
132
|
+
#
|
133
|
+
# @return [Measurement]
|
134
|
+
# The current +Measurement+ instance with updated +quantity+ and +unit+.
|
135
|
+
#
|
136
|
+
# @author {Harshal V. Ladhe}[https://shivam091.github.io/]
|
137
|
+
# @since 1.0.0
|
36
138
|
def convert_to!(target_unit)
|
37
139
|
measurement = convert_to(target_unit)
|
38
140
|
@quantity, @unit = measurement.quantity, measurement.unit
|
39
141
|
|
40
142
|
self
|
41
143
|
end
|
42
|
-
|
144
|
+
alias_method :to!, :convert_to!
|
145
|
+
alias_method :in!, :convert_to!
|
146
|
+
alias_method :as!, :convert_to!
|
43
147
|
|
148
|
+
# Returns an object representation of the +Measurement+.
|
149
|
+
#
|
150
|
+
# @param [TrueClass|FalseClass] dump If +true+, returns the dump representation.
|
151
|
+
#
|
152
|
+
# @return [Object] An object representation of the +Measurement+.
|
153
|
+
#
|
154
|
+
# @author {Harshal V. Ladhe}[https://shivam091.github.io/]
|
155
|
+
# @since 1.0.0
|
44
156
|
def inspect(dump: false)
|
45
157
|
dump ? super() : to_s
|
46
158
|
end
|
47
159
|
|
160
|
+
# Returns a string representation of the +Measurement+.
|
161
|
+
#
|
162
|
+
# @return [String] A string representation of the +Measurement+.
|
163
|
+
#
|
164
|
+
# @author {Harshal V. Ladhe}[https://shivam091.github.io/]
|
165
|
+
# @since 1.0.0
|
48
166
|
def to_s
|
49
167
|
"#{quantity} #{unit}"
|
50
168
|
end
|
51
169
|
|
170
|
+
# Returns the +quantity+ of the +measurement+.
|
171
|
+
#
|
172
|
+
# @return [Numeric] Quantity of the measurement.
|
173
|
+
#
|
174
|
+
# @author {Harshal V. Ladhe}[https://shivam091.github.io/]
|
175
|
+
# @since 1.5.0
|
52
176
|
def quantity
|
53
177
|
case @quantity
|
54
178
|
when Rational
|
@@ -61,14 +185,90 @@ module UnitMeasurements
|
|
61
185
|
class << self
|
62
186
|
extend Forwardable
|
63
187
|
|
64
|
-
|
65
|
-
raise BaseError, "`Measurement` does not have a `unit_group` object. You cannot directly use `Measurement`. Instead, build a new unit group by calling `UnitMeasurements.build`."
|
66
|
-
end
|
67
|
-
|
188
|
+
# Methods delegated from the unit group.
|
68
189
|
def_delegators :unit_group, :primitive, :units, :unit_names, :unit_with_name_and_aliases,
|
69
190
|
:unit_names_with_aliases, :unit_for, :unit_for!, :defined?,
|
70
191
|
:unit_or_alias?, :[]
|
71
192
|
|
193
|
+
# Parses an input string and returns a +Measurement+ instance depending on
|
194
|
+
# the input string. This method first normalizes the +input+ internally,
|
195
|
+
# using the +Normalizer+ before parsing it using the +Parser+.
|
196
|
+
#
|
197
|
+
# If only the source unit is provided, it returns a new +Measurement+
|
198
|
+
# instance with the quantity in the source unit.If both source and target
|
199
|
+
# units are provided in the input string, it returns a new +Measurement+
|
200
|
+
# instance with the quantity converted to the target unit.
|
201
|
+
#
|
202
|
+
# @example Parsing string representing a complex number and source unit:
|
203
|
+
# UnitMeasurements::Length.parse("2+3i km")
|
204
|
+
# => 2.0+3.0i km
|
205
|
+
#
|
206
|
+
# @example Parsing string representing a complex number, source, and target units:
|
207
|
+
# UnitMeasurements::Length.parse("2+3i km to m")
|
208
|
+
# => 2000.0+3000.0i m
|
209
|
+
#
|
210
|
+
# @example Parsing string representing a rational or mixed rational number and source unit:
|
211
|
+
# UnitMeasurements::Length.parse("½ km")
|
212
|
+
# => 0.5 km
|
213
|
+
#
|
214
|
+
# UnitMeasurements::Length.parse("2/3 km")
|
215
|
+
# => 0.666666666666667 km
|
216
|
+
#
|
217
|
+
# UnitMeasurements::Length.parse("2 ½ km")
|
218
|
+
# => 2.5 km
|
219
|
+
#
|
220
|
+
# UnitMeasurements::Length.parse("2 1/2 km")
|
221
|
+
# => 2.5 km
|
222
|
+
#
|
223
|
+
# @example Parsing string representing a rational or mixed rational number, source, and target units:
|
224
|
+
# UnitMeasurements::Length.parse("½ km to m")
|
225
|
+
# => 500.0 km
|
226
|
+
#
|
227
|
+
# UnitMeasurements::Length.parse("2/3 km to m")
|
228
|
+
# => 666.666666666667 m
|
229
|
+
#
|
230
|
+
# UnitMeasurements::Length.parse("2 ½ km to m")
|
231
|
+
# => 2500.0 m
|
232
|
+
#
|
233
|
+
# UnitMeasurements::Length.parse("2 1/2 km to m")
|
234
|
+
# => 2500.0 m
|
235
|
+
#
|
236
|
+
# @example Parsing string representing a scientific number and source unit:
|
237
|
+
# UnitMeasurements::Length.parse("2e² km")
|
238
|
+
# => 200.0 km
|
239
|
+
#
|
240
|
+
# UnitMeasurements::Length.parse("2e+2 km")
|
241
|
+
# => 200.0 km
|
242
|
+
#
|
243
|
+
# UnitMeasurements::Length.parse("2e⁻² km")
|
244
|
+
# => 0.02 km
|
245
|
+
#
|
246
|
+
# @example Parsing string representing a scientific number, source, and target units:
|
247
|
+
#
|
248
|
+
# UnitMeasurements::Length.parse("2e+2 km to m")
|
249
|
+
# => 200000.0 m
|
250
|
+
#
|
251
|
+
# UnitMeasurements::Length.parse("2e⁻² km to m")
|
252
|
+
# => 20.0 m
|
253
|
+
#
|
254
|
+
# @example Parsing string representing a ratio and source unit:
|
255
|
+
# UnitMeasurements::Length.parse("1:2 km")
|
256
|
+
# => 0.5 km
|
257
|
+
#
|
258
|
+
# @example Parsing string representing a ratio, source, and target units:
|
259
|
+
# UnitMeasurements::Length.parse("1:2 km to m")
|
260
|
+
# => 500.0 m
|
261
|
+
#
|
262
|
+
# @param [String] input The input string to be parsed.
|
263
|
+
#
|
264
|
+
# @return [Measurement] The +Measurement+ instance.
|
265
|
+
#
|
266
|
+
# @see Parser
|
267
|
+
# @see Normalizer
|
268
|
+
# @see CONVERSION_STRING_REGEXP
|
269
|
+
# @see _parse
|
270
|
+
# @author {Harshal V. Ladhe}[https://shivam091.github.io/]
|
271
|
+
# @since 1.0.0
|
72
272
|
def parse(input)
|
73
273
|
input = Normalizer.normalize(input)
|
74
274
|
source, target = input.match(CONVERSION_STRING_REGEXP)&.captures
|
@@ -78,6 +278,30 @@ module UnitMeasurements
|
|
78
278
|
|
79
279
|
private
|
80
280
|
|
281
|
+
# @private
|
282
|
+
# The class attribute representing an instance of +UnitGroup+.
|
283
|
+
#
|
284
|
+
# @return [UnitGroup] An instance of +UnitGroup+.
|
285
|
+
#
|
286
|
+
# @raise [BaseError]
|
287
|
+
# If directly invoked on +Measurement+ rather than its subclasses.
|
288
|
+
#
|
289
|
+
# @see UnitGroup
|
290
|
+
# @author {Harshal V. Ladhe}[https://shivam091.github.io/]
|
291
|
+
# @since 1.0.0
|
292
|
+
def unit_group
|
293
|
+
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`."
|
294
|
+
end
|
295
|
+
|
296
|
+
# @private
|
297
|
+
# Parses the normalized string to return the +Measurement+ instance.
|
298
|
+
#
|
299
|
+
# @param [String] string String to be parsed.
|
300
|
+
#
|
301
|
+
# @return [Measurement] The +Measurement+ instance.
|
302
|
+
#
|
303
|
+
# @author {Harshal V. Ladhe}[https://shivam091.github.io/]
|
304
|
+
# @since 1.0.0
|
81
305
|
def _parse(string)
|
82
306
|
quantity, unit = Parser.parse(string)
|
83
307
|
|
@@ -87,6 +311,15 @@ module UnitMeasurements
|
|
87
311
|
|
88
312
|
private
|
89
313
|
|
314
|
+
# @private
|
315
|
+
# Converts the measurement quantity to a suitable format for internal use.
|
316
|
+
#
|
317
|
+
# @param [Numeric|String] quantity The quantity of the measurement.
|
318
|
+
#
|
319
|
+
# @return [Numeric] The converted quantity.
|
320
|
+
#
|
321
|
+
# @author {Harshal V. Ladhe}[https://shivam091.github.io/]
|
322
|
+
# @since 1.0.0
|
90
323
|
def convert_quantity(quantity)
|
91
324
|
case quantity
|
92
325
|
when Float
|
@@ -103,8 +336,18 @@ module UnitMeasurements
|
|
103
336
|
end
|
104
337
|
end
|
105
338
|
|
339
|
+
# @private
|
340
|
+
# Returns the +Unit+ instance associated with the +value+ provided.
|
341
|
+
#
|
342
|
+
# @param [String|Unit] value
|
343
|
+
# The value representing a unit name or +Unit+ instance.
|
344
|
+
#
|
345
|
+
# @return [Unit] The +Unit+ instance associated with +value+.
|
346
|
+
#
|
347
|
+
# @author {Harshal V. Ladhe}[https://shivam091.github.io/]
|
348
|
+
# @since 1.0.0
|
106
349
|
def unit_from_unit_or_name!(value)
|
107
|
-
value.is_a?(Unit) ? value : self.class.unit_group.unit_for!(value)
|
350
|
+
value.is_a?(Unit) ? value : self.class.send(:unit_group).unit_for!(value)
|
108
351
|
end
|
109
352
|
end
|
110
353
|
end
|
@@ -3,7 +3,22 @@
|
|
3
3
|
# -*- warn_indent: true -*-
|
4
4
|
|
5
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
|
6
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
|
7
22
|
EXPONENTS_SYMBOLS = {
|
8
23
|
"⁰" => "0",
|
9
24
|
"¹" => "1",
|
@@ -19,6 +34,10 @@ module UnitMeasurements
|
|
19
34
|
"⁻" => "-",
|
20
35
|
}.freeze
|
21
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
|
22
41
|
FRACTIONS_SYMBOLS = {
|
23
42
|
"¼" => "1/4",
|
24
43
|
"½" => "1/2",
|
@@ -41,11 +60,81 @@ module UnitMeasurements
|
|
41
60
|
"↉" => "0/3",
|
42
61
|
}.freeze
|
43
62
|
|
44
|
-
|
45
|
-
|
46
|
-
|
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
|
+
|
47
102
|
|
48
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
|
49
138
|
def normalize(string)
|
50
139
|
string.dup.tap do |str|
|
51
140
|
if str =~ Regexp.new(EXPONENT_REGEX)
|
@@ -3,18 +3,144 @@
|
|
3
3
|
# -*- warn_indent: true -*-
|
4
4
|
|
5
5
|
module UnitMeasurements
|
6
|
+
# The +UnitMeasurements::Parser+ class provides methods for parsing strings to
|
7
|
+
# extract quantity and associated unit. It can handle various formats, including
|
8
|
+
# complex numbers, scientific numbers, and rational numbers for the +quantity+.
|
9
|
+
#
|
10
|
+
# @see Measurement
|
11
|
+
# @author {Harshal V. Ladhe}[https://shivam091.github.io/]
|
12
|
+
# @since 1.0.0
|
6
13
|
class Parser
|
7
|
-
|
14
|
+
# Matches any character sequence that does not consist of digits, whitespace,
|
15
|
+
# or forward slashes.
|
16
|
+
#
|
17
|
+
# @author {Harshal V. Ladhe}[https://shivam091.github.io/]
|
18
|
+
# @since 1.0.0
|
19
|
+
UNIT_REGEX = /
|
20
|
+
( # Start of the capturing group
|
21
|
+
[^\d\s\/] # Match any character that is not a digit, whitespace, or a forward slash
|
22
|
+
.* # Match zero or more of any character (except for a newline)
|
23
|
+
) # End of the capturing group
|
24
|
+
/x.freeze
|
8
25
|
|
9
|
-
|
10
|
-
|
11
|
-
|
26
|
+
# Matches scientific numbers (e.g., +1.23e-4+).
|
27
|
+
#
|
28
|
+
# @author {Harshal V. Ladhe}[https://shivam091.github.io/]
|
29
|
+
# @since 1.0.0
|
30
|
+
SCIENTIFIC_NUMBER = /
|
31
|
+
( # Start of a capturing group (denoted by parentheses)
|
32
|
+
[+-]? # Match an optional plus or minus sign (+ or -)
|
33
|
+
\d* # Match zero or more digits
|
34
|
+
\.? # Match an optional dot (.)
|
35
|
+
\d+ # Match one or more digits
|
36
|
+
(?: # Start of a non-capturing group
|
37
|
+
[Ee] # Match either 'E' or 'e'
|
38
|
+
[+-]? # Match an optional plus or minus sign (+ or -)
|
39
|
+
)? # End of the non-capturing group; ? makes it optional
|
40
|
+
\d* # Match zero or more digits
|
41
|
+
) # End of the capturing group
|
42
|
+
/x.freeze
|
12
43
|
|
13
|
-
|
14
|
-
|
15
|
-
|
44
|
+
# Matches rational numbers (e.g., +1/2+).
|
45
|
+
#
|
46
|
+
# @author {Harshal V. Ladhe}[https://shivam091.github.io/]
|
47
|
+
# @since 1.0.0
|
48
|
+
RATIONAL_NUMBER = /
|
49
|
+
( # Start of the first capturing group
|
50
|
+
[+-]? # Match an optional plus or minus sign (+ or -)
|
51
|
+
\d+ # Match one or more digits
|
52
|
+
\s+ # Match one or more whitespace characters
|
53
|
+
)? # End of the first capturing group
|
54
|
+
( # Start of the second capturing group (the fraction part)
|
55
|
+
(\d+) # Start of the third capturing group (one or more digits, the numerator)
|
56
|
+
\/ # Match a forward slash - the division symbol
|
57
|
+
(\d+) # Start of the fourth capturing group (one or more digits, the denominator)
|
58
|
+
) # End of the second capturing group (the fraction part)
|
59
|
+
/x.freeze
|
60
|
+
|
61
|
+
# Matches complex numbers (e.g., +1-2i+).
|
62
|
+
#
|
63
|
+
# @author {Harshal V. Ladhe}[https://shivam091.github.io/]
|
64
|
+
# @since 1.0.0
|
65
|
+
COMPLEX_NUMBER = /
|
66
|
+
#{SCIENTIFIC_NUMBER} # Pattern for scientific number
|
67
|
+
#{SCIENTIFIC_NUMBER} # Pattern for scientific number
|
68
|
+
i # Match the letter 'i' (the imaginary unit)
|
69
|
+
/x.freeze
|
70
|
+
|
71
|
+
# Matches strings containing scientific numbers and unit.
|
72
|
+
#
|
73
|
+
# @author {Harshal V. Ladhe}[https://shivam091.github.io/]
|
74
|
+
# @since 1.0.0
|
75
|
+
SCIENTIFIC_REGEX = /
|
76
|
+
\A # Anchor at the start of the string
|
77
|
+
#{SCIENTIFIC_NUMBER} # Match a scientific number (as defined earlier)
|
78
|
+
\s* # Match zero or more whitespace characters
|
79
|
+
#{UNIT_REGEX}? # Match a unit, the '?' makes it optional
|
80
|
+
\z # Anchor at the end of the string
|
81
|
+
/x.freeze
|
82
|
+
|
83
|
+
# Matches strings containing rational numbers and unit.
|
84
|
+
#
|
85
|
+
# @author {Harshal V. Ladhe}[https://shivam091.github.io/]
|
86
|
+
# @since 1.0.0
|
87
|
+
RATIONAL_REGEX = /
|
88
|
+
\A # Anchor at the start of the string
|
89
|
+
#{RATIONAL_NUMBER} # Match a rational number (as defined earlier)
|
90
|
+
\s* # Match zero or more whitespace characters
|
91
|
+
#{UNIT_REGEX}? # Match a unit, the '?' makes it optional
|
92
|
+
\z
|
93
|
+
/x.freeze
|
94
|
+
|
95
|
+
# Matches strings containing complex numbers and unit.
|
96
|
+
#
|
97
|
+
# @author {Harshal V. Ladhe}[https://shivam091.github.io/]
|
98
|
+
# @since 1.0.0
|
99
|
+
COMPLEX_REGEX = /
|
100
|
+
\A # Anchor at the start of the string
|
101
|
+
#{COMPLEX_NUMBER} # Match a complex number (as defined earlier)
|
102
|
+
\s* # Match zero or more whitespace characters
|
103
|
+
#{UNIT_REGEX}? # Match a unit, the '?' makes it optional
|
104
|
+
\z
|
105
|
+
/x.freeze
|
16
106
|
|
17
107
|
class << self
|
108
|
+
# Parses a string to extract a +quantity+ and its associated +unit+. This
|
109
|
+
# method first extracts a +quantity+ and converts it to +Float+ before
|
110
|
+
# returning it.
|
111
|
+
#
|
112
|
+
# To get the correct parsed results, you must first normalize the +string+
|
113
|
+
# with +Normalizer+ if using the parser standalone.
|
114
|
+
#
|
115
|
+
# @example Parsing string representing a complex number:
|
116
|
+
# UnitMeasurements::Parser.parse("1+2i m")
|
117
|
+
# => [(1.0+2.0i), "m"]
|
118
|
+
#
|
119
|
+
# @example Parsing string representing a rational number:
|
120
|
+
# UnitMeasurements::Parser.parse("1/2 m")
|
121
|
+
# => [0.5, "m"]
|
122
|
+
#
|
123
|
+
# @example Parsing string representing a mixed rational number:
|
124
|
+
# UnitMeasurements::Parser.parse("2 1/2 km")
|
125
|
+
# => [2.5, "km"]
|
126
|
+
#
|
127
|
+
# @example Parsing string representing a scientific number:
|
128
|
+
# UnitMeasurements::Parser.parse("1e+2 km")
|
129
|
+
# => [100.0, "km"]
|
130
|
+
#
|
131
|
+
# @param [String] string
|
132
|
+
# The input string containing a +quantity+ and an optional +unit+.
|
133
|
+
#
|
134
|
+
# @return [Array<Numeric, String|NilClass>]
|
135
|
+
# The parsed +quantity+ and the +unit+ associated with it (or +nil+ if
|
136
|
+
# no unit is specified in the +string+).
|
137
|
+
#
|
138
|
+
# @raise [ParseError] If the string is invalid and cannot be parsed.
|
139
|
+
#
|
140
|
+
# @see Measurement
|
141
|
+
# @see Normalizer
|
142
|
+
# @author {Harshal V. Ladhe}[https://shivam091.github.io/]
|
143
|
+
# @since 1.0.0
|
18
144
|
def parse(string)
|
19
145
|
case string
|
20
146
|
when COMPLEX_REGEX then parse_complex(string)
|
@@ -26,6 +152,18 @@ module UnitMeasurements
|
|
26
152
|
|
27
153
|
private
|
28
154
|
|
155
|
+
# @private
|
156
|
+
# Parses a string representing a complex number with an optional unit.
|
157
|
+
#
|
158
|
+
# @param [String] string
|
159
|
+
# The input string containing a complex number and an optional unit.
|
160
|
+
#
|
161
|
+
# @return [Array<Numeric, String|NilClass>]
|
162
|
+
# The parsed complex number and the associated unit (or +nil+ if no unit
|
163
|
+
# is specified in the string).
|
164
|
+
#
|
165
|
+
# @author {Harshal V. Ladhe}[https://shivam091.github.io/]
|
166
|
+
# @since 1.0.0
|
29
167
|
def parse_complex(string)
|
30
168
|
real, imaginary, unit = string.match(COMPLEX_REGEX)&.captures
|
31
169
|
quantity = Complex(real.to_f, imaginary.to_f)
|
@@ -33,6 +171,18 @@ module UnitMeasurements
|
|
33
171
|
[quantity, unit]
|
34
172
|
end
|
35
173
|
|
174
|
+
# @private
|
175
|
+
# Parses a string representing a scientific number with an optional unit.
|
176
|
+
#
|
177
|
+
# @param [String] string
|
178
|
+
# The input string containing a scientific number and an optional unit.
|
179
|
+
#
|
180
|
+
# @return [Array<Numeric, String|NilClass>]
|
181
|
+
# The parsed scientific number and the associated unit (or +nil+ if no unit
|
182
|
+
# is specified in the string).
|
183
|
+
#
|
184
|
+
# @author {Harshal V. Ladhe}[https://shivam091.github.io/]
|
185
|
+
# @since 1.0.0
|
36
186
|
def parse_scientific(string)
|
37
187
|
whole, unit = string.match(SCIENTIFIC_REGEX)&.captures
|
38
188
|
quantity = whole.to_f
|
@@ -40,6 +190,18 @@ module UnitMeasurements
|
|
40
190
|
[quantity, unit]
|
41
191
|
end
|
42
192
|
|
193
|
+
# @private
|
194
|
+
# Parses a string representing a rational number with an optional unit.
|
195
|
+
#
|
196
|
+
# @param [String] string
|
197
|
+
# The input string containing a rational number and an optional unit.
|
198
|
+
#
|
199
|
+
# @return [Array<Numeric, String|NilClass>]
|
200
|
+
# The parsed rational number and the associated unit (or +nil+ if no unit
|
201
|
+
# is specified in the string).
|
202
|
+
#
|
203
|
+
# @author {Harshal V. Ladhe}[https://shivam091.github.io/]
|
204
|
+
# @since 1.0.0
|
43
205
|
def parse_rational(string)
|
44
206
|
whole, _, numerator, denominator, unit = string.match(RATIONAL_REGEX)&.captures
|
45
207
|
|