tfe-phone 0.9.9.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,213 @@
1
+ # An object representing a phone number.
2
+ #
3
+ # The phone number is recorded in 3 separate parts:
4
+ # * country_code - e.g. '385', '386'
5
+ # * area_code - e.g. '91', '47'
6
+ # * number - e.g. '5125486', '451588'
7
+ #
8
+ # All parts are mandatory, but country code and area code can be set for all phone numbers using
9
+ # Phone.default_country_code
10
+ # Phone.default_area_code
11
+ #
12
+ require 'active_support'
13
+ require File.join(File.dirname(__FILE__), 'phone_country')
14
+ class Phone
15
+ NUMBER = '([^0][0-9]{1,7})$'
16
+ DEFAULT_AREA_CODE = '[2-9][0-8][0-9]' # USA
17
+
18
+ attr_accessor :country_code, :area_code, :number
19
+
20
+ cattr_accessor :default_country_code
21
+ cattr_accessor :default_area_code
22
+ cattr_accessor :named_formats
23
+
24
+ # length of first number part (using multi number format)
25
+ cattr_accessor :n1_length
26
+ # default length of first number part
27
+ @@n1_length = 3
28
+
29
+ @@named_formats = {
30
+ :default => "+%c%a%n",
31
+ :europe => '+%c (0) %a %f %l',
32
+ :us => "(%a) %f-%l"
33
+ }
34
+
35
+ def initialize(*hash_or_args)
36
+ if hash_or_args.first.is_a?(Hash)
37
+ hash_or_args = hash_or_args.first
38
+ keys = {:number => :number, :area_code => :area_code, :country_code => :country_code}
39
+ else
40
+ keys = {:number => 0, :area_code => 1, :country_code => 2}
41
+ end
42
+
43
+ self.number = hash_or_args[ keys[:number] ]
44
+ self.area_code = hash_or_args[ keys[:area_code] ] || self.default_area_code
45
+ self.country_code = hash_or_args[ keys[:country_code] ] || self.default_country_code
46
+
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?
50
+ end
51
+
52
+ # create a new phone number by parsing a string
53
+ # the format of the string is detect automatically (from FORMATS)
54
+ def self.parse(string, options={})
55
+ if string.present?
56
+ PhoneCountry.load
57
+ string = normalize(string)
58
+
59
+ options[:country_code] ||= self.default_country_code
60
+ options[:area_code] ||= self.default_area_code
61
+
62
+ parts = split_to_parts(string, options)
63
+
64
+ pn = Phone.new(parts) if parts
65
+ end
66
+ end
67
+
68
+ # is this string a valid phone number?
69
+ def self.valid?(string)
70
+ begin
71
+ parse(string).present?
72
+ rescue RuntimeError # if we encountered exceptions (missing country code, missing area code etc)
73
+ return false
74
+ end
75
+ end
76
+
77
+ # split string into hash with keys :country_code, :area_code and :number
78
+ def self.split_to_parts(string, options = {})
79
+ country = detect_country(string)
80
+
81
+ if country
82
+ options[:country_code] = country.country_code
83
+ string = string.gsub(country.country_code_regexp, '0')
84
+ else
85
+ if options[:country_code]
86
+ country = PhoneCountry.find_by_country_code options[:country_code]
87
+ end
88
+ end
89
+
90
+ if country.nil?
91
+ if options[:country_code].nil?
92
+ raise "Must enter country code or set default country code"
93
+ else
94
+ raise "Could not find country with country code #{options[:country_code]}"
95
+ end
96
+ end
97
+
98
+ format = detect_format(string, country)
99
+
100
+ return nil if format.nil?
101
+
102
+ parts = string.match formats(country)[format]
103
+
104
+ case format
105
+ when :short
106
+ {:number => parts[2], :area_code => parts[1], :country_code => options[:country_code]}
107
+ when :really_short
108
+ {:number => parts[1], :area_code => options[:area_code], :country_code => options[:country_code]}
109
+ end
110
+ end
111
+
112
+ # detect country from the string entered
113
+ def self.detect_country(string)
114
+ detected_country = nil
115
+ # find if the number has a country code
116
+ PhoneCountry.all.each_pair do |country_code, country|
117
+ if string =~ country.country_code_regexp
118
+ detected_country = country
119
+ end
120
+ end
121
+ detected_country
122
+ end
123
+
124
+ def self.formats(country)
125
+ area_code_regexp = country.area_code || DEFAULT_AREA_CODE
126
+ {
127
+ # 047451588, 013668734
128
+ :short => Regexp.new('^0(' + area_code_regexp + ')' + NUMBER),
129
+ # 451588
130
+ :really_short => Regexp.new('^' + NUMBER)
131
+ }
132
+ end
133
+
134
+ # detect format (from FORMATS) of input string
135
+ def self.detect_format(string_with_number, country)
136
+ arr = []
137
+ formats(country).each_pair do |format, regexp|
138
+ arr << format if string_with_number =~ regexp
139
+ end
140
+
141
+ raise "Detected more than 1 format for #{string_with_number}" if arr.size > 1
142
+ arr.first
143
+ end
144
+
145
+ # fix string so it's easier to parse, remove extra characters etc.
146
+ def self.normalize(string_with_number)
147
+ string_with_number.gsub("(0)", "").gsub(/[^0-9+]/, '').gsub(/^00/, '+')
148
+ end
149
+
150
+ # format area_code with trailing zero (e.g. 91 as 091)
151
+ def area_code_long
152
+ "0" + area_code if area_code
153
+ end
154
+
155
+ # first n characters of :number
156
+ def number1
157
+ number[0...self.class.n1_length]
158
+ end
159
+
160
+ # everything left from number after the first n characters (see number1)
161
+ def number2
162
+ n2_length = number.size - self.class.n1_length
163
+ number[-n2_length, n2_length]
164
+ end
165
+
166
+ # Formats the phone number.
167
+ #
168
+ # if the method argument is a String, it is used as a format string, with the following fields being interpolated:
169
+ #
170
+ # * %c - country_code (385)
171
+ # * %a - area_code (91)
172
+ # * %A - area_code with leading zero (091)
173
+ # * %n - number (5125486)
174
+ # * %n1 - first @@n1_length characters of number (configured through Phone.n1_length), default is 3 (512)
175
+ # * %n2 - last characters of number (5486)
176
+ #
177
+ # if the method argument is a Symbol, it is used as a lookup key for a format String in Phone.named_formats
178
+ # pn.format(:europe)
179
+ def format(fmt)
180
+ if fmt.is_a?(Symbol)
181
+ raise "The format #{fmt} doesn't exist'" unless named_formats.has_key?(fmt)
182
+ format_number named_formats[fmt]
183
+ else
184
+ format_number(fmt)
185
+ end
186
+ end
187
+
188
+ # the default format is "+%c%a%n"
189
+ def to_s
190
+ format(:default)
191
+ end
192
+
193
+ # does this number belong to the default country code?
194
+ def has_default_country_code?
195
+ country_code == self.class.default_country_code
196
+ end
197
+
198
+ # does this number belong to the default area code?
199
+ def has_default_area_code?
200
+ area_code == self.class.default_area_code
201
+ end
202
+
203
+ private
204
+
205
+ def format_number(fmt)
206
+ fmt.gsub("%c", country_code || "").
207
+ gsub("%a", area_code || "").
208
+ gsub("%A", area_code_long || "").
209
+ gsub("%n", number || "").
210
+ gsub("%f", number1 || "").
211
+ gsub("%l", number2 || "")
212
+ end
213
+ end
@@ -0,0 +1,28 @@
1
+ require 'active_support'
2
+ class PhoneCountry < Struct.new(:name, :country_code, :char_2_code, :area_code)
3
+ cattr_accessor :all
4
+
5
+ def self.load
6
+ return @@all if @@all.present?
7
+
8
+ data_file = File.join(File.dirname(__FILE__), '..', 'data', 'phone_countries.yml')
9
+
10
+ @@all = {}
11
+ YAML.load(File.read(data_file)).each_pair do |key, c|
12
+ @@all[key] = PhoneCountry.new(c[:name], c[:country_code], c[:char_2_code], c[:area_code])
13
+ end
14
+ @@all
15
+ end
16
+
17
+ def to_s
18
+ name
19
+ end
20
+
21
+ def self.find_by_country_code(code)
22
+ @@all[code]
23
+ end
24
+
25
+ def country_code_regexp
26
+ Regexp.new("^[+]#{country_code}")
27
+ end
28
+ end
@@ -0,0 +1,108 @@
1
+ require File.dirname(__FILE__) + '/test_helper'
2
+
3
+ class PhoneCountryTest < Test::Unit::TestCase
4
+ def test_parse_germany_local
5
+ pn = Phone.parse('+49 714 1605832')
6
+
7
+ assert_equal pn.country_code, '49'
8
+ assert_equal pn.area_code, '714'
9
+ assert_equal pn.number, '1605832'
10
+ end
11
+
12
+ def test_parse_germany_mobile
13
+ pn = Phone.parse('+49 162 3499558')
14
+
15
+ assert_equal pn.country_code, '49'
16
+ assert_equal pn.area_code, '162'
17
+ assert_equal pn.number, '3499558'
18
+ end
19
+
20
+ def test_parse_hungary_mobile
21
+ pn = Phone.parse('+36 30 5517999')
22
+
23
+ assert_equal pn.country_code, '36'
24
+ assert_equal pn.area_code, '30'
25
+ assert_equal pn.number, '5517999'
26
+ end
27
+
28
+ def test_parse_slovenia_local
29
+ #Maribor
30
+ pn = Phone.parse '+ 386 2 23 46 611'
31
+
32
+ assert_equal pn.country_code, '386'
33
+ assert_equal pn.area_code, '2'
34
+ assert_equal pn.number, '2346611'
35
+ end
36
+
37
+ def test_parse_slovenia_local_2
38
+ # Koper
39
+ pn = Phone.parse '+ 386 5 23 46 611'
40
+
41
+ assert_equal pn.country_code, '386'
42
+ assert_equal pn.area_code, '5'
43
+ assert_equal pn.number, '2346611'
44
+ end
45
+
46
+ def test_parse_slovenia_mobile
47
+ # Mobitel
48
+ pn = Phone.parse('+386 51 258999')
49
+
50
+ assert_equal pn.country_code, '386'
51
+ assert_equal pn.area_code, '51'
52
+ assert_equal pn.number, '258999'
53
+ end
54
+
55
+ def test_parse_serbia_local
56
+ # Beograd
57
+ pn = Phone.parse('+381 11 24 33 836')
58
+
59
+ assert_equal pn.country_code, '381'
60
+ assert_equal pn.area_code, '11'
61
+ assert_equal pn.number, '2433836'
62
+ end
63
+
64
+
65
+ def test_parse_serbia_mobile
66
+ # mts
67
+ pn = Phone.parse('+381 648542987')
68
+
69
+ assert_equal pn.country_code, '381'
70
+ assert_equal pn.area_code, '64'
71
+ assert_equal pn.number, '8542987'
72
+ end
73
+
74
+ def test_parse_bosnia_local
75
+ pn = Phone.parse '+387 33 25 02 33'
76
+
77
+ assert_equal pn.country_code, '387'
78
+ assert_equal pn.area_code, '33'
79
+ assert_equal pn.number, '250233'
80
+ end
81
+
82
+ def test_parse_south_africa_local
83
+ # Telkom
84
+ pn = Phone.parse('+27 11 555 5555')
85
+
86
+ assert_equal pn.country_code, '27'
87
+ assert_equal pn.area_code, '11'
88
+ assert_equal pn.number, '5555555'
89
+ end
90
+
91
+ def test_parse_south_africa_mobile
92
+ # Vodacom
93
+ pn = Phone.parse('+27 82 555 5555')
94
+
95
+ assert_equal pn.country_code, '27'
96
+ assert_equal pn.area_code, '82'
97
+ assert_equal pn.number, '5555555'
98
+ end
99
+
100
+ def test_parse_south_africa_tollfree
101
+ # Telkom
102
+ pn = Phone.parse('+27 800 123 321')
103
+
104
+ assert_equal pn.country_code, '27'
105
+ assert_equal pn.area_code, '800'
106
+ assert_equal pn.number, '123321'
107
+ end
108
+ end
@@ -0,0 +1,207 @@
1
+ require File.dirname(__FILE__) + '/test_helper'
2
+
3
+ class PhoneTest < Test::Unit::TestCase
4
+
5
+ def test_number_without_country_code_initialize
6
+ Phone.default_country_code = nil
7
+
8
+ assert_raise RuntimeError do
9
+ pn = Phone.new '5125486', '91'
10
+ end
11
+ end
12
+
13
+ def test_number_without_country_and_area_code_initialize
14
+ Phone.default_country_code = nil
15
+ Phone.default_area_code = nil
16
+
17
+ assert_raise RuntimeError do
18
+ pn = Phone.new '451588'
19
+ end
20
+ end
21
+
22
+ def test_number_with_default_area_code_initialize
23
+ Phone.default_country_code = '385'
24
+ Phone.default_area_code = '47'
25
+
26
+ pn = Phone.new '451588'
27
+ assert pn.number == '451588'
28
+ assert pn.area_code == '47'
29
+ assert pn.country_code == '385'
30
+ end
31
+
32
+ def test_number_with_default_country_code_initialize
33
+ Phone.default_country_code = '386'
34
+
35
+ pn = Phone.new '5125486', '91'
36
+ assert pn.number == '5125486'
37
+ assert pn.area_code == '91'
38
+ assert pn.country_code == '386'
39
+ end
40
+
41
+ def test_number_with_country_code_initialize
42
+ Phone.default_country_code = '387'
43
+
44
+ pn = Phone.new '5125486', '91', '385'
45
+ assert pn.number == '5125486'
46
+ assert pn.area_code == '91'
47
+ assert pn.country_code == '385'
48
+ end
49
+
50
+ def test_parse_empty
51
+ assert_equal Phone.parse(''), nil
52
+ assert_equal Phone.parse(nil), nil
53
+ end
54
+
55
+ def test_parse_long_without_special_characters
56
+ pn = Phone.parse "+385915125486"
57
+
58
+ assert_equal pn.number, '5125486'
59
+ assert_equal pn.area_code, '91'
60
+ assert_equal pn.country_code, '385'
61
+ end
62
+
63
+ def test_parse_zagreb_long_without_special_characters
64
+ pn = Phone.parse "+38513668734"
65
+
66
+ assert_equal pn.number, '3668734'
67
+ assert_equal pn.area_code, '1'
68
+ assert_equal pn.country_code, '385'
69
+ end
70
+
71
+ def test_parse_long_with_special_characters
72
+ pn = Phone.parse "+ 385 (91) 512 / 5486 "
73
+
74
+ assert pn.number == '5125486'
75
+ assert pn.area_code == '91'
76
+ assert pn.country_code == '385'
77
+ end
78
+
79
+ def test_parse_long_with_leading_zeros
80
+ pn = Phone.parse "00385915125486"
81
+
82
+ assert pn.number == '5125486'
83
+ assert pn.area_code == '91'
84
+ assert pn.country_code == '385'
85
+ end
86
+
87
+ def test_parse_zagreb_long_with_leading_zeros
88
+ pn = Phone.parse "0038513668734"
89
+
90
+ assert pn.number == '3668734'
91
+ assert pn.area_code == '1'
92
+ assert pn.country_code == '385'
93
+ end
94
+
95
+ def test_parse_short_without_special_characters_without_country
96
+ Phone.default_country_code = nil
97
+
98
+ assert_raise RuntimeError do
99
+ pn = Phone.parse "0915125486"
100
+ end
101
+ end
102
+
103
+ def test_parse_short_without_special_characters_with_country
104
+ Phone.default_country_code = '385'
105
+
106
+ pn = Phone.parse "044885047"
107
+
108
+ assert_equal pn.number, '885047'
109
+ assert_equal pn.area_code, '44'
110
+ assert pn.country_code == '385'
111
+ end
112
+
113
+ def test_parse_zagreb_short_without_special_characters_with_country
114
+ Phone.default_country_code = '385'
115
+
116
+ pn = Phone.parse "013668734"
117
+
118
+ assert_equal pn.number, '3668734'
119
+ assert_equal pn.area_code, '1'
120
+ assert_equal pn.country_code, '385'
121
+ end
122
+
123
+ def test_parse_short_with_special_characters_without_country
124
+ Phone.default_country_code = nil
125
+
126
+ assert_raise RuntimeError do
127
+ pn = Phone.parse "091/512-5486"
128
+ end
129
+ end
130
+
131
+ def test_parse_long_with_zero_in_brackets
132
+ Phone.default_country_code = nil
133
+
134
+ pn = Phone.parse '+385 (0)1 366 8111'
135
+ assert_equal pn.country_code, '385'
136
+ assert_equal pn.area_code, '1'
137
+ assert_equal pn.number, '3668111'
138
+ end
139
+
140
+
141
+
142
+ def test_to_s
143
+ Phone.default_country_code = nil
144
+ pn = Phone.new '5125486', '91', '385'
145
+ assert pn.to_s == '+385915125486'
146
+ end
147
+
148
+ def test_to_s_without_country_code
149
+ Phone.default_country_code = '385'
150
+ pn = Phone.new '5125486', '91'
151
+ assert pn.format("0%a%n") == '0915125486'
152
+ end
153
+
154
+ def test_format_special_with_country_code
155
+ Phone.default_country_code = nil
156
+ pn = Phone.new '5125486', '91', '385'
157
+ assert pn.format("+ %c (%a) %n") == '+ 385 (91) 5125486'
158
+ end
159
+
160
+ def test_format_special_without_country_code
161
+ Phone.default_country_code = '385'
162
+ pn = Phone.new '5125486', '91'
163
+ assert_equal pn.format("%A/%f-%l"), '091/512-5486'
164
+ end
165
+
166
+ def test_format_with_symbol_specifier
167
+ Phone.default_country_code = nil
168
+ pn = Phone.new '5125486', '91', '385'
169
+ assert_equal pn.format(:europe), '+385 (0) 91 512 5486'
170
+ end
171
+
172
+ def test_doesnt_validate
173
+ assert_equal Phone.valid?('asdas'), false
174
+ assert_equal Phone.valid?('385915125486'), false
175
+ end
176
+
177
+ def test_validates
178
+ Phone.default_country_code = nil
179
+ assert_equal Phone.valid?('00385915125486'), true
180
+ assert_equal Phone.valid?('+385915125486'), true
181
+ assert_equal Phone.valid?('+385 (91) 512 5486'), true
182
+ assert_equal Phone.valid?('+38547451588'), true
183
+
184
+ Phone.default_country_code = '385'
185
+ assert_equal Phone.valid?('0915125486'), true
186
+ assert_equal Phone.valid?('091/512-5486'), true
187
+ assert_equal Phone.valid?('091/512-5486'), true
188
+ assert_equal Phone.valid?('091 512 54 86'), true
189
+ assert_equal Phone.valid?('091-512-54-86'), true
190
+ assert_equal Phone.valid?('047/451-588'), true
191
+ end
192
+
193
+ def test_has_default_country_code
194
+ Phone.default_country_code = '385'
195
+
196
+ assert_equal Phone.parse('+38547451588').has_default_country_code?, true
197
+ assert_equal Phone.parse('+38647451588').has_default_country_code?, false
198
+ end
199
+
200
+ def test_has_default_area_code
201
+ Phone.default_country_code = '385'
202
+ Phone.default_area_code = '47'
203
+
204
+ assert_equal Phone.parse('047/451-588').has_default_area_code?, true
205
+ assert_equal Phone.parse('032/336-1456').has_default_area_code?, false
206
+ end
207
+ end