Skip to content

Commit dd338cb

Browse files
committed
Refactor parse method for improved readability and maintainability by extracting validation logic into separate methods
1 parent 0112fca commit dd338cb

File tree

1 file changed

+54
-60
lines changed

1 file changed

+54
-60
lines changed

lib/ruby_units/unit.rb

Lines changed: 54 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -2040,40 +2040,35 @@ def unit_signature
20402040
# @todo This should either be a separate class or at least a class method
20412041
def parse(passed_unit_string = "0")
20422042
unit_string = passed_unit_string.dup
2043-
unit_string = "#{Regexp.last_match(1)} USD" if unit_string =~ /\$\s*(#{NUMBER_REGEX})/
2043+
unit_string = "#{Regexp.last_match('usd')} USD" if unit_string =~ /\$\s*(?<usd>#{NUMBER_REGEX})/
20442044
unit_string.gsub!("\u00b0".encode("utf-8"), "deg") if unit_string.encoding == Encoding::UTF_8
20452045

20462046
unit_string.gsub!(/(\d)[_,](\d)/, '\1\2') # remove underscores and commas in numbers
20472047

20482048
unit_string.gsub!(/[%'"#]/, "%" => "percent", "'" => "feet", '"' => "inch", "#" => "pound")
20492049
if unit_string.start_with?(COMPLEX_NUMBER)
20502050
match = unit_string.match(COMPLEX_REGEX)
2051-
real_str = match[:real]
2052-
imaginary_str = match[:imaginary]
2051+
real_str, imaginary_str, unit_s = match.values_at(:real, :imaginary, :unit)
20532052
real = Float(real_str) if real_str
20542053
imaginary = Float(imaginary_str)
2055-
unit_s = match[:unit]
20562054
real_as_int = real.to_i if real
20572055
real = real_as_int if real_as_int == real
20582056
imaginary_as_int = imaginary.to_i
20592057
imaginary = imaginary_as_int if imaginary_as_int == imaginary
20602058
complex = Complex(real || 0, imaginary)
20612059
complex_real = complex.real
20622060
complex = complex.to_i if complex.imaginary.zero? && complex_real == complex_real.to_i
2063-
result = unit_class.new(unit_s || 1) * complex
2064-
copy(result)
2065-
return
2061+
return copy(unit_class.new(unit_s || 1) * complex)
20662062
end
20672063

20682064
if unit_string.start_with?(RATIONAL_NUMBER)
20692065
match = unit_string.match(RATIONAL_REGEX)
2070-
numerator = Integer(match[:numerator])
2071-
denominator = Integer(match[:denominator])
2072-
proper_string = match[:proper]
2066+
numerator_string, denominator_string, proper_string, unit_s = match.values_at(:numerator, :denominator, :proper, :unit)
2067+
numerator = Integer(numerator_string)
2068+
denominator = Integer(denominator_string)
20732069
raise ArgumentError, "Improper fractions must have a whole number part" if proper_string && !proper_string.match?(/^#{INTEGER_REGEX}$/)
20742070

20752071
proper = proper_string.to_i
2076-
unit_s = match[:unit]
20772072
fraction = Rational(numerator, denominator)
20782073
rational = if proper.negative?
20792074
(proper - fraction)
@@ -2082,15 +2077,12 @@ def parse(passed_unit_string = "0")
20822077
end
20832078
rational_as_int = rational.to_int
20842079
rational = rational_as_int if rational_as_int == rational
2085-
result = unit_class.new(unit_s || 1) * rational
2086-
copy(result)
2087-
return
2080+
return copy(unit_class.new(unit_s || 1) * rational)
20882081
end
20892082

20902083
match = unit_string.match(NUMBER_REGEX)
2091-
unit_str = match[:unit]
2084+
unit_str, scalar_str = match.values_at(:unit, :scalar)
20922085
unit = unit_class.cached.get(unit_str)
2093-
scalar_str = match[:scalar]
20942086
mult = scalar_str == "" ? 1.0 : scalar_str.to_f
20952087
mult_as_int = mult.to_int
20962088
mult = mult_as_int if mult_as_int == mult
@@ -2113,63 +2105,50 @@ def parse(passed_unit_string = "0")
21132105
unit_string.gsub!(/[<>]/, "")
21142106

21152107
if (match = unit_string.match(TIME_REGEX))
2116-
hours = match[:hour]
2117-
minutes = match[:min]
2118-
seconds = match[:sec]
2119-
milliseconds = match[:msec]
2108+
hours, minutes, seconds, milliseconds = match.values_at(:hour, :min, :sec, :msec)
21202109
raise ArgumentError, "Invalid Duration" if [hours, minutes, seconds, milliseconds].all?(&:nil?)
21212110

2122-
result = unit_class.new("#{hours || 0} hours") +
2123-
unit_class.new("#{minutes || 0} minutes") +
2124-
unit_class.new("#{seconds || 0} seconds") +
2125-
unit_class.new("#{milliseconds || 0} milliseconds")
2126-
copy(result)
2127-
return
2111+
return copy(unit_class.new("#{hours || 0} hours") +
2112+
unit_class.new("#{minutes || 0} minutes") +
2113+
unit_class.new("#{seconds || 0} seconds") +
2114+
unit_class.new("#{milliseconds || 0} milliseconds"))
21282115
end
21292116

2130-
# Special processing for unusual unit strings
21312117
# feet -- 6'5"
21322118
if (match = unit_string.match(FEET_INCH_REGEX))
2133-
feet = Integer(match[:feet])
2134-
inches = match[:inches]
2135-
result = if feet.negative?
2136-
unit_class.new("#{feet} ft") - unit_class.new("#{inches} inches")
2137-
else
2138-
unit_class.new("#{feet} ft") + unit_class.new("#{inches} inches")
2139-
end
2140-
copy(result)
2141-
return
2119+
feet_str, inches = match.values_at(:feet, :inches)
2120+
feet = Integer(feet_str)
2121+
return copy(if feet.negative?
2122+
unit_class.new("#{feet} ft") - unit_class.new("#{inches} inches")
2123+
else
2124+
unit_class.new("#{feet} ft") + unit_class.new("#{inches} inches")
2125+
end)
21422126
end
21432127

21442128
# weight -- 8 lbs 12 oz
21452129
if (match = unit_string.match(LBS_OZ_REGEX))
2146-
pounds = Integer(match[:pounds])
2147-
oz = match[:oz]
2148-
result = if pounds.negative?
2149-
unit_class.new("#{pounds} lbs") - unit_class.new("#{oz} oz")
2150-
else
2151-
unit_class.new("#{pounds} lbs") + unit_class.new("#{oz} oz")
2152-
end
2153-
copy(result)
2154-
return
2130+
pounds_str, oz = match.values_at(:pounds, :oz)
2131+
pounds = Integer(pounds_str)
2132+
return copy(if pounds.negative?
2133+
unit_class.new("#{pounds} lbs") - unit_class.new("#{oz} oz")
2134+
else
2135+
unit_class.new("#{pounds} lbs") + unit_class.new("#{oz} oz")
2136+
end)
21552137
end
21562138

21572139
# stone -- 3 stone 5, 2 stone, 14 stone 3 pounds, etc.
21582140
if (match = unit_string.match(STONE_LB_REGEX))
2159-
stone = Integer(match[:stone])
2160-
pounds = match[:pounds]
2161-
result = if stone.negative?
2162-
unit_class.new("#{stone} stone") - unit_class.new("#{pounds} lbs")
2163-
else
2164-
unit_class.new("#{stone} stone") + unit_class.new("#{pounds} lbs")
2165-
end
2166-
copy(result)
2167-
return
2141+
stone_str, pounds = match.values_at(:stone, :pounds)
2142+
stone = Integer(stone_str)
2143+
return copy(if stone.negative?
2144+
unit_class.new("#{stone} stone") - unit_class.new("#{pounds} lbs")
2145+
else
2146+
unit_class.new("#{stone} stone") + unit_class.new("#{pounds} lbs")
2147+
end)
21682148
end
21692149

21702150
# more than one per. I.e., "1 m/s/s"
2171-
raise(ArgumentError, "'#{passed_unit_string}' Unit not recognized") if unit_string.count("/") > 1
2172-
raise(ArgumentError, "'#{passed_unit_string}' Unit not recognized #{unit_string}") if unit_string =~ /\s[02-9]/
2151+
validate_unit_string_format(passed_unit_string, unit_string)
21732152

21742153
@scalar, top, bottom = unit_string.scan(UNIT_STRING_REGEX)[0] # parse the string into parts
21752154
top.scan(TOP_REGEX).each do |item|
@@ -2212,13 +2191,11 @@ def parse(passed_unit_string = "0")
22122191
# eliminate all known terms from this string. This is a quick check to see if the passed unit
22132192
# contains terms that are not defined.
22142193
used = "#{top} #{bottom}".gsub(unit_match_regex, "").gsub(%r{[\d*, "'_^/$]}, "")
2215-
raise(ArgumentError, "'#{passed_unit_string}' Unit not recognized") unless used.empty?
2194+
invalid_unit(passed_unit_string) unless used.empty?
22162195

22172196
prefix_map = unit_class.prefix_map
22182197
unit_map = unit_class.unit_map
2219-
transform_units = lambda do |item|
2220-
prefix = item[0]
2221-
unit = item[1]
2198+
transform_units = lambda do |(prefix, unit)|
22222199
prefix_value = prefix_map[prefix]
22232200
unit_value = unit_map[unit]
22242201
prefix_value ? [prefix_value, unit_value] : [unit_value]
@@ -2232,5 +2209,22 @@ def parse(passed_unit_string = "0")
22322209
@denominator = UNITY_ARRAY if @denominator.empty?
22332210
self
22342211
end
2212+
2213+
def validate_unit_string_format(passed_unit_string, unit_string)
2214+
slash_count = unit_string.count("/")
2215+
return if slash_count <= 1 && unit_string !~ /\s[02-9]/
2216+
2217+
if slash_count > 1
2218+
invalid_unit(passed_unit_string)
2219+
else
2220+
invalid_unit(passed_unit_string, unit_string)
2221+
end
2222+
end
2223+
2224+
def invalid_unit(unit_string, additional_info = nil)
2225+
error_msg = "'#{unit_string}' Unit not recognized"
2226+
error_msg += " #{additional_info}" if additional_info
2227+
raise ArgumentError, error_msg
2228+
end
22352229
end
22362230
end

0 commit comments

Comments
 (0)