phonelib 0.6.36 → 0.8.9
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.
- checksums.yaml +5 -5
- data/README.md +36 -3
- data/Rakefile +7 -0
- data/data/extended_data.dat +0 -0
- data/data/phone_data.dat +0 -0
- data/lib/phonelib/core.rb +109 -3
- data/lib/phonelib/data_importer.rb +55 -3
- data/lib/phonelib/phone.rb +6 -1
- data/lib/phonelib/phone_analyzer.rb +35 -7
- data/lib/phonelib/phone_analyzer_helper.rb +18 -3
- data/lib/phonelib/phone_extended_data.rb +3 -3
- data/lib/phonelib/phone_formatter.rb +23 -10
- data/lib/phonelib/version.rb +1 -1
- data/lib/phonelib.rb +13 -1
- data/lib/validators/phone_validator.rb +1 -1
- data/lib/validators/phone_validator3.rb +130 -0
- metadata +40 -12
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
|
-
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: e3414f233adef031f8260b96cb0d293b8aaee972043544d01084a9f840a60824
|
4
|
+
data.tar.gz: 7d0b91d44164cc5d124a0303334b90959ec4e6bfa1768b5772fa98373b740f74
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: fac30ff6c40df1d8f5f85d09bf48e717d938beb12ec257d5048a7046c07efdfd0eac6321909126eb9ce18da8bd0c3e48bb71fd5d43e15f66906f59e2dda2cd89
|
7
|
+
data.tar.gz: dc761323a8babda14e47b4bedaec35a89d822ee617c72330490426d90a2545553eaf9bcc2e238d72d7b239ae979f21bc9c167727a42e5766e377d8b0f95dd663
|
data/README.md
CHANGED
@@ -2,7 +2,7 @@
|
|
2
2
|
|
3
3
|
[](https://www.jetbrains.com/ruby/)
|
4
4
|
[](http://badge.fury.io/rb/phonelib)
|
5
|
-
[](https://dl.circleci.com/status-badge/redirect/gh/daddyz/phonelib/tree/master)
|
6
6
|
[](https://codeclimate.com/github/daddyz/phonelib/coverage)
|
7
7
|
[](https://codeclimate.com/github/daddyz/phonelib)
|
8
8
|
[](http://inch-ci.org/github/daddyz/phonelib)
|
@@ -11,6 +11,11 @@ Phonelib is a gem allowing you to validate phone number. All validations are bas
|
|
11
11
|
Currently it can make basic validations and formatting to e164 international number format and national number format with prefix.
|
12
12
|
But it still doesn't include all Google's library functionality.
|
13
13
|
|
14
|
+
## Incorrect parsing or validation
|
15
|
+
|
16
|
+
In case your phone number is incorrectly parsed, you can check original libphonenumber for result [here](https://rawgit.com/googlei18n/libphonenumber/master/javascript/i18n/phonenumbers/demo-compiled.html) and in case of same parse result [open an issue for them](https://issuetracker.google.com/issues/new?component=192347&template=829703). This gem's data is based on it.
|
17
|
+
If you can't wait for libphonenumber to resolve the issue, try to use ```Phonelib.add_additional_regex``` and ```Phonelib.additional_regexes``` methods.
|
18
|
+
|
14
19
|
## Information
|
15
20
|
|
16
21
|
### Change Log
|
@@ -37,10 +42,11 @@ gem 'phonelib'
|
|
37
42
|
|
38
43
|
Run the bundle command to install it.
|
39
44
|
|
40
|
-
To set the default country (country names are [ISO 3166-1 Alpha-2](http://en.wikipedia.org/wiki/ISO_3166-1_alpha-2) codes), create a initializer in <tt>config/initializers/phonelib.rb</tt>:
|
45
|
+
To set the default country or several default countries for parsing (country names are [ISO 3166-1 Alpha-2](http://en.wikipedia.org/wiki/ISO_3166-1_alpha-2) codes), create a initializer in <tt>config/initializers/phonelib.rb</tt>:
|
41
46
|
|
42
47
|
``` ruby
|
43
48
|
Phonelib.default_country = "CN"
|
49
|
+
Phonelib.default_country = ['CN', 'FR']
|
44
50
|
```
|
45
51
|
|
46
52
|
To use the ability to parse special numbers (Short Codes, Emergency etc.) you can set ```Phonelib.parse_special```. This is disabled by default
|
@@ -61,6 +67,18 @@ To disable sanitizing of passed phone number (keeping digits only)
|
|
61
67
|
Phonelib.strict_check = true
|
62
68
|
```
|
63
69
|
|
70
|
+
To disable country reset during parsing in case phone starts with + sign and country specified but country phone prefix doesn't match phone's prefix
|
71
|
+
|
72
|
+
``` ruby
|
73
|
+
Phonelib.ignore_plus = true
|
74
|
+
```
|
75
|
+
|
76
|
+
To change sanitized symbols on parsed number, so non-specified symbols won't be wiped and will fail the parsing
|
77
|
+
|
78
|
+
``` ruby
|
79
|
+
Phonelib.sanitize_regex = '[\.\-\(\) \;\+]'
|
80
|
+
```
|
81
|
+
|
64
82
|
To disable sanitizing of double prefix on passed phone number
|
65
83
|
|
66
84
|
```ruby
|
@@ -86,6 +104,21 @@ In case you need to overwrite some Google's libphonenumber library data, you nee
|
|
86
104
|
Phonelib.override_phone_data = '/path/to/override_phone_data.dat'
|
87
105
|
```
|
88
106
|
|
107
|
+
In case you want to add some custom or still not updated regex patterns for certain type you can use additional regexes feature in a following way:
|
108
|
+
|
109
|
+
``` ruby
|
110
|
+
Phonelib.add_additional_regex :us, Phonelib::Core::MOBILE, '[5]{10}' # this will add number 1-555-555-5555 to be valid
|
111
|
+
Phonelib.add_additional_regex :gb, Phonelib::Core::MOBILE, '[1]{5}' # this will add number 44-11-111 to be valid
|
112
|
+
# you can also specify all regexes using this method
|
113
|
+
Phonelib.additional_regexes = [[:us, :mobile, "[5]{10}"], [:gb, :mobile, "[1]{5}"]]
|
114
|
+
# or just use dump method to keep them altogether
|
115
|
+
Phonelib.dump_additional_regexes # => [["US", :mobile, "[5]{10}"], ["GB", :mobile, "[1]{5}"]
|
116
|
+
```
|
117
|
+
|
118
|
+
(!) For a list of available types refer to this readme.
|
119
|
+
|
120
|
+
(!) Please note that regex should be added as string
|
121
|
+
|
89
122
|
In case phone number that was passed for parsing has "+" sign in the beginning, library will try to detect a country regarding the provided one.
|
90
123
|
|
91
124
|
### ActiveRecord Integration
|
@@ -231,7 +264,7 @@ phone.full_e164 # returns e164 phone representation with extension
|
|
231
264
|
phone.full_international # returns formatted international number with extension
|
232
265
|
```
|
233
266
|
|
234
|
-
You can pass <tt>false</tt> to <tt>national</tt> and <tt>international</tt> methods in order to get unformatted
|
267
|
+
You can pass <tt>false</tt> to <tt>national</tt> and <tt>international</tt> methods in order to get unformatted representations
|
235
268
|
|
236
269
|
``` ruby
|
237
270
|
phone.international(false) # returns unformatted international phone
|
data/Rakefile
CHANGED
@@ -22,6 +22,13 @@ end
|
|
22
22
|
Bundler::GemHelper.install_tasks
|
23
23
|
|
24
24
|
require 'rspec/core/rake_task'
|
25
|
+
module TempFixForRakeLastComment
|
26
|
+
def last_comment
|
27
|
+
last_description
|
28
|
+
end
|
29
|
+
end
|
30
|
+
Rake::Application.send :include, TempFixForRakeLastComment
|
31
|
+
|
25
32
|
RSpec::Core::RakeTask.new(:spec) do |t|
|
26
33
|
if defined? Rails
|
27
34
|
puts 'Rails found! Running tests with Rails'
|
data/data/extended_data.dat
CHANGED
Binary file
|
data/data/phone_data.dat
CHANGED
Binary file
|
data/lib/phonelib/core.rb
CHANGED
@@ -4,12 +4,35 @@ module Phonelib
|
|
4
4
|
# @private variable will include hash with data for validation
|
5
5
|
@@phone_data = nil
|
6
6
|
|
7
|
+
# eagerly initialize the gem, loads data into memory. not required, initialization is done lazily otherwise, but
|
8
|
+
# may be desirable in production enviroments to avoid initialization time on first use.
|
9
|
+
def eager_load!
|
10
|
+
return if @@skip_eager_loading
|
11
|
+
phone_data
|
12
|
+
phone_ext_data
|
13
|
+
end
|
14
|
+
|
15
|
+
@@skip_eager_loading = false
|
16
|
+
def skip_eager_loading!
|
17
|
+
@@skip_eager_loading = true
|
18
|
+
end
|
19
|
+
|
7
20
|
# getter for phone data for other modules of gem, can be used outside
|
8
21
|
# @return [Hash] all data for phone parsing
|
9
22
|
def phone_data
|
10
23
|
@@phone_data ||= load_data.freeze
|
11
24
|
end
|
12
25
|
|
26
|
+
# @private getter for phone data indexed by country code (internal use only)
|
27
|
+
def data_by_country_codes
|
28
|
+
@@data_by_country_codes ||= phone_data.each_value.group_by { |d| d[COUNTRY_CODE] }.freeze
|
29
|
+
end
|
30
|
+
|
31
|
+
# @private getter for all international prefixes in phone_data
|
32
|
+
def phone_data_int_prefixes
|
33
|
+
@@all_int_prefixes ||= phone_data.map {|k,v| v[:international_prefix] }.select { |v| v != '' }.compact.uniq.join('|').freeze
|
34
|
+
end
|
35
|
+
|
13
36
|
# @private used to cache frequently-used regular expressions
|
14
37
|
@@phone_regexp_cache = {}
|
15
38
|
|
@@ -30,14 +53,14 @@ module Phonelib
|
|
30
53
|
@@default_country = nil
|
31
54
|
|
32
55
|
# getter method for default_country variable
|
33
|
-
# @return [String
|
56
|
+
# @return [String,Symbol,Array<String,Symbol>,nil] Default country ISO2 code or codes used for parsing
|
34
57
|
def default_country
|
35
58
|
@@default_country
|
36
59
|
end
|
37
60
|
|
38
61
|
# setter method for default_country variable
|
39
|
-
# @param country [String
|
40
|
-
# @return [String
|
62
|
+
# @param country [String,Symbol,Array<String,Symbol>] Default country ISO2 code or codes used for parsing
|
63
|
+
# @return [String,Symbol,Array<String,Symbol>] Default country ISO2 code or codes used for parsing
|
41
64
|
def default_country=(country)
|
42
65
|
@@default_country = country
|
43
66
|
end
|
@@ -107,6 +130,38 @@ module Phonelib
|
|
107
130
|
@@strict_check = strict
|
108
131
|
end
|
109
132
|
|
133
|
+
# @private don't use plus sign for automatic country change
|
134
|
+
@@ignore_plus = false
|
135
|
+
|
136
|
+
# getter for ignore plus flag
|
137
|
+
# @return [Boolean] Flag defines whether to reset country in case number has + and country prefix doesn't match
|
138
|
+
def ignore_plus
|
139
|
+
@@ignore_plus
|
140
|
+
end
|
141
|
+
|
142
|
+
# setter for ignore plus flag
|
143
|
+
# @param ignore_plus [Boolean] ignore plus sign or not
|
144
|
+
# @return [Boolean] Flag defines whether to ignore plus for country reset during validations in case country prefix doesn't match
|
145
|
+
def ignore_plus=(ignore_plus)
|
146
|
+
@@ignore_plus = ignore_plus
|
147
|
+
end
|
148
|
+
|
149
|
+
# @private sanitizing regex, matching symbols will get removed from parsed number, must be string
|
150
|
+
@@sanitize_regex = '[^0-9]+'
|
151
|
+
|
152
|
+
# getter for sanitize regex
|
153
|
+
# @return [String] regex of symbols to wipe from parsed number
|
154
|
+
def sanitize_regex
|
155
|
+
@@sanitize_regex
|
156
|
+
end
|
157
|
+
|
158
|
+
# setter for sanitize regex
|
159
|
+
# @param regex [String] symbols to wipe from parsed number
|
160
|
+
# @return [String] regex of symbols to wipe from parsed number
|
161
|
+
def sanitize_regex=(regex)
|
162
|
+
@@sanitize_regex = regex.is_a?(String) ? regex : regex.to_s
|
163
|
+
end
|
164
|
+
|
110
165
|
# @private strict double prefix check for validator, doesn't sanitize number
|
111
166
|
@@strict_double_prefix_check = false
|
112
167
|
|
@@ -133,6 +188,42 @@ module Phonelib
|
|
133
188
|
@@override_phone_data
|
134
189
|
end
|
135
190
|
|
191
|
+
@@additional_regexes = {}
|
192
|
+
# setter for data file to use
|
193
|
+
def additional_regexes=(data)
|
194
|
+
return unless data.is_a?(Array)
|
195
|
+
@@additional_regexes = {}
|
196
|
+
data.each do |row|
|
197
|
+
next if row.size != 3
|
198
|
+
add_additional_regex(*row)
|
199
|
+
end
|
200
|
+
end
|
201
|
+
|
202
|
+
def add_additional_regex(country, type, national_regex)
|
203
|
+
return unless Phonelib::Core::TYPES_DESC.keys.include?(type.to_sym)
|
204
|
+
return unless national_regex.is_a?(String)
|
205
|
+
@@phone_data = @@data_by_country_codes = nil
|
206
|
+
@@additional_regexes[country.to_s.upcase] ||= {}
|
207
|
+
@@additional_regexes[country.to_s.upcase][type] ||= []
|
208
|
+
@@additional_regexes[country.to_s.upcase][type] << national_regex
|
209
|
+
end
|
210
|
+
|
211
|
+
def dump_additional_regexes
|
212
|
+
rows = []
|
213
|
+
@@additional_regexes.each do |country, types|
|
214
|
+
types.each do |type, regexes|
|
215
|
+
regexes.each do |regex|
|
216
|
+
rows << [country, type, regex]
|
217
|
+
end
|
218
|
+
end
|
219
|
+
end
|
220
|
+
rows
|
221
|
+
end
|
222
|
+
|
223
|
+
def additional_regexes
|
224
|
+
@@additional_regexes
|
225
|
+
end
|
226
|
+
|
136
227
|
@@vanity_conversion = false
|
137
228
|
# setter for vanity phone numbers chars replacement
|
138
229
|
def vanity_conversion=(value)
|
@@ -377,6 +468,21 @@ module Phonelib
|
|
377
468
|
override_data_file = Marshal.load(File.binread(override_phone_data))
|
378
469
|
default_data.merge!(override_data_file)
|
379
470
|
end
|
471
|
+
additional_regexes.each do |country, types|
|
472
|
+
types.each do |type, regex|
|
473
|
+
default_data[country][Core::TYPES][type] ||= {}
|
474
|
+
[Core::VALID_PATTERN, Core::POSSIBLE_PATTERN].each do |key|
|
475
|
+
if default_data[country][Core::TYPES][type][key]
|
476
|
+
default_data[country][Core::TYPES][type][key] << "|#{regex.join('|')}"
|
477
|
+
else
|
478
|
+
default_data[country][Core::TYPES][type][key] = regex.join('|')
|
479
|
+
end
|
480
|
+
if type != Core::GENERAL
|
481
|
+
default_data[country][Core::TYPES][Core::GENERAL][key] << "|#{regex.join('|')}"
|
482
|
+
end
|
483
|
+
end
|
484
|
+
end
|
485
|
+
end
|
380
486
|
default_data
|
381
487
|
end
|
382
488
|
|
@@ -18,7 +18,49 @@ module Phonelib
|
|
18
18
|
include Phonelib::DataImporterHelper
|
19
19
|
|
20
20
|
# countries that can have double country prefix in number
|
21
|
-
DOUBLE_COUNTRY_CODES_COUNTRIES = %w(IN DE BR IT NO PL CU)
|
21
|
+
DOUBLE_COUNTRY_CODES_COUNTRIES = %w(IN DE BR IT NO PL CU VN)
|
22
|
+
FORMAT_SHARING = {
|
23
|
+
'CA' => 'US',
|
24
|
+
'CC' => 'AU',
|
25
|
+
'CX' => 'AU',
|
26
|
+
'DM' => 'US',
|
27
|
+
'DO' => 'US',
|
28
|
+
'EH' => 'MA',
|
29
|
+
'GD' => 'US',
|
30
|
+
'GG' => 'GB',
|
31
|
+
'GU' => 'US',
|
32
|
+
'IM' => 'GB',
|
33
|
+
'JE' => 'GB',
|
34
|
+
'JM' => 'US',
|
35
|
+
'KN' => 'US',
|
36
|
+
'KY' => 'US',
|
37
|
+
'KZ' => 'RU',
|
38
|
+
'LC' => 'US',
|
39
|
+
'MF' => 'GP',
|
40
|
+
'MP' => 'US',
|
41
|
+
'MS' => 'US',
|
42
|
+
'PR' => 'US',
|
43
|
+
'SJ' => 'NO',
|
44
|
+
'SX' => 'US',
|
45
|
+
'TA' => 'SH',
|
46
|
+
'TC' => 'US',
|
47
|
+
'TT' => 'US',
|
48
|
+
'UM' => 'US',
|
49
|
+
'VA' => 'IT',
|
50
|
+
'VC' => 'US',
|
51
|
+
'VG' => 'US',
|
52
|
+
'VI' => 'US',
|
53
|
+
'YT' => 'RE',
|
54
|
+
'AG' => 'US',
|
55
|
+
'AI' => 'US',
|
56
|
+
'AS' => 'US',
|
57
|
+
'AX' => 'FI',
|
58
|
+
'BB' => 'US',
|
59
|
+
'BL' => 'GP',
|
60
|
+
'BM' => 'US',
|
61
|
+
'BQ' => 'CW',
|
62
|
+
'BS' => 'US',
|
63
|
+
}
|
22
64
|
|
23
65
|
# main data file in repo
|
24
66
|
MAIN_FILE = 'resources/PhoneNumberMetadata.xml'
|
@@ -55,6 +97,7 @@ module Phonelib
|
|
55
97
|
import_main_data
|
56
98
|
import_short_data
|
57
99
|
import_alternate_formats
|
100
|
+
process_format_links
|
58
101
|
import_geocoding_data
|
59
102
|
import_timezone_data
|
60
103
|
import_carrier_data
|
@@ -115,6 +158,15 @@ module Phonelib
|
|
115
158
|
end
|
116
159
|
end
|
117
160
|
|
161
|
+
# some countries missing formats, and are linking them to another countries
|
162
|
+
def process_format_links
|
163
|
+
FORMAT_SHARING.each do |destination, source|
|
164
|
+
next unless @data[destination]
|
165
|
+
@data[destination][:formats] ||= []
|
166
|
+
@data[destination][:formats] = @data[destination][:formats] + @data[source][:formats]
|
167
|
+
end
|
168
|
+
end
|
169
|
+
|
118
170
|
# method parses geocoding data dir
|
119
171
|
def import_geocoding_data
|
120
172
|
puts 'IMPORTING GEOCODING DATA'
|
@@ -145,10 +197,10 @@ module Phonelib
|
|
145
197
|
|
146
198
|
require 'open-uri'
|
147
199
|
require 'csv'
|
148
|
-
io = open('http://download.geonames.org/export/dump/countryInfo.txt')
|
200
|
+
io = URI.open('http://download.geonames.org/export/dump/countryInfo.txt')
|
149
201
|
csv = CSV.new(io, {col_sep: "\t"})
|
150
202
|
csv.each do |row|
|
151
|
-
next if row[0].start_with?('#') || row[0].empty?
|
203
|
+
next if row[0].nil? || row[0].start_with?('#') || row[0].empty? || row[0].size != 2
|
152
204
|
|
153
205
|
@countries[row[0]] = row[4]
|
154
206
|
end
|
data/lib/phonelib/phone.rb
CHANGED
@@ -7,6 +7,9 @@ module Phonelib
|
|
7
7
|
# @!attribute [r] extension
|
8
8
|
# @return [String] phone extension passed for parsing after a number
|
9
9
|
attr_reader :extension
|
10
|
+
# @!attribute [r] national_number
|
11
|
+
# @return [String] phone national number
|
12
|
+
attr_reader :national_number
|
10
13
|
|
11
14
|
# including module that has all phone analyzing methods
|
12
15
|
include Phonelib::PhoneAnalyzer
|
@@ -51,7 +54,7 @@ module Phonelib
|
|
51
54
|
def sanitized
|
52
55
|
@sanitized ||=
|
53
56
|
vanity_converted(@original).gsub(
|
54
|
-
Phonelib.strict_check ? cr('^\+') : cr(
|
57
|
+
Phonelib.strict_check ? cr('^\+') : cr(Phonelib.sanitize_regex),
|
55
58
|
'')
|
56
59
|
end
|
57
60
|
|
@@ -175,6 +178,8 @@ module Phonelib
|
|
175
178
|
|
176
179
|
# @private extracts extension from passed phone number if provided
|
177
180
|
def separate_extension(original)
|
181
|
+
return [original, ''] unless Phonelib.extension_separate_symbols
|
182
|
+
|
178
183
|
regex = if Phonelib.extension_separate_symbols.is_a?(Array)
|
179
184
|
cr("#{Phonelib.extension_separate_symbols.join('|')}")
|
180
185
|
else
|
@@ -16,8 +16,31 @@ module Phonelib
|
|
16
16
|
# * +passed_country+ - Country provided for parsing. Must be ISO code of
|
17
17
|
# country (2 letters) like 'US', 'us' or :us for United States
|
18
18
|
def analyze(phone, passed_country)
|
19
|
-
|
19
|
+
countries = country_or_default_country passed_country
|
20
20
|
|
21
|
+
return analyze_single_country(phone, countries.first, passed_country) if countries.size == 1
|
22
|
+
|
23
|
+
results = {}
|
24
|
+
countries.map do |country|
|
25
|
+
results.merge! analyze_single_country(phone, country, passed_country)
|
26
|
+
end
|
27
|
+
|
28
|
+
pick_results(results)
|
29
|
+
end
|
30
|
+
|
31
|
+
private
|
32
|
+
|
33
|
+
# pick best result when several countries specified
|
34
|
+
def pick_results(results)
|
35
|
+
[:valid, :possible].each do |key|
|
36
|
+
final = results.select { |_k, v| v[key].any? }
|
37
|
+
return decorate_analyze_result(final) if final.size > 0
|
38
|
+
end
|
39
|
+
|
40
|
+
decorate_analyze_result(results)
|
41
|
+
end
|
42
|
+
|
43
|
+
def analyze_single_country(phone, country, passed_country)
|
21
44
|
result = parse_country(phone, country)
|
22
45
|
d_result = case
|
23
46
|
when result && result.values.find { |e| e[:valid].any? }
|
@@ -32,8 +55,6 @@ module Phonelib
|
|
32
55
|
better_result(result, d_result)
|
33
56
|
end
|
34
57
|
|
35
|
-
private
|
36
|
-
|
37
58
|
# method checks which result is better to return
|
38
59
|
def better_result(base_result, result = nil)
|
39
60
|
base_result ||= {}
|
@@ -103,15 +124,22 @@ module Phonelib
|
|
103
124
|
#
|
104
125
|
# * +phone+ - phone number for parsing
|
105
126
|
def detect_and_parse(phone, country)
|
106
|
-
|
107
|
-
|
127
|
+
countries_data = country_code_candidates_for(phone).flat_map { |code|
|
128
|
+
Phonelib.data_by_country_codes[code] || []
|
129
|
+
}
|
130
|
+
countries_data.each_with_object({}) do |data, result|
|
131
|
+
key = data[:id]
|
108
132
|
parsed = parse_single_country(phone, data)
|
109
133
|
if (!Phonelib.strict_double_prefix_check || key == country) && double_prefix_allowed?(data, phone, parsed && parsed[key])
|
110
134
|
parsed = parse_single_country(changed_dp_phone(key, phone), data)
|
111
135
|
end
|
112
136
|
result.merge!(parsed) unless parsed.nil?
|
113
|
-
end
|
114
|
-
|
137
|
+
end.compact
|
138
|
+
end
|
139
|
+
|
140
|
+
def country_code_candidates_for(phone)
|
141
|
+
stripped_phone = phone.gsub(/^(#{Phonelib.phone_data_int_prefixes})/, '')
|
142
|
+
((1..3).map { |length| phone[0, length] } + (1..3).map { |length| stripped_phone[0, length] }).uniq
|
115
143
|
end
|
116
144
|
|
117
145
|
# Create phone representation in e164 format
|
@@ -3,6 +3,16 @@ module Phonelib
|
|
3
3
|
module PhoneAnalyzerHelper
|
4
4
|
private
|
5
5
|
|
6
|
+
def decorate_analyze_result(result)
|
7
|
+
if result.size == 1
|
8
|
+
result
|
9
|
+
else
|
10
|
+
matched_countries = country_or_default_country(nil) & result.keys
|
11
|
+
result = result.keep_if {|k, _v| matched_countries.include?(k) } if matched_countries
|
12
|
+
Hash[result.take(1)]
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
6
16
|
def original_starts_with_plus?
|
7
17
|
original_s[0] == Core::PLUS_SIGN
|
8
18
|
end
|
@@ -24,7 +34,7 @@ module Phonelib
|
|
24
34
|
# defines if to validate against single country or not
|
25
35
|
def passed_country(country)
|
26
36
|
code = country_prefix(country)
|
27
|
-
if Core::PLUS_SIGN == @original[0] && code && !sanitized.start_with?(code)
|
37
|
+
if !Phonelib.ignore_plus && Core::PLUS_SIGN == @original[0] && code && !sanitized.start_with?(code)
|
28
38
|
# in case number passed with + but it doesn't start with passed
|
29
39
|
# country prefix
|
30
40
|
country = nil
|
@@ -48,7 +58,7 @@ module Phonelib
|
|
48
58
|
def country_can_dp?(country)
|
49
59
|
Phonelib.phone_data[country] &&
|
50
60
|
Phonelib.phone_data[country][Core::DOUBLE_COUNTRY_PREFIX_FLAG] &&
|
51
|
-
!original_starts_with_plus?
|
61
|
+
!original_starts_with_plus? && original_s.start_with?(Phonelib.phone_data[country][Core::COUNTRY_CODE])
|
52
62
|
end
|
53
63
|
|
54
64
|
# changes phone to with/without double country prefix
|
@@ -58,6 +68,7 @@ module Phonelib
|
|
58
68
|
|
59
69
|
country_code = Phonelib.phone_data[country][Core::COUNTRY_CODE]
|
60
70
|
if phone.start_with? country_code * 2
|
71
|
+
# remove double prefix in case it is there
|
61
72
|
phone.gsub(cr("^#{country_code}"), '')
|
62
73
|
else
|
63
74
|
"#{country_code}#{phone}"
|
@@ -90,7 +101,11 @@ module Phonelib
|
|
90
101
|
# * +country+ - country passed for parsing
|
91
102
|
def country_or_default_country(country)
|
92
103
|
country ||= (original_starts_with_plus? ? nil : Phonelib.default_country)
|
93
|
-
|
104
|
+
if country.is_a?(Array)
|
105
|
+
country.compact.map { |e| e.to_s.upcase }
|
106
|
+
else
|
107
|
+
[country && country.to_s.upcase]
|
108
|
+
end
|
94
109
|
end
|
95
110
|
|
96
111
|
# constructs full regex for phone validation for provided phone data
|
@@ -49,7 +49,7 @@ module Phonelib
|
|
49
49
|
# * +id_key+ - parameter id key in resolved extended data for number
|
50
50
|
#
|
51
51
|
def get_ext_name(names_key, id_key)
|
52
|
-
return nil unless ext_data[id_key]
|
52
|
+
return nil unless ext_data[id_key]
|
53
53
|
|
54
54
|
res = Phonelib.phone_ext_data[names_key][ext_data[id_key]]
|
55
55
|
return nil unless res
|
@@ -58,7 +58,7 @@ module Phonelib
|
|
58
58
|
|
59
59
|
# @private returns extended data ids for current number
|
60
60
|
def ext_data
|
61
|
-
return @ext_data if @ext_data
|
61
|
+
return @ext_data if defined?(@ext_data) && @ext_data
|
62
62
|
|
63
63
|
result = default_ext_data
|
64
64
|
return result unless possible?
|
@@ -79,7 +79,7 @@ module Phonelib
|
|
79
79
|
# @private default extended data
|
80
80
|
def default_ext_data
|
81
81
|
result = {}
|
82
|
-
EXT_KEYS.each { |key| result[key] =
|
82
|
+
EXT_KEYS.each { |key| result[key] = nil }
|
83
83
|
result
|
84
84
|
end
|
85
85
|
end
|
@@ -5,7 +5,7 @@ module Phonelib
|
|
5
5
|
# @param formatted [Boolean] whether to return numbers only or formatted
|
6
6
|
# @return [String] formatted national number
|
7
7
|
def national(formatted = true)
|
8
|
-
return @national_number unless
|
8
|
+
return @national_number unless possible?
|
9
9
|
format_match, format_string = formatting_data
|
10
10
|
|
11
11
|
if format_match
|
@@ -22,8 +22,8 @@ module Phonelib
|
|
22
22
|
return nil if sanitized.nil? || sanitized.empty?
|
23
23
|
if valid?
|
24
24
|
@national_number
|
25
|
-
elsif
|
26
|
-
sanitized[
|
25
|
+
elsif data_country_code && sanitized.start_with?(data_country_code)
|
26
|
+
sanitized[data_country_code.size..-1]
|
27
27
|
else
|
28
28
|
sanitized
|
29
29
|
end
|
@@ -32,8 +32,17 @@ module Phonelib
|
|
32
32
|
# Returns the country code from the original phone number.
|
33
33
|
# @return [String] matched country phone code
|
34
34
|
def country_code
|
35
|
-
@country_code
|
36
|
-
|
35
|
+
return @country_code if @country_code
|
36
|
+
|
37
|
+
code = Phonelib.phone_data[country] && Phonelib.phone_data[country][Core::COUNTRY_CODE]
|
38
|
+
return @country_code = code unless code == '1' && Phonelib.phone_data[country][Core::LEADING_DIGITS]
|
39
|
+
|
40
|
+
match = e164.match(/\A\+(1(#{Phonelib.phone_data[country][Core::LEADING_DIGITS]}))/)
|
41
|
+
if match
|
42
|
+
@country_code = match[1]
|
43
|
+
else
|
44
|
+
@country_code = '1'
|
45
|
+
end
|
37
46
|
end
|
38
47
|
|
39
48
|
# Returns e164 formatted phone number. Method can receive single string parameter that will be defined as prefix
|
@@ -43,8 +52,8 @@ module Phonelib
|
|
43
52
|
def international(formatted = true, prefix = '+')
|
44
53
|
prefix = formatted if formatted.is_a?(String)
|
45
54
|
return nil if sanitized.empty?
|
46
|
-
return "#{prefix}#{country_prefix_or_not}#{sanitized}" unless
|
47
|
-
return
|
55
|
+
return "#{prefix}#{country_prefix_or_not}#{sanitized}" unless possible?
|
56
|
+
return "#{prefix}#{data_country_code}#{@national_number}" unless formatted
|
48
57
|
|
49
58
|
fmt = @data[country][:format]
|
50
59
|
national = @national_number
|
@@ -53,7 +62,7 @@ module Phonelib
|
|
53
62
|
national = fmt.gsub(/\$\d/) { |el| matches[el[1].to_i] } unless fmt == 'NA'
|
54
63
|
end
|
55
64
|
|
56
|
-
"#{prefix}#{
|
65
|
+
"#{prefix}#{data_country_code} #{national}"
|
57
66
|
end
|
58
67
|
|
59
68
|
# returns national formatted number with extension added
|
@@ -109,6 +118,10 @@ module Phonelib
|
|
109
118
|
|
110
119
|
private
|
111
120
|
|
121
|
+
def data_country_code
|
122
|
+
@data_country_code ||= Phonelib.phone_data[country] && Phonelib.phone_data[country][Core::COUNTRY_CODE]
|
123
|
+
end
|
124
|
+
|
112
125
|
# @private defines if phone can have area code
|
113
126
|
def area_code_possible?
|
114
127
|
return false if impossible?
|
@@ -124,8 +137,8 @@ module Phonelib
|
|
124
137
|
|
125
138
|
# @private defines whether to put country prefix or not
|
126
139
|
def country_prefix_or_not
|
127
|
-
return '' unless
|
128
|
-
sanitized.start_with?(
|
140
|
+
return '' unless data_country_code
|
141
|
+
sanitized.start_with?(data_country_code) ? '' : data_country_code
|
129
142
|
end
|
130
143
|
|
131
144
|
# @private returns extension with separator defined
|
data/lib/phonelib/version.rb
CHANGED
data/lib/phonelib.rb
CHANGED
@@ -12,5 +12,17 @@ module Phonelib
|
|
12
12
|
end
|
13
13
|
|
14
14
|
if defined?(ActiveModel) || defined?(Rails)
|
15
|
-
|
15
|
+
if RUBY_VERSION >= '3.0.0'
|
16
|
+
autoload :PhoneValidator, 'validators/phone_validator3'
|
17
|
+
else
|
18
|
+
autoload :PhoneValidator, 'validators/phone_validator'
|
19
|
+
end
|
20
|
+
|
21
|
+
if defined?(Rails)
|
22
|
+
class Phonelib::Railtie < Rails::Railtie
|
23
|
+
initializer 'phonelib' do |app|
|
24
|
+
app.config.eager_load_namespaces << Phonelib
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
16
28
|
end
|
@@ -62,7 +62,7 @@ class PhoneValidator < ActiveModel::EachValidator
|
|
62
62
|
@phone = parse(value, specified_country(record))
|
63
63
|
valid = phone_valid? && valid_types? && valid_country? && valid_extensions?
|
64
64
|
|
65
|
-
record.errors.add(attribute, message, options) unless valid
|
65
|
+
record.errors.add(attribute, message, **options) unless valid
|
66
66
|
end
|
67
67
|
|
68
68
|
private
|
@@ -0,0 +1,130 @@
|
|
1
|
+
# Validator class for phone validations
|
2
|
+
#
|
3
|
+
# ==== Examples
|
4
|
+
#
|
5
|
+
# Validates that attribute is a valid phone number.
|
6
|
+
# If empty value passed for attribute it fails.
|
7
|
+
#
|
8
|
+
# class Phone < ActiveRecord::Base
|
9
|
+
# attr_accessible :number
|
10
|
+
# validates :number, phone: true
|
11
|
+
# end
|
12
|
+
#
|
13
|
+
# Validates that attribute is a possible phone number.
|
14
|
+
# If empty value passed for attribute it fails.
|
15
|
+
#
|
16
|
+
# class Phone < ActiveRecord::Base
|
17
|
+
# attr_accessible :number
|
18
|
+
# validates :number, phone: { possible: true }
|
19
|
+
# end
|
20
|
+
#
|
21
|
+
# Validates that attribute is a valid phone number.
|
22
|
+
# Empty value is allowed to be passed.
|
23
|
+
#
|
24
|
+
# class Phone < ActiveRecord::Base
|
25
|
+
# attr_accessible :number
|
26
|
+
# validates :number, phone: { allow_blank: true }
|
27
|
+
# end
|
28
|
+
#
|
29
|
+
# Validates that attribute is a valid phone number of specified type(s).
|
30
|
+
# It is also possible to check that attribute is a possible number of specified
|
31
|
+
# type(s). Symbol or array accepted.
|
32
|
+
#
|
33
|
+
# class Phone < ActiveRecord::Base
|
34
|
+
# attr_accessible :number, :mobile
|
35
|
+
# validates :number, phone: { types: [:mobile, :fixed], allow_blank: true }
|
36
|
+
# validates :mobile, phone: { possible: true, types: :mobile }
|
37
|
+
# end
|
38
|
+
#
|
39
|
+
# validates that phone is valid and it is from specified country or countries
|
40
|
+
#
|
41
|
+
# class Phone < ActiveRecord::Base
|
42
|
+
# attr_accessible :number
|
43
|
+
# validates :number, phone: { countries: [:us, :ca] }
|
44
|
+
# end
|
45
|
+
#
|
46
|
+
# Validates that attribute does not include an extension.
|
47
|
+
# The default setting is to allow extensions
|
48
|
+
#
|
49
|
+
# class Phone < ActiveRecord::Base
|
50
|
+
# attr_accessible :number
|
51
|
+
# validates :number, phone: { extensions: false }
|
52
|
+
# end
|
53
|
+
#
|
54
|
+
class PhoneValidator < ActiveModel::EachValidator
|
55
|
+
# Include all core methods
|
56
|
+
include Phonelib::Core
|
57
|
+
|
58
|
+
# Validation method
|
59
|
+
def validate_each(record, attribute, value)
|
60
|
+
return if options[:allow_blank] && value.blank?
|
61
|
+
|
62
|
+
@phone = parse(value, specified_country(record))
|
63
|
+
valid = phone_valid? && valid_types? && valid_country? && valid_extensions?
|
64
|
+
|
65
|
+
record.errors.add(attribute, message, **options) unless valid
|
66
|
+
end
|
67
|
+
|
68
|
+
private
|
69
|
+
|
70
|
+
def message
|
71
|
+
options[:message] || :invalid
|
72
|
+
end
|
73
|
+
|
74
|
+
def phone_valid?
|
75
|
+
@phone.send(options[:possible] ? :possible? : :valid?)
|
76
|
+
end
|
77
|
+
|
78
|
+
def valid_types?
|
79
|
+
return true unless options[:types]
|
80
|
+
(phone_types & types).size > 0
|
81
|
+
end
|
82
|
+
|
83
|
+
def valid_country?
|
84
|
+
return true unless options[:countries]
|
85
|
+
(phone_countries & countries).size > 0
|
86
|
+
end
|
87
|
+
|
88
|
+
def valid_extensions?
|
89
|
+
return true if !options.has_key?(:extensions) || options[:extensions]
|
90
|
+
@phone.extension.empty?
|
91
|
+
end
|
92
|
+
|
93
|
+
def specified_country(record)
|
94
|
+
return unless options[:country_specifier]
|
95
|
+
|
96
|
+
if options[:country_specifier].is_a?(Symbol)
|
97
|
+
record.send(options[:country_specifier])
|
98
|
+
else
|
99
|
+
options[:country_specifier].call(record)
|
100
|
+
end
|
101
|
+
end
|
102
|
+
|
103
|
+
# @private
|
104
|
+
def phone_types
|
105
|
+
method = options[:possible] ? :possible_types : :types
|
106
|
+
phone_types = @phone.send(method)
|
107
|
+
if (phone_types & [Phonelib::Core::FIXED_OR_MOBILE]).size > 0
|
108
|
+
phone_types += [Phonelib::Core::FIXED_LINE, Phonelib::Core::MOBILE]
|
109
|
+
end
|
110
|
+
phone_types
|
111
|
+
end
|
112
|
+
|
113
|
+
# @private
|
114
|
+
def phone_countries
|
115
|
+
method = options[:possible] ? :countries : :valid_countries
|
116
|
+
@phone.send(method)
|
117
|
+
end
|
118
|
+
|
119
|
+
# @private
|
120
|
+
def types
|
121
|
+
types = options[:types].is_a?(Array) ? options[:types] : [options[:types]]
|
122
|
+
types.map(&:to_sym)
|
123
|
+
end
|
124
|
+
|
125
|
+
# @private
|
126
|
+
def countries
|
127
|
+
countries = options[:countries].is_a?(Array) ? options[:countries] : [options[:countries]]
|
128
|
+
countries.map { |c| c.to_s.upcase }
|
129
|
+
end
|
130
|
+
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: phonelib
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.8.9
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Vadim Senderovich
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2024-06-02 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: rake
|
@@ -16,28 +16,28 @@ dependencies:
|
|
16
16
|
requirements:
|
17
17
|
- - "<"
|
18
18
|
- !ruby/object:Gem::Version
|
19
|
-
version: '
|
19
|
+
version: '14.0'
|
20
20
|
type: :development
|
21
21
|
prerelease: false
|
22
22
|
version_requirements: !ruby/object:Gem::Requirement
|
23
23
|
requirements:
|
24
24
|
- - "<"
|
25
25
|
- !ruby/object:Gem::Version
|
26
|
-
version: '
|
26
|
+
version: '14.0'
|
27
27
|
- !ruby/object:Gem::Dependency
|
28
28
|
name: nokogiri
|
29
29
|
requirement: !ruby/object:Gem::Requirement
|
30
30
|
requirements:
|
31
31
|
- - "~>"
|
32
32
|
- !ruby/object:Gem::Version
|
33
|
-
version: 1.
|
33
|
+
version: '1.15'
|
34
34
|
type: :development
|
35
35
|
prerelease: false
|
36
36
|
version_requirements: !ruby/object:Gem::Requirement
|
37
37
|
requirements:
|
38
38
|
- - "~>"
|
39
39
|
- !ruby/object:Gem::Version
|
40
|
-
version: 1.
|
40
|
+
version: '1.15'
|
41
41
|
- !ruby/object:Gem::Dependency
|
42
42
|
name: pry
|
43
43
|
requirement: !ruby/object:Gem::Requirement
|
@@ -72,14 +72,14 @@ dependencies:
|
|
72
72
|
requirements:
|
73
73
|
- - "~>"
|
74
74
|
- !ruby/object:Gem::Version
|
75
|
-
version: 1.0.
|
75
|
+
version: 1.0.9
|
76
76
|
type: :development
|
77
77
|
prerelease: false
|
78
78
|
version_requirements: !ruby/object:Gem::Requirement
|
79
79
|
requirements:
|
80
80
|
- - "~>"
|
81
81
|
- !ruby/object:Gem::Version
|
82
|
-
version: 1.0.
|
82
|
+
version: 1.0.9
|
83
83
|
- !ruby/object:Gem::Dependency
|
84
84
|
name: simplecov
|
85
85
|
requirement: !ruby/object:Gem::Requirement
|
@@ -94,6 +94,34 @@ dependencies:
|
|
94
94
|
- - ">="
|
95
95
|
- !ruby/object:Gem::Version
|
96
96
|
version: '0'
|
97
|
+
- !ruby/object:Gem::Dependency
|
98
|
+
name: benchmark-ips
|
99
|
+
requirement: !ruby/object:Gem::Requirement
|
100
|
+
requirements:
|
101
|
+
- - ">="
|
102
|
+
- !ruby/object:Gem::Version
|
103
|
+
version: '0'
|
104
|
+
type: :development
|
105
|
+
prerelease: false
|
106
|
+
version_requirements: !ruby/object:Gem::Requirement
|
107
|
+
requirements:
|
108
|
+
- - ">="
|
109
|
+
- !ruby/object:Gem::Version
|
110
|
+
version: '0'
|
111
|
+
- !ruby/object:Gem::Dependency
|
112
|
+
name: benchmark-memory
|
113
|
+
requirement: !ruby/object:Gem::Requirement
|
114
|
+
requirements:
|
115
|
+
- - ">="
|
116
|
+
- !ruby/object:Gem::Version
|
117
|
+
version: '0'
|
118
|
+
type: :development
|
119
|
+
prerelease: false
|
120
|
+
version_requirements: !ruby/object:Gem::Requirement
|
121
|
+
requirements:
|
122
|
+
- - ">="
|
123
|
+
- !ruby/object:Gem::Version
|
124
|
+
version: '0'
|
97
125
|
- !ruby/object:Gem::Dependency
|
98
126
|
name: rack-cache
|
99
127
|
requirement: !ruby/object:Gem::Requirement
|
@@ -114,14 +142,14 @@ dependencies:
|
|
114
142
|
requirements:
|
115
143
|
- - '='
|
116
144
|
- !ruby/object:Gem::Version
|
117
|
-
version:
|
145
|
+
version: 2.3.1
|
118
146
|
type: :development
|
119
147
|
prerelease: false
|
120
148
|
version_requirements: !ruby/object:Gem::Requirement
|
121
149
|
requirements:
|
122
150
|
- - '='
|
123
151
|
- !ruby/object:Gem::Version
|
124
|
-
version:
|
152
|
+
version: 2.3.1
|
125
153
|
description: |2
|
126
154
|
Google libphonenumber library was taken as a basis for
|
127
155
|
this gem. Gem uses its data file for validations and number formatting.
|
@@ -148,6 +176,7 @@ files:
|
|
148
176
|
- lib/phonelib/version.rb
|
149
177
|
- lib/tasks/phonelib_tasks.rake
|
150
178
|
- lib/validators/phone_validator.rb
|
179
|
+
- lib/validators/phone_validator3.rb
|
151
180
|
homepage: https://github.com/daddyz/phonelib
|
152
181
|
licenses:
|
153
182
|
- MIT
|
@@ -168,8 +197,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
168
197
|
- !ruby/object:Gem::Version
|
169
198
|
version: '0'
|
170
199
|
requirements: []
|
171
|
-
|
172
|
-
rubygems_version: 2.6.14
|
200
|
+
rubygems_version: 3.1.6
|
173
201
|
signing_key:
|
174
202
|
specification_version: 4
|
175
203
|
summary: Gem validates phone numbers with Google libphonenumber database
|