daddyz/phonelib

View on GitHub
lib/phonelib/phone_formatter.rb

Summary

Maintainability
A
3 hrs
Test Coverage
A
98%
# frozen_string_literal: true

module Phonelib
  # module includes all formatting methods
  module PhoneFormatter
    # Returns formatted national number
    # @param formatted [Boolean] whether to return numbers only or formatted
    # @return [String] formatted national number
    def national(formatted = true)
      return @national_number unless possible?
      format_match, format_string = formatting_data

      if format_match
        out = format_string.gsub(/\$\d/) { |el| format_match[el[1].to_i] }
        formatted ? out : out.gsub(/[^0-9]/, '')
      else
        @national_number
      end
    end

    # Returns the raw national number that was defined during parsing
    # @return [String] raw national number
    def raw_national
      return nil if sanitized.nil? || sanitized.empty?
      if valid?
        @national_number
      elsif data_country_code && sanitized.start_with?(data_country_code)
        sanitized[data_country_code.size..-1]
      else
        sanitized
      end
    end

    # Returns the country code from the original phone number.
    # @return [String] matched country phone code
    def country_code
      return @country_code if @country_code

      code = Phonelib.phone_data[country] && Phonelib.phone_data[country][Core::COUNTRY_CODE]
      return @country_code = code unless code == '1' && Phonelib.phone_data[country][Core::LEADING_DIGITS]

      match = e164.match(/\A\+(1(#{Phonelib.phone_data[country][Core::LEADING_DIGITS]}))/)
      if match
        @country_code = match[1]
      else
        @country_code = '1'
      end
    end

    # Returns e164 formatted phone number. Method can receive single string parameter that will be defined as prefix
    # @param formatted [Boolean] whether to return numbers only or formatted
    # @param prefix [String] prefix to be placed before the number, "+" by default
    # @return [String] formatted international number
    def international(formatted = true, prefix = '+')
      prefix = formatted if formatted.is_a?(String)
      return nil if sanitized.empty?
      return "#{prefix}#{country_prefix_or_not}#{sanitized}" unless possible?
      return "#{prefix}#{data_country_code}#{@national_number}" unless formatted

      fmt = @data[country][:format]
      national = @national_number
      if (matches = @national_number.match(cr(fmt[Core::PATTERN])))
        fmt = fmt[:intl_format] || fmt[:format]
        national = fmt.gsub(/\$\d/) { |el| matches[el[1].to_i] } unless fmt == 'NA'
      end

      "#{prefix}#{data_country_code} #{national}"
    end

    # returns national formatted number with extension added
    # @return [String] formatted national number with extension
    def full_national
      "#{national}#{formatted_extension}"
    end

    # returns international formatted number with extension added
    # @param prefix [String] prefix to be placed before the number, "+" by default
    # @return [String] formatted internation phone number with extension
    def full_international(prefix = '+')
      "#{international(true, prefix)}#{formatted_extension}"
    end

    # returns e164 format of phone with extension added
    # @param prefix [String] prefix to be placed before the number, "+" by default
    # @return [String] phone formatted in E164 format with extension
    def full_e164(prefix = '+')
      "#{e164(prefix)}#{formatted_extension}"
    end

    # Returns e164 unformatted phone number
    # @param prefix [String] prefix to be placed before the number, "+" by default
    # @return [String] phone formatted in E164 format
    def e164(prefix = '+')
      international = self.international(false, '')
      international && "#{prefix}#{international}"
    end

    # returns area code of parsed number
    # @return [String|nil] parsed phone area code if available
    def area_code
      return nil unless area_code_possible?

      format_match, _format_string = formatting_data
      take_group = 1
      if type == Core::MOBILE && Core::AREA_CODE_MOBILE_TOKENS[country] && \
         format_match[1] == Core::AREA_CODE_MOBILE_TOKENS[country]
        take_group = 2
      end
      format_match[take_group]
    end

    def method_missing(method, *args)
      prefix_methods = %w(international_ full_international_ e164_ full_e164_)
      method_s = method.to_s
      prefix_methods.each do |key|
        return send(key[0..-2], method_s.gsub(key, '')) if method_s.start_with?(key)
      end
      super
    end

    private

    def data_country_code
      @data_country_code ||= Phonelib.phone_data[country] && Phonelib.phone_data[country][Core::COUNTRY_CODE]
    end

    # @private defines if phone can have area code
    def area_code_possible?
      return false if impossible?

      # has national prefix
      return false unless @data[country][Core::NATIONAL_PREFIX] || country == 'IT'
      # fixed or mobile
      return false unless Core::AREA_CODE_TYPES.include?(type)
      # mobile && mexico, argentina, brazil
      return false if type == Core::MOBILE && !Core::AREA_CODE_MOBILE_COUNTRIES.include?(country)
      true
    end

    # @private defines whether to put country prefix or not
    def country_prefix_or_not
      return '' unless data_country_code
      sanitized.start_with?(data_country_code) ? '' : data_country_code
    end

    # @private returns extension with separator defined
    def formatted_extension
      return '' if @extension.nil? || @extension.empty?

      "#{Phonelib.extension_separator}#{@extension}"
    end

    # @private Get needable data for formatting phone as national number
    def formatting_data
      return @formatting_data if defined?(@formatting_data)

      data = @data[country]
      format = data[:format]
      prefix = data[Core::NATIONAL_PREFIX]
      rule = format[Core::NATIONAL_PREFIX_RULE] ||
             data[Core::NATIONAL_PREFIX_RULE] || '$1'

      # change rule's constants to values
      rule = rule.gsub(/(\$NP|\$FG)/, '$NP' => prefix, '$FG' => '$1')

      # add space to format groups, change first group to rule,
      format_string = format[:format].gsub(/(\d)\$/, '\\1 $')
      if format_string.include? '$1'
    format_string.gsub! '$1', rule
      else
    format_string = rule.gsub('$1', '') + format_string
      end

      @formatting_data =
          [@national_number.match(/#{format[Core::PATTERN]}/), format_string]
    end
  end
end