lib/humanize_fraction/humanizer.rb in humanize_fraction-0.1.0 vs lib/humanize_fraction/humanizer.rb in humanize_fraction-0.1.1
- old
+ new
@@ -1,98 +1,105 @@
module HumanizeFraction
class Humanizer
# From: https://github.com/thechrisoshow/fractional/blob/master/lib/fractional.rb
- SINGLE_FRACTION = /^\s*(\-?\d+)\/(\-?\d+)\s*$/
- MIXED_FRACTION = /^\s*(\-?\d*)\s+(\d+)\/(\d+)\s*$/
+ SINGLE_FRACTION = /\A\s*(\-?\d+)\/(\-?\d+)\s*\z/
+ MIXED_FRACTION = /\A\s*(\-?\d*)\s+(\d+)\/(\d+)\s*\z/
- attr_reader :numerator, :denominator, :integer_part, :quarter, :shorthand
+ # Numbers that should be prefixed with `a` instead of `an` even though they
+ # start with a vowel.
+ NUMBERS_STARTING_WITH_SILENT_VOWEL = [
+ "one",
+ ]
- def initialize(numerator:, denominator:, integer_part: nil, shorthand: false, quarter: false)
+ attr_reader :numerator, :denominator, :whole_part
+
+ def initialize(numerator:, denominator:, whole_part: nil)
[numerator, denominator].each do |number|
if !number.is_a?(Integer)
raise ArgumentError, "Expected Integers for numerator/denominator but got #{number.class.name}"
end
end
- if !integer_part.nil? && !integer_part.is_a?(Integer)
- raise ArgumentError, "Expected Integer or NilClass for integer_part but got #{integer_part.class.name}"
+ if !whole_part.nil? && !whole_part.is_a?(Integer)
+ raise ArgumentError, "Expected Integer or NilClass for whole_part but got #{whole_part.class.name}"
end
- @integer_part = integer_part
+ @whole_part = whole_part
@numerator = numerator
@denominator = denominator
- @shorthand = shorthand
- @quarter = quarter
end
- alias_method :quarter?, :quarter
- alias_method :shorthand?, :shorthand
- def to_s
+ def to_s(shorthand: false, quarter: false)
+ humanized_denominator = humanize_denominator(shorthand: shorthand, quarter: quarter)
words = []
- words << humanize_integer_part if !integer_part.nil?
- words << humanize_numerator
- words << humanize_denominator
+ words << humanize_whole_part if !whole_part.nil?
+ words << humanize_numerator(humanized_denominator, shorthand: shorthand)
+ words << humanized_denominator
words.join(" ")
end
def self.from_string(string, options = {})
if !string.is_a?(String)
raise ArgumentError, "Expected String but got #{string.class.name}"
end
if string_is_mixed_fraction?(string)
whole, numerator, denominator = string.scan(MIXED_FRACTION).flatten.map(&:to_i)
- new(integer_part: whole, numerator: numerator, denominator: denominator, **options)
+ new(whole_part: whole, numerator: numerator, denominator: denominator, **options)
elsif string_is_single_fraction?(string)
numerator, denominator = string.split("/").map(&:to_i)
new(numerator: numerator, denominator: denominator, **options)
else
raise ArgumentError, "Unable to extract fraction from string #{string}"
end
end
private
- def humanize_integer_part
- "#{integer_part.humanize} and"
+ def humanize_whole_part
+ "#{whole_part.humanize} and"
end
- def humanize_numerator
- number = if numerator == 1 && shorthand?
- # 8 is the only(?) case where you want to prefix with `an` instead of `a`.
- first_digit(denominator) == 8 ? "an" : "a"
+ def humanize_numerator(humanized_denominator, shorthand:)
+ number = if numerator == 1 && shorthand
+ first_number = humanized_denominator.split(" ").first
+ indefinite_article(first_number)
else
numerator.humanize
end
number
end
- def humanize_denominator
+ def humanize_denominator(shorthand:, quarter:)
number = case denominator
when 2
"half"
when 4
- quarter? ? "quarter" : "fourth"
+ quarter ? "quarter" : "fourth"
else
- denominator.localize(:en).to_rbnf_s("SpelloutRules", "spellout-ordinal")
+ I18n.with_locale(:en) { denominator.to_words(ordinal: true) }
end
# Handle case of `a millionth`, `a thousandth`, etc.
- if shorthand? && denominator >= 100 &&
- first_digit(denominator) == 1 && remaining_digits_zeros?(denominator)
+ if shorthand && denominator >= 100 && one_followed_by_zeros?(denominator)
number.sub!(/\Aone\s/, "")
end
if numerator != 1
number = ActiveSupport::Inflector.pluralize(number)
end
number
end
- def first_digit(number)
- number.to_s.split("").first.to_i
+ # Checks number is a 1 followed by only zeros, e.g. 10 or 10000000
+ def one_followed_by_zeros?(number)
+ digits = number.to_s.split("")
+ first_digit = digits.shift
+ first_digit.to_i == 1 && digits.size > 0 && digits.map(&:to_i).all?(&:zero?)
end
- # Checks if everything after first digit are zeros.
- def remaining_digits_zeros?(number)
- numbers = number.to_s.split("")
- numbers.shift
- numbers.size > 0 && numbers.map(&:to_i).all?(&:zero?)
+ def indefinite_article(humanized_number)
+ if humanized_number.match(/\A[aeiou]/) &&
+ !NUMBERS_STARTING_WITH_SILENT_VOWEL.include?(humanized_number)
+ "an"
+ else
+ "a"
+ end
end
def self.string_is_mixed_fraction?(value)
value&.match(MIXED_FRACTION)
end