telephone_number 0.3.0 → 1.0.0
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 +4 -4
- data/README.md +7 -1
- data/lib/telephone_number/class_methods.rb +7 -2
- data/lib/telephone_number/data_importer.rb +14 -7
- data/lib/telephone_number/formatter.rb +56 -12
- data/lib/telephone_number/number.rb +8 -51
- data/lib/telephone_number/parser.rb +39 -10
- data/lib/telephone_number/phone_data.rb +8 -1
- data/lib/telephone_number/version.rb +1 -1
- data/lib/telephone_number.rb +1 -7
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 9176d842a4931a224fbc4e89bcf4f643e6bb4ab2
|
4
|
+
data.tar.gz: 88a4b6d3439017b5a9b68a15ae752c790373a475
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 6c1edb3e9c8f23ff5b8b43bf803995aac9c4c189c86ae453fe03c19d664af3bd8379be03c6aeaea8ad7f4a838efb1d0ac19dafb13bef84c90607a9af36bc51b4
|
7
|
+
data.tar.gz: c078804473a1a25106b6911b1bf379ca7727f5387a9dcfe8c34947c2f9b478bc1d868b083796943380497c631221355ef5f15f5ee0f6444bb518d01c909f9251
|
data/README.md
CHANGED
@@ -2,7 +2,13 @@
|
|
2
2
|
|
3
3
|
# What is it?
|
4
4
|
|
5
|
-
TelephoneNumber is global phone number validation gem based on Google's [libphonenumber](https://github.com/googlei18n/libphonenumber) library.
|
5
|
+
TelephoneNumber is global phone number validation gem based on Google's [libphonenumber](https://github.com/googlei18n/libphonenumber) library.
|
6
|
+
|
7
|
+
## Demo
|
8
|
+
|
9
|
+
Feel free to check out our demo!
|
10
|
+
|
11
|
+
[Numberjack](http://numberjack.io)
|
6
12
|
|
7
13
|
## Installation
|
8
14
|
|
@@ -1,15 +1,20 @@
|
|
1
1
|
module TelephoneNumber
|
2
2
|
module ClassMethods
|
3
|
-
def parse(number, country)
|
3
|
+
def parse(number, country = Parser.detect_country(number))
|
4
4
|
TelephoneNumber::Number.new(number, country)
|
5
5
|
end
|
6
6
|
|
7
|
-
def valid?(number, country, keys = [])
|
7
|
+
def valid?(number, country = Parser.detect_country(number), keys = [])
|
8
8
|
parse(number, country).valid?(keys)
|
9
9
|
end
|
10
10
|
|
11
11
|
def invalid?(*args)
|
12
12
|
!valid?(*args)
|
13
13
|
end
|
14
|
+
|
15
|
+
# generates binary file from xml that user gives us
|
16
|
+
def generate_override_file(file)
|
17
|
+
DataImporter.new(file, override: true).import!
|
18
|
+
end
|
14
19
|
end
|
15
20
|
end
|
@@ -1,16 +1,17 @@
|
|
1
1
|
module TelephoneNumber
|
2
2
|
require 'nokogiri'
|
3
3
|
class DataImporter
|
4
|
-
attr_reader :data, :file
|
4
|
+
attr_reader :data, :file, :override
|
5
5
|
|
6
|
-
def initialize(file_name)
|
6
|
+
def initialize(file_name, override: false)
|
7
7
|
@data = {}
|
8
8
|
@file = File.open(file_name) { |f| Nokogiri::XML(f) }
|
9
|
+
@override = override
|
9
10
|
end
|
10
11
|
|
11
|
-
def import!
|
12
|
+
def import!
|
12
13
|
parse_main_data
|
13
|
-
save_data_file
|
14
|
+
save_data_file
|
14
15
|
end
|
15
16
|
|
16
17
|
def parse_main_data
|
@@ -28,7 +29,7 @@ module TelephoneNumber
|
|
28
29
|
private
|
29
30
|
|
30
31
|
def load_formats(country_data, territory)
|
31
|
-
|
32
|
+
formats_arr = territory.css('availableFormats numberFormat').map do |format|
|
32
33
|
{}.tap do |fhash|
|
33
34
|
format.attributes.values.each do |attr|
|
34
35
|
key = underscore(attr.name).to_sym
|
@@ -44,6 +45,9 @@ module TelephoneNumber
|
|
44
45
|
end
|
45
46
|
end
|
46
47
|
end
|
48
|
+
|
49
|
+
return if override && formats_arr.empty?
|
50
|
+
country_data[PhoneData::FORMATS] = formats_arr
|
47
51
|
end
|
48
52
|
|
49
53
|
def load_validations(country_data, territory)
|
@@ -54,6 +58,7 @@ module TelephoneNumber
|
|
54
58
|
element.elements.each { |child| validation_hash[underscore(child.name).to_sym] = child.text.delete("\n ") }
|
55
59
|
end
|
56
60
|
end
|
61
|
+
country_data.delete(PhoneData::VALIDATIONS) if country_data[PhoneData::VALIDATIONS].empty? && override
|
57
62
|
end
|
58
63
|
|
59
64
|
def load_base_attributes(country_data, territory)
|
@@ -68,7 +73,9 @@ module TelephoneNumber
|
|
68
73
|
end
|
69
74
|
|
70
75
|
def load_references(country_data, territory)
|
71
|
-
|
76
|
+
ref_arr = territory.css('references sourceUrl').map(&:text)
|
77
|
+
return if override && ref_arr.empty?
|
78
|
+
country_data[:references] = ref_arr
|
72
79
|
end
|
73
80
|
|
74
81
|
def underscore(camel_cased_word)
|
@@ -81,7 +88,7 @@ module TelephoneNumber
|
|
81
88
|
word
|
82
89
|
end
|
83
90
|
|
84
|
-
def save_data_file
|
91
|
+
def save_data_file
|
85
92
|
data_file = override ? 'telephone_number_data_override_file.dat' : "#{File.dirname(__FILE__)}/../../data/telephone_number_data_file.dat"
|
86
93
|
File.open(data_file, 'wb+') { |f| Marshal.dump(@data, f) }
|
87
94
|
end
|
@@ -1,13 +1,56 @@
|
|
1
1
|
module TelephoneNumber
|
2
|
-
|
2
|
+
class Formatter
|
3
|
+
extend Forwardable
|
4
|
+
|
5
|
+
attr_reader :normalized_number, :phone_data, :valid
|
6
|
+
|
7
|
+
delegate [:country_data, :country] => :phone_data
|
8
|
+
|
9
|
+
def initialize(number_obj, phone_data)
|
10
|
+
@normalized_number = number_obj.normalized_number
|
11
|
+
@phone_data = phone_data
|
12
|
+
@valid = number_obj.valid?
|
13
|
+
end
|
14
|
+
|
15
|
+
def national_number(formatted: true)
|
16
|
+
if formatted
|
17
|
+
@formatted_national_number ||= build_national_number
|
18
|
+
else
|
19
|
+
@national_number ||= build_national_number(formatted: false)
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
def e164_number(formatted: true)
|
24
|
+
if formatted
|
25
|
+
@formatted_e164_number ||= build_e164_number
|
26
|
+
else
|
27
|
+
@e164_number ||= build_e164_number(formatted: false)
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
def international_number(formatted: true)
|
32
|
+
if formatted
|
33
|
+
@formatted_international_number ||= build_international_number
|
34
|
+
else
|
35
|
+
@international_number ||= build_international_number(formatted: false)
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
alias_method :valid?, :valid
|
40
|
+
|
41
|
+
private
|
42
|
+
|
43
|
+
def number_format
|
44
|
+
@number_format ||= extract_number_format
|
45
|
+
end
|
46
|
+
|
3
47
|
def build_national_number(formatted: true)
|
4
48
|
return normalized_or_default if !valid? || number_format.nil?
|
5
49
|
captures = normalized_number.match(Regexp.new(number_format[PhoneData::PATTERN])).captures
|
6
50
|
national_prefix_formatting_rule = number_format[PhoneData::NATIONAL_PREFIX_FORMATTING_RULE] \
|
7
51
|
|| country_data[PhoneData::NATIONAL_PREFIX_FORMATTING_RULE]
|
8
52
|
|
9
|
-
|
10
|
-
formatted_string = format(format_string, *captures)
|
53
|
+
formatted_string = format(ruby_format_string(number_format[PhoneData::FORMAT]), *captures)
|
11
54
|
captures.delete(PhoneData::MOBILE_TOKEN_COUNTRIES[country])
|
12
55
|
|
13
56
|
if national_prefix_formatting_rule
|
@@ -17,30 +60,31 @@ module TelephoneNumber
|
|
17
60
|
formatted_string.sub!(captures[0], national_prefix_string)
|
18
61
|
end
|
19
62
|
|
20
|
-
formatted ? formatted_string : sanitize(formatted_string)
|
63
|
+
formatted ? formatted_string : Parser.sanitize(formatted_string)
|
21
64
|
end
|
22
65
|
|
23
66
|
def build_e164_number(formatted: true)
|
67
|
+
return normalized_or_default if !country_data || normalized_number.empty?
|
24
68
|
formatted_string = "+#{country_data[PhoneData::COUNTRY_CODE]}#{normalized_number}"
|
25
|
-
formatted ? formatted_string : sanitize(formatted_string)
|
69
|
+
formatted ? formatted_string : Parser.sanitize(formatted_string)
|
26
70
|
end
|
27
71
|
|
28
72
|
def build_international_number(formatted: true)
|
29
73
|
return normalized_or_default if !valid? || number_format.nil?
|
30
74
|
captures = normalized_number.match(Regexp.new(number_format[PhoneData::PATTERN])).captures
|
31
75
|
key = number_format.fetch(PhoneData::INTL_FORMAT, 'NA') != 'NA' ? PhoneData::INTL_FORMAT : PhoneData::FORMAT
|
32
|
-
|
33
|
-
formatted_string
|
34
|
-
formatted ? formatted_string : sanitize(formatted_string)
|
76
|
+
formatted_string = "+#{country_data[PhoneData::COUNTRY_CODE]} #{format(ruby_format_string(number_format[key]), *captures)}"
|
77
|
+
formatted ? formatted_string : Parser.sanitize(formatted_string)
|
35
78
|
end
|
36
79
|
|
37
|
-
|
80
|
+
def ruby_format_string(format_string)
|
81
|
+
format_string.gsub(/(\$\d)/) { |cap| "%#{cap.reverse}s" }
|
82
|
+
end
|
38
83
|
|
39
84
|
def normalized_or_default
|
40
|
-
return normalized_number
|
85
|
+
return normalized_number unless TelephoneNumber.default_format_string && TelephoneNumber.default_format_pattern
|
41
86
|
captures = normalized_number.match(TelephoneNumber.default_format_pattern).captures
|
42
|
-
|
43
|
-
format(format_string, *captures)
|
87
|
+
format(ruby_format_string(TelephoneNumber.default_format_string), *captures)
|
44
88
|
end
|
45
89
|
|
46
90
|
def extract_number_format
|
@@ -1,59 +1,16 @@
|
|
1
1
|
module TelephoneNumber
|
2
2
|
class Number
|
3
|
-
|
4
|
-
include TelephoneNumber::Formatter
|
3
|
+
extend Forwardable
|
5
4
|
|
6
|
-
attr_reader :
|
5
|
+
attr_reader :phone_data, :parser, :formatter
|
7
6
|
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
@original_number = sanitize(number).freeze
|
12
|
-
@country = country.upcase.to_sym
|
13
|
-
@country_data = PhoneData.phone_data[@country]
|
14
|
-
|
15
|
-
# normalized_number is basically a "best effort" at national number without
|
16
|
-
# any formatting. This is what we will use to derive formats, validations and
|
17
|
-
# basically anything else that uses google data
|
18
|
-
@normalized_number = build_normalized_number
|
19
|
-
end
|
20
|
-
|
21
|
-
def valid_types
|
22
|
-
@valid_types ||= validate
|
23
|
-
end
|
24
|
-
|
25
|
-
def valid?(keys = [])
|
26
|
-
keys.empty? ? !valid_types.empty? : !(valid_types & keys.map(&:to_sym)).empty?
|
27
|
-
end
|
7
|
+
delegate [:valid?, :valid_types, :normalized_number] => :parser
|
8
|
+
delegate [:national_number, :e164_number, :international_number] => :formatter
|
28
9
|
|
29
|
-
def
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
@unformatted_national_number ||= build_national_number(formatted: false)
|
34
|
-
end
|
35
|
-
end
|
36
|
-
|
37
|
-
def e164_number(formatted: true)
|
38
|
-
if formatted
|
39
|
-
@formatted_e164_number ||= build_e164_number
|
40
|
-
else
|
41
|
-
@e164_number ||= build_e164_number(formatted: false)
|
42
|
-
end
|
43
|
-
end
|
44
|
-
|
45
|
-
def international_number(formatted: true)
|
46
|
-
if formatted
|
47
|
-
@formatted_international_number ||= build_international_number
|
48
|
-
else
|
49
|
-
@international_number ||= build_international_number(formatted: false)
|
50
|
-
end
|
51
|
-
end
|
52
|
-
|
53
|
-
private
|
54
|
-
|
55
|
-
def number_format
|
56
|
-
@number_format ||= extract_number_format
|
10
|
+
def initialize(number, country)
|
11
|
+
@phone_data = PhoneData.new(country)
|
12
|
+
@parser = Parser.new(number, @phone_data)
|
13
|
+
@formatter = Formatter.new(self, @phone_data)
|
57
14
|
end
|
58
15
|
end
|
59
16
|
end
|
@@ -1,11 +1,39 @@
|
|
1
1
|
module TelephoneNumber
|
2
|
-
|
2
|
+
class Parser
|
3
3
|
KEYS_TO_SKIP = [PhoneData::GENERAL, PhoneData::AREA_CODE_OPTIONAL]
|
4
|
+
extend Forwardable
|
4
5
|
|
5
|
-
|
6
|
-
|
6
|
+
delegate [:country_data, :country] => :phone_data
|
7
|
+
attr_reader :sanitized_number, :original_number, :normalized_number, :phone_data
|
8
|
+
|
9
|
+
def initialize(original_number, phone_data)
|
10
|
+
@sanitized_number = self.class.sanitize(original_number)
|
11
|
+
@phone_data = phone_data
|
12
|
+
@original_number = original_number
|
13
|
+
@normalized_number = build_normalized_number
|
14
|
+
end
|
15
|
+
|
16
|
+
def valid_types
|
17
|
+
@valid_types ||= validate
|
18
|
+
end
|
19
|
+
|
20
|
+
def valid?(keys = [])
|
21
|
+
keys.empty? ? !valid_types.empty? : !(valid_types & keys.map(&:to_sym)).empty?
|
22
|
+
end
|
23
|
+
|
24
|
+
def self.detect_country(number)
|
25
|
+
sanitized_number = sanitize(number)
|
26
|
+
detected_country = PhoneData.phone_data.detect(->{[]}) do |two_letter, value|
|
27
|
+
sanitized_number =~ Regexp.new("^#{value[:country_code]}") && new(sanitized_number, PhoneData.new(two_letter)).valid?
|
28
|
+
end.first
|
7
29
|
end
|
8
30
|
|
31
|
+
def self.sanitize(input_number)
|
32
|
+
return input_number.to_s.gsub(/\D/, '')
|
33
|
+
end
|
34
|
+
|
35
|
+
private
|
36
|
+
|
9
37
|
# returns an array of valid types for the normalized number
|
10
38
|
# if array is empty, we can assume that the number is invalid
|
11
39
|
def validate
|
@@ -17,10 +45,11 @@ module TelephoneNumber
|
|
17
45
|
end.compact
|
18
46
|
end
|
19
47
|
|
20
|
-
|
21
|
-
|
48
|
+
# normalized_number is basically a "best effort" at national number without
|
49
|
+
# any formatting. This is what we will use to derive formats, validations and
|
50
|
+
# basically anything else that uses google data
|
22
51
|
def build_normalized_number
|
23
|
-
return
|
52
|
+
return sanitized_number unless country_data
|
24
53
|
country_code = country_data[PhoneData::COUNTRY_CODE]
|
25
54
|
|
26
55
|
number_with_correct_prefix = parse_prefix
|
@@ -30,20 +59,20 @@ module TelephoneNumber
|
|
30
59
|
reg_string << "(#{country_data[PhoneData::VALIDATIONS][PhoneData::GENERAL][PhoneData::VALID_PATTERN]})$"
|
31
60
|
|
32
61
|
match_result = number_with_correct_prefix.match(Regexp.new(reg_string))
|
33
|
-
return
|
62
|
+
return sanitized_number unless match_result
|
34
63
|
prefix_results = [match_result[1], match_result[2]]
|
35
64
|
number_with_correct_prefix.sub(prefix_results.join, '')
|
36
65
|
end
|
37
66
|
|
38
67
|
def parse_prefix
|
39
|
-
return
|
40
|
-
duped =
|
68
|
+
return sanitized_number unless country_data[:national_prefix_for_parsing]
|
69
|
+
duped = sanitized_number.dup
|
41
70
|
match_object = duped.match(Regexp.new(country_data[:national_prefix_for_parsing]))
|
42
71
|
|
43
72
|
# we need to do the "start_with?" here because we need to make sure it's not finding
|
44
73
|
# something in the middle of the number. However, we can't modify the regex to do this
|
45
74
|
# for us because it will offset the match groups that are referenced in the transform rules
|
46
|
-
return
|
75
|
+
return sanitized_number unless match_object && duped.start_with?(match_object[0])
|
47
76
|
if country_data[:national_prefix_transform_rule]
|
48
77
|
transform_national_prefix(duped, match_object)
|
49
78
|
else
|
@@ -1,5 +1,5 @@
|
|
1
1
|
module TelephoneNumber
|
2
|
-
|
2
|
+
class PhoneData
|
3
3
|
AREA_CODE_OPTIONAL = :area_code_optional
|
4
4
|
COUNTRY_CODE = :country_code
|
5
5
|
FIXED_LINE = :fixed_line
|
@@ -28,6 +28,8 @@ module TelephoneNumber
|
|
28
28
|
VOICEMAIL = :voicemail
|
29
29
|
VOIP = :voip
|
30
30
|
|
31
|
+
attr_reader :country_data, :country
|
32
|
+
|
31
33
|
def self.phone_data
|
32
34
|
@@phone_data ||= load_data
|
33
35
|
end
|
@@ -39,6 +41,11 @@ module TelephoneNumber
|
|
39
41
|
override_data = Marshal.load(File.binread(TelephoneNumber.override_file)) if TelephoneNumber.override_file
|
40
42
|
return main_data.deep_deep_merge!(override_data)
|
41
43
|
end
|
44
|
+
|
45
|
+
def initialize(country)
|
46
|
+
@country = country.to_s.upcase.to_sym
|
47
|
+
@country_data = self.class.phone_data[@country]
|
48
|
+
end
|
42
49
|
end
|
43
50
|
end
|
44
51
|
|
data/lib/telephone_number.rb
CHANGED
@@ -1,5 +1,6 @@
|
|
1
1
|
require 'telephone_number/version'
|
2
2
|
require 'utilities/hash'
|
3
|
+
require 'forwardable'
|
3
4
|
|
4
5
|
module TelephoneNumber
|
5
6
|
autoload :DataImporter, 'telephone_number/data_importer'
|
@@ -44,11 +45,4 @@ module TelephoneNumber
|
|
44
45
|
def self.default_format_pattern
|
45
46
|
@@default_format_pattern
|
46
47
|
end
|
47
|
-
|
48
|
-
|
49
|
-
# generates binary file from xml that user gives us
|
50
|
-
def self.generate_override_file(file)
|
51
|
-
DataImporter.new(file).import!(override: true)
|
52
|
-
end
|
53
|
-
|
54
48
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: telephone_number
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 1.0.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- MOBI Wireless Management
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2017-
|
11
|
+
date: 2017-04-19 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bundler
|