phone 1.2.3 → 1.3.0.beta0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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
|