phony 1.9.0 → 2.19.14

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