telephone_number 0.0.0 → 0.1.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/.gitignore +15 -0
- data/.hound.yml +3 -0
- data/.ruby-version +1 -0
- data/.ruby.yml +1614 -0
- data/.travis.yml +7 -0
- data/CODE_OF_CONDUCT.md +74 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +21 -0
- data/README.md +81 -0
- data/Rakefile +19 -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 +27105 -0
- data/lib/telephone_number/class_methods.rb +11 -0
- data/lib/telephone_number/data_importer.rb +75 -0
- data/lib/telephone_number/number.rb +27 -0
- data/lib/telephone_number/parser.rb +39 -0
- data/lib/telephone_number/phone_data.rb +37 -0
- data/lib/telephone_number/version.rb +3 -0
- data/lib/telephone_number.rb +28 -0
- data/telephone_number.gemspec +30 -0
- data/telephone_number_data_file.xml +27105 -0
- metadata +133 -9
@@ -0,0 +1,75 @@
|
|
1
|
+
module TelephoneNumber
|
2
|
+
require 'nokogiri'
|
3
|
+
class DataImporter
|
4
|
+
attr_reader :data, :file
|
5
|
+
|
6
|
+
def initialize(file_name)
|
7
|
+
@data = {}
|
8
|
+
@file = File.open(file_name){ |f| Nokogiri::XML(f) }
|
9
|
+
end
|
10
|
+
|
11
|
+
def import!(override: false)
|
12
|
+
parse_main_data
|
13
|
+
save_data_file(override: override)
|
14
|
+
end
|
15
|
+
|
16
|
+
def parse_main_data
|
17
|
+
file.css("territories territory").each do |territory|
|
18
|
+
country_code = territory.attributes["id"].value.to_sym
|
19
|
+
@data[country_code] ||= {}
|
20
|
+
|
21
|
+
load_base_attributes(@data[country_code], territory)
|
22
|
+
load_references(@data[country_code], territory)
|
23
|
+
load_validations(@data[country_code], territory)
|
24
|
+
load_formats(@data[country_code], territory)
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
private
|
29
|
+
|
30
|
+
def load_formats(country_data, territory)
|
31
|
+
country_data[TelephoneNumber::PhoneData::FORMATS] = territory.css("availableFormats numberFormat").map do |format|
|
32
|
+
format_hash = {}.tap do |fhash|
|
33
|
+
format.attributes.values.each { |attr| fhash[attr.name.to_sym] = attr.value.delete("\n ") }
|
34
|
+
format.elements.each { |child| fhash[child.name.to_sym] = child.text.delete("\n ") }
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
def load_validations(country_data, territory)
|
40
|
+
country_data[TelephoneNumber::PhoneData::VALIDATIONS] = {}
|
41
|
+
territory.elements.each do |element|
|
42
|
+
next if element.name == "references" || element.name == "availableFormats"
|
43
|
+
country_data[TelephoneNumber::PhoneData::VALIDATIONS][underscore(element.name).to_sym] = {}.tap do |validation_hash|
|
44
|
+
element.elements.each{|child| validation_hash[underscore(child.name).to_sym] = child.text.delete("\n ")}
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
def load_base_attributes(country_data, territory)
|
50
|
+
territory.attributes.each do |key, value_object|
|
51
|
+
country_data[underscore(key).to_sym] = value_object.value
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
def load_references(country_data, territory)
|
56
|
+
country_data[:references] = territory.css("references sourceUrl").map(&:text)
|
57
|
+
end
|
58
|
+
|
59
|
+
def underscore(camel_cased_word)
|
60
|
+
return camel_cased_word unless camel_cased_word =~ /[A-Z-]|::/
|
61
|
+
word = camel_cased_word.to_s.gsub(/::/, '/')
|
62
|
+
word.gsub!(/([A-Z\d]+)([A-Z][a-z])/,'\1_\2')
|
63
|
+
word.gsub!(/([a-z\d])([A-Z])/,'\1_\2')
|
64
|
+
word.tr!("-", "_")
|
65
|
+
word.downcase!
|
66
|
+
word
|
67
|
+
end
|
68
|
+
|
69
|
+
def save_data_file(override: false)
|
70
|
+
data_file = override ? "telephone_number_data_override_file.dat" : "#{File.dirname(__FILE__)}/../../data/telephone_number_data_file.dat"
|
71
|
+
File.open(data_file, 'wb+') { |f| Marshal.dump(@data, f) }
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
@@ -0,0 +1,27 @@
|
|
1
|
+
module TelephoneNumber
|
2
|
+
class Number
|
3
|
+
include TelephoneNumber::Parser
|
4
|
+
|
5
|
+
attr_reader :original_number, :country, :e164_number, :national_number
|
6
|
+
|
7
|
+
def initialize(number, country)
|
8
|
+
return unless number && country
|
9
|
+
|
10
|
+
@original_number = sanitize(number)
|
11
|
+
@country = country.upcase.to_sym
|
12
|
+
@national_number, @e164_number = extract_number_types(@original_number, @country)
|
13
|
+
end
|
14
|
+
|
15
|
+
def valid_types
|
16
|
+
@valid_types ||= validate(e164_number, country)
|
17
|
+
end
|
18
|
+
|
19
|
+
def valid?(keys = [])
|
20
|
+
keys.empty? ? !valid_types.empty? : !(valid_types & keys.map(&:to_sym)).empty?
|
21
|
+
end
|
22
|
+
|
23
|
+
def format
|
24
|
+
@format ||= extract_format(national_number, country)
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,39 @@
|
|
1
|
+
module TelephoneNumber
|
2
|
+
module Parser
|
3
|
+
KEYS_TO_SKIP = [TelephoneNumber::PhoneData::GENERAL,
|
4
|
+
TelephoneNumber::PhoneData::AREA_CODE_OPTIONAL]
|
5
|
+
|
6
|
+
def sanitize(input_number)
|
7
|
+
return input_number.gsub(/[^0-9]/, "")
|
8
|
+
end
|
9
|
+
|
10
|
+
def extract_number_types(input_number, country)
|
11
|
+
country_data = TelephoneNumber::PhoneData.phone_data[country.to_sym]
|
12
|
+
|
13
|
+
return [input_number, nil] unless country_data
|
14
|
+
country_code = country_data[TelephoneNumber::PhoneData::COUNTRY_CODE]
|
15
|
+
|
16
|
+
reg_string = "^(#{country_code})?"
|
17
|
+
reg_string += "(#{country_data[TelephoneNumber::PhoneData::NATIONAL_PREFIX]})?"
|
18
|
+
reg_string += "(#{country_data[TelephoneNumber::PhoneData::VALIDATIONS]\
|
19
|
+
[TelephoneNumber::PhoneData::GENERAL]\
|
20
|
+
[TelephoneNumber::PhoneData::VALID_PATTERN]})$"
|
21
|
+
|
22
|
+
match_result = input_number.match(Regexp.new(reg_string)) || []
|
23
|
+
|
24
|
+
prefix_results = [match_result[1], match_result[2]]
|
25
|
+
without_prefix = input_number.sub(prefix_results.join, "")
|
26
|
+
[without_prefix, "#{country_code}#{without_prefix}"]
|
27
|
+
end
|
28
|
+
|
29
|
+
def validate(normalized_number, country)
|
30
|
+
country_data = TelephoneNumber::PhoneData.phone_data[country.to_sym]
|
31
|
+
return [] unless country_data
|
32
|
+
applicable_keys = country_data[TelephoneNumber::PhoneData::VALIDATIONS].reject{ |key, _value| KEYS_TO_SKIP.include?(key) }
|
33
|
+
applicable_keys.map do |phone_type, validations|
|
34
|
+
full = "^(#{country_data[TelephoneNumber::PhoneData::COUNTRY_CODE]})(#{validations[TelephoneNumber::PhoneData::VALID_PATTERN]})$"
|
35
|
+
phone_type if normalized_number =~ Regexp.new(full)
|
36
|
+
end.compact
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
module TelephoneNumber
|
2
|
+
module PhoneData
|
3
|
+
VALIDATIONS = :validations.freeze
|
4
|
+
FORMATS = :formats.freeze
|
5
|
+
GENERAL = :general_desc.freeze
|
6
|
+
PREMIUM_RATE = :premium_rate.freeze
|
7
|
+
TOLL_FREE = :toll_free.freeze
|
8
|
+
SHARED_COST = :shared_cost.freeze
|
9
|
+
VOIP = :voip.freeze
|
10
|
+
PERSONAL_NUMBER = :personal_number.freeze
|
11
|
+
UAN = :uan.freeze
|
12
|
+
VOICEMAIL = :voicemail.freeze
|
13
|
+
FIXED_LINE = :fixed_line.freeze
|
14
|
+
MOBILE = :mobile.freeze
|
15
|
+
NO_INTERNATIONAL_DIALING = :no_international_dialling.freeze
|
16
|
+
AREA_CODE_OPTIONAL = :area_code_optional.freeze
|
17
|
+
VALID_PATTERN = :national_number_pattern.freeze
|
18
|
+
POSSIBLE_PATTERN = :possible_number_pattern.freeze
|
19
|
+
NATIONAL_PREFIX = :national_prefix.freeze
|
20
|
+
COUNTRY_CODE = :country_code.freeze
|
21
|
+
LEADING_DIGITS = :leading_digits.freeze
|
22
|
+
INTERNATIONAL_PREFIX = :international_prefix.freeze
|
23
|
+
|
24
|
+
def self.phone_data
|
25
|
+
@@phone_data ||= load_data
|
26
|
+
end
|
27
|
+
|
28
|
+
def self.load_data
|
29
|
+
data_file = "#{File.dirname(__FILE__)}/../../data/telephone_number_data_file.dat"
|
30
|
+
main_data = Marshal.load(File.binread(data_file))
|
31
|
+
override_data = {}
|
32
|
+
override_data = Marshal.load(File.binread(TelephoneNumber.override_file)) if TelephoneNumber.override_file
|
33
|
+
return main_data.merge!(override_data)
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
@@ -0,0 +1,28 @@
|
|
1
|
+
require "telephone_number/version"
|
2
|
+
|
3
|
+
module TelephoneNumber
|
4
|
+
autoload :DataImporter, 'telephone_number/data_importer'
|
5
|
+
autoload :Parser, 'telephone_number/parser'
|
6
|
+
autoload :Number, 'telephone_number/number'
|
7
|
+
autoload :PhoneData, 'telephone_number/phone_data'
|
8
|
+
autoload :ClassMethods, 'telephone_number/class_methods'
|
9
|
+
|
10
|
+
extend ClassMethods
|
11
|
+
|
12
|
+
# allows users to override the default data
|
13
|
+
@@override_file = nil
|
14
|
+
|
15
|
+
def self.override_file=(file_name)
|
16
|
+
@@override_file = file_name
|
17
|
+
end
|
18
|
+
|
19
|
+
def self.override_file
|
20
|
+
@@override_file
|
21
|
+
end
|
22
|
+
|
23
|
+
# generates binary file from xml that user gives us
|
24
|
+
def self.generate_override_file(file)
|
25
|
+
DataImporter.new(file).import!(override: true)
|
26
|
+
end
|
27
|
+
|
28
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'telephone_number/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = "telephone_number"
|
8
|
+
spec.version = TelephoneNumber::VERSION
|
9
|
+
spec.author = 'MOBI Wireless Management'
|
10
|
+
spec.email = ["adam.fernung@mobiwm.com", "josh.wetzel@mobiwm.com"]
|
11
|
+
spec.summary = 'Phone number validation'
|
12
|
+
spec.homepage = "https://github.com/mobi/telephone_number"
|
13
|
+
spec.license = "MIT"
|
14
|
+
|
15
|
+
|
16
|
+
spec.files = `git ls-files -z`.split("\x0").reject do |f|
|
17
|
+
f.match(%r{^(test|spec|features)/})
|
18
|
+
end
|
19
|
+
spec.bindir = "exe"
|
20
|
+
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
21
|
+
spec.require_paths = ["lib"]
|
22
|
+
|
23
|
+
spec.add_development_dependency "bundler"
|
24
|
+
spec.add_development_dependency "rake"
|
25
|
+
spec.add_development_dependency "minitest"
|
26
|
+
spec.add_development_dependency "pry"
|
27
|
+
spec.add_development_dependency "pry-byebug"
|
28
|
+
spec.add_development_dependency "nokogiri"
|
29
|
+
spec.add_development_dependency "minitest-focus"
|
30
|
+
end
|