phony 1.9.0 → 2.19.14

Sign up to get free protection for your applications and to get access to all the features.
Files changed (82) hide show
  1. checksums.yaml +7 -0
  2. data/README.textile +64 -112
  3. data/lib/phony/config.rb +91 -0
  4. data/lib/phony/countries/argentina.rb +355 -0
  5. data/lib/phony/countries/austria.rb +56 -22
  6. data/lib/phony/countries/bangladesh.rb +57 -0
  7. data/lib/phony/countries/belarus.rb +133 -0
  8. data/lib/phony/countries/brazil.rb +101 -95
  9. data/lib/phony/countries/cambodia.rb +131 -0
  10. data/lib/phony/countries/china.rb +13 -6
  11. data/lib/phony/countries/croatia.rb +23 -0
  12. data/lib/phony/countries/georgia.rb +94 -0
  13. data/lib/phony/countries/germany.rb +66 -42
  14. data/lib/phony/countries/guinea.rb +46 -0
  15. data/lib/phony/countries/india.rb +50 -0
  16. data/lib/phony/countries/indonesia.rb +55 -0
  17. data/lib/phony/countries/ireland.rb +35 -28
  18. data/lib/phony/countries/italy.rb +272 -166
  19. data/lib/phony/countries/japan.rb +468 -0
  20. data/lib/phony/countries/kyrgyzstan.rb +120 -0
  21. data/lib/phony/countries/latvia.rb +43 -0
  22. data/lib/phony/countries/libya.rb +116 -0
  23. data/lib/phony/countries/malaysia.rb +31 -7
  24. data/lib/phony/countries/moldova.rb +53 -0
  25. data/lib/phony/countries/montenegro.rb +30 -0
  26. data/lib/phony/countries/myanmar.rb +55 -0
  27. data/lib/phony/countries/namibia.rb +37 -0
  28. data/lib/phony/countries/nepal.rb +73 -0
  29. data/lib/phony/countries/netherlands.rb +17 -5
  30. data/lib/phony/countries/pakistan.rb +121 -0
  31. data/lib/phony/countries/paraguay.rb +147 -0
  32. data/lib/phony/countries/{russia_kazakhstan_abhasia_south_osetia.rb → russia_kazakhstan_abkhasia_south_ossetia.rb} +35 -24
  33. data/lib/phony/countries/saudi_arabia.rb +40 -0
  34. data/lib/phony/countries/serbia.rb +47 -0
  35. data/lib/phony/countries/somalia.rb +26 -0
  36. data/lib/phony/countries/south_korea.rb +19 -10
  37. data/lib/phony/countries/sweden.rb +58 -38
  38. data/lib/phony/countries/taiwan.rb +28 -0
  39. data/lib/phony/countries/tajikistan.rb +79 -0
  40. data/lib/phony/countries/turkmenistan.rb +76 -0
  41. data/lib/phony/countries/ukraine.rb +630 -0
  42. data/lib/phony/countries/united_kingdom.rb +639 -44
  43. data/lib/phony/countries/uruguay.rb +53 -0
  44. data/lib/phony/countries/vietnam.rb +133 -0
  45. data/lib/phony/countries/zimbabwe.rb +39 -0
  46. data/lib/phony/countries.rb +901 -301
  47. data/lib/phony/country.rb +177 -20
  48. data/lib/phony/country_codes.rb +119 -101
  49. data/lib/phony/dsl.rb +113 -68
  50. data/lib/phony/local_splitters/fixed.rb +25 -1
  51. data/lib/phony/local_splitters/regex.rb +16 -2
  52. data/lib/phony/national_code.rb +7 -7
  53. data/lib/phony/national_splitters/default.rb +35 -3
  54. data/lib/phony/national_splitters/dsl.rb +12 -7
  55. data/lib/phony/national_splitters/fixed.rb +7 -1
  56. data/lib/phony/national_splitters/none.rb +7 -3
  57. data/lib/phony/national_splitters/regex.rb +6 -0
  58. data/lib/phony/national_splitters/variable.rb +13 -9
  59. data/lib/phony/trunk_code.rb +57 -0
  60. data/lib/phony/vanity.rb +3 -3
  61. data/lib/phony.rb +239 -55
  62. data/spec/functional/config_spec.rb +44 -0
  63. data/spec/functional/plausibility_spec.rb +656 -0
  64. data/spec/lib/phony/countries_spec.rb +1207 -119
  65. data/spec/lib/phony/country_codes_spec.rb +99 -81
  66. data/spec/lib/phony/country_spec.rb +54 -14
  67. data/spec/lib/phony/dsl_spec.rb +2 -2
  68. data/spec/lib/phony/local_splitters/fixed_spec.rb +4 -4
  69. data/spec/lib/phony/local_splitters/regex_spec.rb +50 -2
  70. data/spec/lib/phony/national_code_spec.rb +34 -34
  71. data/spec/lib/phony/national_splitters/default_spec.rb +34 -0
  72. data/spec/lib/phony/national_splitters/fixed_spec.rb +12 -6
  73. data/spec/lib/phony/national_splitters/none_spec.rb +13 -3
  74. data/spec/lib/phony/national_splitters/regex_spec.rb +1 -1
  75. data/spec/lib/phony/national_splitters/variable_spec.rb +11 -5
  76. data/spec/lib/phony/trunk_code_spec.rb +85 -0
  77. data/spec/lib/phony/vanity_spec.rb +15 -19
  78. data/spec/lib/phony_spec.rb +59 -277
  79. metadata +67 -34
  80. data/lib/phony/validator.rb +0 -26
  81. data/lib/phony/validators.rb +0 -88
  82. data/spec/lib/phony/validations_spec.rb +0 -109
data/lib/phony/country.rb CHANGED
@@ -1,51 +1,208 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Phony
2
-
4
+
3
5
  #
4
6
  #
5
7
  class Country
6
8
 
7
9
  attr_accessor :codes
8
-
9
- #
10
+
11
+ @@international_absolute_format = '+%s%s%s%s%s'
12
+ @@international_relative_format = '00%s%s%s%s%s'
13
+ @@national_format = '%s%s%s%s'
14
+
15
+ @@default_space = ' '
16
+ @@default_local_space = ' '
17
+ @@default_parentheses = false
18
+
19
+ # TODO Doc.
10
20
  #
11
21
  def initialize *codes
12
22
  @codes = codes
13
23
  end
14
-
24
+
25
+ # DSL method.
26
+ #
15
27
  # Chain two codes together.
16
28
  #
17
- def |(other)
29
+ def | other
18
30
  self.codes = self.codes + other.codes
19
31
  self
20
32
  end
21
-
33
+
34
+ # Options.
35
+ #
36
+ # TODO Rewrite.
37
+ #
38
+ def with cc, options = {}
39
+ @cc = cc
40
+
41
+ @invalid_ndcs = options[:invalid_ndcs]
42
+
43
+ @format = options[:format]
44
+ @space = options[:space]
45
+ @local_space = options[:local_space]
46
+ @parentheses = options[:parentheses]
47
+ end
48
+
22
49
  # A number is split with the code handlers as given in the initializer.
23
50
  #
24
51
  # Note: If the ndc is nil, it will not return it.
25
52
  #
53
+ # @return [Trunk, String (ndc), Array<String> (national pieces)]
54
+ #
26
55
  def split national_number
27
- @codes.each do |code|
28
- zero, ndc, *rest = code.split national_number
29
- return [zero, ndc, *rest] if rest && !rest.empty?
56
+ _, trunk, ndc, *rest = internal_split national_number
57
+ [trunk, ndc, *rest]
58
+ end
59
+ #
60
+ #
61
+ # @return [Splitters::Local, Trunk, String (ndc), Array<String> (national pieces)]
62
+ #
63
+ def internal_split national_number
64
+ trunk = nil
65
+ @codes.each do |national_splitter|
66
+ new_trunk, ndc, *rest = national_splitter.split national_number
67
+ trunk ||= new_trunk
68
+ return [national_splitter.local_splitter, trunk, ndc, *rest] if rest && !rest.empty?
30
69
  end
70
+
71
+ # Best effort.
72
+ [nil, trunk, national_number, []]
31
73
  end
32
74
 
75
+ # Format the number, given the national part of it.
76
+ #
77
+ def format national_number, options = {}
78
+ type = options[:format] || @format
79
+ space = options[:spaces] || @space || @@default_space
80
+ local_space = options[:local_spaces] || @local_space || space || @@default_local_space
81
+ parentheses = options[:parentheses]
82
+ parentheses = @parentheses || @@default_parentheses if parentheses.nil?
83
+ use_trunk = options[:trunk]
84
+
85
+ trunk, ndc, *local_pieces = split national_number
86
+
87
+ local = format_local local_pieces, local_space
88
+
89
+ format_cc_ndc trunk, ndc, local, type, space, parentheses, use_trunk
90
+ end
91
+ def format_local local, local_space
92
+ if local.empty?
93
+ EMPTY_STRING
94
+ else
95
+ local.compact!
96
+ local.join local_space.to_s
97
+ end
98
+ end
99
+ def format_cc_ndc trunk, ndc, local, type, space, parentheses, use_trunk
100
+ # Note: We mark NDCs that are of type "none" with false (nil trips plausible?). This would result in false being printed.
101
+ # Therefore we set NDC to nil when formatting.
102
+ ndc = nil if ndc == false
103
+ case type
104
+ when String
105
+ trunk &&= trunk.format(space, use_trunk)
106
+ type % { :trunk => trunk, :cc => @cc, :ndc => ndc, :local => local }
107
+ when nil, :international_absolute, :international, :+
108
+ if ndc
109
+ format_with_ndc(@@international_absolute_format, @cc, format_ndc(ndc, parentheses), local, space)
110
+ else
111
+ format_without_ndc(@@international_absolute_format, @cc, local, space)
112
+ end
113
+ when :international_relative
114
+ if ndc
115
+ format_with_ndc(@@international_relative_format, @cc, format_ndc(ndc, parentheses), local, space)
116
+ else
117
+ format_without_ndc(@@international_relative_format, @cc, local, space)
118
+ end
119
+ when :national
120
+ trunk &&= trunk.format(space, use_trunk)
121
+ if ndc && !ndc.empty?
122
+ @@national_format % [trunk, format_ndc(ndc, parentheses), space, local]
123
+ else
124
+ @@national_format % [trunk, nil, nil, local]
125
+ end
126
+ when :local
127
+ local
128
+ end
129
+ end
130
+ def format_ndc ndc, parentheses
131
+ ndc = nil if ndc == false # TODO
132
+ parentheses ? "(#{ndc})" : ndc
133
+ end
134
+ def format_with_ndc format, cc, ndc, local, space
135
+ format % [cc, space, ndc, space, local]
136
+ end
137
+ def format_without_ndc format, cc, local, space
138
+ format % [cc, space, local, nil, nil]
139
+ end
140
+
141
+ # Cleans all non-numeric characters.
142
+ #
143
+ @@basic_cleaning_pattern = /\(0|\D/
144
+ # Clean number of all non-numeric characters and return a copy.
145
+ #
146
+ def clean number
147
+ clean! number && number.dup
148
+ end
149
+ # Clean number of all non-numeric characters and return it.
150
+ #
151
+ def clean! number
152
+ number.gsub!(@@basic_cleaning_pattern, EMPTY_STRING) || number
153
+ end
154
+
33
155
  # Removes 0s from partially normalized numbers
34
156
  # such as 410443643533.
35
- #
157
+ #
36
158
  # Example:
37
159
  # 410443643533 -> 41443643533
38
160
  #
39
161
  # In some cases it doesn't, like Italy.
40
162
  #
41
- def normalize national_number
42
- @codes.each do |code|
43
- normalized = code.normalize national_number
44
- return normalized if normalized && !normalized.empty?
163
+ # Note: Options such as CC
164
+ #
165
+ def normalize national_number, options = {}
166
+ clean! national_number
167
+ normalized = @codes.reduce national_number do |number, code|
168
+ result = code.normalize number, options
169
+ break result if result
170
+ number
45
171
  end
46
- return nil
172
+ normalized
47
173
  end
48
-
174
+
175
+ # Tests for plausibility of this national number.
176
+ #
177
+ def plausible? rest, hints = {}
178
+ local, _, ndc, *rest = internal_split rest
179
+
180
+ # Element based checking.
181
+ #
182
+ # Note: ndc == false means the country has none.
183
+ #
184
+ return false if ndc.nil?
185
+ return false if ndc && ndc.empty?
186
+ return false if @invalid_ndcs && @invalid_ndcs === ndc
187
+
188
+ # # A valid range for the rest is 0 or 3+ total digits.
189
+ # #
190
+ # return false if (1..2) === rest_size
191
+
192
+ # National destination code plausible?
193
+ #
194
+ ndc_needed = hints[:ndc]
195
+ return false if ndc_needed && !(ndc_needed === ndc)
196
+
197
+ # If there is no local part, we can assume it's not a plausible number.
198
+ # (Or, not defined correctly in Phony yet)
199
+ return false unless local
200
+
201
+ # Local code specific checks.
202
+ #
203
+ return local.plausible? rest, hints
204
+ end
205
+
49
206
  # Is this national number a vanity number?
50
207
  #
51
208
  def vanity? national_number
@@ -54,10 +211,10 @@ module Phony
54
211
  #
55
212
  #
56
213
  def vanity_to_number vanity_number
57
- zero, ndc, *rest = split vanity_number
214
+ _, ndc, *rest = split vanity_number
58
215
  "#{ndc}#{Vanity.replace(rest.join)}"
59
216
  end
60
-
217
+
61
218
  end
62
-
63
- end
219
+
220
+ end
@@ -1,145 +1,163 @@
1
1
  module Phony
2
2
 
3
- EMPTY_STRING = ''
3
+ EMPTY_STRING = '' unless defined?(EMPTY_STRING)
4
4
 
5
5
  # Handles determining the correct national code handler.
6
6
  #
7
7
  class CountryCodes
8
8
 
9
- attr_reader :splitter_mapping
9
+ attr_reader :countries
10
10
  attr_accessor :international_absolute_format, :international_relative_format, :national_format
11
11
 
12
- def initialize
13
- @international_absolute_format = '+%s%s%s'
14
- @international_relative_format = '00%s%s%s'
15
- @national_format = '%s%s'
12
+ # Singleton instance.
13
+ #
14
+ def self.instance
15
+ @instance ||= new
16
+ end
17
+
18
+ # Add the given country to the mapping under the
19
+ # given country code.
20
+ #
21
+ def add country_code, country
22
+ country_code = country_code.to_s
23
+ optimized_country_code_access = country_code.size
16
24
 
17
- @normalize_format = '%s%s'
18
- @default_space = ' '
25
+ @countries ||= {}
26
+ @countries[optimized_country_code_access] ||= {}
27
+ @countries[optimized_country_code_access][country_code] = country
19
28
  end
20
29
 
21
- def self.instance
22
- @instance ||= new
30
+ # Get the Country object for the given CC.
31
+ #
32
+ def [] cc
33
+ countries[cc.size][cc]
23
34
  end
24
35
 
25
- @@basic_normalizing_pattern = /^0+|\D/
36
+ # Clean number of all non-numeric characters, initial zeros or (0.
37
+ #
38
+ @@basic_cleaning_pattern = /\A00?|\(0|\D/
39
+ # Clean number of all non-numeric characters, initial zeros or (0 and return it.
40
+ #
26
41
  def clean number
27
42
  clean! number && number.dup
28
43
  end
44
+ # Clean number of all non-numeric characters, initial zeros or (0 and return a copy.
45
+ #
29
46
  def clean! number
30
- # Remove non-digit chars.
31
- #
32
- number.gsub!(@@basic_normalizing_pattern, EMPTY_STRING) || number
47
+ number.gsub!(@@basic_cleaning_pattern, EMPTY_STRING) || number
33
48
  end
34
- def normalize number
35
- clean! number
36
- national_handler, cc, rest = split_cc number
37
- @normalize_format % [cc, national_handler.normalize(rest)]
49
+
50
+ # 00 for the standard international call prefix.
51
+ # http://en.wikipedia.org/wiki/List_of_international_call_prefixes
52
+ #
53
+ # We can't know from what country that person was calling, so we
54
+ # can't remove the intl' call prefix.
55
+ #
56
+ # We remove:
57
+ # * 0 or 00 at the very beginning.
58
+ # * (0) anywhere.
59
+ # * Non-digits.
60
+ #
61
+ def normalize number, options = {}
62
+ country = if cc = options[:cc]
63
+ self[cc]
64
+ else
65
+ clean! number
66
+ country, cc, number = partial_split number
67
+ country
68
+ end
69
+ number = country.normalize number, cc: cc
70
+ countrify! number, cc
38
71
  end
39
72
 
40
73
  # Splits this number into cc, ndc and locally split number parts.
41
74
  #
42
75
  def split number
43
- country_handler, cc, rest = split_cc number
44
- [cc, *country_handler.split(rest)]
76
+ # Split the number into country, cc, and national part.
77
+ country, cc, national_number = partial_split number
78
+
79
+ # Split the national number into ndc and local part.
80
+ _, ndc, *local = country.split national_number
81
+
82
+ [cc, ndc, *local]
45
83
  end
46
84
 
47
- def formatted number, options = {}
48
- format_cc_ndc_local options[:format], options[:spaces] || @default_space, *split(number)
49
- end
50
- # Formats country code and national destination code.
85
+ # Format the number.
51
86
  #
52
- def format_cc_ndc_local format, space, cc, zero, ndc, *parts
53
- cc_ndc = format_cc_ndc format, space, cc, zero, ndc
54
- local = if parts.empty?
55
- cc_ndc = cc_ndc.slice 0...cc_ndc.rindex(space.to_s)
56
- EMPTY_STRING
57
- else
58
- format_local(space, parts) unless parts.empty?
59
- end
60
- cc_ndc.empty?? local : "#{cc_ndc}#{space}#{local}"
61
- end
62
- #
63
- # TODO This method needs an overhaul.
64
- #
65
- def format_cc_ndc format, space, cc, zero, ndc
66
- case format
67
- when nil, :international_absolute, :international, :+
68
- ndc ?
69
- @international_absolute_format % [cc, space, ndc] :
70
- @international_absolute_format % [cc, nil, nil]
71
- when :international_relative
72
- ndc ?
73
- @international_relative_format % [cc, space, ndc] :
74
- @international_relative_format % [cc, nil, nil]
75
- when :national
76
- ndc && !ndc.empty? ?
77
- @national_format % [zero, ndc] :
78
- @national_format % [zero, nil]
79
- when :local
80
- EMPTY_STRING
81
- end
82
- end
83
- def format_local space, parts_ary
84
- parts_ary.compact!
85
- parts_ary.join space.to_s
87
+ def format number, options = {}
88
+ country, _, national_number = partial_split number
89
+ country.format national_number, options
86
90
  end
91
+ alias formatted format
87
92
 
93
+ # Is this number plausible?
88
94
  #
89
- #
90
- def service? number
91
- country_handler, cc, rest = split_cc number
92
- country_handler.service? rest
93
- end
94
- def mobile? number
95
- country_handler, cc, rest = split_cc number
96
- country_handler.mobile? rest
97
- end
98
- def landline? number
99
- country_handler, cc, rest = split_cc number
100
- country_handler.landline? rest
101
- end
95
+ def plausible? number, hints = {}
96
+ normalized = clean number
97
+
98
+ # False if it fails the basic check.
99
+ #
100
+ return false unless (4..16) === normalized.size # unless hints[:check_length] == false
101
+
102
+ country, cc, rest = partial_split normalized
103
+
104
+ # Country code plausible?
105
+ #
106
+ cc_needed = hints[:cc]
107
+ return false if cc_needed && !(cc_needed === cc)
102
108
 
109
+ # Country specific tests.
110
+ #
111
+ country.plausible? rest, hints
112
+ rescue StandardError
113
+ return false
114
+ end
115
+
103
116
  # Is the given number a vanity number?
104
117
  #
105
118
  def vanity? number
106
- country_handler, _, rest = split_cc number
107
- country_handler.vanity? rest
119
+ country, _, national = partial_split number
120
+ country.vanity? national
108
121
  end
109
122
  # Converts a vanity number into a normalized E164 number.
110
123
  #
111
124
  def vanity_to_number vanity_number
112
- country_handler, cc, rest = split_cc vanity_number
113
- "#{cc}#{country_handler.vanity_to_number(rest)}"
125
+ country, cc, national = partial_split vanity_number
126
+ "#{cc}#{country.vanity_to_number(national)}"
114
127
  end
115
128
 
116
- def split_cc rest
117
- presumed_cc = ''
118
- 1.upto(3) do |i|
119
- presumed_cc << rest.slice!(0..0)
120
- country_handler = splitter_mapping[i][presumed_cc]
121
- return [country_handler, presumed_cc, rest] if country_handler
129
+ private
130
+
131
+ # Return a country for the number.
132
+ #
133
+ def country_for number
134
+ country, _ = partial_split number
135
+ country
136
+ end
137
+
138
+ # Split off the country and the cc, and also return the national number part.
139
+ #
140
+ def partial_split number
141
+ cc = ''
142
+ 1.upto(3) do |i|
143
+ cc << number.slice!(0..0)
144
+ country = countries[i][cc]
145
+ return [country, cc, number] if country
146
+ end
147
+ # This line is never reached as CCs are in prefix code.
148
+ end
149
+
150
+ # Adds the country code to the front
151
+ # if it does not already start with it.
152
+ #
153
+ # Note: This won't be correct in some cases, but it is the best we can do.
154
+ #
155
+ def countrify number, cc
156
+ countrify!(number, cc) || number
157
+ end
158
+ def countrify! number, cc
159
+ number.sub!(/\A/, cc) # @countrify_regex, @cc
122
160
  end
123
- # This line is never reached as CCs are in prefix code.
124
- end
125
-
126
- # # TODO
127
- # #
128
- # def self.with_cc cc
129
- # mapping[cc.size][cc.to_s]
130
- # end
131
-
132
- # Add the given country to the mapping under the
133
- # given country code.
134
- #
135
- def add country_code, country
136
- country_code = country_code.to_s
137
- optimized_country_code_access = country_code.size
138
-
139
- @splitter_mapping ||= {}
140
- @splitter_mapping[optimized_country_code_access] ||= {}
141
- @splitter_mapping[optimized_country_code_access][country_code] = country
142
- end
143
161
 
144
162
  end
145
163