phonelib 0.6.55 → 0.8.2

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: ecdda9f8093d20e648458be7dcd8f53d3ff55f47250a69af51cd4414d3792842
4
- data.tar.gz: e7cdff4ba507a3b9a1e4919fc67154777f55b76a02096791932d92488c3382be
3
+ metadata.gz: ba0b6be5c2a7713ded6c8bbc68a5d12d022b75cbc495bbdcd13cace46ee8467a
4
+ data.tar.gz: 6ad47b7928886d2c3222e072a2b2e73566baffe8462fa6a8ee353b9ff7630bca
5
5
  SHA512:
6
- metadata.gz: bfc44e2043ea2115ee1852a5bf2e21e07fe9bd27d0501a6c44451ff4e5d22150580cdf196c5886e1867610be4424fe17ae5e09754ce516a8541e7bc8080e2b54
7
- data.tar.gz: eb7da4439a2d40378b6f38d6d386cc7b55ecc2fd7400f368ccac40c6770ed5142d510c243e1d2a9687519dd0261265dafa6960221ed2e4881f5bbeb92d338c9f
6
+ metadata.gz: 9a9dd8cc0c6b18a192575c169ff213e2bcf762d1a93f4ecc07aad89729a3173e03fe31e86e35ed4e583446410933367ef7ae9a6f365b47a55e75dd1c53b0b521
7
+ data.tar.gz: a468f6781963b983e54e367a0ddb99525ae2f5c1da8ca9b04829c248bf2ffe85705c9f96088eb09a39250cbb616d507288c84e54a27dee11cf42c4bd533dcb7f
data/README.md CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  [![Built in integration with JetBrains RubyMine](https://github.com/daddyz/phonelib/blob/master/icon_RubyMine.png?raw=true)](https://www.jetbrains.com/ruby/)
4
4
  [![Gem Version](https://badge.fury.io/rb/phonelib.svg)](http://badge.fury.io/rb/phonelib)
5
- [![Build Status](https://travis-ci.com/daddyz/phonelib.svg?branch=master)](http://travis-ci.com/daddyz/phonelib)
5
+ [![CircleCI](https://dl.circleci.com/status-badge/img/gh/daddyz/phonelib/tree/master.svg?style=shield)](https://dl.circleci.com/status-badge/redirect/gh/daddyz/phonelib/tree/master)
6
6
  [![](https://codeclimate.com/github/daddyz/phonelib/badges/coverage.svg)](https://codeclimate.com/github/daddyz/phonelib/coverage)
7
7
  [![](https://codeclimate.com/github/daddyz/phonelib/badges/gpa.svg)](https://codeclimate.com/github/daddyz/phonelib)
8
8
  [![Inline docs](http://inch-ci.org/github/daddyz/phonelib.svg?branch=master)](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,12 @@ 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
+
64
76
  To change sanitized symbols on parsed number, so non-specified symbols won't be wiped and will fail the parsing
65
77
 
66
78
  ``` ruby
@@ -252,7 +264,7 @@ phone.full_e164 # returns e164 phone representation with extension
252
264
  phone.full_international # returns formatted international number with extension
253
265
  ```
254
266
 
255
- You can pass <tt>false</tt> to <tt>national</tt> and <tt>international</tt> methods in order to get unformatted representaions
267
+ You can pass <tt>false</tt> to <tt>national</tt> and <tt>international</tt> methods in order to get unformatted representations
256
268
 
257
269
  ``` ruby
258
270
  phone.international(false) # returns unformatted international phone
Binary file
data/data/phone_data.dat CHANGED
Binary file
data/lib/phonelib/core.rb CHANGED
@@ -10,6 +10,16 @@ module Phonelib
10
10
  @@phone_data ||= load_data.freeze
11
11
  end
12
12
 
13
+ # @private getter for phone data indexed by country code (internal use only)
14
+ def data_by_country_codes
15
+ @@data_by_country_codes ||= phone_data.each_value.group_by { |d| d[COUNTRY_CODE] }.freeze
16
+ end
17
+
18
+ # @private getter for all international prefixes in phone_data
19
+ def phone_data_int_prefixes
20
+ @@all_int_prefixes ||= phone_data.map {|k,v| v[:international_prefix] }.select { |v| v != '' }.compact.uniq.join('|').freeze
21
+ end
22
+
13
23
  # @private used to cache frequently-used regular expressions
14
24
  @@phone_regexp_cache = {}
15
25
 
@@ -30,14 +40,14 @@ module Phonelib
30
40
  @@default_country = nil
31
41
 
32
42
  # getter method for default_country variable
33
- # @return [String|nil] Default country set for parsing or nil
43
+ # @return [String,Symbol,Array<String,Symbol>,nil] Default country ISO2 code or codes used for parsing
34
44
  def default_country
35
45
  @@default_country
36
46
  end
37
47
 
38
48
  # setter method for default_country variable
39
- # @param country [String|Symbol] default country ISO2 code used for parsing
40
- # @return [String|nil] Default country set for parsing or nil
49
+ # @param country [String,Symbol,Array<String,Symbol>] Default country ISO2 code or codes used for parsing
50
+ # @return [String,Symbol,Array<String,Symbol>] Default country ISO2 code or codes used for parsing
41
51
  def default_country=(country)
42
52
  @@default_country = country
43
53
  end
@@ -107,6 +117,22 @@ module Phonelib
107
117
  @@strict_check = strict
108
118
  end
109
119
 
120
+ # @private don't use plus sign for automatic country change
121
+ @@ignore_plus = false
122
+
123
+ # getter for ignore plus flag
124
+ # @return [Boolean] Flag defines whether to reset country in case number has + and country prefix doesn't match
125
+ def ignore_plus
126
+ @@ignore_plus
127
+ end
128
+
129
+ # setter for ignore plus flag
130
+ # @param ignore_plus [Boolean] ignore plus sign or not
131
+ # @return [Boolean] Flag defines whether to ignore plus for country reset during validations in case country prefix doesn't match
132
+ def ignore_plus=(ignore_plus)
133
+ @@ignore_plus = ignore_plus
134
+ end
135
+
110
136
  # @private sanitizing regex, matching symbols will get removed from parsed number, must be string
111
137
  @@sanitize_regex = '[^0-9]+'
112
138
 
@@ -163,7 +189,7 @@ module Phonelib
163
189
  def add_additional_regex(country, type, national_regex)
164
190
  return unless Phonelib::Core::TYPES_DESC.keys.include?(type.to_sym)
165
191
  return unless national_regex.is_a?(String)
166
- @@phone_data = nil
192
+ @@phone_data = @@data_by_country_codes = nil
167
193
  @@additional_regexes[country.to_s.upcase] ||= {}
168
194
  @@additional_regexes[country.to_s.upcase][type] ||= []
169
195
  @@additional_regexes[country.to_s.upcase][type] << national_regex
@@ -19,6 +19,48 @@ module Phonelib
19
19
 
20
20
  # countries that can have double country prefix in number
21
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'
@@ -178,6 +178,8 @@ module Phonelib
178
178
 
179
179
  # @private extracts extension from passed phone number if provided
180
180
  def separate_extension(original)
181
+ return [original, ''] unless Phonelib.extension_separate_symbols
182
+
181
183
  regex = if Phonelib.extension_separate_symbols.is_a?(Array)
182
184
  cr("#{Phonelib.extension_separate_symbols.join('|')}")
183
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
- country = country_or_default_country passed_country
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
- result = {}
107
- Phonelib.phone_data.each do |key, data|
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
- result
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
- country && country.to_s.upcase
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
@@ -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 country_code && sanitized.start_with?(country_code)
26
- sanitized[country_code.size..-1]
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 ||= Phonelib.phone_data[country] && \
36
- Phonelib.phone_data[country][Core::COUNTRY_CODE]
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
@@ -44,7 +53,7 @@ module Phonelib
44
53
  prefix = formatted if formatted.is_a?(String)
45
54
  return nil if sanitized.empty?
46
55
  return "#{prefix}#{country_prefix_or_not}#{sanitized}" unless valid?
47
- return "#{prefix}#{country_code}#{@national_number}" unless formatted
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}#{country_code} #{national}"
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 country_code
128
- sanitized.start_with?(country_code) ? '' : country_code
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
@@ -1,4 +1,4 @@
1
1
  module Phonelib
2
2
  # @private
3
- VERSION = '0.6.55'
3
+ VERSION = '0.8.2'
4
4
  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.6.55
4
+ version: 0.8.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Vadim Senderovich
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2022-01-10 00:00:00.000000000 Z
11
+ date: 2023-06-05 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rake
@@ -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