phonejack 1.2.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 +7 -0
- data/.gitignore +15 -0
- data/.hound.yml +4 -0
- data/.rubocop.default.yml +1632 -0
- data/.rubocop.yml +9 -0
- data/.travis.yml +12 -0
- data/CODE_OF_CONDUCT.md +74 -0
- data/Gemfile +6 -0
- data/LICENSE +21 -0
- data/README.md +191 -0
- data/Rakefile +26 -0
- data/bin/console +12 -0
- data/bin/setup +8 -0
- data/data/telephone_number_data_file.dat +0 -0
- data/data/telephone_number_data_file.xml +26498 -0
- data/lib/active_model/phonejack_validator.rb +9 -0
- data/lib/phonejack.rb +17 -0
- data/lib/phonejack/class_methods.rb +40 -0
- data/lib/phonejack/country.rb +67 -0
- data/lib/phonejack/data_importer.rb +96 -0
- data/lib/phonejack/formatter.rb +76 -0
- data/lib/phonejack/number.rb +17 -0
- data/lib/phonejack/number_format.rb +14 -0
- data/lib/phonejack/number_validation.rb +10 -0
- data/lib/phonejack/parser.rb +68 -0
- data/lib/phonejack/test_data_generator.rb +116 -0
- data/lib/phonejack/version.rb +3 -0
- data/lib/utilities/hash.rb +47 -0
- data/phonejack.gemspec +31 -0
- metadata +199 -0
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
class PhonejackValidator < ActiveModel::EachValidator
|
|
2
|
+
def validate_each(record, attribute, value)
|
|
3
|
+
country = options[:country].call(record) if options.key?(:country)
|
|
4
|
+
valid_types = options.fetch(:types, [])
|
|
5
|
+
args = [value, country, valid_types]
|
|
6
|
+
|
|
7
|
+
record.errors.add(attribute, :invalid) if Phonejack.invalid?(*args)
|
|
8
|
+
end
|
|
9
|
+
end
|
data/lib/phonejack.rb
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
require 'phonejack/version'
|
|
2
|
+
require 'utilities/hash'
|
|
3
|
+
require 'active_model/phonejack_validator' if defined?(ActiveModel)
|
|
4
|
+
|
|
5
|
+
module Phonejack
|
|
6
|
+
autoload :DataImporter, 'phonejack/data_importer'
|
|
7
|
+
autoload :TestDataGenerator, 'phonejack/test_data_generator'
|
|
8
|
+
autoload :Parser, 'phonejack/parser'
|
|
9
|
+
autoload :Number, 'phonejack/number'
|
|
10
|
+
autoload :Formatter, 'phonejack/formatter'
|
|
11
|
+
autoload :Country, 'phonejack/country'
|
|
12
|
+
autoload :NumberFormat, 'phonejack/number_format'
|
|
13
|
+
autoload :NumberValidation, 'phonejack/number_validation'
|
|
14
|
+
autoload :ClassMethods, 'phonejack/class_methods'
|
|
15
|
+
|
|
16
|
+
extend ClassMethods
|
|
17
|
+
end
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
module Phonejack
|
|
2
|
+
module ClassMethods
|
|
3
|
+
attr_accessor :override_file, :default_format_string
|
|
4
|
+
attr_reader :default_format_pattern
|
|
5
|
+
|
|
6
|
+
def default_format_pattern=(format_string)
|
|
7
|
+
@default_format_pattern = Regexp.new(format_string)
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
def parse(number, country = detect_country(number))
|
|
11
|
+
Phonejack::Number.new(sanitize(number), country)
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def valid?(number, country = detect_country(number), keys = [])
|
|
15
|
+
parse(number, country).valid?(keys)
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def invalid?(*args)
|
|
19
|
+
!valid?(*args)
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def sanitize(input_number)
|
|
23
|
+
input_number.to_s.gsub(/\D/, '')
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def detect_country(number)
|
|
27
|
+
sanitized_number = sanitize(number)
|
|
28
|
+
detected_country = Country.all_countries.detect do |country|
|
|
29
|
+
sanitized_number.start_with?(country.country_code) && valid?(sanitized_number, country.country_id)
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
detected_country.country_id.to_sym if detected_country
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
# generates binary file from xml that user gives us
|
|
36
|
+
def generate_override_file(file)
|
|
37
|
+
DataImporter.new(file, override: true).import!
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
end
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
module Phonejack
|
|
2
|
+
class Country
|
|
3
|
+
attr_reader :country_code, :national_prefix, :national_prefix_formatting_rule,
|
|
4
|
+
:national_prefix_for_parsing, :national_prefix_transform_rule, :international_prefix,
|
|
5
|
+
:formats, :validations, :mobile_token, :country_id, :general_validation, :main_country_for_code
|
|
6
|
+
|
|
7
|
+
MOBILE_TOKEN_COUNTRIES = { AR: '9' }
|
|
8
|
+
|
|
9
|
+
def initialize(data_hash)
|
|
10
|
+
@country_code = data_hash[:country_code]
|
|
11
|
+
@country_id = data_hash[:id]
|
|
12
|
+
@formats = data_hash.fetch(:formats, []).map { |format| NumberFormat.new(format) }
|
|
13
|
+
@general_validation = NumberValidation.new(:general_desc, data_hash[:validations][:general_desc]) if data_hash.fetch(:validations, {})[:general_desc]
|
|
14
|
+
@international_prefix = Regexp.new(data_hash[:international_prefix]) if data_hash[:international_prefix]
|
|
15
|
+
@main_country_for_code = data_hash[:main_country_for_code] == 'true'
|
|
16
|
+
@mobile_token = MOBILE_TOKEN_COUNTRIES[@country_id.to_sym]
|
|
17
|
+
@national_prefix = data_hash[:national_prefix]
|
|
18
|
+
@national_prefix_formatting_rule = data_hash[:national_prefix_formatting_rule]
|
|
19
|
+
@national_prefix_for_parsing = Regexp.new(data_hash[:national_prefix_for_parsing]) if data_hash[:national_prefix_for_parsing]
|
|
20
|
+
@national_prefix_transform_rule = data_hash[:national_prefix_transform_rule]
|
|
21
|
+
@validations = data_hash.fetch(:validations, {})
|
|
22
|
+
.except(:general_desc, :area_code_optional)
|
|
23
|
+
.map { |name, data| NumberValidation.new(name, data) }
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def detect_format(number)
|
|
27
|
+
native_format = formats.detect do |format|
|
|
28
|
+
number =~ Regexp.new("^(#{format.leading_digits})") && number =~ Regexp.new("^(#{format.pattern})$")
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
return native_format if native_format || main_country_for_code
|
|
32
|
+
parent_country.detect_format(number) if parent_country
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def parent_country
|
|
36
|
+
return if main_country_for_code
|
|
37
|
+
Country.all_countries.detect do |country|
|
|
38
|
+
country.country_code == self.country_code && country.main_country_for_code
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def full_general_pattern
|
|
43
|
+
%r{^(#{country_code})?(#{national_prefix})?(?<national_num>#{general_validation.pattern})$}
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def self.phone_data
|
|
47
|
+
@phone_data ||= load_data
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def self.find(country_id)
|
|
51
|
+
data = phone_data[country_id.to_s.upcase.to_sym]
|
|
52
|
+
new(data) if data
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
def self.load_data
|
|
56
|
+
data_file = "#{File.dirname(__FILE__)}/../../data/telephone_number_data_file.dat"
|
|
57
|
+
main_data = Marshal.load(File.binread(data_file))
|
|
58
|
+
override_data = {}
|
|
59
|
+
override_data = Marshal.load(File.binread(Phonejack.override_file)) if Phonejack.override_file
|
|
60
|
+
return main_data.deep_deep_merge!(override_data)
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
def self.all_countries
|
|
64
|
+
@all_countries ||= phone_data.values.map {|data| new(data)}
|
|
65
|
+
end
|
|
66
|
+
end
|
|
67
|
+
end
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
module Phonejack
|
|
2
|
+
require 'nokogiri'
|
|
3
|
+
class DataImporter
|
|
4
|
+
attr_reader :data, :file, :override
|
|
5
|
+
|
|
6
|
+
def initialize(file_name, override: false)
|
|
7
|
+
@data = {}
|
|
8
|
+
@file = File.open(file_name) { |f| Nokogiri::XML(f) }
|
|
9
|
+
@override = override
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def import!
|
|
13
|
+
parse_main_data
|
|
14
|
+
save_data_file
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def parse_main_data
|
|
18
|
+
file.css('territories territory').each do |territory|
|
|
19
|
+
country_code = territory.attributes['id'].value.to_sym
|
|
20
|
+
@data[country_code] ||= {}
|
|
21
|
+
|
|
22
|
+
load_base_attributes(@data[country_code], territory)
|
|
23
|
+
load_references(@data[country_code], territory)
|
|
24
|
+
load_validations(@data[country_code], territory)
|
|
25
|
+
load_formats(@data[country_code], territory)
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
private
|
|
30
|
+
|
|
31
|
+
def load_formats(country_data, territory)
|
|
32
|
+
formats_arr = territory.css('availableFormats numberFormat').map do |format|
|
|
33
|
+
{}.tap do |fhash|
|
|
34
|
+
format.attributes.values.each do |attr|
|
|
35
|
+
key = underscore(attr.name).to_sym
|
|
36
|
+
fhash[key] = if key == :national_prefix_formatting_rule
|
|
37
|
+
attr.value
|
|
38
|
+
else
|
|
39
|
+
attr.value.delete("\n ")
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
format.elements.each do |child|
|
|
43
|
+
key = underscore(child.name).to_sym
|
|
44
|
+
fhash[key] = [:format, :intl_format].include?(key) ? child.text : child.text.delete("\n ")
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
return if override && formats_arr.empty?
|
|
50
|
+
country_data[:formats] = formats_arr
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
def load_validations(country_data, territory)
|
|
54
|
+
country_data[:validations] = {}
|
|
55
|
+
territory.elements.each do |element|
|
|
56
|
+
next if element.name == 'references' || element.name == 'availableFormats'
|
|
57
|
+
country_data[:validations][underscore(element.name).to_sym] = {}.tap do |validation_hash|
|
|
58
|
+
element.elements.each { |child| validation_hash[underscore(child.name).to_sym] = child.text.delete("\n ") }
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
country_data.delete(:validations) if country_data[:validations].empty? && override
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
def load_base_attributes(country_data, territory)
|
|
65
|
+
territory.attributes.each do |key, value_object|
|
|
66
|
+
underscored_key = underscore(key).to_sym
|
|
67
|
+
country_data[underscored_key] = if underscored_key == :national_prefix_for_parsing
|
|
68
|
+
value_object.value.delete("\n ")
|
|
69
|
+
else
|
|
70
|
+
value_object.value
|
|
71
|
+
end
|
|
72
|
+
end
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
def load_references(country_data, territory)
|
|
76
|
+
ref_arr = territory.css('references sourceUrl').map(&:text)
|
|
77
|
+
return if override && ref_arr.empty?
|
|
78
|
+
country_data[:references] = ref_arr
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
def underscore(camel_cased_word)
|
|
82
|
+
return camel_cased_word unless camel_cased_word =~ /[A-Z-]|::/
|
|
83
|
+
word = camel_cased_word.to_s.gsub(/::/, '/')
|
|
84
|
+
word.gsub!(/([A-Z\d]+)([A-Z][a-z])/, '\1_\2')
|
|
85
|
+
word.gsub!(/([a-z\d])([A-Z])/, '\1_\2')
|
|
86
|
+
word.tr!('-', '_')
|
|
87
|
+
word.downcase!
|
|
88
|
+
word
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
def save_data_file
|
|
92
|
+
data_file = override ? 'telephone_number_data_override_file.dat' : "#{File.dirname(__FILE__)}/../../data/telephone_number_data_file.dat"
|
|
93
|
+
File.open(data_file, 'wb+') { |f| Marshal.dump(@data, f) }
|
|
94
|
+
end
|
|
95
|
+
end
|
|
96
|
+
end
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
module Phonejack
|
|
2
|
+
class Formatter
|
|
3
|
+
|
|
4
|
+
attr_reader :normalized_number, :country, :valid, :original_number
|
|
5
|
+
|
|
6
|
+
def initialize(number_obj)
|
|
7
|
+
@normalized_number = number_obj.normalized_number
|
|
8
|
+
@country = number_obj.country
|
|
9
|
+
@valid = number_obj.valid?
|
|
10
|
+
@original_number = number_obj.original_number
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def national_number(formatted: true)
|
|
14
|
+
return original_or_default if !valid? || !number_format
|
|
15
|
+
build_national_number(formatted: formatted)
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def e164_number(formatted: true)
|
|
19
|
+
return original_or_default if !valid?
|
|
20
|
+
build_e164_number(formatted: formatted)
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def international_number(formatted: true)
|
|
24
|
+
return original_or_default if !valid? || !number_format
|
|
25
|
+
build_international_number(formatted: formatted)
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
alias_method :valid?, :valid
|
|
29
|
+
|
|
30
|
+
private
|
|
31
|
+
|
|
32
|
+
def number_format
|
|
33
|
+
@number_format ||= country.detect_format(normalized_number)
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def build_national_number(formatted: true)
|
|
37
|
+
captures = normalized_number.match(number_format.pattern).captures
|
|
38
|
+
national_prefix_formatting_rule = number_format.national_prefix_formatting_rule || country.national_prefix_formatting_rule
|
|
39
|
+
|
|
40
|
+
formatted_string = format(ruby_format_string(number_format.format), *captures)
|
|
41
|
+
captures.delete(country.mobile_token)
|
|
42
|
+
|
|
43
|
+
if national_prefix_formatting_rule
|
|
44
|
+
national_prefix_string = national_prefix_formatting_rule.dup
|
|
45
|
+
national_prefix_string.gsub!(/\$NP/, country.national_prefix)
|
|
46
|
+
national_prefix_string.gsub!(/\$FG/, captures[0])
|
|
47
|
+
formatted_string.sub!(captures[0], national_prefix_string)
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
formatted ? formatted_string : Phonejack.sanitize(formatted_string)
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
def build_e164_number(formatted: true)
|
|
54
|
+
formatted_string = "+#{country.country_code}#{normalized_number}"
|
|
55
|
+
formatted ? formatted_string : Phonejack.sanitize(formatted_string)
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
def build_international_number(formatted: true)
|
|
59
|
+
return original_or_default if !valid? || number_format.nil?
|
|
60
|
+
captures = normalized_number.match(number_format.pattern).captures
|
|
61
|
+
key = number_format.intl_format || number_format.format
|
|
62
|
+
formatted_string = "+#{country.country_code} #{format(ruby_format_string(key), *captures)}"
|
|
63
|
+
formatted ? formatted_string : Phonejack.sanitize(formatted_string)
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
def ruby_format_string(format_string)
|
|
67
|
+
format_string.gsub(/(\$\d)/) { |cap| "%#{cap.reverse}s" }
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
def original_or_default
|
|
71
|
+
return original_number unless Phonejack.default_format_string && Phonejack.default_format_pattern
|
|
72
|
+
captures = original_number.match(Phonejack.default_format_pattern).captures
|
|
73
|
+
format(ruby_format_string(Phonejack.default_format_string), *captures)
|
|
74
|
+
end
|
|
75
|
+
end
|
|
76
|
+
end
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
module Phonejack
|
|
2
|
+
class Number
|
|
3
|
+
extend Forwardable
|
|
4
|
+
|
|
5
|
+
attr_reader :country, :parser, :formatter, :original_number
|
|
6
|
+
|
|
7
|
+
delegate [:valid?, :valid_types, :normalized_number] => :parser
|
|
8
|
+
delegate [:national_number, :e164_number, :international_number] => :formatter
|
|
9
|
+
|
|
10
|
+
def initialize(number, country)
|
|
11
|
+
@original_number = number
|
|
12
|
+
@country = Country.find(country)
|
|
13
|
+
@parser = Parser.new(self)
|
|
14
|
+
@formatter = Formatter.new(self)
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
end
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
module Phonejack
|
|
2
|
+
class NumberFormat
|
|
3
|
+
|
|
4
|
+
attr_reader :pattern, :leading_digits, :format, :national_prefix_formatting_rule, :intl_format
|
|
5
|
+
|
|
6
|
+
def initialize(data_hash)
|
|
7
|
+
@pattern = Regexp.new(data_hash[:pattern]) if data_hash[:pattern]
|
|
8
|
+
@leading_digits = Regexp.new(data_hash[:leading_digits]) if data_hash[:leading_digits]
|
|
9
|
+
@format = data_hash[:format]
|
|
10
|
+
@intl_format = data_hash[:intl_format]
|
|
11
|
+
@national_prefix_formatting_rule = data_hash[:national_prefix_formatting_rule]
|
|
12
|
+
end
|
|
13
|
+
end
|
|
14
|
+
end
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
module Phonejack
|
|
2
|
+
class Parser
|
|
3
|
+
attr_reader :original_number, :normalized_number, :country
|
|
4
|
+
|
|
5
|
+
def initialize(number_obj)
|
|
6
|
+
@original_number = number_obj.original_number
|
|
7
|
+
@country = number_obj.country
|
|
8
|
+
@normalized_number = build_normalized_number if @country
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def valid_types
|
|
12
|
+
@valid_types ||= validate
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def valid?(keys = [])
|
|
16
|
+
keys.empty? ? !valid_types.empty? : !(valid_types & keys.map(&:to_sym)).empty?
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
private
|
|
20
|
+
|
|
21
|
+
# normalized_number is basically a "best effort" at national number without
|
|
22
|
+
# any formatting. This is what we will use to derive formats, validations and
|
|
23
|
+
# basically anything else that uses google data
|
|
24
|
+
def build_normalized_number
|
|
25
|
+
match_result = parse_prefix.match(country.full_general_pattern)
|
|
26
|
+
match_result ? match_result[:national_num] : original_number
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
# returns an array of valid types for the normalized number
|
|
30
|
+
# if array is empty, we can assume that the number is invalid
|
|
31
|
+
def validate
|
|
32
|
+
return [] unless country
|
|
33
|
+
country.validations.select do |validation|
|
|
34
|
+
normalized_number =~ Regexp.new("^(#{validation.pattern})$")
|
|
35
|
+
end.map(&:name)
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def parse_prefix
|
|
39
|
+
return original_number unless country.national_prefix_for_parsing
|
|
40
|
+
duped = original_number.dup
|
|
41
|
+
match_object = duped.match(country.national_prefix_for_parsing)
|
|
42
|
+
|
|
43
|
+
# we need to do the "start_with?" here because we need to make sure it's not finding
|
|
44
|
+
# something in the middle of the number. However, we can't modify the regex to do this
|
|
45
|
+
# for us because it will offset the match groups that are referenced in the transform rules
|
|
46
|
+
return original_number unless match_object && duped.start_with?(match_object[0])
|
|
47
|
+
if country.national_prefix_transform_rule
|
|
48
|
+
transform_national_prefix(duped, match_object)
|
|
49
|
+
else
|
|
50
|
+
duped.sub!(match_object[0], '')
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
def transform_national_prefix(duped, match_object)
|
|
55
|
+
if country.mobile_token && match_object.captures.any?
|
|
56
|
+
format(build_format_string, duped.sub!(match_object[0], match_object[1]))
|
|
57
|
+
elsif match_object.captures.none?
|
|
58
|
+
duped.sub!(match_object[0], '')
|
|
59
|
+
else
|
|
60
|
+
format(build_format_string, *match_object.captures)
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
def build_format_string
|
|
65
|
+
country.national_prefix_transform_rule.gsub(/(\$\d)/) { |cap| "%#{cap.reverse}s" }
|
|
66
|
+
end
|
|
67
|
+
end
|
|
68
|
+
end
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
module Phonejack
|
|
2
|
+
require 'nokogiri'
|
|
3
|
+
require 'httparty'
|
|
4
|
+
require 'yaml'
|
|
5
|
+
|
|
6
|
+
class TestDataGenerator
|
|
7
|
+
URL = "https://libphonenumber.appspot.com/phonenumberparser?number=%s&country=%s".freeze
|
|
8
|
+
COUNTRIES = {
|
|
9
|
+
AE: %w(971529933171 971553006144 971551000291),
|
|
10
|
+
AR: %w(111512345678 380151234567 299154104587 01112345678),
|
|
11
|
+
AU: %w(0467703037),
|
|
12
|
+
BE: %w(32498485960 32477702206 32474095692),
|
|
13
|
+
BO: %w(59178500348 59178006138 59178006139),
|
|
14
|
+
BR: %w(011992339376 1123456789 11961234567),
|
|
15
|
+
BY: %w(80152450911 294911911 152450911),
|
|
16
|
+
CA: %w(16135550119 16135550171 16135550112 16135550194
|
|
17
|
+
16135550122 16135550131 15146708700 14169158200),
|
|
18
|
+
CH: %w(41794173875 41795061129 41795820985),
|
|
19
|
+
CL: %w(961234567 221234567),
|
|
20
|
+
CN: %w(15694876068 13910503766 15845989469 05523245954 04717158875 03748086894),
|
|
21
|
+
CO: %w(3211234567 12345678),
|
|
22
|
+
CR: %w(22123456 83123456),
|
|
23
|
+
DE: %w(15222503070),
|
|
24
|
+
DK: %w(4524453744 4551622172 4542428484),
|
|
25
|
+
EC: %w(593992441504 593984657155 593984015053),
|
|
26
|
+
EE: %w(37253629280 37253682997 37254004517),
|
|
27
|
+
ES: %w(34606217800 34667751353 34646570628),
|
|
28
|
+
FR: %w(0607114556),
|
|
29
|
+
GB: %w(448444156790 442079308181 442076139800 442076361000
|
|
30
|
+
07780991912 442076299400 442072227888),
|
|
31
|
+
HK: %w(64636251),
|
|
32
|
+
HU: %w(36709311285 36709311279 36709311206),
|
|
33
|
+
IE: %w(0863634875),
|
|
34
|
+
IN: %w(915622231515 912942433300 912912510101 911126779191
|
|
35
|
+
912224818000 917462223999 912266653366 912266325757
|
|
36
|
+
914066298585 911242451234 911166566162 911123890606
|
|
37
|
+
911123583754 5622231515 2942433300 2912510101
|
|
38
|
+
1126779191 2224818000 7462223999 2266653366
|
|
39
|
+
2266325757 4066298585 1242451234 1166566162
|
|
40
|
+
1123890606 1123583754 09176642499),
|
|
41
|
+
IT: %w(393478258998 393440161350),
|
|
42
|
+
JP: %w(312345678 9012345678),
|
|
43
|
+
KR: %w(821036424812 821053812833 821085894820),
|
|
44
|
+
MX: %w(4423593227 14423593227),
|
|
45
|
+
NL: %w(31610958780 31610000852 31611427604),
|
|
46
|
+
NO: %w(4792272668 4797065876 4792466013),
|
|
47
|
+
NZ: %w(64212715077 6421577017 64212862111),
|
|
48
|
+
PE: %w(51994156035 51987527881 51972737259),
|
|
49
|
+
PH: %w(639285588185 639285588262 639285548190),
|
|
50
|
+
PL: %w(48665666003 48885882321 48885889958),
|
|
51
|
+
QA: %w(97470482288 97474798678),
|
|
52
|
+
RO: %w(40724242563 40727798526 40727735377),
|
|
53
|
+
SA: %w(966503891468 966501543349 966500939012),
|
|
54
|
+
SE: %w(46708922920 46723985268 46761001966),
|
|
55
|
+
SG: %w(96924755),
|
|
56
|
+
TR: %w(905497728782 905497728780 905497728781),
|
|
57
|
+
TT: %w(18687804765 18687804843 18687804752),
|
|
58
|
+
TW: %w(886905627933 886905627901 886905627925),
|
|
59
|
+
US: %w(16502530000 14044879000 15123435283 13032450086 16175751300
|
|
60
|
+
3175083345 13128404100 12485934000 19497941600 14257395600 13103106000
|
|
61
|
+
16086699600 12125650000 14123456700 14157360000 12068761800
|
|
62
|
+
12023461100 3175082333),
|
|
63
|
+
VE: %w(584149993108 584248407260 584248271518),
|
|
64
|
+
ZA: %w(27826187617 27823014578 27828840632)
|
|
65
|
+
}.freeze
|
|
66
|
+
def self.import!
|
|
67
|
+
output_hash = {}
|
|
68
|
+
fetch_data(output_hash)
|
|
69
|
+
write_file(output_hash)
|
|
70
|
+
true
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
def self.fetch_data(output_hash)
|
|
74
|
+
COUNTRIES.each do |key, value|
|
|
75
|
+
output_hash[key] = {}
|
|
76
|
+
|
|
77
|
+
value.each_with_index do |num, counter|
|
|
78
|
+
page = HTTParty.get(format(URL, num, key.to_s))
|
|
79
|
+
parsed_page = Nokogiri::HTML.parse(page)
|
|
80
|
+
body = parsed_page.elements.first.elements.css('body').first
|
|
81
|
+
parsed_data = parse_remote_data(counter, body.elements.css('table')[2])
|
|
82
|
+
output_hash[key].merge!(parsed_data)
|
|
83
|
+
end
|
|
84
|
+
end
|
|
85
|
+
return output_hash
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
def self.parse_remote_data(counter, table)
|
|
89
|
+
output = { counter.to_s => {} }
|
|
90
|
+
table.elements.each do |row|
|
|
91
|
+
next if row.elements.one?
|
|
92
|
+
key = case row.elements.css('th').text
|
|
93
|
+
when 'E164 format'
|
|
94
|
+
:e164_formatted
|
|
95
|
+
when 'National format'
|
|
96
|
+
:national_formatted
|
|
97
|
+
when 'International format'
|
|
98
|
+
:international_formatted
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
output[counter.to_s][key] = row.elements.css('td').text if key
|
|
102
|
+
end
|
|
103
|
+
return output
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
def self.write_file(data)
|
|
107
|
+
File.open('test/valid_numbers.yml', 'w') do |file|
|
|
108
|
+
file.write "# This file is generated automatically by TestDataGenerator. \n" \
|
|
109
|
+
"# Any changes made to this file will be overridden next time test data is generated. \n" \
|
|
110
|
+
"# Please edit TestDataGenerator if you need to add test cases. \n"
|
|
111
|
+
|
|
112
|
+
file.write data.to_yaml
|
|
113
|
+
end
|
|
114
|
+
end
|
|
115
|
+
end
|
|
116
|
+
end
|