lib/countries/country.rb



module ISO3166; end

class ISO3166::Country
  Codes = YAML.load_file(File.join(File.dirname(__FILE__), '..', 'data', 'countries.yaml'))
  Translations = YAML.load_file(File.join(File.dirname(__FILE__), '..', 'cache', 'translations.yaml'))
  Data = {}
  Codes.each do |alpha2|
    Data[alpha2] = YAML.load_file(File.join(File.dirname(__FILE__), '..', 'data', 'countries', "#{alpha2}.yaml"))[alpha2]
    Data[alpha2] = Data[alpha2].merge(Translations[alpha2])
  end
  Names = I18nData.countries.values.sort_by { |d| d[0] }

  AttrReaders = [
    :number,
    :alpha2,
    :alpha3,
    :currency,
    :name,
    :names,
    :latitude,
    :longitude,
    :continent,
    :region,
    :subregion,
    :world_region,
    :country_code,
    :national_destination_code_lengths,
    :national_number_lengths,
    :international_prefix,
    :national_prefix,
    :address_format,
    :translations,
    :ioc,
    :gec,
    :un_locode,
    :languages,
    :nationality,
    :address_format,
    :dissolved_on,
    :eu_member,
    :alt_currency,
    :vat_rates,
    :postal_code,
    :min_longitude,
    :min_latitude,
    :max_longitude,
    :max_latitude,
    :latitude_dec,
    :longitude_dec
  ]

  AttrReaders.each do |meth|
    define_method meth do
      @data[meth.to_s]
    end
  end

  attr_reader :data

  def initialize(country_data)
    @data = country_data.is_a?(Hash) ? country_data : Data[country_data.to_s.upcase]
  end

  def valid?
    !(@data.nil? || @data.empty?)
  end

  alias_method :zip, :postal_code
  alias_method :zip?, :postal_code
  alias_method :postal_code?, :postal_code

  def ==(other)
    other == data
  end

  def currency
    ISO4217::Currency.from_code(@data['currency'])
  end

  def currency_code
    @data['currency']
  end

  def subdivisions
    @subdivisions ||= subdivisions? ? YAML.load_file(File.join(File.dirname(__FILE__), '..', 'data', 'subdivisions', "#{alpha2}.yaml")) : {}
  end

  alias_method :states, :subdivisions

  def subdivisions?
    File.exist?(File.join(File.dirname(__FILE__), '..', 'data', 'subdivisions', "#{alpha2}.yaml"))
  end

  def in_eu?
    @data['eu_member'].nil? ? false : @data['eu_member']
  end

  def to_s
    @data['name']
  end

  def translation(locale = 'en')
    @data['translations'][locale.downcase]
  end

  private

  class << self
    def new(country_data)
      if country_data.is_a?(Hash) || Data.keys.include?(country_data.to_s.upcase)
        super
      end
    end

    def all(&blk)
      blk ||= proc { |country, data| [data['name'], country] }
      Data.map &blk
    end

    alias_method :countries, :all

    def all_translated(locale = 'en')
      translations(locale).values
    end

    def translations(locale = 'en')
      I18nData.countries(locale.upcase)
    end

    def search(query)
      country = new(query.to_s.upcase)
      (country && country.valid?) ? country : nil
    end

    def [](query)
      search(query)
    end

    def method_missing(*m)
      regex = m.first.to_s.match(/^find_(all_)?(country_|countries_)?by_(.+)/)
      super unless regex

      countries = find_by(Regexp.last_match[3], m[1], Regexp.last_match[2])
      Regexp.last_match[1] ? countries : countries.last
    end

    def find_all_by(attribute, val)
      attributes, value = parse_attributes(attribute, val)

      Data.select do |_, v|
        attributes.map do |attr|
          Array(v[attr]).any? { |n| value === n.to_s.downcase }
        end.include?(true)
      end
    end

    protected

    def parse_attributes(attribute, val)
      fail "Invalid attribute name '#{attribute}'" unless AttrReaders.include?(attribute.to_sym)

      attributes = Array(attribute.to_s)
      if attributes == ['name']
        attributes << 'names'
        # TODO: Revisit when better data from i18n_data
        # attributes << 'translated_names'
      end

      val = (val.is_a?(Regexp) ? Regexp.new(val.source, 'i') : val.to_s.downcase)

      [attributes, val]
    end

    def find_by(attribute, value, obj = nil)
      find_all_by(attribute.downcase, value).map do |country|
        obj.nil? ? country : new(country.last)
      end
    end
  end
end

def ISO3166::Country(country_data_or_country)
  case country_data_or_country
  when ISO3166::Country
    country_data_or_country
  when String, Symbol
    ISO3166::Country.search(country_data_or_country)
  else
    fail TypeError, "can't convert #{country_data_or_country.class.name} into ISO3166::Country"
  end
end