elskwid-phone 0.9.9.2

Sign up to get free protection for your applications and to get access to all the features.
data/lib/phone.rb ADDED
@@ -0,0 +1,249 @@
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 File.join(File.dirname(__FILE__), 'support') unless defined? ActiveSupport
13
+ require File.join(File.dirname(__FILE__), 'phone_country')
14
+ class Phone
15
+ NUMBER = '([0-9]{1,8})$'
16
+ DEFAULT_AREA_CODE = '[2-9][0-8][0-9]' # USA
17
+
18
+ attr_accessor :country_code, :area_code, :number, :extension
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
+ :default_with_extension => "+%c%a%nx%x",
32
+ :europe => '+%c (0) %a %f %l',
33
+ :us => "(%a) %f-%l"
34
+ }
35
+
36
+ def initialize(*hash_or_args)
37
+ if hash_or_args.first.is_a?(Hash)
38
+ hash_or_args = hash_or_args.first
39
+ keys = {:number => :number, :area_code => :area_code, :country_code => :country_code, :extension => :extension}
40
+ else
41
+ keys = {:number => 0, :area_code => 1, :country_code => 2, :extension => 3}
42
+ end
43
+
44
+ self.number = hash_or_args[ keys[:number] ]
45
+ self.area_code = hash_or_args[ keys[:area_code] ] || self.default_area_code
46
+ self.country_code = hash_or_args[ keys[:country_code] ] || self.default_country_code
47
+ self.extension = hash_or_args[ keys[:extension] ]
48
+
49
+ raise "Must enter number" if self.number.blank?
50
+ raise "Must enter area code or set default area code" if self.area_code.blank?
51
+ raise "Must enter country code or set default country code" if self.country_code.blank?
52
+ end
53
+
54
+ # create a new phone number by parsing a string
55
+ # the format of the string is detect automatically (from FORMATS)
56
+ def self.parse(string, options={})
57
+ if string.present?
58
+ PhoneCountry.load
59
+ extension = extract_extension(string)
60
+ string = normalize(string)
61
+
62
+ options[:country_code] ||= self.default_country_code
63
+ options[:area_code] ||= self.default_area_code
64
+
65
+ parts = split_to_parts(string, options)
66
+
67
+ pn = Phone.new(parts) if parts
68
+ if pn.present? and extension.present?
69
+ pn.extension = extension
70
+ end
71
+ return pn
72
+ end
73
+ end
74
+
75
+ # is this string a valid phone number?
76
+ def self.valid?(string)
77
+ begin
78
+ parse(string).present?
79
+ rescue RuntimeError # if we encountered exceptions (missing country code, missing area code etc)
80
+ return false
81
+ end
82
+ end
83
+
84
+ # split string into hash with keys :country_code, :area_code and :number
85
+ def self.split_to_parts(string, options = {})
86
+ country = detect_country(string)
87
+
88
+ if country
89
+ options[:country_code] = country.country_code
90
+ string = string.gsub(country.country_code_regexp, '0')
91
+ else
92
+ if options[:country_code]
93
+ country = PhoneCountry.find_by_country_code options[:country_code]
94
+ end
95
+ end
96
+
97
+ if country.nil?
98
+ if options[:country_code].nil?
99
+ raise "Must enter country code or set default country code"
100
+ else
101
+ raise "Could not find country with country code #{options[:country_code]}"
102
+ end
103
+ end
104
+
105
+ format = detect_format(string, country)
106
+
107
+ return nil if format.nil?
108
+
109
+ parts = string.match formats(country)[format]
110
+
111
+ case format
112
+ when :short
113
+ {:number => parts[2], :area_code => parts[1], :country_code => options[:country_code]}
114
+ when :really_short
115
+ {:number => parts[1], :area_code => options[:area_code], :country_code => options[:country_code]}
116
+ end
117
+ end
118
+
119
+ # detect country from the string entered
120
+ def self.detect_country(string)
121
+ detected_country = nil
122
+ # find if the number has a country code
123
+ PhoneCountry.all.each_pair do |country_code, country|
124
+ if string =~ country.country_code_regexp
125
+ detected_country = country
126
+ end
127
+ end
128
+ detected_country
129
+ end
130
+
131
+ def self.formats(country)
132
+ area_code_regexp = country.area_code || DEFAULT_AREA_CODE
133
+ {
134
+ # 047451588, 013668734
135
+ :short => Regexp.new('^0?(' + area_code_regexp + ')' + NUMBER),
136
+ # 451588
137
+ :really_short => Regexp.new('^' + NUMBER)
138
+ }
139
+ end
140
+
141
+ # detect format (from FORMATS) of input string
142
+ def self.detect_format(string_with_number, country)
143
+ arr = []
144
+ formats(country).each_pair do |format, regexp|
145
+ arr << format if string_with_number =~ regexp
146
+ end
147
+
148
+ # raise "Detected more than 1 format for #{string_with_number}" if arr.size > 1
149
+ if arr.length > 1
150
+ # puts %Q{detect_format: more than one format found - #{arr.inspect}}
151
+ return :really_short
152
+ end
153
+ arr.first
154
+ end
155
+
156
+ # fix string so it's easier to parse, remove extra characters etc.
157
+ def self.normalize(string_with_number)
158
+ string_with_number.gsub("(0)", "").gsub(/[^0-9+]/, '').gsub(/^00/, '+')
159
+ end
160
+
161
+ # pull off anything that look like an extension
162
+ #TODO: refactor things so this doesn't change string as a side effect
163
+ #
164
+ def self.extract_extension(string)
165
+ return nil if string.nil?
166
+ if string.sub! /[ ]*(ext|ex|x|xt|#|:)+[^0-9]*\(*([-0-9]{1,})\)*#?$/i, ''
167
+ extension = $2
168
+ return extension
169
+ end
170
+ #
171
+ # We already returned any recognizable extension.
172
+ # However, we might still have extra junk to the right
173
+ # of the phone number proper, so just chop it off.
174
+ #
175
+ idx = string.rindex(/[0-9]/)
176
+ return nil if idx.nil?
177
+ return nil if idx == (string.length - 1) # at the end
178
+ string.slice!((idx+1)..-1) # chop it
179
+ return nil
180
+ end
181
+
182
+ # format area_code with trailing zero (e.g. 91 as 091)
183
+ # format area_code with trailing zero (e.g. 91 as 091)
184
+ def area_code_long
185
+ "0" + area_code if area_code
186
+ end
187
+
188
+ # first n characters of :number
189
+ def number1
190
+ number[0...self.class.n1_length]
191
+ end
192
+
193
+ # everything left from number after the first n characters (see number1)
194
+ def number2
195
+ n2_length = number.size - self.class.n1_length
196
+ number[-n2_length, n2_length]
197
+ end
198
+
199
+ # Formats the phone number.
200
+ #
201
+ # if the method argument is a String, it is used as a format string, with the following fields being interpolated:
202
+ #
203
+ # * %c - country_code (385)
204
+ # * %a - area_code (91)
205
+ # * %A - area_code with leading zero (091)
206
+ # * %n - number (5125486)
207
+ # * %f - first @@n1_length characters of number (configured through Phone.n1_length), default is 3 (512)
208
+ # * %l - last characters of number (5486)
209
+ # * %x - entire extension
210
+ #
211
+ # if the method argument is a Symbol, it is used as a lookup key for a format String in Phone.named_formats
212
+ # pn.format(:europe)
213
+ def format(fmt)
214
+ if fmt.is_a?(Symbol)
215
+ raise "The format #{fmt} doesn't exist'" unless named_formats.has_key?(fmt)
216
+ format_number named_formats[fmt]
217
+ else
218
+ format_number(fmt)
219
+ end
220
+ end
221
+
222
+ # the default format is "+%c%a%n"
223
+ def to_s
224
+ format(:default)
225
+ end
226
+
227
+ # does this number belong to the default country code?
228
+ def has_default_country_code?
229
+ country_code == self.class.default_country_code
230
+ end
231
+
232
+ # does this number belong to the default area code?
233
+ def has_default_area_code?
234
+ area_code == self.class.default_area_code
235
+ end
236
+
237
+ private
238
+
239
+ def format_number(fmt)
240
+ result = fmt.gsub("%c", country_code || "").
241
+ gsub("%a", area_code || "").
242
+ gsub("%A", area_code_long || "").
243
+ gsub("%n", number || "").
244
+ gsub("%f", number1 || "").
245
+ gsub("%l", number2 || "").
246
+ gsub("%x", extension || "")
247
+ return result
248
+ end
249
+ end
@@ -0,0 +1,27 @@
1
+ class PhoneCountry < Struct.new(:name, :country_code, :char_2_code, :area_code)
2
+ cattr_accessor :all
3
+
4
+ def self.load
5
+ return @@all if @@all.present?
6
+
7
+ data_file = File.join(File.dirname(__FILE__), '..', 'data', 'phone_countries.yml')
8
+
9
+ @@all = {}
10
+ YAML.load(File.read(data_file)).each_pair do |key, c|
11
+ @@all[key] = PhoneCountry.new(c[:name], c[:country_code], c[:char_2_code], c[:area_code])
12
+ end
13
+ @@all
14
+ end
15
+
16
+ def to_s
17
+ name
18
+ end
19
+
20
+ def self.find_by_country_code(code)
21
+ @@all[code]
22
+ end
23
+
24
+ def country_code_regexp
25
+ Regexp.new("^[+]#{country_code}")
26
+ end
27
+ end
data/lib/support.rb ADDED
@@ -0,0 +1,78 @@
1
+ require 'yaml'
2
+ # support methods to remove dependencies on ActiveSupport
3
+ class String
4
+ def present?
5
+ !blank?
6
+ end
7
+
8
+ def blank?
9
+ if respond_to?(:empty?) && respond_to?(:strip)
10
+ empty? or strip.empty?
11
+ elsif respond_to?(:empty?)
12
+ empty?
13
+ else
14
+ !self
15
+ end
16
+ end
17
+ end
18
+
19
+ class Hash
20
+ alias_method :blank?, :empty?
21
+
22
+ def present?
23
+ !blank?
24
+ end
25
+ end
26
+
27
+ class Object
28
+ def present?
29
+ self.class!=NilClass
30
+ end
31
+ end
32
+
33
+ class NilClass #:nodoc:
34
+ def blank?
35
+ true
36
+ end
37
+
38
+ def present?
39
+ false
40
+ end
41
+ end
42
+
43
+ module Accessorize
44
+ module ClassMethods
45
+ def cattr_accessor(*syms)
46
+ syms.flatten.each do |sym|
47
+ class_eval(<<-EOS, __FILE__, __LINE__)
48
+ unless defined? @@#{sym}
49
+ @@#{sym} = nil
50
+ end
51
+
52
+ def self.#{sym}
53
+ @@#{sym}
54
+ end
55
+
56
+ def #{sym}=(value)
57
+ @@#{sym} = value
58
+ end
59
+
60
+ def self.#{sym}=(value)
61
+ @@#{sym} = value
62
+ end
63
+
64
+ def #{sym}
65
+ @@#{sym}
66
+ end
67
+ EOS
68
+ end
69
+ end
70
+ end
71
+
72
+
73
+ def self.included(receiver)
74
+ receiver.extend ClassMethods
75
+ end
76
+ end
77
+
78
+ Object.send(:include, Accessorize)
@@ -0,0 +1,10 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '/../test_helper')
2
+
3
+ ## Bosnia and Herzegovina
4
+ class BATest < Test::Unit::TestCase
5
+
6
+ def test_local
7
+ parse_test('+387 33 25 02 33', '387', '33', '250233')
8
+ end
9
+
10
+ end
@@ -0,0 +1,14 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '/../test_helper')
2
+
3
+ ## Germany
4
+ class DETest < Test::Unit::TestCase
5
+
6
+ def test_local
7
+ parse_test('+49 714 1605832', '49', '714', '1605832')
8
+ end
9
+
10
+ def test_mobile
11
+ parse_test('+49 162 3499558', '49', '162', '3499558')
12
+ end
13
+
14
+ end
@@ -0,0 +1,18 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '/../test_helper')
2
+
3
+ ## France
4
+ class FRTest < Test::Unit::TestCase
5
+
6
+ def test_local
7
+ parse_test('+33 4 75 06 07 07', '33', '4', '75060707')
8
+ end
9
+
10
+ def test_mobile
11
+ parse_test('+33 6 11 22 33 44', '33', '6', '11223344')
12
+ end
13
+
14
+ def test_voip
15
+ parse_test('+33 9 11 22 33 44', '33', '9', '11223344')
16
+ end
17
+
18
+ end
@@ -0,0 +1,196 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '/../test_helper')
2
+
3
+ # http://stakeholders.ofcom.org.uk/telecoms/numbering/guidance-tele-no/numbers-for-drama
4
+ #
5
+ # Geographic Area Geographic Area Code Telephone Number Range
6
+ # (1000 numbers in each range)
7
+ # Leeds 0113 496 0000 to 496 0999
8
+ # Sheffield 0114 496 0000 to 496 0999
9
+ # Nottingham 0115 496 0000 to 496 0999
10
+ # Leicester 0116 496 0000 to 496 0999
11
+ # Bristol 0117 496 0000 to 496 0999
12
+ # Reading 0118 496 0000 to 496 0999
13
+ # Birmingham 0121 496 0000 to 496 0999
14
+ # Edinburgh 0131 496 0000 to 496 0999
15
+ # Glasgow 0141 496 0000 to 496 0999
16
+ # Liverpool 0151 496 0000 to 496 0999
17
+ # Manchester 0161 496 0000 to 496 0999
18
+ # London 020 7946 0000 to 7946 0999
19
+ # Tyneside/Durham
20
+ #/Sunderland 0191 498 0000 to 498 0999
21
+ # Northern Ireland 028 9018 0000 to 9018 0999
22
+ # Cardiff 029 2018 0000 to 2018 0999
23
+ # No area 01632 960000 to 960999
24
+ #
25
+ # Other Telephone Numbers
26
+ #
27
+ # Telephone Number Type Telephone Number Range
28
+ # (1000 numbers in each range)
29
+ # Mobile 07700 900000 to 900999
30
+ # Freephone 08081 570000 to 570999
31
+ # Premium Rate Services 0909 8790000 to 8790999
32
+ # UK Wide 03069 990000 to 990999
33
+
34
+ class GBTest < Test::Unit::TestCase
35
+
36
+ ## SHORT CODES
37
+ # London 020 7946 0000 to 7946 0999
38
+ def test_london
39
+ parse_test('+44 20 7946 0123', '44', '20', '79460123')
40
+ end
41
+
42
+ # Northern Ireland 028 9018 0000 to 9018 0999
43
+ def test_northern_ireland
44
+ parse_test('+44 28 9018 0123', '44', '28', '90180123')
45
+ end
46
+
47
+ # Cardiff 029 2018 0000 to 2018 0999
48
+ def test_cardiff
49
+ parse_test('+44 29 2018 0123', '44', '29', '20180123')
50
+ end
51
+
52
+ # Leeds 0113 496 0000 to 496 0999
53
+ def test_leeds
54
+ parse_test('+44 113 496 0123', '44', '113', '4960123')
55
+ end
56
+
57
+ # Sheffield 0114 496 0000 to 496 0999
58
+ def test_sheffield
59
+ parse_test('+44 114 496 0123', '44', '114', '4960123')
60
+ end
61
+
62
+ # Nottingham 0115 496 0000 to 496 0999
63
+ def test_nottingham
64
+ parse_test('+44 115 496 0123', '44', '115', '4960123')
65
+ end
66
+
67
+ # Leicester 0116 496 0000 to 496 0999
68
+ def test_leicester
69
+ parse_test('+44 116 496 0123', '44', '116', '4960123')
70
+ end
71
+
72
+ # Bristol 0117 496 0000 to 496 0999
73
+ def test_bristol
74
+ parse_test('+44 117 496 0123', '44', '117', '4960123')
75
+ end
76
+
77
+ # Reading 0118 496 0000 to 496 0999
78
+ def test_reading
79
+ parse_test('+44 118 496 0123', '44', '118', '4960123')
80
+ end
81
+
82
+ # Birmingham 0121 496 0000 to 496 0999
83
+ def test_birmingham
84
+ parse_test('+44 121 496 0123', '44', '121', '4960123')
85
+ end
86
+
87
+ # Edinburgh 0131 496 0000 to 496 0999
88
+ def test_edinburgh
89
+ parse_test('+44 131 496 0123', '44', '131', '4960123')
90
+ end
91
+
92
+ # Glasgow 0141 496 0000 to 496 0999
93
+ def test_glasgow
94
+ parse_test('+44 141 496 0123', '44', '141', '4960123')
95
+ end
96
+
97
+ # Liverpool 0151 496 0000 to 496 0999
98
+ def test_liverpool
99
+ parse_test('+44 151 496 0123', '44', '151', '4960123')
100
+ end
101
+
102
+ # Manchester 0161 496 0000 to 496 0999
103
+ def test_manchester
104
+ parse_test('+44 161 496 0123', '44', '161', '4960123')
105
+ end
106
+
107
+ # Tyneside/Durham
108
+ #/Sunderland 0191 498 0000 to 498 0999
109
+ def test_tyneside
110
+ parse_test('+44 191 496 0123', '44', '191', '4960123')
111
+ end
112
+
113
+ ## LONG CODES
114
+
115
+ # 01202 — Bournemouth (BO)
116
+ def test_bournemouth
117
+ parse_test('+44 1202 96 0123', '44', '1202', '960123')
118
+ end
119
+
120
+ # 01326 — Falmouth (FA)
121
+ def test_falmouth
122
+ parse_test('+44 1326 96 0123', '44', '1326', '960123')
123
+ end
124
+
125
+ # 01420 — Alton (HA)
126
+ def test_alton
127
+ parse_test('+44 1420 96 0123', '44', '1420', '960123')
128
+ end
129
+
130
+ # 01598 — Lynton (LY)
131
+ def test_lynton
132
+ parse_test('+44 1598 96 0123', '44', '1598', '960123')
133
+ end
134
+
135
+ # 01637 — Newquay (NE)
136
+ def test_newquay
137
+ parse_test('+44 1637 96 0123', '44', '1637', '960123')
138
+ end
139
+
140
+ # 01700 — Rothesay (RO)
141
+ def test_rothesay
142
+ parse_test('+44 1700 96 0123', '44', '1700', '960123')
143
+ end
144
+
145
+ # 01951 — Colonsay
146
+ def test_colonsay
147
+ parse_test('+44 1951 96 0123', '44', '1951', '960123')
148
+ end
149
+
150
+ # No area 01632 960000 to 960999
151
+ def test_no_area
152
+ parse_test('+44 1632 96 0123', '44', '1632', '960123')
153
+ end
154
+
155
+ # Personal numbering 070 xxxx xxxx
156
+ def test_personal_numbering
157
+ parse_test('+44 70 00001234', '44', '70', '00001234')
158
+ end
159
+
160
+ # Mobile 07700 900000 to 900999
161
+ def test_mobile
162
+ parse_test('+44 7700 900345', '44', '7700', '900345')
163
+ end
164
+
165
+ def test_mobile_2
166
+ parse_test('+44 7778 900345', '44', '7778', '900345')
167
+ end
168
+
169
+ # Freephone 08081 570000 to 570999
170
+ def test_freephone
171
+ parse_test('+44 808 1570123', '44', '808', '1570123')
172
+ end
173
+
174
+ def test_freephone_2
175
+ parse_test('+44 873 1570123', '44', '873', '1570123')
176
+ end
177
+
178
+ # Premium Rate Services 0909 8790000 to 8790999
179
+ def test_premium
180
+ parse_test('+44 909 8790999', '44', '909', '8790999')
181
+ end
182
+
183
+ def test_premium2
184
+ parse_test('+44 910 8790123', '44', '910', '8790123')
185
+ end
186
+
187
+ # UK Wide 03069 990000 to 990999
188
+ def test_wide
189
+ parse_test('+44 306 9990123', '44', '306', '9990123')
190
+ end
191
+
192
+ def test_wide_2
193
+ parse_test('+44 339 9990123', '44', '339', '9990123')
194
+ end
195
+
196
+ end