countries-phone_numbers 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 8d299eda3bc0d2e84c08d735a501c4c47398964c
4
+ data.tar.gz: b33baf81c54d96734b71fc0f82383d35704e4b12
5
+ SHA512:
6
+ metadata.gz: 68f5972c6d39e1106b576408360cbdb69c4cdcd81c56af2e7a85193b41edc22dc6af128ae05c6a4d519f96b124bc8b90c30a0bd58e2081431cf28cf44cc79c65
7
+ data.tar.gz: 42d5ee3f67a0874ec07b5a303be69a1a172b590e7c724e45c67e988b3073f4a672e3ebadd8afc1f65d0d1900fa763766ff8539ac88ce2d0dd2b6576a82851a17
data/.gitignore ADDED
@@ -0,0 +1,17 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ Gemfile.lock
7
+ InstalledFiles
8
+ _yardoc
9
+ coverage
10
+ doc/
11
+ lib/bundler/man
12
+ pkg
13
+ rdoc
14
+ spec/reports
15
+ test/tmp
16
+ test/version_tmp
17
+ tmp
data/.rspec ADDED
@@ -0,0 +1,2 @@
1
+ --color
2
+ --format progress
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in countries-phone_numbers.gemspec
4
+ gemspec
data/LICENSE.txt ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2013 Ian Lloyd
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,52 @@
1
+ # Countries::PhoneNumbers
2
+
3
+ Integrate phone number to country lookup functionality into the ever-popular [Countries](https://github.com/hexorx/countries) gem using the excellent [Phony](https://github.com/floere/phony) gem. This lets you find the country for a given phone number quickly and easily.
4
+
5
+ ## Installation
6
+
7
+ Add this line to your application's Gemfile:
8
+
9
+ gem 'countries-plus-phonenumbers'
10
+
11
+ And then execute:
12
+
13
+ $ bundle
14
+
15
+ Or install it yourself as:
16
+
17
+ $ gem install countries-plus-phonenumbers
18
+
19
+ ## Usage
20
+
21
+ It's easy. Just use the `Country.find_country_by_phone_number(number)` or `Country.find_by_phone_number(number)` methods just like you would for any other Country search. And just like with default Country search methods, it will return either an array of data or a Country object.
22
+
23
+ Time for some examples. Let's say you need to call your best pal, President Obama in the US White House.
24
+
25
+ country = Country.find_country_by_phone_number( '+1 202-456-1111' )
26
+ => #<Country:0x007febbac72ad8 @data={"continent"=>"North America", "address_format"=>"{{recipient}}\n{{street}}\n{{city}} {{region}} {{postalcode}}\n{{country}}", "alpha2"=>"US", "alpha3"=>"USA", "country_code"=>"1", "currency"=>"USD", "international_prefix"=>"011", "ioc"=>"USA", "latitude"=>"38 00 N", "longitude"=>"97 00 W", "name"=>"United States", "names"=>["United States of America", "Vereinigte Staaten von Amerika", "États-Unis", "Estados Unidos", "アメリカ合衆国", "Verenigde Staten"], "translations"=>{"en"=>"United States of America", "it"=>"Stati Uniti D'America", "de"=>"Vereinigte Staaten von Amerika", "fr"=>"États-Unis", "es"=>"Estados Unidos", "ja"=>"アメリカ合衆国", "nl"=>"Verenigde Staten"}, "national_destination_code_lengths"=>[3], "national_number_lengths"=>[10], "national_prefix"=>"1", "number"=>"840", "region"=>"Americas", "subregion"=>"Northern America", "un_locode"=>"US", "languages"=>["en"], "nationality"=>"American"}>
27
+
28
+ This gives you the normal country object. Treat it like you would any other search from the Country gem.
29
+
30
+ country.name
31
+ => "United States"
32
+
33
+ country.alpha2
34
+ => "US"
35
+
36
+ ## What about countries that share the same Country Code?
37
+
38
+ When multiple countries share a single country code - for instance, the North American Number Plan (NANP) - CountryDetectors are employed to provide additional analysis and intepretation. These detectors are not perfect, but they should catch most ambiguous situations.
39
+
40
+ The detectors are configured in `country_detectors.yaml`.
41
+
42
+ ## Why are there a boatload of pending specs?
43
+
44
+ Any entries in the Country gem that do not have test cases will appear as pending specs. This is to help track which countries still need solid tests. If you can help by offering some numbers to validate, please contribute!
45
+
46
+ ## Contributing
47
+
48
+ 1. Fork it
49
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
50
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
51
+ 4. Push to the branch (`git push origin my-new-feature`)
52
+ 5. Create new Pull Request
data/Rakefile ADDED
@@ -0,0 +1 @@
1
+ require "bundler/gem_tasks"
@@ -0,0 +1,27 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'countries/phone_numbers/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "countries-phone_numbers"
8
+ spec.version = Countries::PhoneNumbers::VERSION
9
+ spec.authors = ["Ian Lloyd"]
10
+ spec.email = ["ian.w.lloyd@me.com"]
11
+ spec.description = %q{Find countries by phone numbers}
12
+ spec.summary = %q{Integrate phone number searching into the Country gem using Phony}
13
+ spec.homepage = "http://github.com/illoyd/countries-phone_numbers"
14
+ spec.license = "MIT"
15
+
16
+ spec.files = `git ls-files`.split($/)
17
+ spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
18
+ spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
19
+ spec.require_paths = ["lib"]
20
+
21
+ spec.add_dependency 'countries', '>= 0.9'
22
+ spec.add_dependency 'phony', '>= 1.9'
23
+
24
+ spec.add_development_dependency "bundler", "~> 1.3"
25
+ spec.add_development_dependency "rake"
26
+ spec.add_development_dependency "rspec"
27
+ end
@@ -0,0 +1,116 @@
1
+ require 'countries'
2
+ require 'phony'
3
+
4
+ class ISO3166::Country
5
+
6
+ def self.normalize_phone_number( number )
7
+ Phony.normalize( number )
8
+ end
9
+
10
+ def self.tokenize_phone_number( number )
11
+ Phony.split( Phony.normalize( number ) )
12
+ end
13
+
14
+ ##
15
+ # Find the first country by the given telephone number. Returns an array of country code and data
16
+ def self.find_by_phone_number( number )
17
+ found = find_all_by_phone_number(number)
18
+ found.nil? ? nil : found.first
19
+ end
20
+
21
+ ##
22
+ # Find all possible countries for the given telephone number.
23
+ # Generally we try to eliminate duplicates by using country code specific detectors.
24
+ def self.find_all_by_phone_number( number )
25
+ normalised_number = tokenize_phone_number( number )
26
+
27
+ # Is this a detector country?
28
+ detector = phone_number_detectors[normalised_number.first]
29
+ if detector
30
+ return detector.find_all_by_phone_number( normalised_number )
31
+
32
+ # Otherwise ask the general code base for the number
33
+ else
34
+ return self.find_all_by_country_code( normalised_number.first )
35
+ end
36
+
37
+ rescue
38
+ return nil
39
+ end
40
+
41
+ ##
42
+ # Find the first country by the given telephone number. Returns the country object itself.
43
+ def self.find_country_by_phone_number( number )
44
+ found = find_all_countries_by_phone_number(number)
45
+ found.nil? ? nil : found.first
46
+ end
47
+
48
+ ##
49
+ # Find all possible countries for the given telephone number.
50
+ # Generally we try to eliminate duplicates by using country code specific detectors.
51
+ def self.find_all_countries_by_phone_number( number )
52
+ normalised_number = tokenize_phone_number( number )
53
+
54
+ # Is this a detector country?
55
+ detector = phone_number_detector_for normalised_number.first
56
+ if detector
57
+ return detector.find_all_countries_by_phone_number( normalised_number )
58
+
59
+ # Otherwise ask the general code base for the number
60
+ else
61
+ return self.find_all_countries_by_country_code( normalised_number.first )
62
+ end
63
+
64
+ rescue
65
+ return nil
66
+ end
67
+
68
+ ##
69
+ # Get the requested phone number detector based on the phone number's prefix.
70
+ def self.phone_number_detector_for( prefix )
71
+ prefix = prefix.to_s
72
+ detector = phone_number_detectors[ prefix ]
73
+ if detector.is_a? Hash
74
+ detector = Countries::PhoneNumbers::CountryDetector.build( detector )
75
+ phone_number_detectors[ prefix ] = detector
76
+ end
77
+ detector
78
+ end
79
+
80
+ ##
81
+ # Find all countries with shared country codes.
82
+ def self.shared_country_codes
83
+ codes = Country.all.map { |cc| Country[cc[1]].country_code }.uniq
84
+ shared = codes.each_with_object({}){ |cc,h| h[cc] = Country.find_all_countries_by_country_code(cc) }
85
+ shared.reject!{ |key,entry| entry.nil? or entry.count <= 1 }
86
+ shared.each{ |cc,countries| shared[cc] = countries.map{ |c| c.name } }
87
+ end
88
+
89
+ ##
90
+ # Find all countries with shared country codes and do not have a dedicated detector.
91
+ def self.unresolved_country_codes
92
+ self.shared_country_codes.reject{ |key,value| self.phone_number_detectors.keys.include? key or key == '' }
93
+ end
94
+
95
+ protected
96
+
97
+ ##
98
+ # Collection of country detectors. Detectors are instantiated only when first needed.
99
+ def self.phone_number_detectors
100
+ @@phone_number_detectors ||= load_phone_number_detector_config
101
+ end
102
+
103
+ ##
104
+ # Load phone number detector configuration.
105
+ def self.load_phone_number_detector_config
106
+ filename = File.join( 'lib', 'countries', 'phone_numbers', 'country_detectors.yaml' )
107
+ config = {}
108
+ File.open( filename ) do |file|
109
+ YAML.load_documents( file ) do |doc|
110
+ config[doc['applies_to'].to_s] = doc
111
+ end
112
+ end
113
+ config
114
+ end
115
+
116
+ end
@@ -0,0 +1,44 @@
1
+ class Countries::PhoneNumbers::CountryDetector
2
+
3
+ attr_accessor :country_codes, :applies_to, :default
4
+
5
+ def self.build config
6
+ # Build a new config tool based on the given strategy
7
+ return case
8
+ when config.include?('start_with')
9
+ Countries::PhoneNumbers::StartWithCountryDetector.new config
10
+ when config.include?('one_of')
11
+ Countries::PhoneNumbers::OneOfCountryDetector.new config
12
+ else
13
+ Countries::PhoneNumbers::CountryDetector.new config
14
+ end
15
+ end
16
+
17
+ def initialize config
18
+ self.applies_to = config['applies_to'].to_s
19
+ self.default = config['default'].to_s
20
+ end
21
+
22
+ def find_all_by_phone_number number
23
+ [ find_by_phone_number(number) ]
24
+ end
25
+
26
+ def find_by_phone_number number
27
+ return Country.find_by_alpha2 find_alpha2(number)
28
+ end
29
+
30
+ def find_all_countries_by_phone_number number
31
+ [ find_country_by_phone_number(number) ]
32
+ end
33
+
34
+ def find_country_by_phone_number number
35
+ return Country[find_alpha2(number)]
36
+ end
37
+
38
+ protected
39
+
40
+ def find_alpha2(number)
41
+ self.default
42
+ end
43
+
44
+ end
@@ -0,0 +1,123 @@
1
+ --- # North American Numbering Plan
2
+ applies_to: 1
3
+ default: US
4
+ one_of: {
5
+ CA: [ # Canada
6
+ 403, 587, 780, 825, # Alberta
7
+ 236, 250, 604, 672, 778, # British Columbia
8
+ 204, 431, # Manitoba
9
+ 506, # New Brunswick
10
+ 709, # Newfoundland and Labrador
11
+ 902, # Nova Scotia
12
+ 226, 249, 289, 343, 365, 416, 437, 519, 613, 647, 705, 807, 905, # Ontario
13
+ 902, # Prince Edward Island
14
+ 418, 438, 450, 514, 579, 581, 819, 873, # Quebec
15
+ 306, 639, # Saskatchewan
16
+ 867 # Yukon, Northwest Territories, and Nunavut
17
+ ],
18
+ AS: [ 684 ], # American Samoa
19
+ AI: [ 264 ], # Anguila
20
+ AG: [ 268 ], # Antigua & Barbuda
21
+ BS: [ 242 ], # The Bahamas
22
+ BB: [ 246 ], # Barbados
23
+ BM: [ 441 ], # Bermuda
24
+ VG: [ 284 ], # British Virgin Islands
25
+ KY: [ 345 ], # Cayman Islands
26
+ DM: [ 767 ], # Dominica
27
+ DO: [ 809, 829, 849 ], # Dominican Republic
28
+ GD: [ 473 ], # Grenada
29
+ GU: [ 671 ], # Guam
30
+ JM: [ 876 ], # Jamaica
31
+ MS: [ 664 ], # Montserrat
32
+ KN: [ 869 ], # St Kitts & Nevis
33
+ MP: [ 670 ], # Northern Mariana Islands
34
+ PR: [ 787, 939 ], # Puerto Rico
35
+ LC: [ 758 ], # St. Lucia
36
+ VC: [ 784 ], # St Vincent & the Grenadines
37
+ SX: [ 721 ], # Sint Martin
38
+ TT: [ 868 ], # Trinidad & Tobago
39
+ TC: [ 649 ], # Turks & Caicos Islands
40
+ VI: [ 340 ] # Virgin Islands
41
+ }
42
+
43
+ --- # Russia & Kazakhastan
44
+ applies_to: 7
45
+ default: RU
46
+ start_with: {
47
+ KZ: [ 6, 7 ]
48
+ }
49
+
50
+ --- # Italy & Vatican City
51
+ applies_to: 39
52
+ default: IT
53
+ start_with: {
54
+ VA: [ '06698' ]
55
+ }
56
+
57
+ --- # United Kingdom, Jersey, Gurnssey, & Isle of Man
58
+ applies_to: 44
59
+ default: GB
60
+ start_with: {
61
+ GG: [ 1481, 7781, 7839, 7911 ],
62
+ JE: [ 1534, 7509, 7797, 7937, 7700, 7829 ],
63
+ IM: [ 1624, 7624, 7524, 7924 ],
64
+ }
65
+
66
+ --- # Norway & Svalbard
67
+ applies_to: 47
68
+ default: 'NO'
69
+ start_with: {
70
+ SJ: [ 79 ]
71
+ }
72
+
73
+ --- # Australian Telephone Numbering Plan
74
+ applies_to: 61
75
+ default: AU
76
+ start_with: {
77
+ CX: [ 89164 ],
78
+ CC: [ 89162 ]
79
+ }
80
+
81
+ --- # Morocco & Western Sahara
82
+ applies_to: 212
83
+ default: MA
84
+ start_with: {
85
+ SH: [ 5288, 5289 ]
86
+ }
87
+
88
+ --- # Ascension Island (part of St Helena)
89
+ applies_to: 247
90
+ default: SH
91
+
92
+ --- # Reunion & Mayotte
93
+ applies_to: 262
94
+ default: RE
95
+ start_with: {
96
+ YT: [ 269, 639 ]
97
+ }
98
+
99
+ --- # Finland & Analand Island
100
+ applies_to: 358
101
+ default: FI
102
+ start_with: {
103
+ AX: [ 18, 457 ]
104
+ }
105
+
106
+ --- # Falklands; South Sandwith Islands
107
+ applies_to: 500
108
+ default: FK
109
+
110
+ --- # Guadalupe; Saint Barthelemy; Saint Martin (default to Guadalupe)
111
+ applies_to: 590
112
+ default: GP
113
+
114
+ --- # Netherlands Antilles; Bonaire, Sint Eustatius and Saba; Curacao
115
+ applies_to: 599
116
+ default: AN
117
+ start_with: {
118
+ CW: [ 97 ]
119
+ }
120
+
121
+ --- # Norfolk Island & Antartica (We're just going to ignore Antartica)
122
+ applies_to: 672
123
+ default: NF
@@ -0,0 +1,27 @@
1
+ class Countries::PhoneNumbers::OneOfCountryDetector < Countries::PhoneNumbers::CountryDetector
2
+
3
+ def initialize( config )
4
+ super config
5
+ self.country_codes = config['one_of']
6
+
7
+ # Standardise all country codes
8
+ self.country_codes.each do |alpha2, codes|
9
+ self.country_codes[alpha2.to_s] = codes.map{ |code| code.to_s }
10
+ end
11
+ end
12
+
13
+ protected
14
+
15
+ def find_alpha2 number
16
+ # Split the given number unless it is an array (assumes that it has already been split)
17
+ number = Country.tokenize_phone_number(number) unless number.is_a?(Array)
18
+
19
+ # Loop over all prefixes
20
+ self.country_codes.each do |alpha2, codes|
21
+ return alpha2.to_s if codes.include? number[1]
22
+ end
23
+
24
+ return self.default
25
+ end
26
+
27
+ end
@@ -0,0 +1,30 @@
1
+ class Countries::PhoneNumbers::StartWithCountryDetector < Countries::PhoneNumbers::CountryDetector
2
+
3
+ def initialize( config )
4
+ super config
5
+ self.country_codes = config['start_with']
6
+
7
+ # Standardise all country codes
8
+ self.country_codes.each do |alpha2, codes|
9
+ self.country_codes[alpha2.to_s] = codes.map{ |code| code.to_s }
10
+ end
11
+ end
12
+
13
+ protected
14
+
15
+ def find_alpha2 number
16
+ # Split the given number unless it is an array (assumes that it has already been split)
17
+ number = Country.tokenize_phone_number(number) unless number.is_a?(Array)
18
+ number = number.drop(1).join
19
+
20
+ # Loop over all prefixes
21
+ self.country_codes.each do |alpha2, codes|
22
+ codes.each do |prefix|
23
+ return alpha2 if number.start_with? prefix.to_s
24
+ end
25
+ end
26
+
27
+ return self.default
28
+ end
29
+
30
+ end
@@ -0,0 +1,5 @@
1
+ module Countries
2
+ module PhoneNumbers
3
+ VERSION = "0.0.1"
4
+ end
5
+ end
@@ -0,0 +1,18 @@
1
+ require "countries/phone_numbers/version"
2
+
3
+ # Primary gems
4
+ require 'countries'
5
+ require 'phony'
6
+
7
+ # Country::PhoneNumber
8
+ require 'countries/phone_numbers/country_detector'
9
+ require 'countries/phone_numbers/one_of_country_detector'
10
+ require 'countries/phone_numbers/start_with_country_detector'
11
+
12
+ # Gem extensions
13
+ require 'countries/iso3166'
14
+
15
+ module Countries
16
+ module PhoneNumbers
17
+ end
18
+ end
@@ -0,0 +1,38 @@
1
+ require 'spec_helper'
2
+ require 'countries/phone_numbers'
3
+
4
+ describe ISO3166::Country, '#find_*_by_phone_number' do
5
+ subject { Country }
6
+
7
+ # Test a bogus phone number
8
+ it 'ignores bogus phone numbers (single digit)' do
9
+ Country.find_country_by_phone_number( '5' ).should == nil
10
+ end
11
+
12
+ # Test another bogus phone number
13
+ it 'ignores bogus phone numbers (text)' do
14
+ Country.find_country_by_phone_number( 'abc' ).should == nil
15
+ end
16
+
17
+ # Test all data given in the 'TEST' block above
18
+ TEST_DATA.each do |alpha2, numbers|
19
+ numbers.each do |number|
20
+ it "recognises #{number} as #{alpha2.to_s}" do
21
+ Country.find_country_by_phone_number(number).should_not be_nil
22
+ Country.find_country_by_phone_number(number).alpha2.should == alpha2.to_s
23
+ end
24
+ it "returns only one country for #{number}" do
25
+ Country.find_all_countries_by_phone_number(number).should have(1).country
26
+ end
27
+ end
28
+ end
29
+
30
+ Country.unresolved_country_codes.each do |country_code, countries|
31
+ pending "Need to resolve CC: #{country_code} for #{countries.join(', ')}."
32
+ end
33
+
34
+ (Country.all.map{ |country| country[1] } - TEST_DATA.keys).each do |alpha2|
35
+ pending "No tests for #{Country[alpha2].name} (#{alpha2})."
36
+ end
37
+
38
+ end
@@ -0,0 +1,24 @@
1
+ require 'rubygems'
2
+ require 'bundler/setup'
3
+ require 'countries/phone_numbers'
4
+
5
+ require 'yaml'
6
+ TEST_DATA = YAML.load_file( File.join( File.dirname(__FILE__), 'test_data.yaml' ) )
7
+
8
+ # This file was generated by the `rspec --init` command. Conventionally, all
9
+ # specs live under a `spec` directory, which RSpec adds to the `$LOAD_PATH`.
10
+ # Require this file using `require "spec_helper"` to ensure that it is only
11
+ # loaded once.
12
+ #
13
+ # See http://rubydoc.info/gems/rspec-core/RSpec/Core/Configuration
14
+ RSpec.configure do |config|
15
+ config.treat_symbols_as_metadata_keys_with_true_values = true
16
+ config.run_all_when_everything_filtered = true
17
+ config.filter_run :focus
18
+
19
+ # Run specs in random order to surface order dependencies. If you find an
20
+ # order dependency and want to debug it, you can fix the order by providing
21
+ # the seed, which is printed after each run.
22
+ # --seed 1234
23
+ config.order = 'random'
24
+ end