require 'yaml' begin require 'ftools' rescue LoadError require 'fileutils' # ftools is now fileutils in Ruby 1.9 end # Fix to autoload in Rails 3 if defined?(Rails) if Rails::VERSION::MAJOR > 2 require 'carmen/railtie' else require 'carmen/action_view_helpers' end end require 'carmen/version' module Carmen class << self attr_accessor :default_country, :default_locale, :excluded_countries, :excluded_states, :priority_countries end self.default_country = 'US' self.default_locale = :en self.excluded_countries = [] self.excluded_states = {} self.priority_countries = [] @data_path = File.join(File.dirname(__FILE__), '..', 'data') @states = Dir[File.join(@data_path, '/states/*.yml')].map do |file_name| [File.basename(file_name, '.yml').upcase, YAML.load_file(file_name)] end # Raised when attempting to retrieve states for an unsupported country class StatesNotSupported < RuntimeError; end # Raised when attempting to work with a country not in the data set class NonexistentCountry < RuntimeError; end # Raised when attemting to switch to a locale which does not exist class UnavailableLocale < RuntimeError; end # Returns a list of all countries def self.countries(options={}) # Use specified locale or fall back to default locale locale = (options.delete(:locale) || @default_locale).to_s # Load the country list for the specified locale @countries ||= {} unless @countries[locale] # Check if data in the specified locale is available localized_data = File.join(@data_path, "countries", "#{locale}.yml") unless File.exists?(localized_data) localized_data = File.join(@data_path, "countries", "#{@default_locale}.yml") # raise(UnavailableLocale, "Could not load countries for '#{locale}' locale") end # As the data exists, load it @countries[locale] = YAML.load_file(localized_data) end # Return data after filtering excluded countries and prepending prepended countries result = @countries[locale].reject { |c| excluded_countries.include?( c[1] ) } priority_countries.map { |code| [ search_collection(result, code, 1, 0), code ] } + result end # Returns the country name corresponding to the supplied country code, optionally using the specified locale. # Carmen::country_name('TR') => 'Turkey' # Carmen::country_name('TR', :locale => :de) => 'Türkei' def self.country_name(country_code, options={}) search_collection(countries(options), country_code, 1, 0) end # Returns the country code corresponding to the supplied country name # Carmen::country_code('Canada') => 'CA' def self.country_code(country_name, options={}) search_collection(countries(options), country_name, 0, 1) end # Returns an array of all country codes # Carmen::country_codes => ['AF', 'AX', 'AL', ... ] def self.country_codes countries.map {|c| c[1] } end # Returns an array of all country names, optionally using the specified locale. # Carmen::country_names => ['Afghanistan', 'Aland Islands', 'Albania', ... ] # Carmen::country_names(:locale => :de) => ['Afghanistan', 'Åland', 'Albanien', ... ] def self.country_names(options={}) countries(options).map {|c| c[0] } end # Returns the state name corresponding to the supplied state code within the default country # Carmen::state_code('New Hampshire') => 'NH' def self.state_name(state_code, country_code = Carmen.default_country, options={}) search_collection(self.states(country_code, options), state_code, 1, 0) end # Returns the state code corresponding to the supplied state name within the specified country # Carmen::state_code('IL', 'US') => Illinois def self.state_code(state_name, country_code = Carmen.default_country, options={}) search_collection(self.states(country_code, options), state_name, 0, 1) end # Returns an array of state names within the default code # Carmen::state_names('US') => ['Alabama', 'Arkansas', ... ] def self.state_names(country_code = Carmen.default_country, options={}) self.states(country_code, options).map{|name, code| name} end # Returns an array of state codes within the specified country code # Carmen::state_codes('US') => ['AL', 'AR', ... ] def self.state_codes(country_code = Carmen.default_country) self.states(country_code).map{|name, code| code} end # Returns an array structure of state names and codes within the specified country code, or within the default country # if none is provided. # Carmen::states('US') => [['Alabama', 'AL'], ['Arkansas', 'AR'], ... ] # Carmen::states => [['Alabama', 'AL'], ['Arkansas', 'AR'], ... ] def self.states(country_code = Carmen.default_country, options={}) raise NonexistentCountry.new("Country not found for code #{country_code}") unless country_codes.include?(country_code) results = if states?(country_code) search_collection(@states, country_code, 0, 1) else [] end if excluded_states[country_code] results.reject { |s| excluded_states[country_code].include?(s[1]) } else results end end # Returns whether states are supported for the given country code # Carmen::states?('US') => true # Carmen::states?('ZZ') => false def self.states?(country_code, options={}) @states.any? do |array| k,v = array k == country_code end end protected def self.search_collection(collection, value, index_to_match, index_to_retrieve) value = value.to_s return nil if collection.nil? || value =~ /^\s*$/ collection.each do |m| return m[index_to_retrieve] if m[index_to_match].downcase == value.downcase end # In case we didn't get any results we'll try a broader search (via Regexp) collection.each do |m| return m[index_to_retrieve] if m[index_to_match].downcase.match(Regexp.escape(value.downcase)) end nil end end