phone 1.2.3 → 1.3.0.beta0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.document +3 -0
- data/.gitignore +5 -0
- data/.ruby-version +1 -0
- data/.travis.yml +14 -0
- data/.yardopts +1 -0
- data/ChangeLog.md +50 -0
- data/Gemfile +11 -0
- data/LICENSE.txt +22 -0
- data/README.md +183 -0
- data/Rakefile +38 -0
- data/data/{phone_countries.yml → phone/countries.yml} +16 -14
- data/lib/phone.rb +75 -53
- data/lib/phone/country.rb +45 -0
- data/lib/{errors.rb → phone/errors.rb} +0 -0
- data/lib/phone/version.rb +4 -0
- data/phone.gemspec +25 -0
- data/test/countries/au_test.rb +11 -11
- data/test/countries/ba_test.rb +4 -4
- data/test/countries/be_test.rb +12 -11
- data/test/countries/de_test.rb +7 -7
- data/test/countries/es_test.rb +33 -0
- data/test/countries/fr_test.rb +5 -5
- data/test/countries/gb_test.rb +42 -41
- data/test/countries/hr_test.rb +14 -14
- data/test/countries/hu_test.rb +5 -5
- data/test/countries/ie_test.rb +20 -0
- data/test/countries/nl_test.rb +43 -43
- data/test/countries/nz_test.rb +8 -0
- data/test/countries/pt_test.rb +18 -18
- data/test/countries/rs_test.rb +7 -7
- data/test/countries/se_test.rb +44 -43
- data/test/countries/si_test.rb +7 -7
- data/test/countries/ua_test.rb +4 -4
- data/test/countries/us_test.rb +6 -6
- data/test/countries/uy_test.rb +22 -0
- data/test/countries/za_test.rb +4 -4
- data/test/extension_test.rb +3 -3
- data/test/helper.rb +41 -0
- data/test/phone_test.rb +99 -53
- data/test_usa_phones_with_extensions.csv +99 -0
- metadata +112 -30
- data/LICENSE +0 -19
- data/Readme.rdoc +0 -126
- data/lib/country.rb +0 -36
- data/lib/support.rb +0 -78
- data/test/test_helper.rb +0 -15
data/lib/phone.rb
CHANGED
@@ -1,5 +1,5 @@
|
|
1
1
|
# An object representing a phone number.
|
2
|
-
#
|
2
|
+
#
|
3
3
|
# The phone number is recorded in 3 separate parts:
|
4
4
|
# * country_code - e.g. '385', '386'
|
5
5
|
# * area_code - e.g. '91', '47'
|
@@ -9,34 +9,55 @@
|
|
9
9
|
# Phone.default_country_code
|
10
10
|
# Phone.default_area_code
|
11
11
|
#
|
12
|
-
require
|
13
|
-
require
|
14
|
-
require File.join(File.dirname(__FILE__), 'errors')
|
12
|
+
require "phone/country"
|
13
|
+
require "phone/errors"
|
15
14
|
|
16
15
|
module Phoner
|
17
16
|
class Phone
|
18
|
-
|
19
|
-
|
17
|
+
module ClassAttributes
|
18
|
+
attr_accessor :default_country_code
|
19
|
+
attr_accessor :default_area_code
|
20
|
+
attr_accessor :named_formats
|
21
|
+
attr_accessor :n1_length
|
22
|
+
end
|
23
|
+
extend ClassAttributes
|
20
24
|
|
21
|
-
|
25
|
+
module ClassAttributeAccessors
|
26
|
+
def default_country_code
|
27
|
+
self.class.default_country_code
|
28
|
+
end
|
22
29
|
|
23
|
-
|
24
|
-
|
25
|
-
|
30
|
+
def default_area_code
|
31
|
+
self.class.default_area_code
|
32
|
+
end
|
26
33
|
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
34
|
+
def named_formats
|
35
|
+
self.class.named_formats
|
36
|
+
end
|
37
|
+
|
38
|
+
def n1_length
|
39
|
+
self.class.n1_length
|
40
|
+
end
|
41
|
+
end
|
42
|
+
include ClassAttributeAccessors
|
43
|
+
|
44
|
+
NUMBER = '([0-9]{1,8})$'
|
45
|
+
DEFAULT_AREA_CODE = '[0-9][0-9][0-9]' # any 3 digits
|
31
46
|
|
32
|
-
|
47
|
+
attr_accessor :country_code, :area_code, :number, :extension
|
48
|
+
|
49
|
+
self.named_formats = {
|
33
50
|
:default => "+%c%a%n",
|
34
51
|
:default_with_extension => "+%c%a%nx%x",
|
35
52
|
:europe => '+%c (0) %a %f %l',
|
36
53
|
:us => "(%a) %f-%l"
|
37
54
|
}
|
38
55
|
|
39
|
-
|
56
|
+
# length of first number part (using multi number format)
|
57
|
+
# default length of first number part
|
58
|
+
self.n1_length = 3
|
59
|
+
|
60
|
+
def initialize(*hash_or_args)
|
40
61
|
if hash_or_args.first.is_a?(Hash)
|
41
62
|
hash_or_args = hash_or_args.first
|
42
63
|
keys = {:number => :number, :area_code => :area_code, :country_code => :country_code, :extension => :extension}
|
@@ -44,41 +65,39 @@ module Phoner
|
|
44
65
|
keys = {:number => 0, :area_code => 1, :country_code => 2, :extension => 3}
|
45
66
|
end
|
46
67
|
|
47
|
-
self.number = hash_or_args[ keys[:number] ]
|
48
|
-
self.area_code = hash_or_args[ keys[:area_code] ] || self.default_area_code
|
49
|
-
self.country_code = hash_or_args[ keys[:country_code] ] || self.default_country_code
|
68
|
+
self.number = hash_or_args[ keys[:number] ].to_s.strip
|
69
|
+
self.area_code = (hash_or_args[ keys[:area_code] ] || self.default_area_code).to_s.strip
|
70
|
+
self.country_code = (hash_or_args[ keys[:country_code] ] || self.default_country_code).to_s.strip
|
50
71
|
self.extension = hash_or_args[ keys[:extension] ]
|
51
72
|
|
52
|
-
raise
|
53
|
-
raise AreaCodeError, "Must enter area code or set default area code" if self.area_code.
|
54
|
-
raise CountryCodeError, "Must enter country code or set default country code" if self.country_code.
|
73
|
+
raise BlankNumberError, "Must enter number" if self.number.empty?
|
74
|
+
raise AreaCodeError, "Must enter area code or set default area code" if self.area_code.empty?
|
75
|
+
raise CountryCodeError, "Must enter country code or set default country code" if self.country_code.empty?
|
55
76
|
end
|
56
77
|
|
57
78
|
# create a new phone number by parsing a string
|
58
79
|
# the format of the string is detect automatically (from FORMATS)
|
59
|
-
def self.parse(string, options={})
|
60
|
-
if string.
|
61
|
-
Country.load
|
62
|
-
extension = extract_extension(string)
|
63
|
-
string = normalize(string)
|
80
|
+
def self.parse(string, options={})
|
81
|
+
return if string.to_s.empty?
|
64
82
|
|
65
|
-
|
66
|
-
|
83
|
+
Country.load
|
84
|
+
extension = extract_extension(string)
|
85
|
+
string = normalize(string)
|
67
86
|
|
68
|
-
|
87
|
+
options[:country_code] ||= self.default_country_code
|
88
|
+
options[:area_code] ||= self.default_area_code
|
69
89
|
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
return pn
|
75
|
-
end
|
90
|
+
parts = split_to_parts(string, options)
|
91
|
+
|
92
|
+
pn = Phone.new(parts.merge(:extension => extension)) if parts
|
93
|
+
return pn
|
76
94
|
end
|
77
95
|
|
96
|
+
|
78
97
|
# is this string a valid phone number?
|
79
|
-
def self.valid?(string)
|
98
|
+
def self.valid?(string, options = {})
|
80
99
|
begin
|
81
|
-
parse(string).
|
100
|
+
!parse(string, options).nil?
|
82
101
|
# if we encountered exceptions (missing country code, missing area code etc)
|
83
102
|
rescue PhoneError
|
84
103
|
return false
|
@@ -90,7 +109,7 @@ module Phoner
|
|
90
109
|
country = detect_country(string)
|
91
110
|
|
92
111
|
if country
|
93
|
-
options[:country_code] = country.country_code
|
112
|
+
options[:country_code] = country.country_code
|
94
113
|
string = string.gsub(country.country_code_regexp, '0')
|
95
114
|
else
|
96
115
|
if options[:country_code]
|
@@ -107,17 +126,20 @@ module Phoner
|
|
107
126
|
end
|
108
127
|
|
109
128
|
format = detect_format(string, country)
|
129
|
+
return nil if format.nil?
|
110
130
|
|
111
|
-
|
131
|
+
# Override the format IF overriding options are not present
|
132
|
+
format = :short if options[:area_code].nil?
|
112
133
|
|
113
134
|
parts = string.match formats(country)[format]
|
135
|
+
return nil if parts.nil?
|
114
136
|
|
115
|
-
case format
|
137
|
+
case format
|
116
138
|
when :short
|
117
|
-
{:number => parts[2], :area_code => parts[1], :country_code => options[:country_code]}
|
139
|
+
{:number => parts[2], :area_code => parts[1], :country_code => options[:country_code]}
|
118
140
|
when :really_short
|
119
|
-
{:number => parts[1], :area_code => options[:area_code], :country_code => options[:country_code]}
|
120
|
-
end
|
141
|
+
{:number => parts[1], :area_code => options[:area_code], :country_code => options[:country_code]}
|
142
|
+
end
|
121
143
|
end
|
122
144
|
|
123
145
|
# detect country from the string entered
|
@@ -129,7 +151,7 @@ module Phoner
|
|
129
151
|
detected_country = country
|
130
152
|
end
|
131
153
|
end
|
132
|
-
detected_country
|
154
|
+
detected_country
|
133
155
|
end
|
134
156
|
|
135
157
|
def self.formats(country)
|
@@ -139,7 +161,7 @@ module Phoner
|
|
139
161
|
:short => Regexp.new('^0?(' + area_code_regexp + ')' + NUMBER),
|
140
162
|
# 451588
|
141
163
|
:really_short => Regexp.new('^' + NUMBER)
|
142
|
-
}
|
164
|
+
}
|
143
165
|
end
|
144
166
|
|
145
167
|
# detect format (from FORMATS) of input string
|
@@ -201,8 +223,8 @@ module Phoner
|
|
201
223
|
end
|
202
224
|
|
203
225
|
# Formats the phone number.
|
204
|
-
#
|
205
|
-
# if the method argument is a String, it is used as a format string, with the following fields being interpolated:
|
226
|
+
#
|
227
|
+
# if the method argument is a String, it is used as a format string, with the following fields being interpolated:
|
206
228
|
#
|
207
229
|
# * %c - country_code (385)
|
208
230
|
# * %a - area_code (91)
|
@@ -214,7 +236,7 @@ module Phoner
|
|
214
236
|
#
|
215
237
|
# if the method argument is a Symbol, it is used as a lookup key for a format String in Phone.named_formats
|
216
238
|
# pn.format(:europe)
|
217
|
-
def format(fmt)
|
239
|
+
def format(fmt)
|
218
240
|
if fmt.is_a?(Symbol)
|
219
241
|
raise "The format #{fmt} doesn't exist'" unless named_formats.has_key?(fmt)
|
220
242
|
format_number named_formats[fmt]
|
@@ -237,24 +259,24 @@ module Phoner
|
|
237
259
|
def has_default_area_code?
|
238
260
|
area_code == self.class.default_area_code
|
239
261
|
end
|
240
|
-
|
262
|
+
|
241
263
|
# comparison of 2 phone objects
|
242
264
|
def ==(other)
|
243
265
|
methods = [:country_code, :area_code, :number, :extension]
|
244
266
|
methods.all? { |method| other.respond_to?(method) && send(method) == other.send(method) }
|
245
|
-
end
|
267
|
+
end
|
246
268
|
|
247
269
|
private
|
248
270
|
|
249
271
|
def format_number(fmt)
|
250
272
|
result = fmt.gsub("%c", country_code || "").
|
251
273
|
gsub("%a", area_code || "").
|
252
|
-
gsub("%A", area_code_long || "").
|
274
|
+
gsub("%A", area_code_long || "").
|
253
275
|
gsub("%n", number || "").
|
254
276
|
gsub("%f", number1 || "").
|
255
277
|
gsub("%l", number2 || "").
|
256
278
|
gsub("%x", extension || "")
|
257
279
|
return result
|
258
280
|
end
|
259
|
-
end
|
281
|
+
end
|
260
282
|
end
|
@@ -0,0 +1,45 @@
|
|
1
|
+
require 'yaml'
|
2
|
+
|
3
|
+
module Phoner
|
4
|
+
class Country < Struct.new(:name, :country_code, :char_2_code, :char_3_code, :area_code)
|
5
|
+
module All
|
6
|
+
attr_accessor :all
|
7
|
+
end
|
8
|
+
extend All
|
9
|
+
|
10
|
+
def all
|
11
|
+
self.class.all
|
12
|
+
end
|
13
|
+
|
14
|
+
def self.load
|
15
|
+
return self.all if !self.all.nil? && !self.all.empty?
|
16
|
+
|
17
|
+
data_file = File.expand_path(File.join('..','..','data', 'phone', 'countries.yml'), File.dirname(__FILE__))
|
18
|
+
|
19
|
+
self.all = {}
|
20
|
+
YAML.load(File.read(data_file)).each_pair do |key, c|
|
21
|
+
self.all[key] = Country.new(c[:name], c[:country_code], c[:char_2_code], c[:char_3_code], c[:area_code])
|
22
|
+
end
|
23
|
+
self.all
|
24
|
+
end
|
25
|
+
|
26
|
+
def to_s
|
27
|
+
name
|
28
|
+
end
|
29
|
+
|
30
|
+
def self.find_by_country_code(code)
|
31
|
+
self.all[code]
|
32
|
+
end
|
33
|
+
|
34
|
+
def self.find_by_country_isocode(isocode)
|
35
|
+
if country = self.all.detect{|c|c[1].char_3_code.downcase == isocode}
|
36
|
+
country[1]
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
def country_code_regexp
|
41
|
+
@country_code_regexp ||= Regexp.new("^[+]#{country_code}")
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
end
|
File without changes
|
data/phone.gemspec
ADDED
@@ -0,0 +1,25 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
|
3
|
+
require File.expand_path("../lib/phone/version", __FILE__)
|
4
|
+
|
5
|
+
Gem::Specification.new do |gem|
|
6
|
+
gem.name = "phone"
|
7
|
+
gem.version = Phone::VERSION
|
8
|
+
gem.summary = %q{Phone number parsing, validation and formatting}
|
9
|
+
gem.description = %q{Phone number parsing, validation and formatting in Ruby}
|
10
|
+
gem.license = "MIT"
|
11
|
+
gem.authors = ["Tomislav Carr", "Todd Eichel", "Don Morrison"]
|
12
|
+
gem.email = ["tomislav@infinum.hr", "todd@toddeichel.com", "don@elskwid.net"]
|
13
|
+
gem.homepage = "https://github.com/carr/phone#readme"
|
14
|
+
|
15
|
+
gem.files = `git ls-files`.split($/)
|
16
|
+
gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
|
17
|
+
gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
|
18
|
+
gem.require_paths = ["lib"]
|
19
|
+
|
20
|
+
gem.add_development_dependency "bundler", "~> 1.2"
|
21
|
+
gem.add_development_dependency "minitest", "~> 5.0"
|
22
|
+
gem.add_development_dependency "rake", "~> 10.0"
|
23
|
+
gem.add_development_dependency "rubygems-tasks", "~> 0.2"
|
24
|
+
gem.add_development_dependency "yard", "~> 0.8"
|
25
|
+
end
|
data/test/countries/au_test.rb
CHANGED
@@ -1,49 +1,49 @@
|
|
1
|
-
require
|
1
|
+
require "helper"
|
2
2
|
|
3
3
|
# 0x 5551 reserved for fictitious use. (not including x=3)
|
4
4
|
# 0x 7010 reserved for fictitious use.
|
5
5
|
|
6
6
|
## Australia
|
7
|
-
class AUTest < Test
|
8
|
-
|
7
|
+
class AUTest < Minitest::Test
|
8
|
+
|
9
9
|
# 00 Emergency and International access
|
10
10
|
# 01 Alternate phone services
|
11
11
|
# 014 Satellite phone services
|
12
12
|
# 016 Paging [+3D or +6D]
|
13
13
|
# 018 Analogue (AMPS) mobile phone - few numbers still in use [+6D]
|
14
14
|
# 0198 Data networks [+2D or +6D] - e.g. 0198 379 000 is the Dial-Up POP number for iiNet
|
15
|
-
|
15
|
+
|
16
16
|
# 02 Central East region (NSW, ACT)
|
17
17
|
def test_central_east
|
18
18
|
parse_test('+61 2 5551 1234', '61', '2', '55511234')
|
19
19
|
end
|
20
|
-
|
20
|
+
|
21
21
|
# 03 South-east region (VIC, TAS)
|
22
22
|
def test_south_east
|
23
23
|
parse_test('+61 3 5551 1234', '61', '3', '55511234')
|
24
24
|
end
|
25
|
-
|
25
|
+
|
26
26
|
# 04 Mobile services (Digital - GSM, CDMA, 3G)
|
27
27
|
def test_mobile
|
28
28
|
parse_test('+61 4 5551 1234', '61', '4', '55511234')
|
29
29
|
end
|
30
|
-
|
30
|
+
|
31
31
|
# 05 Universal/Personal numberings (uncommon)
|
32
32
|
def test_personal
|
33
33
|
parse_test('+61 5 5551 1234', '61', '5', '55511234')
|
34
34
|
end
|
35
|
-
|
35
|
+
|
36
36
|
# 07 North-east region (QLD)
|
37
37
|
def test_north_east
|
38
38
|
parse_test('+61 7 5551 1234', '61', '7', '55511234')
|
39
39
|
end
|
40
|
-
|
40
|
+
|
41
41
|
# 08 Central and West region (SA, NT, WA)
|
42
42
|
def test_central
|
43
43
|
parse_test('+61 8 5551 1234', '61', '8', '55511234')
|
44
44
|
end
|
45
|
-
|
45
|
+
|
46
46
|
# (Geographical region boundaries do not exactly follow state borders.)
|
47
47
|
# 1 Non-geographic numbers
|
48
|
-
|
48
|
+
|
49
49
|
end
|
data/test/countries/ba_test.rb
CHANGED
@@ -1,10 +1,10 @@
|
|
1
|
-
require
|
1
|
+
require "helper"
|
2
2
|
|
3
3
|
## Bosnia and Herzegovina
|
4
|
-
class BATest < Test
|
5
|
-
|
4
|
+
class BATest < Minitest::Test
|
5
|
+
|
6
6
|
def test_local
|
7
7
|
parse_test('+387 33 25 02 33', '387', '33', '250233')
|
8
8
|
end
|
9
|
-
|
9
|
+
|
10
10
|
end
|
data/test/countries/be_test.rb
CHANGED
@@ -1,8 +1,9 @@
|
|
1
|
-
|
1
|
+
# -*- coding: utf-8 -*-
|
2
|
+
require "helper"
|
2
3
|
|
3
4
|
## Belgium
|
4
|
-
class BETest < Test
|
5
|
-
|
5
|
+
class BETest < Minitest::Test
|
6
|
+
|
6
7
|
## single digit
|
7
8
|
# 02: Brussels (Bruxelles/Brussel)
|
8
9
|
def test_brussels
|
@@ -20,7 +21,7 @@ class BETest < Test::Unit::TestCase
|
|
20
21
|
def test_gent
|
21
22
|
parse_test('+32 9 1234567', '32', '9', '1234567')
|
22
23
|
end
|
23
|
-
|
24
|
+
|
24
25
|
## two digit
|
25
26
|
# 010: Wavre (Waver)
|
26
27
|
def test_wavre
|
@@ -45,7 +46,7 @@ class BETest < Test::Unit::TestCase
|
|
45
46
|
def test_waremme
|
46
47
|
parse_test('+32 19 123456', '32', '19', '123456')
|
47
48
|
end
|
48
|
-
|
49
|
+
|
49
50
|
# 050: Brugge (Bruges), Zeebrugge
|
50
51
|
def test_brugge
|
51
52
|
parse_test('+32 50 123456', '32', '50', '123456')
|
@@ -65,7 +66,7 @@ class BETest < Test::Unit::TestCase
|
|
65
66
|
def test_oostende
|
66
67
|
parse_test('+32 59 123456', '32', '59', '123456')
|
67
68
|
end
|
68
|
-
|
69
|
+
|
69
70
|
# 060: Chimay
|
70
71
|
def test_chimay
|
71
72
|
parse_test('+32 60 123456', '32', '60', '123456')
|
@@ -80,13 +81,13 @@ class BETest < Test::Unit::TestCase
|
|
80
81
|
end
|
81
82
|
# 068: Ath (Aat)
|
82
83
|
# 069: Tournai (Doornik)
|
83
|
-
|
84
|
+
|
84
85
|
# 070: Specialty Numbers (i.e. bus information or bank information)
|
85
86
|
def test_specialty
|
86
87
|
parse_test('+32 70 123456', '32', '70', '123456')
|
87
88
|
end
|
88
89
|
# 071: Charleroi
|
89
|
-
|
90
|
+
|
90
91
|
# 081: Namur (Namen)
|
91
92
|
def test_namur
|
92
93
|
parse_test('+32 81 123456', '32', '81', '123456')
|
@@ -103,14 +104,14 @@ class BETest < Test::Unit::TestCase
|
|
103
104
|
def test_toll_free
|
104
105
|
parse_test('+32 800 12345', '32', '800', '12345')
|
105
106
|
end
|
106
|
-
|
107
|
+
|
107
108
|
# 090x: Premium numbers (0900, 0901, 0902, 0903, 0904, 0905, 0906, 0907, 0908, 0909)
|
108
109
|
def test_premium_900
|
109
110
|
parse_test('+32 900 12345', '32', '900', '12345')
|
110
111
|
end
|
111
|
-
|
112
|
+
|
112
113
|
def test_premium_901
|
113
114
|
parse_test('+32 901 12345', '32', '901', '12345')
|
114
115
|
end
|
115
|
-
|
116
|
+
|
116
117
|
end
|