telephone_number 0.0.0 → 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|