phonelib 0.6.36 → 0.8.9

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
- SHA1:
3
- metadata.gz: ebb5c4a03e76dff6c8aafae4aca658e6703542bf
4
- data.tar.gz: 62e417bddb31c30731c9db39419840c7337558aa
2
+ SHA256:
3
+ metadata.gz: e3414f233adef031f8260b96cb0d293b8aaee972043544d01084a9f840a60824
4
+ data.tar.gz: 7d0b91d44164cc5d124a0303334b90959ec4e6bfa1768b5772fa98373b740f74
5
5
  SHA512:
6
- metadata.gz: 61fb6b7dee5132bf7c63cb5a604cd2fb2ab9782ba20bc6df8b4a0367cb497da9b596f475dc00031f93ee4580d2a98ae13ea5feb43bdc6a273f87b3a77941c969
7
- data.tar.gz: 978d3dcb0236573a1b2425949e0a5716abe196d0052d59e30dd71fae5b794094dda948de9a243dfc91c2f900867ad73164504e101243eead4c9d6c98df10a983
6
+ metadata.gz: fac30ff6c40df1d8f5f85d09bf48e717d938beb12ec257d5048a7046c07efdfd0eac6321909126eb9ce18da8bd0c3e48bb71fd5d43e15f66906f59e2dda2cd89
7
+ data.tar.gz: dc761323a8babda14e47b4bedaec35a89d822ee617c72330490426d90a2545553eaf9bcc2e238d72d7b239ae979f21bc9c167727a42e5766e377d8b0f95dd663
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.org/daddyz/phonelib.png?branch=master)](http://travis-ci.org/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,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 representaions
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'
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|nil] Default country set for parsing or nil
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|Symbol] default country ISO2 code used for parsing
40
- # @return [String|nil] Default country set for parsing or nil
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
@@ -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('[^0-9]+'),
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
- 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
@@ -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] > 0
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] = 0 }
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 valid?
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 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
@@ -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 valid?
47
- return country_code + @national_number unless formatted
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}#{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.36'
3
+ VERSION = '0.8.9'
4
4
  end
data/lib/phonelib.rb CHANGED
@@ -12,5 +12,17 @@ module Phonelib
12
12
  end
13
13
 
14
14
  if defined?(ActiveModel) || defined?(Rails)
15
- autoload :PhoneValidator, 'validators/phone_validator'
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.6.36
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: 2019-06-17 00:00:00.000000000 Z
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: '11.0'
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: '11.0'
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.8.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.8.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.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.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: 1.8.6
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: 1.8.6
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
- rubyforge_project:
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