phony 1.9.0 → 2.0.0.beta1
Sign up to get free protection for your applications and to get access to all the features.
- data/README.textile +1 -1
- data/lib/phony.rb +28 -15
- data/lib/phony/countries.rb +609 -210
- data/lib/phony/countries/austria.rb +6 -1
- data/lib/phony/countries/bangladesh.rb +55 -0
- data/lib/phony/countries/belarus.rb +130 -0
- data/lib/phony/countries/georgia.rb +91 -0
- data/lib/phony/countries/germany.rb +20 -6
- data/lib/phony/countries/india.rb +50 -0
- data/lib/phony/countries/indonesia.rb +50 -0
- data/lib/phony/countries/italy.rb +38 -67
- data/lib/phony/countries/japan.rb +412 -0
- data/lib/phony/countries/kyrgyzstan.rb +118 -0
- data/lib/phony/countries/latvia.rb +40 -0
- data/lib/phony/countries/libya.rb +114 -0
- data/lib/phony/countries/malaysia.rb +7 -5
- data/lib/phony/countries/moldova.rb +50 -0
- data/lib/phony/countries/montenegro.rb +27 -0
- data/lib/phony/countries/namibia.rb +35 -0
- data/lib/phony/countries/nepal.rb +71 -0
- data/lib/phony/countries/netherlands.rb +3 -2
- data/lib/phony/countries/pakistan.rb +119 -0
- data/lib/phony/countries/paraguay.rb +145 -0
- data/lib/phony/countries/russia_kazakhstan_abhasia_south_osetia.rb +6 -6
- data/lib/phony/countries/serbia.rb +34 -0
- data/lib/phony/countries/somali.rb +22 -0
- data/lib/phony/countries/south_korea.rb +1 -1
- data/lib/phony/countries/sweden.rb +1 -1
- data/lib/phony/countries/taiwan.rb +51 -0
- data/lib/phony/countries/tajikistan.rb +76 -0
- data/lib/phony/countries/turkmenistan.rb +73 -0
- data/lib/phony/countries/ukraine.rb +614 -0
- data/lib/phony/countries/uruguay.rb +51 -0
- data/lib/phony/countries/zimbabwe.rb +37 -0
- data/lib/phony/country.rb +41 -0
- data/lib/phony/country_codes.rb +45 -18
- data/lib/phony/dsl.rb +33 -7
- data/lib/phony/local_splitters/fixed.rb +14 -1
- data/lib/phony/local_splitters/regex.rb +12 -1
- data/lib/phony/national_code.rb +7 -3
- data/lib/phony/national_splitters/default.rb +13 -1
- data/lib/phony/national_splitters/dsl.rb +9 -7
- data/lib/phony/national_splitters/fixed.rb +6 -0
- data/lib/phony/national_splitters/none.rb +6 -0
- data/lib/phony/national_splitters/regex.rb +6 -0
- data/lib/phony/national_splitters/variable.rb +7 -1
- data/spec/lib/phony/countries_spec.rb +684 -16
- data/spec/lib/phony/local_splitters/regex_spec.rb +41 -0
- data/spec/lib/phony/validations_spec.rb +542 -10
- data/spec/lib/phony_spec.rb +20 -6
- metadata +29 -9
- data/lib/phony/validator.rb +0 -26
- data/lib/phony/validators.rb +0 -88
@@ -0,0 +1,51 @@
|
|
1
|
+
# Uruguay (Eastern Republic of)
|
2
|
+
# https://www.numberingplans.com/?page=dialling&sub=areacodes
|
3
|
+
# https://www.numberingplans.com/?page=plans&sub=phonenr&alpha_2_input=UY
|
4
|
+
|
5
|
+
ndcs_with_7_subscriber_numbers = %w(2 4)
|
6
|
+
|
7
|
+
ndcs_with_6_subscriber_numbers = %w(
|
8
|
+
21
|
9
|
+
22
|
10
|
+
23
|
11
|
+
24
|
12
|
+
25
|
13
|
+
26
|
14
|
+
27
|
15
|
+
29
|
16
|
+
42
|
17
|
+
)
|
18
|
+
|
19
|
+
ndcs_with_5_subscriber_numbers = %w(
|
20
|
+
433
|
21
|
+
434
|
22
|
+
436
|
23
|
+
437
|
24
|
+
438
|
25
|
+
443
|
26
|
+
444
|
27
|
+
445
|
28
|
+
447
|
29
|
+
452
|
30
|
+
453
|
31
|
+
455
|
32
|
+
462
|
33
|
+
463
|
34
|
+
464
|
35
|
+
472
|
36
|
+
473
|
37
|
+
)
|
38
|
+
|
39
|
+
|
40
|
+
Phony.define do
|
41
|
+
country '598',
|
42
|
+
one_of('800') >> split(3,2) | # freephone
|
43
|
+
one_of('805') >> split(3,2) | # shared cost
|
44
|
+
one_of(%w(900 901 902 903 904 905 906 907 908)) >> split(3,2) | # premium rate
|
45
|
+
one_of('909') >> split(3,2) | # ISP
|
46
|
+
one_of(ndcs_with_5_subscriber_numbers) >> split(3,2) |
|
47
|
+
one_of(ndcs_with_6_subscriber_numbers) >> split(3,3) |
|
48
|
+
one_of(%w(93 94 95 96 97 98 99)) >> split(3,3) | # mobile
|
49
|
+
one_of(ndcs_with_7_subscriber_numbers) >> split(3,4) |
|
50
|
+
fixed(2) >> split(3,3)
|
51
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
# Zimbabwe, https://www.numberingplans.com/?page=plans&sub=phonenr&alpha_2_input=ZW
|
2
|
+
|
3
|
+
Phony.define do
|
4
|
+
country '263',
|
5
|
+
one_of(%w(2582 2583 5483)) >>
|
6
|
+
matched_split(/\A\d{6}\z/ => [3,3],
|
7
|
+
/\A\d+\z/ => [3] ) | # geographic [XXXX] XXX(XXX)
|
8
|
+
one_of(%w(147 204 205 222 227 228 238 248 2582 2583 271 272 274 276 281 283
|
9
|
+
284 286 287 288 289 298 308 317 371 375 376 379 383 387 389 517 518
|
10
|
+
557 558 628 637 667 668 687 688 698 848)) >>
|
11
|
+
matched_split(/\A\d{6}\z/ => [3,3],
|
12
|
+
/\A\d+\z/ => [3] ) | # geographic [XXX] XXX(XXX)
|
13
|
+
one_of(%w(270)) >>
|
14
|
+
matched_split(/\A\d{6}\z/ => [3,3],
|
15
|
+
/\A\d+\z/ => [3,2] ) | # geographic [XXX] XXXXX(X)
|
16
|
+
one_of(%w(273 275 277 278 279 282 285)) >>
|
17
|
+
matched_split(/\A\d{6}\z/ => [3,3],
|
18
|
+
/\A\d+\z/ => [2,2] ) | # geographic [XXX] XXXX(XX)
|
19
|
+
one_of(%w(912)) >>
|
20
|
+
matched_split(/\A\d{8}\z/ => [4,4], /\A\d+\z/ => [3]) | # mobile
|
21
|
+
one_of(%w(13 14 15 16 17 18 19 21 24 26 29 30 31 32 34 35 36 50 55 57 58 59 60
|
22
|
+
63 64 65 66 69)) >>
|
23
|
+
matched_split(/\A\d{6}\z/ => [3,3],
|
24
|
+
/\A\d+\z/ => [3] ) | # geographic [XX] XXX(XXX)
|
25
|
+
one_of(%w(20)) >>
|
26
|
+
matched_split(/\A\d{6}\z/ => [3,3],
|
27
|
+
/\A\d+\z/ => [3,2] ) | # geographic [XXX] XXXXX(X)
|
28
|
+
one_of(%w(25 33 39 51 52 53 54 56 61 62 67 68 )) >>
|
29
|
+
matched_split(/\A\d{6}\z/ => [3,3],
|
30
|
+
/\A\d+\z/ => [2,2] ) | # geographic [XXX] XXXX(XX)
|
31
|
+
one_of(%w(86)) >> split(4,4) | # VoIP telephony
|
32
|
+
one_of(%w(71 73 77)) >> split(4,3) | # mobile
|
33
|
+
one_of(%w(4 9)) >>
|
34
|
+
matched_split(/\A\d{6}\z/ => [4,4],
|
35
|
+
/\A\d+\z/ => [3,2] ) | # geographic [X] XXXXX(XXX)
|
36
|
+
fixed(2) >> split(3,3)
|
37
|
+
end
|
data/lib/phony/country.rb
CHANGED
@@ -19,6 +19,14 @@ module Phony
|
|
19
19
|
self
|
20
20
|
end
|
21
21
|
|
22
|
+
# Options.
|
23
|
+
#
|
24
|
+
# TODO Rewrite.
|
25
|
+
#
|
26
|
+
def with options = {}
|
27
|
+
@invalid_ndcs = options[:invalid_ndcs] || []
|
28
|
+
end
|
29
|
+
|
22
30
|
# A number is split with the code handlers as given in the initializer.
|
23
31
|
#
|
24
32
|
# Note: If the ndc is nil, it will not return it.
|
@@ -29,6 +37,12 @@ module Phony
|
|
29
37
|
return [zero, ndc, *rest] if rest && !rest.empty?
|
30
38
|
end
|
31
39
|
end
|
40
|
+
def split_ndc national_number
|
41
|
+
@codes.each do |code|
|
42
|
+
zero, ndc, *rest = code.split national_number
|
43
|
+
return [code.local_splitter, zero, ndc, *rest] if rest && !rest.empty?
|
44
|
+
end
|
45
|
+
end
|
32
46
|
|
33
47
|
# Removes 0s from partially normalized numbers
|
34
48
|
# such as 410443643533.
|
@@ -46,6 +60,33 @@ module Phony
|
|
46
60
|
return nil
|
47
61
|
end
|
48
62
|
|
63
|
+
# Tests for plausibility of this national number.
|
64
|
+
#
|
65
|
+
def plausible? rest, hints = {}
|
66
|
+
local, _, ndc, *rest = split_ndc rest
|
67
|
+
|
68
|
+
# Element based checking.
|
69
|
+
#
|
70
|
+
# Note: ndc == false means the country has none.
|
71
|
+
#
|
72
|
+
return false if ndc.nil?
|
73
|
+
return false if ndc && ndc.empty?
|
74
|
+
return false if @invalid_ndcs.include? ndc # TODO Refactor.
|
75
|
+
|
76
|
+
# # A valid range for the rest is 0 or 3+ total digits.
|
77
|
+
# #
|
78
|
+
# return false if (1..2) === rest_size
|
79
|
+
|
80
|
+
# National destination code plausible?
|
81
|
+
#
|
82
|
+
ndc_needed = hints[:ndc]
|
83
|
+
return false if ndc_needed && !(ndc_needed === ndc)
|
84
|
+
|
85
|
+
# Local code specific checks.
|
86
|
+
#
|
87
|
+
return local.plausible? rest, hints
|
88
|
+
end
|
89
|
+
|
49
90
|
# Is this national number a vanity number?
|
50
91
|
#
|
51
92
|
def vanity? national_number
|
data/lib/phony/country_codes.rb
CHANGED
@@ -22,7 +22,13 @@ module Phony
|
|
22
22
|
@instance ||= new
|
23
23
|
end
|
24
24
|
|
25
|
-
|
25
|
+
# 00 for the standard international call prefix.
|
26
|
+
# http://en.wikipedia.org/wiki/List_of_international_call_prefixes
|
27
|
+
#
|
28
|
+
# We can't know from what country that person was calling, so we
|
29
|
+
# can't remove the intl' call prefix.
|
30
|
+
#
|
31
|
+
@@basic_normalizing_pattern = /^00?|\D/
|
26
32
|
def clean number
|
27
33
|
clean! number && number.dup
|
28
34
|
end
|
@@ -33,15 +39,15 @@ module Phony
|
|
33
39
|
end
|
34
40
|
def normalize number
|
35
41
|
clean! number
|
36
|
-
|
37
|
-
@normalize_format % [cc,
|
42
|
+
country, cc, rest = split_cc number
|
43
|
+
@normalize_format % [cc, country.normalize(rest)]
|
38
44
|
end
|
39
45
|
|
40
46
|
# Splits this number into cc, ndc and locally split number parts.
|
41
47
|
#
|
42
48
|
def split number
|
43
|
-
|
44
|
-
[cc, *
|
49
|
+
country, cc, rest = split_cc number
|
50
|
+
[cc, *country.split(rest)]
|
45
51
|
end
|
46
52
|
|
47
53
|
def formatted number, options = {}
|
@@ -88,40 +94,61 @@ module Phony
|
|
88
94
|
#
|
89
95
|
#
|
90
96
|
def service? number
|
91
|
-
|
92
|
-
|
97
|
+
country, cc, rest = split_cc number
|
98
|
+
country.service? rest
|
93
99
|
end
|
94
100
|
def mobile? number
|
95
|
-
|
96
|
-
|
101
|
+
country, cc, rest = split_cc number
|
102
|
+
country.mobile? rest
|
97
103
|
end
|
98
104
|
def landline? number
|
99
|
-
|
100
|
-
|
105
|
+
country, cc, rest = split_cc number
|
106
|
+
country.landline? rest
|
101
107
|
end
|
102
108
|
|
103
109
|
# Is the given number a vanity number?
|
104
110
|
#
|
105
111
|
def vanity? number
|
106
|
-
|
107
|
-
|
112
|
+
country, _, rest = split_cc number
|
113
|
+
country.vanity? rest
|
108
114
|
end
|
109
115
|
# Converts a vanity number into a normalized E164 number.
|
110
116
|
#
|
111
117
|
def vanity_to_number vanity_number
|
112
|
-
|
113
|
-
"#{cc}#{
|
118
|
+
country, cc, rest = split_cc vanity_number
|
119
|
+
"#{cc}#{country.vanity_to_number(rest)}"
|
114
120
|
end
|
115
|
-
|
121
|
+
|
116
122
|
def split_cc rest
|
117
123
|
presumed_cc = ''
|
118
124
|
1.upto(3) do |i|
|
119
125
|
presumed_cc << rest.slice!(0..0)
|
120
|
-
|
121
|
-
return [
|
126
|
+
country = splitter_mapping[i][presumed_cc]
|
127
|
+
return [country, presumed_cc, rest] if country
|
122
128
|
end
|
123
129
|
# This line is never reached as CCs are in prefix code.
|
124
130
|
end
|
131
|
+
|
132
|
+
def plausible? number, hints = {}
|
133
|
+
normalized = clean number
|
134
|
+
|
135
|
+
# False if it fails the basic check.
|
136
|
+
#
|
137
|
+
return false unless (4..15) === normalized.size
|
138
|
+
|
139
|
+
country, cc, rest = split_cc normalized
|
140
|
+
|
141
|
+
# Country code plausible?
|
142
|
+
#
|
143
|
+
cc_needed = hints[:cc]
|
144
|
+
return false if cc_needed && !(cc_needed === cc)
|
145
|
+
|
146
|
+
# Country specific tests.
|
147
|
+
#
|
148
|
+
country.plausible? rest, hints
|
149
|
+
rescue StandardError => e
|
150
|
+
return false
|
151
|
+
end
|
125
152
|
|
126
153
|
# # TODO
|
127
154
|
# #
|
data/lib/phony/dsl.rb
CHANGED
@@ -49,9 +49,30 @@ module Phony
|
|
49
49
|
# Example:
|
50
50
|
# country '27', # CC, followed by rules, for example fixed(2) >> ...
|
51
51
|
#
|
52
|
-
def country country_code,
|
53
|
-
|
54
|
-
Phony::
|
52
|
+
def country country_code, definition, options = {}
|
53
|
+
definition.with options
|
54
|
+
Phony::CountryCodes.instance.add country_code, definition
|
55
|
+
end
|
56
|
+
|
57
|
+
class Trunk
|
58
|
+
|
59
|
+
def initialize code, options = {}
|
60
|
+
@code = code
|
61
|
+
@normalize = options[:normalize]
|
62
|
+
end
|
63
|
+
|
64
|
+
def national_splitter= national_splitter
|
65
|
+
@national_splitter = national_splitter
|
66
|
+
end
|
67
|
+
|
68
|
+
def >> local_splitter
|
69
|
+
@national_splitter.country_for local_splitter, @normalize, @code
|
70
|
+
end
|
71
|
+
|
72
|
+
end
|
73
|
+
|
74
|
+
def trunk code, options = {}
|
75
|
+
Trunk.new code, options
|
55
76
|
end
|
56
77
|
|
57
78
|
# National matcher & splitters.
|
@@ -68,6 +89,7 @@ module Phony
|
|
68
89
|
# Example:
|
69
90
|
# country '33', fixed(1) >> split(2,2,2,2) # France, uses a fixed NDC of size 1.
|
70
91
|
#
|
92
|
+
|
71
93
|
def fixed length, options = {}
|
72
94
|
options[:zero] = true if options[:zero].nil?
|
73
95
|
NationalSplitters::Fixed.instance_for length, options
|
@@ -122,7 +144,6 @@ module Phony
|
|
122
144
|
#
|
123
145
|
raise "Regexp /#{regex.source}/ needs a group in it that defines which digits belong to the NDC." unless regex.source =~ /\(/
|
124
146
|
|
125
|
-
on_fail_take = options.delete :on_fail_take
|
126
147
|
NationalSplitters::Regex.instance_for regex, options[:on_fail_take], options
|
127
148
|
end
|
128
149
|
|
@@ -158,6 +179,10 @@ module Phony
|
|
158
179
|
# :fallback => [2,2,2,2])
|
159
180
|
#
|
160
181
|
def matched_split options = {}
|
182
|
+
# TODO: Refactor: it's a workaround. It creates high coupling with Phony::LocalSplitters::Regex.
|
183
|
+
options.each do |_, format|
|
184
|
+
format << format.pop + 10
|
185
|
+
end
|
161
186
|
Phony::LocalSplitters::Regex.instance_for options
|
162
187
|
end
|
163
188
|
|
@@ -173,10 +198,11 @@ module Phony
|
|
173
198
|
# fixed(3, :zero => false) >> split(3,4),
|
174
199
|
# invalid_ndcs('911') # The regexp /911/ would also work.
|
175
200
|
#
|
176
|
-
def invalid_ndcs ndc
|
177
|
-
|
201
|
+
def invalid_ndcs *ndc
|
202
|
+
Validators::NDC.new invalid: ndc
|
178
203
|
end
|
179
204
|
|
205
|
+
|
180
206
|
end
|
181
207
|
|
182
|
-
end
|
208
|
+
end
|
@@ -27,7 +27,8 @@ module Phony
|
|
27
27
|
# The format [3, 2, 2] splits a number like '3332222' into ['333', '22', '22'].
|
28
28
|
#
|
29
29
|
def initialize format = nil
|
30
|
-
@format = format || [3, 2, 2]
|
30
|
+
@format = format && format.dup || [3, 2, 2]
|
31
|
+
# @format << @format.pop + 10 # Allow for call-through numbers with an arbitrary size.
|
31
32
|
end
|
32
33
|
|
33
34
|
# Split a local number according to an assumed country specific format.
|
@@ -43,6 +44,18 @@ module Phony
|
|
43
44
|
end
|
44
45
|
end
|
45
46
|
|
47
|
+
# TODO Fix length fudging.
|
48
|
+
#
|
49
|
+
def plausible? rest, hints = {}
|
50
|
+
(length > 10 ? length - 10 : length) === rest.inject(0) { |total, part| total + part.size }
|
51
|
+
end
|
52
|
+
|
53
|
+
# A valid length.
|
54
|
+
#
|
55
|
+
def length
|
56
|
+
@length ||= @format.inject { |total, part| total + part }
|
57
|
+
end
|
58
|
+
|
46
59
|
end
|
47
60
|
|
48
61
|
end
|
@@ -42,6 +42,17 @@ module Phony
|
|
42
42
|
split_with number, fallback
|
43
43
|
end
|
44
44
|
|
45
|
+
def plausible? rest, hints = {}
|
46
|
+
number = rest.inject('', :+)
|
47
|
+
mapping.each do |regex, format|
|
48
|
+
next unless number =~ regex
|
49
|
+
length = format.inject(0, :+)
|
50
|
+
length -= 10 if length > 10
|
51
|
+
return number.length == length
|
52
|
+
end
|
53
|
+
false
|
54
|
+
end
|
55
|
+
|
45
56
|
private
|
46
57
|
|
47
58
|
def split_with number, format
|
@@ -51,7 +62,7 @@ module Phony
|
|
51
62
|
result
|
52
63
|
end
|
53
64
|
end
|
54
|
-
|
65
|
+
|
55
66
|
end
|
56
67
|
|
57
68
|
end
|
data/lib/phony/national_code.rb
CHANGED
@@ -3,13 +3,17 @@ module Phony
|
|
3
3
|
# NationalCodes have a special numbers splitter, a national code splitter and a local code splitter.
|
4
4
|
#
|
5
5
|
class NationalCode
|
6
|
-
|
6
|
+
|
7
|
+
attr_reader :local_splitter
|
8
|
+
|
7
9
|
#
|
8
10
|
#
|
9
|
-
def initialize national_splitter, local_splitter, normalize = nil
|
11
|
+
def initialize national_splitter, local_splitter, normalize = nil, trunk_code = '0'
|
10
12
|
@national_splitter = national_splitter
|
11
13
|
@local_splitter = local_splitter
|
12
14
|
@normalize = normalize != false
|
15
|
+
@trunk_code = trunk_code
|
16
|
+
@trunk_code_replacement = /\A#{@trunk_code}+/
|
13
17
|
end
|
14
18
|
|
15
19
|
# Split gets a number without country code and splits it into
|
@@ -27,7 +31,7 @@ module Phony
|
|
27
31
|
#
|
28
32
|
def normalize national_number
|
29
33
|
return national_number unless @normalize
|
30
|
-
national_number.gsub(
|
34
|
+
national_number.gsub(@trunk_code_replacement, EMPTY_STRING)
|
31
35
|
end
|
32
36
|
|
33
37
|
end
|
@@ -1,7 +1,9 @@
|
|
1
1
|
module Phony
|
2
2
|
|
3
3
|
module NationalSplitters
|
4
|
-
|
4
|
+
|
5
|
+
# TODO Default = Fixed.new(...)?
|
6
|
+
#
|
5
7
|
class Default < DSL
|
6
8
|
|
7
9
|
def self.instance_for
|
@@ -11,6 +13,16 @@ module Phony
|
|
11
13
|
def split national_number
|
12
14
|
[national_number]
|
13
15
|
end
|
16
|
+
|
17
|
+
def plausible? rest, size, hints = {}
|
18
|
+
true
|
19
|
+
end
|
20
|
+
|
21
|
+
# A valid length (at least 3).
|
22
|
+
#
|
23
|
+
def length
|
24
|
+
3
|
25
|
+
end
|
14
26
|
|
15
27
|
end
|
16
28
|
end
|