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.
Files changed (53) hide show
  1. data/README.textile +1 -1
  2. data/lib/phony.rb +28 -15
  3. data/lib/phony/countries.rb +609 -210
  4. data/lib/phony/countries/austria.rb +6 -1
  5. data/lib/phony/countries/bangladesh.rb +55 -0
  6. data/lib/phony/countries/belarus.rb +130 -0
  7. data/lib/phony/countries/georgia.rb +91 -0
  8. data/lib/phony/countries/germany.rb +20 -6
  9. data/lib/phony/countries/india.rb +50 -0
  10. data/lib/phony/countries/indonesia.rb +50 -0
  11. data/lib/phony/countries/italy.rb +38 -67
  12. data/lib/phony/countries/japan.rb +412 -0
  13. data/lib/phony/countries/kyrgyzstan.rb +118 -0
  14. data/lib/phony/countries/latvia.rb +40 -0
  15. data/lib/phony/countries/libya.rb +114 -0
  16. data/lib/phony/countries/malaysia.rb +7 -5
  17. data/lib/phony/countries/moldova.rb +50 -0
  18. data/lib/phony/countries/montenegro.rb +27 -0
  19. data/lib/phony/countries/namibia.rb +35 -0
  20. data/lib/phony/countries/nepal.rb +71 -0
  21. data/lib/phony/countries/netherlands.rb +3 -2
  22. data/lib/phony/countries/pakistan.rb +119 -0
  23. data/lib/phony/countries/paraguay.rb +145 -0
  24. data/lib/phony/countries/russia_kazakhstan_abhasia_south_osetia.rb +6 -6
  25. data/lib/phony/countries/serbia.rb +34 -0
  26. data/lib/phony/countries/somali.rb +22 -0
  27. data/lib/phony/countries/south_korea.rb +1 -1
  28. data/lib/phony/countries/sweden.rb +1 -1
  29. data/lib/phony/countries/taiwan.rb +51 -0
  30. data/lib/phony/countries/tajikistan.rb +76 -0
  31. data/lib/phony/countries/turkmenistan.rb +73 -0
  32. data/lib/phony/countries/ukraine.rb +614 -0
  33. data/lib/phony/countries/uruguay.rb +51 -0
  34. data/lib/phony/countries/zimbabwe.rb +37 -0
  35. data/lib/phony/country.rb +41 -0
  36. data/lib/phony/country_codes.rb +45 -18
  37. data/lib/phony/dsl.rb +33 -7
  38. data/lib/phony/local_splitters/fixed.rb +14 -1
  39. data/lib/phony/local_splitters/regex.rb +12 -1
  40. data/lib/phony/national_code.rb +7 -3
  41. data/lib/phony/national_splitters/default.rb +13 -1
  42. data/lib/phony/national_splitters/dsl.rb +9 -7
  43. data/lib/phony/national_splitters/fixed.rb +6 -0
  44. data/lib/phony/national_splitters/none.rb +6 -0
  45. data/lib/phony/national_splitters/regex.rb +6 -0
  46. data/lib/phony/national_splitters/variable.rb +7 -1
  47. data/spec/lib/phony/countries_spec.rb +684 -16
  48. data/spec/lib/phony/local_splitters/regex_spec.rb +41 -0
  49. data/spec/lib/phony/validations_spec.rb +542 -10
  50. data/spec/lib/phony_spec.rb +20 -6
  51. metadata +29 -9
  52. data/lib/phony/validator.rb +0 -26
  53. 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
@@ -22,7 +22,13 @@ module Phony
22
22
  @instance ||= new
23
23
  end
24
24
 
25
- @@basic_normalizing_pattern = /^0+|\D/
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
- national_handler, cc, rest = split_cc number
37
- @normalize_format % [cc, national_handler.normalize(rest)]
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
- country_handler, cc, rest = split_cc number
44
- [cc, *country_handler.split(rest)]
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
- country_handler, cc, rest = split_cc number
92
- country_handler.service? rest
97
+ country, cc, rest = split_cc number
98
+ country.service? rest
93
99
  end
94
100
  def mobile? number
95
- country_handler, cc, rest = split_cc number
96
- country_handler.mobile? rest
101
+ country, cc, rest = split_cc number
102
+ country.mobile? rest
97
103
  end
98
104
  def landline? number
99
- country_handler, cc, rest = split_cc number
100
- country_handler.landline? rest
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
- country_handler, _, rest = split_cc number
107
- country_handler.vanity? rest
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
- country_handler, cc, rest = split_cc vanity_number
113
- "#{cc}#{country_handler.vanity_to_number(rest)}"
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
- country_handler = splitter_mapping[i][presumed_cc]
121
- return [country_handler, presumed_cc, rest] if country_handler
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, country, validator = nil
53
- Phony::CountryCodes.instance.add country_code, country
54
- Phony::Validators.instance.add country_code, validator if validator
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
- Validator.new.ndc_check ndc
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
@@ -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(/^0+/, '')
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