unit_measurements 4.9.0 → 4.11.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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
- attr_reader :quantity, :unit
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
- [:to, :in, :as].each { |method_alias| alias_method method_alias, :convert_to }
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
- [:to!, :in!, :as!].each { |method_alias| alias_method method_alias, :convert_to! }
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
- def unit_group
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
- EXPONENT_REGEX = /([\d]+[Ee]?[+-]?)(#{EXPONENTS_SYMBOLS.keys.join("|")})/.freeze
45
- FRACTION_REGEX = /(#{FRACTIONS_SYMBOLS.keys.join("|")})/.freeze
46
- RATIO_REGEX = /([\d]+):([\d]+)/.freeze
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
- UNIT_REGEX = /([^\d\s\/].*)/.freeze
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
- SCIENTIFIC_NUMBER = /([+-]?\d*\.?\d+(?:[Ee][+-]?)?\d*)/.freeze
10
- RATIONAL_NUMBER = /([+-]?\d+\s+)?((\d+)\/(\d+))/.freeze
11
- COMPLEX_NUMBER = /#{SCIENTIFIC_NUMBER}#{SCIENTIFIC_NUMBER}i/.freeze
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
- SCIENTIFIC_REGEX = /\A#{SCIENTIFIC_NUMBER}\s*#{UNIT_REGEX}?\z/.freeze
14
- RATIONAL_REGEX = /\A#{RATIONAL_NUMBER}\s*#{UNIT_REGEX}?\z/.freeze
15
- COMPLEX_REGEX = /\A#{COMPLEX_NUMBER}\s*#{UNIT_REGEX}?\z/.freeze
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