phonie 1.0.4 → 2.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.
- data/lib/phonie.rb +3 -1
- data/lib/phonie/country.rb +51 -65
- data/lib/phonie/data/phone_countries.yml +415 -415
- data/lib/phonie/phone.rb +58 -85
- data/lib/phonie/version.rb +1 -1
- data/phonie.gemspec +5 -3
- data/test/countries/gr_test.rb +1 -1
- data/test/countries/gu_test.rb +1 -1
- data/test/country_test.rb +5 -5
- data/test/phone_test.rb +21 -21
- data/tools/generate +12 -10
- metadata +8 -6
data/lib/phonie/phone.rb
CHANGED
@@ -1,3 +1,7 @@
|
|
1
|
+
require 'active_model/naming'
|
2
|
+
require 'active_model/translation'
|
3
|
+
require 'active_model/validations'
|
4
|
+
|
1
5
|
# An object representing a phone number.
|
2
6
|
#
|
3
7
|
# The phone number is recorded in 3 separate parts:
|
@@ -11,6 +15,8 @@
|
|
11
15
|
#
|
12
16
|
module Phonie
|
13
17
|
class Phone
|
18
|
+
EXTENSION = /[ ]*(ext|ex|x|xt|#|:)+[^0-9]*\(*([-0-9]{1,})\)*#?$/i
|
19
|
+
|
14
20
|
attr_accessor :country_code, :area_code, :number, :extension, :country
|
15
21
|
|
16
22
|
cattr_accessor :default_country_code
|
@@ -29,6 +35,11 @@ module Phonie
|
|
29
35
|
:us => "(%a) %f-%l"
|
30
36
|
}
|
31
37
|
|
38
|
+
include ActiveModel::Validations
|
39
|
+
validates :country_code, :presence => true
|
40
|
+
validates :area_code, :presence => true
|
41
|
+
validates :number, :presence => true
|
42
|
+
|
32
43
|
def initialize(*hash_or_args)
|
33
44
|
if hash_or_args.first.is_a?(Hash)
|
34
45
|
hash_or_args = hash_or_args.first
|
@@ -37,103 +48,49 @@ module Phonie
|
|
37
48
|
keys = {:number => 0, :area_code => 1, :country_code => 2, :extension => 3, :country => 4}
|
38
49
|
end
|
39
50
|
|
40
|
-
self.number
|
41
|
-
self.area_code
|
51
|
+
self.number = hash_or_args[ keys[:number] ]
|
52
|
+
self.area_code = hash_or_args[ keys[:area_code] ] || self.default_area_code
|
42
53
|
self.country_code = hash_or_args[ keys[:country_code] ] || self.default_country_code
|
43
|
-
self.extension
|
44
|
-
self.country
|
45
|
-
|
46
|
-
# Santity checks
|
47
|
-
raise "Must enter number" if self.number.blank?
|
48
|
-
raise "Must enter area code or set default area code" if self.area_code.blank?
|
49
|
-
raise "Must enter country code or set default country code" if self.country_code.blank?
|
54
|
+
self.extension = hash_or_args[ keys[:extension] ]
|
55
|
+
self.country = hash_or_args[ keys[:country] ]
|
50
56
|
end
|
51
57
|
|
52
|
-
def self.parse!(string, options={})
|
53
|
-
|
58
|
+
def self.parse!(string, options = {})
|
59
|
+
pn = parse(string, options)
|
60
|
+
raise ArgumentError.new("#{string} is not a valid phone number") unless pn && pn.valid?
|
61
|
+
pn
|
54
62
|
end
|
55
63
|
|
56
64
|
# create a new phone number by parsing a string
|
57
65
|
# the format of the string is detect automatically (from FORMATS)
|
58
|
-
def self.parse(string, options={})
|
59
|
-
return
|
66
|
+
def self.parse(string, options = {})
|
67
|
+
return unless string.present?
|
60
68
|
|
61
|
-
|
69
|
+
options[:country_code] ||= self.default_country_code
|
70
|
+
options[:area_code] ||= self.default_area_code
|
62
71
|
|
63
72
|
extension = extract_extension(string)
|
64
73
|
normalized = normalize(string)
|
65
74
|
|
66
|
-
options[:country_code]
|
67
|
-
options[:area_code]
|
68
|
-
|
69
|
-
parts =
|
70
|
-
|
71
|
-
|
72
|
-
if pn.present? and extension.present?
|
73
|
-
pn.extension = extension
|
74
|
-
end
|
75
|
-
pn
|
75
|
+
return unless country = Country.detect(normalized, options[:country_code], options[:area_code])
|
76
|
+
parts = country.parse(normalized, options[:area_code])
|
77
|
+
parts[:country] = country
|
78
|
+
parts[:country_code] = country.country_code
|
79
|
+
parts[:extension] = extension
|
80
|
+
new(parts)
|
76
81
|
end
|
77
82
|
|
78
83
|
# is this string a valid phone number?
|
79
84
|
def self.valid?(string, options = {})
|
80
|
-
|
81
|
-
|
82
|
-
rescue
|
83
|
-
false # don't raise exceptions on parse errors
|
84
|
-
end
|
85
|
+
pn = parse(string, options)
|
86
|
+
pn && pn.valid?
|
85
87
|
end
|
86
88
|
|
87
89
|
def self.is_mobile?(string, options = {})
|
88
90
|
pn = parse(string, options)
|
89
|
-
|
90
|
-
pn.is_mobile?
|
91
|
-
end
|
92
|
-
|
93
|
-
private
|
94
|
-
# split string into hash with keys :country_code, :area_code and :number
|
95
|
-
def self.split_to_parts(string, options = {})
|
96
|
-
country = Country.detect(string, options[:country_code], options[:area_code])
|
97
|
-
|
98
|
-
if country.nil?
|
99
|
-
raise "Could not determine country" if options[:raise_exception_on_error]
|
100
|
-
return nil
|
101
|
-
end
|
102
|
-
|
103
|
-
country.number_parts(string, options[:area_code])
|
91
|
+
pn && pn.is_mobile?
|
104
92
|
end
|
105
93
|
|
106
|
-
# fix string so it's easier to parse, remove extra characters etc.
|
107
|
-
def self.normalize(string_with_number)
|
108
|
-
string_with_number.sub(extension_regex, '').gsub(/\(0\)|[^0-9+]/, '').gsub(/^00/, '+')
|
109
|
-
end
|
110
|
-
|
111
|
-
def self.extension_regex
|
112
|
-
/[ ]*(ext|ex|x|xt|#|:)+[^0-9]*\(*([-0-9]{1,})\)*#?$/i
|
113
|
-
end
|
114
|
-
|
115
|
-
# pull off anything that look like an extension
|
116
|
-
#
|
117
|
-
def self.extract_extension(string)
|
118
|
-
return nil if string.nil?
|
119
|
-
if string.match extension_regex
|
120
|
-
extension = $2
|
121
|
-
return extension
|
122
|
-
end
|
123
|
-
#
|
124
|
-
# We already returned any recognizable extension.
|
125
|
-
# However, we might still have extra junk to the right
|
126
|
-
# of the phone number proper, so just chop it off.
|
127
|
-
#
|
128
|
-
idx = string.rindex(/[0-9]/)
|
129
|
-
return nil if idx.nil?
|
130
|
-
return nil if idx == (string.length - 1) # at the end
|
131
|
-
string.slice!((idx+1)..-1) # chop it
|
132
|
-
return nil
|
133
|
-
end
|
134
|
-
|
135
|
-
public # instance methods
|
136
|
-
|
137
94
|
def area_code_long
|
138
95
|
"0" + area_code if area_code
|
139
96
|
end
|
@@ -157,7 +114,7 @@ module Phonie
|
|
157
114
|
|
158
115
|
# Formats the phone number.
|
159
116
|
#
|
160
|
-
# if the method argument is a String, it is used as a format string, with the following fields being interpolated:
|
117
|
+
# if the method argument is a String, it is used as a format string, with the following fields being interpolated:
|
161
118
|
#
|
162
119
|
# * %c - country_code (385)
|
163
120
|
# * %a - area_code (91)
|
@@ -171,7 +128,7 @@ module Phonie
|
|
171
128
|
# pn.format(:europe)
|
172
129
|
def format(fmt)
|
173
130
|
if fmt.is_a?(Symbol)
|
174
|
-
raise "The format #{fmt} doesn't exist
|
131
|
+
raise ArgumentError.new("The format #{fmt} doesn't exist") unless named_formats.has_key?(fmt)
|
175
132
|
format_number named_formats[fmt]
|
176
133
|
else
|
177
134
|
format_number(fmt)
|
@@ -201,15 +158,31 @@ module Phonie
|
|
201
158
|
|
202
159
|
private
|
203
160
|
|
161
|
+
# split string into hash with keys :country_code, :area_code and :number
|
162
|
+
def self.split_to_parts(string, options = {})
|
163
|
+
country = Country.detect(string, options[:country_code], options[:area_code])
|
164
|
+
country && country.parse(string, options[:area_code])
|
165
|
+
end
|
166
|
+
|
167
|
+
# fix string so it's easier to parse, remove extra characters etc.
|
168
|
+
def self.normalize(string_with_number)
|
169
|
+
string_with_number.sub(EXTENSION, '').gsub(/\(0\)|[^0-9+]/, '').gsub(/^00/, '+')
|
170
|
+
end
|
171
|
+
|
172
|
+
# pull off anything that look like an extension
|
173
|
+
def self.extract_extension(string)
|
174
|
+
return unless string && string.match(EXTENSION)
|
175
|
+
Regexp.last_match[2]
|
176
|
+
end
|
177
|
+
|
204
178
|
def format_number(fmt)
|
205
|
-
|
206
|
-
|
207
|
-
|
208
|
-
|
209
|
-
|
210
|
-
|
211
|
-
|
212
|
-
return result
|
179
|
+
fmt.gsub("%c", country_code || "").
|
180
|
+
gsub("%a", area_code || "").
|
181
|
+
gsub("%A", area_code_long || "").
|
182
|
+
gsub("%n", number || "").
|
183
|
+
gsub("%f", number1 || "").
|
184
|
+
gsub("%l", number2 || "").
|
185
|
+
gsub("%x", extension || "")
|
213
186
|
end
|
214
187
|
end
|
215
188
|
end
|
data/lib/phonie/version.rb
CHANGED
data/phonie.gemspec
CHANGED
@@ -6,8 +6,8 @@ Gem::Specification.new do |s|
|
|
6
6
|
s.name = "phonie"
|
7
7
|
s.version = Phonie::VERSION
|
8
8
|
s.platform = Gem::Platform::RUBY
|
9
|
-
s.authors
|
10
|
-
s.email
|
9
|
+
s.authors = ['Tomislav Car', 'Todd Eichel', 'Don Morrison', 'Wesley Moxam', 'Lance Ivy']
|
10
|
+
s.email = ['tomislav@infinum.hr', 'todd@toddeichel.com', 'elskwid@gmail.com', 'wesley@wmoxam.com', 'lance@kickstarter.com']
|
11
11
|
s.homepage = "http://github.com/wmoxam/phonie"
|
12
12
|
s.summary = %q{Phone number parsing, validation and formatting}
|
13
13
|
s.description = %q{Phone number parsing, validation and formatting}
|
@@ -15,7 +15,9 @@ Gem::Specification.new do |s|
|
|
15
15
|
s.files = `git ls-files`.split("\n")
|
16
16
|
s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
|
17
17
|
s.require_paths = ["lib"]
|
18
|
+
|
19
|
+
s.add_dependency 'activemodel'
|
20
|
+
|
18
21
|
s.add_development_dependency 'rake'
|
19
22
|
s.add_development_dependency 'nokogiri'
|
20
|
-
s.add_development_dependency 'activemodel'
|
21
23
|
end
|
data/test/countries/gr_test.rb
CHANGED
data/test/countries/gu_test.rb
CHANGED
data/test/country_test.rb
CHANGED
@@ -3,10 +3,10 @@ require File.expand_path(File.dirname(__FILE__) + '/test_helper')
|
|
3
3
|
class CountryTest < Phonie::TestCase
|
4
4
|
def test_find_by_country_name
|
5
5
|
country = Phonie::Country.find_by_name('canada')
|
6
|
-
assert_equal country.name
|
6
|
+
assert_equal "Canada", country.name
|
7
7
|
|
8
8
|
country = Phonie::Country.find_by_name('Canada')
|
9
|
-
assert_equal country.name
|
9
|
+
assert_equal "Canada", country.name
|
10
10
|
|
11
11
|
assert_nil Phonie::Country.find_by_name(nil)
|
12
12
|
assert_nil Phonie::Country.find_by_country_code(nil)
|
@@ -15,13 +15,13 @@ class CountryTest < Phonie::TestCase
|
|
15
15
|
|
16
16
|
def test_find_by_country_code
|
17
17
|
country = Phonie::Country.find_by_country_code('NO')
|
18
|
-
assert_equal country.name
|
18
|
+
assert_equal "Norway", country.name
|
19
19
|
end
|
20
20
|
|
21
21
|
def test_find_all_by_phone_code
|
22
22
|
countries = Phonie::Country.find_all_by_phone_code('47')
|
23
|
-
assert_equal countries.length
|
24
|
-
assert_equal countries.first.name
|
23
|
+
assert_equal 1, countries.length
|
24
|
+
assert_equal "Norway", countries.first.name
|
25
25
|
end
|
26
26
|
|
27
27
|
end
|
data/test/phone_test.rb
CHANGED
@@ -9,18 +9,18 @@ class PhoneTest < Phonie::TestCase
|
|
9
9
|
def test_number_without_country_code_initialize
|
10
10
|
Phonie::Phone.default_country_code = nil
|
11
11
|
|
12
|
-
|
13
|
-
|
14
|
-
|
12
|
+
pn = Phonie::Phone.new '5125486', '91'
|
13
|
+
assert !pn.valid?
|
14
|
+
assert_equal ["can't be blank"], pn.errors[:country_code]
|
15
15
|
end
|
16
16
|
|
17
|
-
def
|
18
|
-
Phonie::Phone.default_country_code =
|
17
|
+
def test_number_without_area_code_initialize
|
18
|
+
Phonie::Phone.default_country_code = '1'
|
19
19
|
Phonie::Phone.default_area_code = nil
|
20
20
|
|
21
|
-
|
22
|
-
|
23
|
-
|
21
|
+
pn = Phonie::Phone.new '451588'
|
22
|
+
assert !pn.valid?
|
23
|
+
assert_equal ["can't be blank"], pn.errors[:area_code]
|
24
24
|
end
|
25
25
|
|
26
26
|
def test_number_with_default_area_code_initialize
|
@@ -52,8 +52,8 @@ class PhoneTest < Phonie::TestCase
|
|
52
52
|
end
|
53
53
|
|
54
54
|
def test_parse_empty
|
55
|
-
assert_equal Phonie::Phone.parse('')
|
56
|
-
assert_equal Phonie::Phone.parse(nil)
|
55
|
+
assert_equal nil, Phonie::Phone.parse('')
|
56
|
+
assert_equal nil, Phonie::Phone.parse(nil)
|
57
57
|
end
|
58
58
|
|
59
59
|
def test_parse_short_without_special_characters_without_country
|
@@ -61,7 +61,7 @@ class PhoneTest < Phonie::TestCase
|
|
61
61
|
|
62
62
|
assert_nil Phonie::Phone.parse "0915125486"
|
63
63
|
|
64
|
-
assert_raise
|
64
|
+
assert_raise ArgumentError do
|
65
65
|
Phonie::Phone.parse! "0915125486"
|
66
66
|
end
|
67
67
|
end
|
@@ -71,7 +71,7 @@ class PhoneTest < Phonie::TestCase
|
|
71
71
|
|
72
72
|
assert_nil Phonie::Phone.parse "091/512-5486"
|
73
73
|
|
74
|
-
assert_raise
|
74
|
+
assert_raise ArgumentError do
|
75
75
|
Phonie::Phone.parse! "091/512-5486"
|
76
76
|
end
|
77
77
|
end
|
@@ -97,23 +97,23 @@ class PhoneTest < Phonie::TestCase
|
|
97
97
|
def test_format_special_without_country_code
|
98
98
|
Phonie::Phone.default_country_code = '385'
|
99
99
|
pn = Phonie::Phone.new '5125486', '91'
|
100
|
-
assert_equal pn.format("%A/%f-%l")
|
100
|
+
assert_equal '091/512-5486', pn.format("%A/%f-%l")
|
101
101
|
end
|
102
102
|
|
103
103
|
def test_format_with_symbol_specifier
|
104
104
|
Phonie::Phone.default_country_code = nil
|
105
105
|
pn = Phonie::Phone.new '5125486', '91', '385'
|
106
|
-
assert_equal
|
106
|
+
assert_equal '+385 (0) 91 512 5486', pn.format(:europe)
|
107
107
|
end
|
108
108
|
|
109
109
|
def test_valid
|
110
|
-
|
111
|
-
|
110
|
+
assert Phonie::Phone.valid?('915125486', :country_code => '385')
|
111
|
+
assert Phonie::Phone.valid?('385915125486')
|
112
112
|
end
|
113
113
|
|
114
114
|
def test_doesnt_validate
|
115
|
-
|
116
|
-
|
115
|
+
assert !Phonie::Phone.valid?('asdas')
|
116
|
+
assert !Phonie::Phone.valid?('38591512548678')
|
117
117
|
end
|
118
118
|
|
119
119
|
def test_comparison_true
|
@@ -129,9 +129,9 @@ class PhoneTest < Phonie::TestCase
|
|
129
129
|
end
|
130
130
|
|
131
131
|
def test_parse_number_without_international_code
|
132
|
-
assert_equal
|
133
|
-
assert_equal
|
134
|
-
assert_equal
|
132
|
+
assert_equal nil, Phonie::Phone.parse("90123456")
|
133
|
+
assert_equal "+4790123456", Phonie::Phone.parse("90123456", :country_code => '47').format(:default)
|
134
|
+
assert_equal "+4790123456", Phonie::Phone.parse("90123456", :country_code => '47', :area_code => '').format(:default)
|
135
135
|
end
|
136
136
|
|
137
137
|
end
|
data/tools/generate
CHANGED
@@ -23,15 +23,15 @@ def already_exists?
|
|
23
23
|
end
|
24
24
|
|
25
25
|
def get_country_code
|
26
|
-
YAML.load(File.read(@data_file)).
|
27
|
-
return c[:
|
26
|
+
YAML.load(File.read(@data_file)).each do |c|
|
27
|
+
return c[:iso_3166_code].downcase if c[:name].downcase == @country.downcase
|
28
28
|
end
|
29
29
|
nil
|
30
30
|
end
|
31
31
|
|
32
32
|
def get_country_call_code
|
33
|
-
YAML.load(File.read(@data_file)).
|
34
|
-
return
|
33
|
+
YAML.load(File.read(@data_file)).each do |c|
|
34
|
+
return c[:country_code] if c[:name].downcase == @country.downcase
|
35
35
|
end
|
36
36
|
nil
|
37
37
|
end
|
@@ -48,24 +48,26 @@ class #{@country_code.upcase}Test < Phonie::TestCase
|
|
48
48
|
def test_local
|
49
49
|
parse_test('+#{country_call_code}', '#{country_call_code}', '', '', '#{@country}', false)
|
50
50
|
end
|
51
|
-
|
51
|
+
|
52
52
|
def test_mobile
|
53
53
|
parse_test('+#{country_call_code}', '#{country_call_code}', '', '', '#{@country}', true)
|
54
54
|
end
|
55
|
-
end
|
55
|
+
end
|
56
56
|
eof
|
57
57
|
end
|
58
58
|
puts "Create: #{@test_filename}"
|
59
59
|
end
|
60
60
|
|
61
61
|
def add_missing_fields
|
62
|
-
|
63
|
-
|
62
|
+
arr = YAML.load(File.read(@data_file))
|
63
|
+
arr.find{|c| c[:name].downcase == @country.downcase}.merge!(
|
64
|
+
:area_code => ' ',
|
64
65
|
:local_number_format => ' ',
|
65
66
|
:mobile_format => ' ',
|
66
|
-
:number_format => ' '
|
67
|
+
:number_format => ' '
|
68
|
+
)
|
67
69
|
File.open(@data_file, 'w') do |f|
|
68
|
-
f.puts
|
70
|
+
f.puts arr.to_yaml
|
69
71
|
end
|
70
72
|
|
71
73
|
puts "Modified: #{@data_file}"
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: phonie
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version:
|
4
|
+
version: 2.0.0
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -9,20 +9,21 @@ authors:
|
|
9
9
|
- Todd Eichel
|
10
10
|
- Don Morrison
|
11
11
|
- Wesley Moxam
|
12
|
+
- Lance Ivy
|
12
13
|
autorequire:
|
13
14
|
bindir: bin
|
14
15
|
cert_chain: []
|
15
|
-
date: 2013-
|
16
|
+
date: 2013-05-01 00:00:00.000000000 Z
|
16
17
|
dependencies:
|
17
18
|
- !ruby/object:Gem::Dependency
|
18
|
-
name:
|
19
|
+
name: activemodel
|
19
20
|
requirement: !ruby/object:Gem::Requirement
|
20
21
|
none: false
|
21
22
|
requirements:
|
22
23
|
- - ! '>='
|
23
24
|
- !ruby/object:Gem::Version
|
24
25
|
version: '0'
|
25
|
-
type: :
|
26
|
+
type: :runtime
|
26
27
|
prerelease: false
|
27
28
|
version_requirements: !ruby/object:Gem::Requirement
|
28
29
|
none: false
|
@@ -31,7 +32,7 @@ dependencies:
|
|
31
32
|
- !ruby/object:Gem::Version
|
32
33
|
version: '0'
|
33
34
|
- !ruby/object:Gem::Dependency
|
34
|
-
name:
|
35
|
+
name: rake
|
35
36
|
requirement: !ruby/object:Gem::Requirement
|
36
37
|
none: false
|
37
38
|
requirements:
|
@@ -47,7 +48,7 @@ dependencies:
|
|
47
48
|
- !ruby/object:Gem::Version
|
48
49
|
version: '0'
|
49
50
|
- !ruby/object:Gem::Dependency
|
50
|
-
name:
|
51
|
+
name: nokogiri
|
51
52
|
requirement: !ruby/object:Gem::Requirement
|
52
53
|
none: false
|
53
54
|
requirements:
|
@@ -68,6 +69,7 @@ email:
|
|
68
69
|
- todd@toddeichel.com
|
69
70
|
- elskwid@gmail.com
|
70
71
|
- wesley@wmoxam.com
|
72
|
+
- lance@kickstarter.com
|
71
73
|
executables: []
|
72
74
|
extensions: []
|
73
75
|
extra_rdoc_files: []
|