phonelib 0.7.7 → 0.10.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: dcdc081295ac56df465d03c3ce7a6662248efd0435418bed4ca9d4b7af4b9177
4
- data.tar.gz: 95e6742ba9fe64deabd92d477e41f6a3dc95b9d6d97439721f68d7fb11b5078f
3
+ metadata.gz: d02888169692ebcf0b98bd02f467f3d2241eb1d6687345c5176cd3d7054b4b70
4
+ data.tar.gz: d73a746b3e5d4f9fed365dca8491d8f259016c4304428c39035cf27792f93502
5
5
  SHA512:
6
- metadata.gz: 1783e018ef2a0299251a30e9b228964e186c3c88b1603d1b6f9bd068d4ae240a731127730bd1b4748c149c68f394628d5aa8917337452277c8ec18245fe5a89f
7
- data.tar.gz: e94e439077eb2e33f1fcdb1d1e735a60968258240ba97fd3c730eb236eb7bb8319cf2f0d8dea40fb3c0a8b549a0730201dd17a8e74c837722edcd087255556a9
6
+ metadata.gz: 5f0491a84599e35d6d665df74971250c5521b82e91411e00938a98c720efae6b5fb651107e79574fa0050543540a0c9648fced165f73f2b976e0024d89c9aadb
7
+ data.tar.gz: adb6f0e9095bb1bf35836860f7049b3e5013d0903b710deedb61a71fbd206b5d0f29f2bdfb2d0da4ea75b0a5eec6ee6c0e174a76a3fc7c43b07e236ae2ec6aca
data/README.md CHANGED
@@ -13,7 +13,7 @@ But it still doesn't include all Google's library functionality.
13
13
 
14
14
  ## Incorrect parsing or validation
15
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.
16
+ In case your phone number is incorrectly parsed, you can check original libphonenumber for result [here](https://htmlpreview.github.io/?https://github.com/google/libphonenumber/blob/master/javascript/i18n/phonenumbers/demo-compiled.html) and in case of same parse result [open an issue for them](http://issuetracker.google.com/issues/new?component=192347). This gem's data is based on it.
17
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
18
 
19
19
  ## Information
Binary file
data/data/phone_data.dat CHANGED
Binary file
data/lib/phonelib/core.rb CHANGED
@@ -1,15 +1,40 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Phonelib
2
4
  # main module that includes all basic data and methods
3
5
  module Core
4
6
  # @private variable will include hash with data for validation
5
7
  @@phone_data = nil
6
8
 
9
+ # eagerly initialize the gem, loads data into memory. not required, initialization is done lazily otherwise, but
10
+ # may be desirable in production enviroments to avoid initialization time on first use.
11
+ def eager_load!
12
+ return if @@skip_eager_loading
13
+ phone_data
14
+ phone_ext_data
15
+ end
16
+
17
+ @@skip_eager_loading = false
18
+ def skip_eager_loading!
19
+ @@skip_eager_loading = true
20
+ end
21
+
7
22
  # getter for phone data for other modules of gem, can be used outside
8
23
  # @return [Hash] all data for phone parsing
9
24
  def phone_data
10
25
  @@phone_data ||= load_data.freeze
11
26
  end
12
27
 
28
+ # @private getter for phone data indexed by country code (internal use only)
29
+ def data_by_country_codes
30
+ @@data_by_country_codes ||= phone_data.each_value.group_by { |d| d[COUNTRY_CODE] }.freeze
31
+ end
32
+
33
+ # @private getter for all international prefixes in phone_data
34
+ def phone_data_int_prefixes
35
+ @@all_int_prefixes ||= phone_data.map {|k,v| v[:international_prefix] }.select { |v| v != '' }.compact.uniq.join('|').freeze
36
+ end
37
+
13
38
  # @private used to cache frequently-used regular expressions
14
39
  @@phone_regexp_cache = {}
15
40
 
@@ -30,14 +55,14 @@ module Phonelib
30
55
  @@default_country = nil
31
56
 
32
57
  # getter method for default_country variable
33
- # @return [String|nil] Default country set for parsing or nil
58
+ # @return [String,Symbol,Array<String,Symbol>,nil] Default country ISO2 code or codes used for parsing
34
59
  def default_country
35
60
  @@default_country
36
61
  end
37
62
 
38
63
  # 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
64
+ # @param country [String,Symbol,Array<String,Symbol>] Default country ISO2 code or codes used for parsing
65
+ # @return [String,Symbol,Array<String,Symbol>] Default country ISO2 code or codes used for parsing
41
66
  def default_country=(country)
42
67
  @@default_country = country
43
68
  end
@@ -177,9 +202,9 @@ module Phonelib
177
202
  end
178
203
 
179
204
  def add_additional_regex(country, type, national_regex)
180
- return unless Phonelib::Core::TYPES_DESC.keys.include?(type.to_sym)
205
+ return unless Phonelib::Core::TYPES_DESC.key?(type.to_sym)
181
206
  return unless national_regex.is_a?(String)
182
- @@phone_data = nil
207
+ @@phone_data = @@data_by_country_codes = nil
183
208
  @@additional_regexes[country.to_s.upcase] ||= {}
184
209
  @@additional_regexes[country.to_s.upcase][type] ||= []
185
210
  @@additional_regexes[country.to_s.upcase][type] << national_regex
@@ -356,6 +381,7 @@ module Phonelib
356
381
  carrier_selection_codes: 'Carrier Selection codes',
357
382
  area_code_optional: 'Are code optional'
358
383
  }.freeze
384
+ TYPES_DESC_KEYS = TYPES_DESC.keys.freeze
359
385
 
360
386
  # @private short codes types keys
361
387
  SHORT_CODES = [
@@ -382,6 +408,13 @@ module Phonelib
382
408
  # @private Extended data key for carrier in prefixes hash
383
409
  EXT_CARRIER_KEY = :c
384
410
 
411
+ # @private Static arrays used to avoid allocations
412
+ FIXED_OR_MOBILE_ARRAY = [Core::FIXED_OR_MOBILE].freeze
413
+ FIXED_LINE_OR_MOBILE_ARRAY = [Core::FIXED_LINE, Core::MOBILE].freeze
414
+ POSSIBLE_VALID_ARRAY = [:possible, :valid].freeze
415
+ VALID_POSSIBLE_ARRAY = [:valid, :possible].freeze
416
+ NIL_RESULT_ARRAY = [nil, nil].freeze
417
+
385
418
  # method for parsing phone number.
386
419
  # On first run fills @@phone_data with data present in yaml file
387
420
  # @param phone [String] the phone number to be parsed
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'phonelib/data_importer_helper'
2
4
 
3
5
  module Phonelib
@@ -6,7 +8,7 @@ module Phonelib
6
8
  require 'nokogiri'
7
9
 
8
10
  # official libphonenumber repo for cloning
9
- REPO = 'https://github.com/googlei18n/libphonenumber.git'
11
+ REPO = 'https://github.com/google/libphonenumber.git'
10
12
 
11
13
  # importing function
12
14
  def self.import
@@ -45,6 +47,7 @@ module Phonelib
45
47
  'TA' => 'SH',
46
48
  'TC' => 'US',
47
49
  'TT' => 'US',
50
+ 'UM' => 'US',
48
51
  'VA' => 'IT',
49
52
  'VC' => 'US',
50
53
  'VG' => 'US',
@@ -125,6 +128,9 @@ module Phonelib
125
128
  if country[Core::NATIONAL_PREFIX_TRANSFORM_RULE]
126
129
  country[Core::NATIONAL_PREFIX_TRANSFORM_RULE].gsub!('$', '\\')
127
130
  end
131
+ if country[:id] == '001'
132
+ country[:id] = 'International ' + country[:country_code]
133
+ end
128
134
  @data[country[:id]] = country
129
135
  end
130
136
  end
@@ -160,6 +166,7 @@ module Phonelib
160
166
  # some countries missing formats, and are linking them to another countries
161
167
  def process_format_links
162
168
  FORMAT_SHARING.each do |destination, source|
169
+ next unless @data[destination]
163
170
  @data[destination][:formats] ||= []
164
171
  @data[destination][:formats] = @data[destination][:formats] + @data[source][:formats]
165
172
  end
@@ -195,8 +202,8 @@ module Phonelib
195
202
 
196
203
  require 'open-uri'
197
204
  require 'csv'
198
- io = open('http://api.geonames.org/countryInfoCSV?username=demo&style=full')
199
- csv = CSV.new(io, {col_sep: "\t"})
205
+ io = URI.open('http://download.geonames.org/export/dump/countryInfo.txt')
206
+ csv = CSV.new(io, **{col_sep: "\t"})
200
207
  csv.each do |row|
201
208
  next if row[0].nil? || row[0].start_with?('#') || row[0].empty? || row[0].size != 2
202
209
 
@@ -207,7 +214,7 @@ module Phonelib
207
214
  # adds double country code flag in case country allows
208
215
  def add_double_country_flag(country)
209
216
  if DOUBLE_COUNTRY_CODES_COUNTRIES.include?(country[:id])
210
- country[:double_prefix] = true
217
+ country[Core::DOUBLE_COUNTRY_PREFIX_FLAG] = true
211
218
  end
212
219
  country
213
220
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Phonelib
2
4
  # @private helper module for parsing raw libphonenumber data
3
5
  module DataImporterHelper
@@ -101,7 +103,13 @@ module Phonelib
101
103
  line = str_clean line, false
102
104
  next if line.empty? || line[0] == '#'
103
105
  prefix, line_data = line.split('|')
104
- data[prefix] = line_data && line_data.strip.split('&')
106
+ if line_data
107
+ data[prefix] = if line_data.strip =~ /[^ ]{3,}&[^ ]{3,}/
108
+ line_data.strip.split('&')
109
+ else
110
+ line_data.strip
111
+ end
112
+ end
105
113
  end
106
114
  data
107
115
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Phonelib
2
4
  # class for parsed phone number, includes validation and formatting methods
3
5
  class Phone
@@ -24,7 +26,7 @@ module Phonelib
24
26
  # @return [Phonelib::Phone] parsed phone instance
25
27
  def initialize(phone, country = nil)
26
28
  @original, @extension = separate_extension(phone.to_s)
27
- @extension.gsub!(/[^0-9]/, '') if @extension
29
+ @extension = @extension.gsub(/[^0-9]/, '') if @extension
28
30
 
29
31
  if sanitized.empty?
30
32
  @data = {}
@@ -178,6 +180,8 @@ module Phonelib
178
180
 
179
181
  # @private extracts extension from passed phone number if provided
180
182
  def separate_extension(original)
183
+ return [original, ''] unless Phonelib.extension_separate_symbols
184
+
181
185
  regex = if Phonelib.extension_separate_symbols.is_a?(Array)
182
186
  cr("#{Phonelib.extension_separate_symbols.join('|')}")
183
187
  else
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Phonelib
2
4
  # @private phone analyzing methods module
3
5
  module PhoneAnalyzer
@@ -17,7 +19,6 @@ module Phonelib
17
19
  # country (2 letters) like 'US', 'us' or :us for United States
18
20
  def analyze(phone, passed_country)
19
21
  countries = country_or_default_country passed_country
20
-
21
22
  return analyze_single_country(phone, countries.first, passed_country) if countries.size == 1
22
23
 
23
24
  results = {}
@@ -32,7 +33,7 @@ module Phonelib
32
33
 
33
34
  # pick best result when several countries specified
34
35
  def pick_results(results)
35
- [:valid, :possible].each do |key|
36
+ Core::VALID_POSSIBLE_ARRAY.each do |key|
36
37
  final = results.select { |_k, v| v[key].any? }
37
38
  return decorate_analyze_result(final) if final.size > 0
38
39
  end
@@ -68,12 +69,19 @@ module Phonelib
68
69
  end
69
70
 
70
71
  # replacing national prefix to simplified format
71
- def with_replaced_national_prefix(phone, data)
72
- return phone unless data[Core::NATIONAL_PREFIX_TRANSFORM_RULE]
72
+ def with_replaced_national_prefix(passed_phone, data)
73
+ return passed_phone unless data[Core::NATIONAL_PREFIX_TRANSFORM_RULE]
74
+ phone = if passed_phone.start_with?(data[Core::COUNTRY_CODE]) && !data[Core::DOUBLE_COUNTRY_PREFIX_FLAG]
75
+ passed_phone.delete_prefix(data[Core::COUNTRY_CODE])
76
+ else
77
+ passed_phone
78
+ end
79
+ return passed_phone unless phone.match? cr("^#{type_regex(data[Core::TYPES][Core::GENERAL], Core::POSSIBLE_PATTERN)}$")
80
+
73
81
  pattern = cr("^(?:#{data[Core::NATIONAL_PREFIX_FOR_PARSING]})")
74
82
  match = phone.match pattern
75
83
  if match && match.captures.compact.size > 0
76
- phone.gsub(pattern, data[Core::NATIONAL_PREFIX_TRANSFORM_RULE])
84
+ data[Core::COUNTRY_CODE] + phone.gsub(pattern, data[Core::NATIONAL_PREFIX_TRANSFORM_RULE])
77
85
  else
78
86
  phone
79
87
  end
@@ -124,15 +132,27 @@ module Phonelib
124
132
  #
125
133
  # * +phone+ - phone number for parsing
126
134
  def detect_and_parse(phone, country)
127
- result = {}
128
- Phonelib.phone_data.each do |key, data|
135
+ countries_data = country_code_candidates_for(phone).flat_map { |code|
136
+ Phonelib.data_by_country_codes[code] || []
137
+ }
138
+ countries_data.each_with_object({}) do |data, result|
139
+ key = data[:id]
129
140
  parsed = parse_single_country(phone, data)
141
+ unless parsed && parsed[key] && parsed[key][:valid].size > 0
142
+ replaced_parsed = parse_single_country(with_replaced_national_prefix(phone, data), data)
143
+ parsed = replaced_parsed unless replaced_parsed.nil?
144
+ end
130
145
  if (!Phonelib.strict_double_prefix_check || key == country) && double_prefix_allowed?(data, phone, parsed && parsed[key])
131
- parsed = parse_single_country(changed_dp_phone(key, phone), data)
146
+ parsed2 = parse_single_country(changed_dp_phone(key, phone), data)
147
+ parsed = parsed2 if parsed2 && parsed2[key] && parsed2[key][:valid].size > 0
132
148
  end
133
149
  result.merge!(parsed) unless parsed.nil?
134
- end
135
- result
150
+ end.compact
151
+ end
152
+
153
+ def country_code_candidates_for(phone)
154
+ stripped_phone = phone.gsub(cr("Phonelib.phone_data_int_prefixes") { /^(#{Phonelib.phone_data_int_prefixes})/ }, '')
155
+ ((1..3).map { |length| phone[0, length] } + (1..3).map { |length| stripped_phone[0, length] }).uniq
136
156
  end
137
157
 
138
158
  # Create phone representation in e164 format
@@ -142,13 +162,13 @@ module Phonelib
142
162
  # * +phone+ - phone number for parsing
143
163
  # * +data+ - country data to be based on for creating e164 representation
144
164
  def convert_to_e164(phone, data)
145
- match = phone.match full_regex_for_data(data, Core::VALID_PATTERN, !original_starts_with_plus?)
165
+ match = phone.match full_regex_for_data(data, Core::VALID_PATTERN, !original_starts_with_plus_or_double_zero?)
146
166
  case
147
167
  when match
148
168
  "#{data[Core::COUNTRY_CODE]}#{match.to_a.last}"
149
169
  when phone.match(cr("^#{data[Core::INTERNATIONAL_PREFIX]}"))
150
170
  phone.sub(cr("^#{data[Core::INTERNATIONAL_PREFIX]}"), Core::PLUS_SIGN)
151
- when original_starts_with_plus? && phone.start_with?(data[Core::COUNTRY_CODE])
171
+ when original_starts_with_plus_or_double_zero? && phone.start_with?(data[Core::COUNTRY_CODE])
152
172
  phone
153
173
  else
154
174
  "#{data[Core::COUNTRY_CODE]}#{phone}"
@@ -164,7 +184,11 @@ module Phonelib
164
184
  # * +not_valid+ - specifies that number is not valid by general desc pattern
165
185
  def national_and_data(data, country_match, not_valid = false)
166
186
  result = data.select { |k, _v| k != :types && k != :formats }
167
- phone = country_match.to_a.last
187
+ index = 0
188
+ if Phonelib.additional_regexes.is_a?(Hash) && Phonelib.additional_regexes[data[:id]]
189
+ index = Phonelib.additional_regexes[data[:id]].values.flatten.join('|').scan(/\(/).size
190
+ end
191
+ phone = country_match[-1 - index]
168
192
  result[:national] = phone
169
193
  result[:format] = number_format(phone, data[Core::FORMATS])
170
194
  result.merge! all_number_types(phone, data[Core::TYPES], not_valid)
@@ -205,7 +229,7 @@ module Phonelib
205
229
  def number_format(national, format_data)
206
230
  format_data && format_data.find do |format|
207
231
  (format[Core::LEADING_DIGITS].nil? || \
208
- national.match(cr("^(#{format[Core::LEADING_DIGITS]})"))) && \
232
+ national.match?(cr("^(#{format[Core::LEADING_DIGITS]})"))) && \
209
233
  national.match(cr("^(#{format[Core::PATTERN]})$"))
210
234
  end || Core::DEFAULT_NUMBER_FORMAT
211
235
  end
@@ -226,7 +250,7 @@ module Phonelib
226
250
  type_regex(patterns, Core::VALID_PATTERN)
227
251
  ]
228
252
  else
229
- [nil, nil]
253
+ Core::NIL_RESULT_ARRAY
230
254
  end
231
255
  end
232
256
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Phonelib
2
4
  # @private helper methods for analyser
3
5
  module PhoneAnalyzerHelper
@@ -7,12 +9,15 @@ module Phonelib
7
9
  if result.size == 1
8
10
  result
9
11
  else
12
+ matched_countries = country_or_default_country(nil) & result.keys
13
+ matched_countries = result.keys.take(1) if matched_countries.size == 0
14
+ result = result.keep_if {|k, _v| matched_countries.include?(k) } if matched_countries
10
15
  Hash[result.take(1)]
11
16
  end
12
17
  end
13
18
 
14
- def original_starts_with_plus?
15
- original_s[0] == Core::PLUS_SIGN
19
+ def original_starts_with_plus_or_double_zero?
20
+ original_s[0] == Core::PLUS_SIGN || original_s[0..1] == '00'
16
21
  end
17
22
 
18
23
  # converts symbols in phone to numbers
@@ -49,14 +54,15 @@ module Phonelib
49
54
 
50
55
  # caches regular expression, reusing it for later lookups
51
56
  def cr(regexp)
52
- Phonelib.phone_regexp_cache[regexp] ||= Regexp.new(regexp).freeze
57
+ Phonelib.phone_regexp_cache[regexp] ||= Regexp.new(block_given? ? yield(regexp) : regexp).freeze
53
58
  end
54
59
 
55
60
  # defines whether country can have double country prefix in number
56
61
  def country_can_dp?(country)
57
62
  Phonelib.phone_data[country] &&
58
63
  Phonelib.phone_data[country][Core::DOUBLE_COUNTRY_PREFIX_FLAG] &&
59
- !original_starts_with_plus?
64
+ !original_starts_with_plus_or_double_zero? &&
65
+ original_s.start_with?(Phonelib.phone_data[country][Core::COUNTRY_CODE])
60
66
  end
61
67
 
62
68
  # changes phone to with/without double country prefix
@@ -66,6 +72,7 @@ module Phonelib
66
72
 
67
73
  country_code = Phonelib.phone_data[country][Core::COUNTRY_CODE]
68
74
  if phone.start_with? country_code * 2
75
+ # remove double prefix in case it is there
69
76
  phone.gsub(cr("^#{country_code}"), '')
70
77
  else
71
78
  "#{country_code}#{phone}"
@@ -83,7 +90,7 @@ module Phonelib
83
90
  data[Core::DOUBLE_COUNTRY_PREFIX_FLAG] &&
84
91
  phone =~ cr("^#{data[Core::COUNTRY_CODE]}") &&
85
92
  parsed && (parsed[:valid].nil? || parsed[:valid].empty?) &&
86
- !original_starts_with_plus?
93
+ !original_starts_with_plus_or_double_zero?
87
94
  end
88
95
 
89
96
  # Returns original number passed if it's a string or empty string otherwise
@@ -97,7 +104,8 @@ module Phonelib
97
104
  #
98
105
  # * +country+ - country passed for parsing
99
106
  def country_or_default_country(country)
100
- country ||= (original_starts_with_plus? ? nil : Phonelib.default_country)
107
+ country ||= (original_starts_with_plus_or_double_zero? ? nil : Phonelib.default_country)
108
+
101
109
  if country.is_a?(Array)
102
110
  country.compact.map { |e| e.to_s.upcase }
103
111
  else
@@ -113,14 +121,15 @@ module Phonelib
113
121
  # * +data+ - country data hash
114
122
  # * +country_optional+ - whether to put country code as optional group
115
123
  def full_regex_for_data(data, type, country_optional = true)
116
- regex = []
117
- regex << '0{2}?'
118
- regex << "(#{data[Core::INTERNATIONAL_PREFIX]})?"
119
- regex << "(#{data[Core::COUNTRY_CODE]})#{country_optional ? '?' : ''}"
120
- regex << "(#{data[Core::NATIONAL_PREFIX_FOR_PARSING] || data[Core::NATIONAL_PREFIX]})?"
121
- regex << "(#{type_regex(data[Core::TYPES][Core::GENERAL], type)})" if data[Core::TYPES]
124
+ regex = +"^0{2}?(#{data[Core::INTERNATIONAL_PREFIX]})?(#{data[Core::COUNTRY_CODE]})#{country_optional ? '?' : ''}(#{data[Core::NATIONAL_PREFIX_FOR_PARSING] || data[Core::NATIONAL_PREFIX]})?"
125
+ if data[Core::TYPES]
126
+ regex << "("
127
+ regex << type_regex(data[Core::TYPES][Core::GENERAL], type)
128
+ regex << ")"
129
+ end
130
+ regex << "$"
122
131
 
123
- cr("^#{regex.join}$")
132
+ cr(regex)
124
133
  end
125
134
 
126
135
  # Returns regex for type with special types if needed
@@ -130,11 +139,11 @@ module Phonelib
130
139
  # * +data+ - country types data for single type
131
140
  # * +type+ - possible or valid regex type needed
132
141
  def type_regex(data, type)
133
- regex = [data[type]]
134
142
  if Phonelib.parse_special && data[Core::SHORT] && data[Core::SHORT][type]
135
- regex << data[Core::SHORT][type]
143
+ "#{data[type]}|#{data[Core::SHORT][type]}"
144
+ else
145
+ data[type]
136
146
  end
137
- regex.join('|')
138
147
  end
139
148
 
140
149
  # Check if phone match country data
@@ -144,9 +153,7 @@ module Phonelib
144
153
  # * +phone+ - phone number for parsing
145
154
  # * +data+ - country data
146
155
  def phone_match_data?(phone, data, possible = false)
147
- country_code = "#{data[Core::COUNTRY_CODE]}"
148
- inter_prefix = "(#{data[Core::INTERNATIONAL_PREFIX]})?"
149
- return unless phone.match cr("^0{2}?#{inter_prefix}#{country_code}")
156
+ return unless phone.match?(cr("^0{2}?(#{data[Core::INTERNATIONAL_PREFIX]})?#{data[Core::COUNTRY_CODE]}"))
150
157
 
151
158
  type = possible ? Core::POSSIBLE_PATTERN : Core::VALID_PATTERN
152
159
  phone.match full_regex_for_data(data, type, false)
@@ -155,10 +162,9 @@ module Phonelib
155
162
  # checks if types has both :mobile and :fixed_line and replaces it with
156
163
  # :fixed_or_mobile in case both present
157
164
  def sanitize_fixed_mobile(types)
158
- fixed_mobile = [Core::FIXED_LINE, Core::MOBILE]
159
- [:possible, :valid].each do |key|
160
- if (fixed_mobile - types[key]).empty?
161
- types[key] = types[key] - fixed_mobile + [Core::FIXED_OR_MOBILE]
165
+ Core::POSSIBLE_VALID_ARRAY.each do |key|
166
+ if (Core::FIXED_LINE_OR_MOBILE_ARRAY - types[key]).empty?
167
+ types[key] = types[key] - Core::FIXED_LINE_OR_MOBILE_ARRAY + Core::FIXED_OR_MOBILE_ARRAY
162
168
  end
163
169
  end
164
170
  types
@@ -172,7 +178,7 @@ module Phonelib
172
178
  def types_for_check(data)
173
179
  exclude_list = PhoneAnalyzer::NOT_FOR_CHECK
174
180
  exclude_list += Phonelib::Core::SHORT_CODES unless Phonelib.parse_special
175
- Core::TYPES_DESC.keys - exclude_list + fixed_and_mobile_keys(data)
181
+ Core::TYPES_DESC_KEYS - exclude_list + fixed_and_mobile_keys(data)
176
182
  end
177
183
 
178
184
  # Checks if fixed line pattern and mobile pattern are the same and returns
@@ -183,9 +189,9 @@ module Phonelib
183
189
  # * +data+ - country data
184
190
  def fixed_and_mobile_keys(data)
185
191
  if data[Core::FIXED_LINE] == data[Core::MOBILE]
186
- [Core::FIXED_OR_MOBILE]
192
+ Core::FIXED_OR_MOBILE_ARRAY
187
193
  else
188
- [Core::FIXED_LINE, Core::MOBILE]
194
+ Core::FIXED_LINE_OR_MOBILE_ARRAY
189
195
  end
190
196
  end
191
197
 
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Phonelib
2
4
  # module provides extended data methods for parsed phone
3
5
  module PhoneExtendedData
@@ -16,12 +18,19 @@ module Phonelib
16
18
  Phonelib::Core::EXT_GEO_NAME_KEY
17
19
  end
18
20
 
19
- # Returns timezone of parsed phone number or nil if number is invalid or
20
- # there is no timezone specified in db for this number
21
+ # Returns first timezone (in case several match) of parsed phone number or nil
22
+ # if number is invalid or there is no timezone specified in db for this number
21
23
  # @return [String|nil] timezone for parsed phone
22
24
  def timezone
23
- get_ext_name Phonelib::Core::EXT_TIMEZONES,
24
- Phonelib::Core::EXT_TIMEZONE_KEY
25
+ timezones.first
26
+ end
27
+
28
+ # Returns timezones of parsed phone number or nil if number is invalid or
29
+ # there is no timezone specified in db for this number
30
+ # @return [Array] timezones for parsed phone
31
+ def timezones
32
+ res = get_ext_name Phonelib::Core::EXT_TIMEZONES, Phonelib::Core::EXT_TIMEZONE_KEY
33
+ res.is_a?(Array) ? res : [res]
25
34
  end
26
35
 
27
36
  # Returns carrier of parsed phone number or nil if number is invalid or
@@ -51,9 +60,7 @@ module Phonelib
51
60
  def get_ext_name(names_key, id_key)
52
61
  return nil unless ext_data[id_key]
53
62
 
54
- res = Phonelib.phone_ext_data[names_key][ext_data[id_key]]
55
- return nil unless res
56
- res.size == 1 ? res.first : res
63
+ Phonelib.phone_ext_data[names_key][ext_data[id_key]]
57
64
  end
58
65
 
59
66
  # @private returns extended data ids for current number
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Phonelib
2
4
  # module includes all formatting methods
3
5
  module PhoneFormatter
@@ -5,7 +7,7 @@ module Phonelib
5
7
  # @param formatted [Boolean] whether to return numbers only or formatted
6
8
  # @return [String] formatted national number
7
9
  def national(formatted = true)
8
- return @national_number unless valid?
10
+ return @national_number unless possible?
9
11
  format_match, format_string = formatting_data
10
12
 
11
13
  if format_match
@@ -52,7 +54,7 @@ module Phonelib
52
54
  def international(formatted = true, prefix = '+')
53
55
  prefix = formatted if formatted.is_a?(String)
54
56
  return nil if sanitized.empty?
55
- return "#{prefix}#{country_prefix_or_not}#{sanitized}" unless valid?
57
+ return "#{prefix}#{sanitized}" unless possible?
56
58
  return "#{prefix}#{data_country_code}#{@national_number}" unless formatted
57
59
 
58
60
  fmt = @data[country][:format]
@@ -135,12 +137,6 @@ module Phonelib
135
137
  true
136
138
  end
137
139
 
138
- # @private defines whether to put country prefix or not
139
- def country_prefix_or_not
140
- return '' unless data_country_code
141
- sanitized.start_with?(data_country_code) ? '' : data_country_code
142
- end
143
-
144
140
  # @private returns extension with separator defined
145
141
  def formatted_extension
146
142
  return '' if @extension.nil? || @extension.empty?
@@ -159,7 +155,7 @@ module Phonelib
159
155
  data[Core::NATIONAL_PREFIX_RULE] || '$1'
160
156
 
161
157
  # change rule's constants to values
162
- rule.gsub!(/(\$NP|\$FG)/, '$NP' => prefix, '$FG' => '$1')
158
+ rule = rule.gsub(/(\$NP|\$FG)/, '$NP' => prefix, '$FG' => '$1')
163
159
 
164
160
  # add space to format groups, change first group to rule,
165
161
  format_string = format[:format].gsub(/(\d)\$/, '\\1 $')
@@ -1,4 +1,6 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Phonelib
2
4
  # @private
3
- VERSION = '0.7.7'
5
+ VERSION = '0.10.9'
4
6
  end
data/lib/phonelib.rb CHANGED
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  # main Phonelib module definition
2
4
  module Phonelib
3
5
  # load phonelib classes/modules
@@ -12,9 +14,13 @@ module Phonelib
12
14
  end
13
15
 
14
16
  if defined?(ActiveModel) || defined?(Rails)
15
- if RUBY_VERSION >= '3.0.0'
16
- autoload :PhoneValidator, 'validators/phone_validator3'
17
- else
18
- autoload :PhoneValidator, 'validators/phone_validator'
17
+ autoload :PhoneValidator, 'validators/phone_validator'
18
+
19
+ if defined?(Rails)
20
+ class Phonelib::Railtie < Rails::Railtie
21
+ initializer 'phonelib' do |app|
22
+ app.config.eager_load_namespaces << Phonelib
23
+ end
24
+ end
19
25
  end
20
26
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  namespace :phonelib do
2
4
  desc 'Create database for tests in Rails dummy application'
3
5
  task :create_test_db do
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  # Validator class for phone validations
2
4
  #
3
5
  # ==== Examples
metadata CHANGED
@@ -1,127 +1,15 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: phonelib
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.7.7
4
+ version: 0.10.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: 2023-02-04 00:00:00.000000000 Z
12
- dependencies:
13
- - !ruby/object:Gem::Dependency
14
- name: rake
15
- requirement: !ruby/object:Gem::Requirement
16
- requirements:
17
- - - "<"
18
- - !ruby/object:Gem::Version
19
- version: '14.0'
20
- type: :development
21
- prerelease: false
22
- version_requirements: !ruby/object:Gem::Requirement
23
- requirements:
24
- - - "<"
25
- - !ruby/object:Gem::Version
26
- version: '14.0'
27
- - !ruby/object:Gem::Dependency
28
- name: nokogiri
29
- requirement: !ruby/object:Gem::Requirement
30
- requirements:
31
- - - "~>"
32
- - !ruby/object:Gem::Version
33
- version: 1.13.0
34
- type: :development
35
- prerelease: false
36
- version_requirements: !ruby/object:Gem::Requirement
37
- requirements:
38
- - - "~>"
39
- - !ruby/object:Gem::Version
40
- version: 1.13.0
41
- - !ruby/object:Gem::Dependency
42
- name: pry
43
- requirement: !ruby/object:Gem::Requirement
44
- requirements:
45
- - - ">="
46
- - !ruby/object:Gem::Version
47
- version: '0'
48
- type: :development
49
- prerelease: false
50
- version_requirements: !ruby/object:Gem::Requirement
51
- requirements:
52
- - - ">="
53
- - !ruby/object:Gem::Version
54
- version: '0'
55
- - !ruby/object:Gem::Dependency
56
- name: rspec
57
- requirement: !ruby/object:Gem::Requirement
58
- requirements:
59
- - - '='
60
- - !ruby/object:Gem::Version
61
- version: 2.14.1
62
- type: :development
63
- prerelease: false
64
- version_requirements: !ruby/object:Gem::Requirement
65
- requirements:
66
- - - '='
67
- - !ruby/object:Gem::Version
68
- version: 2.14.1
69
- - !ruby/object:Gem::Dependency
70
- name: codeclimate-test-reporter
71
- requirement: !ruby/object:Gem::Requirement
72
- requirements:
73
- - - "~>"
74
- - !ruby/object:Gem::Version
75
- version: 1.0.0
76
- type: :development
77
- prerelease: false
78
- version_requirements: !ruby/object:Gem::Requirement
79
- requirements:
80
- - - "~>"
81
- - !ruby/object:Gem::Version
82
- version: 1.0.0
83
- - !ruby/object:Gem::Dependency
84
- name: simplecov
85
- requirement: !ruby/object:Gem::Requirement
86
- requirements:
87
- - - ">="
88
- - !ruby/object:Gem::Version
89
- version: '0'
90
- type: :development
91
- prerelease: false
92
- version_requirements: !ruby/object:Gem::Requirement
93
- requirements:
94
- - - ">="
95
- - !ruby/object:Gem::Version
96
- version: '0'
97
- - !ruby/object:Gem::Dependency
98
- name: rack-cache
99
- requirement: !ruby/object:Gem::Requirement
100
- requirements:
101
- - - '='
102
- - !ruby/object:Gem::Version
103
- version: '1.2'
104
- type: :development
105
- prerelease: false
106
- version_requirements: !ruby/object:Gem::Requirement
107
- requirements:
108
- - - '='
109
- - !ruby/object:Gem::Version
110
- version: '1.2'
111
- - !ruby/object:Gem::Dependency
112
- name: json
113
- requirement: !ruby/object:Gem::Requirement
114
- requirements:
115
- - - '='
116
- - !ruby/object:Gem::Version
117
- version: 2.3.1
118
- type: :development
119
- prerelease: false
120
- version_requirements: !ruby/object:Gem::Requirement
121
- requirements:
122
- - - '='
123
- - !ruby/object:Gem::Version
124
- version: 2.3.1
11
+ date: 2025-05-14 00:00:00.000000000 Z
12
+ dependencies: []
125
13
  description: |2
126
14
  Google libphonenumber library was taken as a basis for
127
15
  this gem. Gem uses its data file for validations and number formatting.
@@ -148,11 +36,11 @@ files:
148
36
  - lib/phonelib/version.rb
149
37
  - lib/tasks/phonelib_tasks.rake
150
38
  - lib/validators/phone_validator.rb
151
- - lib/validators/phone_validator3.rb
152
39
  homepage: https://github.com/daddyz/phonelib
153
40
  licenses:
154
41
  - MIT
155
- metadata: {}
42
+ metadata:
43
+ changelog_uri: https://github.com/daddyz/phonelib/releases/tag/v0.10.9
156
44
  post_install_message:
157
45
  rdoc_options:
158
46
  - " --no-private - CHANGELOG.md --readme README.md"
@@ -169,7 +57,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
169
57
  - !ruby/object:Gem::Version
170
58
  version: '0'
171
59
  requirements: []
172
- rubygems_version: 3.0.8
60
+ rubygems_version: 3.1.6
173
61
  signing_key:
174
62
  specification_version: 4
175
63
  summary: Gem validates phone numbers with Google libphonenumber database
@@ -1,130 +0,0 @@
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