floere/phony

View on GitHub
lib/phony/country_codes.rb

Summary

Maintainability
A
1 hr
Test Coverage
module Phony

  EMPTY_STRING = '' unless defined?(EMPTY_STRING)

  # Handles determining the correct national code handler.
  #
  class CountryCodes

    attr_reader   :countries
    attr_accessor :international_absolute_format, :international_relative_format, :national_format

    # Singleton instance.
    #
    def self.instance
      @instance ||= new
    end
    
    # Add the given country to the mapping under the
    # given country code.
    #
    def add country_code, country
      country_code = country_code.to_s
      optimized_country_code_access = country_code.size

      @countries ||= {}
      @countries[optimized_country_code_access] ||= {}
      @countries[optimized_country_code_access][country_code] = country
    end

    # Get the Country object for the given CC.
    #
    def [] cc
      countries[cc.size][cc]
    end

    # Clean number of all non-numeric characters, initial zeros or (0.
    #
    @@basic_cleaning_pattern = /\A00?|\(0|\D/
    # Clean number of all non-numeric characters, initial zeros or (0 and return it.
    #
    def clean number
      clean! number && number.dup
    end
    # Clean number of all non-numeric characters, initial zeros or (0 and return a copy.
    #
    def clean! number
      number.gsub!(@@basic_cleaning_pattern, EMPTY_STRING) || number
    end

    # 00 for the standard international call prefix.
    # http://en.wikipedia.org/wiki/List_of_international_call_prefixes
    #
    # We can't know from what country that person was calling, so we
    # can't remove the intl' call prefix.
    #
    # We remove:
    #  * 0 or 00 at the very beginning.
    #  * (0) anywhere.
    #  * Non-digits.
    #
    def normalize number, options = {}
      country = if cc = options[:cc]
        self[cc]
      else
        clean! number
        country, cc, number = partial_split number
        country
      end
      number = country.normalize number, cc: cc
      countrify! number, cc
    end

    # Splits this number into cc, ndc and locally split number parts.
    #
    def split number
      # Split the number into country, cc, and national part.
      country, cc, national_number = partial_split number
      
      # Split the national number into ndc and local part.
      _, ndc, *local = country.split national_number
      
      [cc, ndc, *local]
    end

    # Format the number.
    #
    def format number, options = {}
      country, _, national_number = partial_split number
      country.format national_number, options
    end
    alias formatted format

    # Is this number plausible?
    #
    def plausible? number, hints = {}
      normalized = clean number

      # False if it fails the basic check.
      #
      return false unless (4..16) === normalized.size # unless hints[:check_length] == false

      country, cc, rest = partial_split normalized
      
      # Was a country calling code given?
      #
      if ccc = hints[:ccc]
        cc, ndc, *local = split ccc
        
        raise ArgumentError.new("The provided ccc option is too long and includes more than a cc ('#{cc}') and ndc ('#{ndc}'). It also includes '#{local.join}'.") unless local.size == 1 && local[0].empty?
        
        hints[:cc] = cc
        hints[:ndc] = ndc
      end

      # Country code plausible?
      #
      cc_needed = hints[:cc]
      return false if cc_needed && !(cc_needed === cc)

      # Country specific tests.
      #
      country.plausible? rest, hints
    rescue ArgumentError
      raise
    rescue StandardError
      return false
    end
    
    # Is the given number a vanity number?
    #
    def vanity? number
      country, _, national = partial_split number
      country.vanity? national
    end
    # Converts a vanity number into a normalized E164 number.
    #
    def vanity_to_number vanity_number
      country, cc, national = partial_split vanity_number
      "#{cc}#{country.vanity_to_number(national)}"
    end

    private
    
      # Return a country for the number.
      #
      def country_for number
        country, _ = partial_split number
        country
      end
          
      # Split off the country and the cc, and also return the national number part.
      #
      def partial_split number
        cc = ''
        1.upto(3) do |i|
          cc << number.slice!(0..0)
          country = countries[i][cc]
          return [country, cc, number] if country
        end
        # This line is never reached as CCs are in prefix code.
      end
      
      # Adds the country code to the front
      # if it does not already start with it.
      #
      # Note: This won't be correct in some cases, but it is the best we can do.
      #
      def countrify number, cc
        countrify!(number, cc) || number
      end
      def countrify! number, cc
        number.sub!(/\A/, cc) # @countrify_regex, @cc
      end

  end

end