phony 1.9.0 → 2.0.0.beta1

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.
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