telephone_number 0.3.0 → 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: da8efdb23ca38191becf7d7a07a6387cce0530cf
4
- data.tar.gz: 16577898ab4e8dccf7a85ed2bf0d2c2faa5a9a99
3
+ metadata.gz: 9176d842a4931a224fbc4e89bcf4f643e6bb4ab2
4
+ data.tar.gz: 88a4b6d3439017b5a9b68a15ae752c790373a475
5
5
  SHA512:
6
- metadata.gz: 3ec45bf229dcceaab6b9893dac3f96352789d45d7923e45551ace2e8fc92da1efcdf71adb2e50350f84783d66d247af666e2be24af78242e7ac9fceb323a2792
7
- data.tar.gz: 3502e8e65b905792c767a6fda80865085eb3fd59d13857f7eb3e82c5e3d800615dbc7fc23c30cccb9ae9f8e44604f74939cde6f2f2dc7d5fe907678c1d64aa27
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. We're currently providing parsing and validation functionality and are actively working on formatting as well as providing extended data.
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!(override: false)
12
+ def import!
12
13
  parse_main_data
13
- save_data_file(override: override)
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
- country_data[PhoneData::FORMATS] = territory.css('availableFormats numberFormat').map do |format|
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
- country_data[:references] = territory.css('references sourceUrl').map(&:text)
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(override: false)
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
- module Formatter
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
- format_string = number_format[PhoneData::FORMAT].gsub(/(\$\d)/) { |cap| "%#{cap.reverse}s" }
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
- format_string = number_format[key].gsub(/(\$\d)/) { |cap| "%#{cap.reverse}s" }
33
- formatted_string = "+#{country_data[PhoneData::COUNTRY_CODE]} #{format(format_string, *captures)}"
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
- private
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 if !TelephoneNumber.default_format_string || !TelephoneNumber.default_format_pattern
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
- format_string = TelephoneNumber.default_format_string.gsub(/(\$\d)/) { |cap| "%#{cap.reverse}s" }
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
- include TelephoneNumber::Parser
4
- include TelephoneNumber::Formatter
3
+ extend Forwardable
5
4
 
6
- attr_reader :original_number, :normalized_number, :country, :country_data
5
+ attr_reader :phone_data, :parser, :formatter
7
6
 
8
- def initialize(number, country)
9
- return unless number && country
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 national_number(formatted: true)
30
- if formatted
31
- @formatted_national_number ||= build_national_number
32
- else
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
- module Parser
2
+ class Parser
3
3
  KEYS_TO_SKIP = [PhoneData::GENERAL, PhoneData::AREA_CODE_OPTIONAL]
4
+ extend Forwardable
4
5
 
5
- def sanitize(input_number)
6
- return input_number.gsub(/[^0-9]/, '')
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
- private
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 original_number unless country_data
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 original_number unless match_result
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 original_number unless country_data[:national_prefix_for_parsing]
40
- duped = original_number.dup
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 original_number unless match_object && duped.start_with?(match_object[0])
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
- module PhoneData
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
 
@@ -1,3 +1,3 @@
1
1
  module TelephoneNumber
2
- VERSION = "0.3.0"
2
+ VERSION = "1.0.0"
3
3
  end
@@ -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.3.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-02-14 00:00:00.000000000 Z
11
+ date: 2017-04-19 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler