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 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