tfe-phone 0.9.9.1

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