phone_wrangler 0.0.0

Sign up to get free protection for your applications and to get access to all the features.
data/.document ADDED
@@ -0,0 +1,5 @@
1
+ README.rdoc
2
+ lib/**/*.rb
3
+ bin/*
4
+ features/**/*.feature
5
+ LICENSE
data/.gitignore ADDED
@@ -0,0 +1,5 @@
1
+ *.sw?
2
+ .DS_Store
3
+ coverage
4
+ rdoc
5
+ pkg
data/LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2009 Ryan Waldron
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.rdoc ADDED
@@ -0,0 +1,18 @@
1
+ = phone_wrangler
2
+
3
+ Description goes here.
4
+
5
+ == Note on Patches/Pull Requests
6
+
7
+ * Fork the project.
8
+ * Make your feature addition or bug fix.
9
+ * Add tests for it. This is important so I don't break it in a
10
+ future version unintentionally.
11
+ * Commit, do not mess with rakefile, version, or history.
12
+ (if you want to have your own version, that is fine but
13
+ bump version in a commit by itself I can ignore when I pull)
14
+ * Send me a pull request. Bonus points for topic branches.
15
+
16
+ == Copyright
17
+
18
+ Copyright (c) 2010 Ryan Waldron. See LICENSE for details.
data/Rakefile ADDED
@@ -0,0 +1,57 @@
1
+ require 'rubygems'
2
+ require 'rake'
3
+
4
+ begin
5
+ require 'jeweler'
6
+ Jeweler::Tasks.new do |gem|
7
+ gem.name = "phone_wrangler"
8
+ gem.summary = %Q{Handle phone numbers intelligently}
9
+ gem.description = %Q{Handle phone numbers intelligently}
10
+ gem.email = "rew@erebor.com"
11
+ gem.homepage = "http://github.com/erebor/phone_wrangler"
12
+ gem.authors = ["Ryan Waldron"]
13
+ gem.add_development_dependency "thoughtbot-shoulda"
14
+ # gem is a Gem::Specification... see http://www.rubygems.org/read/chapter/20 for additional settings
15
+ end
16
+ Jeweler::GemcutterTasks.new
17
+ rescue LoadError
18
+ puts "Jeweler (or a dependency) not available. Install it with: sudo gem install jeweler"
19
+ end
20
+
21
+ require 'rake/testtask'
22
+ Rake::TestTask.new(:test) do |test|
23
+ test.libs << 'lib' << 'test'
24
+ test.pattern = 'test/**/*_test.rb'
25
+ test.verbose = true
26
+ end
27
+
28
+ begin
29
+ require 'rcov/rcovtask'
30
+ Rcov::RcovTask.new do |test|
31
+ test.libs << 'test'
32
+ test.pattern = 'test/**/*_test.rb'
33
+ test.verbose = true
34
+ end
35
+ rescue LoadError
36
+ task :rcov do
37
+ abort "RCov is not available. In order to run rcov, you must: sudo gem install spicycode-rcov"
38
+ end
39
+ end
40
+
41
+ task :test => :check_dependencies
42
+
43
+ task :default => :test
44
+
45
+ require 'rake/rdoctask'
46
+ Rake::RDocTask.new do |rdoc|
47
+ if File.exist?('VERSION')
48
+ version = File.read('VERSION')
49
+ else
50
+ version = ""
51
+ end
52
+
53
+ rdoc.rdoc_dir = 'rdoc'
54
+ rdoc.title = "phone_wrangler #{version}"
55
+ rdoc.rdoc_files.include('README*')
56
+ rdoc.rdoc_files.include('lib/**/*.rb')
57
+ end
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 0.0.0
@@ -0,0 +1,195 @@
1
+ module PhoneWrangler
2
+
3
+ class PhoneNumber
4
+
5
+ NUMBER_PARTS = [:area_code, :prefix, :number, :extension]
6
+ attr_accessor :area_code, :prefix, :number, :extension
7
+
8
+ unless defined? @@default_area_code
9
+ @@default_area_code = nil
10
+ end
11
+
12
+ def self.default_area_code
13
+ @@default_area_code
14
+ end
15
+
16
+ def self.default_area_code=(val)
17
+ @@default_area_code = val
18
+ end
19
+
20
+ def default_area_code
21
+ @@default_area_code
22
+ end
23
+
24
+ #-------------------args-----------------------------------------
25
+ def initialize(args='')
26
+ @raw = args
27
+ self.[] args
28
+ end
29
+
30
+ def set(args)
31
+ self.[] args
32
+ end
33
+
34
+ def [] (args)
35
+ case args
36
+ when String
37
+ parse_from_string(args)
38
+ when Hash
39
+ args = { :area_code => PhoneNumber.default_area_code }.merge(args)
40
+ NUMBER_PARTS.each do |key|
41
+ send("#{key}=", args[key]) if args[key]
42
+ end
43
+ when Array
44
+ self.pack!(args)
45
+ else
46
+ raise ArgumentError.new("Sorry, can't handle arguments of type #{args.class}")
47
+ end
48
+ end
49
+
50
+ def has_area_code?
51
+ ! area_code.nil?
52
+ end
53
+
54
+ def == other
55
+ case other
56
+ when PhoneNumber
57
+ self.unpack == other.unpack
58
+ when String, Hash
59
+ self.unpack == PhoneNumber.new(other).unpack
60
+ else
61
+ false
62
+ end
63
+ end
64
+
65
+ #------------------------------------------------------------
66
+ def to_s(format = nil)
67
+ if format.nil?
68
+ format = ''
69
+ format += "(%a) " unless area_code.nil? or area_code.empty?
70
+ format += "%p-" unless prefix.nil? or area_code.empty?
71
+ format += "%n" unless number.nil? or area_code.empty?
72
+ format += " x%e" unless extension.nil? or area_code.empty?
73
+ end
74
+ format_number(format)
75
+ end
76
+
77
+ def raw
78
+ @raw
79
+ end
80
+
81
+ # TODO: Should #digits method include the extension digits at all? Probably not
82
+ # with an 'x' anyway.
83
+ def digits
84
+ digitstring = ''
85
+ [:area_code, :prefix, :number].each {|part|
86
+ digitstring += self.send(part).to_s unless self.send(part).nil?
87
+ }
88
+ digitstring += " x#{extension}" unless extension.nil?
89
+ digitstring
90
+ end
91
+
92
+ # There are lots of regexp-for-phone-number dissussions, but I found this one most useful:
93
+ # http://stackoverflow.com/questions/123559/a-comprehensive-regex-for-phone-number-validation
94
+ # Nice discussion here, and it had this one, which became the germ of mine. I added optional
95
+ # parentheses around the area code, the /x and spacing for readability, and changed out \W for
96
+ # [.\/-] for the delimiters to tighten what I'd accept a little bit.
97
+ #
98
+ # The original was (in perl)
99
+ # my $us_phone_regex = '1?\s*\W\s*([2-9][0-8][0-9])\W*([2-9][0-9]{2})\W*([0-9]{4})(\se?x?t?(\d*))?';
100
+
101
+ def parse_from_string(raw_string)
102
+ # Optional 1 -./ 256 (opt) 456 -./ 1234 ext(opt) 1234 (opt)
103
+ phone_regexp = /1? \s* [.\/-]? \s* [\(]?([2-9][0-8][0-9])?[\)]? \s* [.\/-]? \s* ([2-9][0-9]{2}) \s* [.\/-]? \s* ([0-9]{4}) \s* (\s*e?x?t?\s*(\d+))?/x
104
+ match = phone_regexp.match(raw_string)
105
+ if ! match.nil?
106
+ # puts "Setting values #{match.captures.pretty_inspect}"
107
+ @area_code = match.captures[0]
108
+ @prefix = match.captures[1]
109
+ @number = match.captures[2]
110
+ @extension = match.captures[4]
111
+ else
112
+ # puts "No matchy :("
113
+ end
114
+
115
+ if ! default_area_code.nil? and ( @area_code.nil? or @area_code.empty? )
116
+ @area_code = default_area_code
117
+ end
118
+ end
119
+
120
+ #------------------------------------------------------------
121
+ def pack(args)
122
+
123
+ phArea = ''
124
+ phPrefix = ''
125
+ phNumber = ''
126
+ phExtension = ''
127
+
128
+ if args.size >= 3
129
+ phArea = args[0].to_s
130
+ phPrefix = args[1].to_s
131
+ phNumber = args[3].to_s
132
+ if args.size == 4
133
+ phExtension = args[4].to_s
134
+ end
135
+ end
136
+
137
+ return phArea + phPrefix + phNumber + phExtension
138
+ end
139
+
140
+ #------------------------------------------------------------
141
+ def pack!(args)
142
+ @phone_number = pack(args)
143
+ end
144
+
145
+ #------------------------------------------------------------
146
+ def unpack
147
+ return {
148
+ :area_code => area_code,
149
+ :prefix => prefix,
150
+ :number => number,
151
+ :extension => extension
152
+ }
153
+ end
154
+
155
+ # This next part borrowed from http://github.com/erebor/phone/blob/master/lib/phone.rb
156
+ private
157
+
158
+ def format_number(fmt)
159
+ fmt.
160
+ # gsub("%c", country_code || "").
161
+ gsub("%a", area_code || "").
162
+ gsub("%p", prefix || "").
163
+ # gsub("%A", area_code_long || "").
164
+ gsub("%n", number || "").
165
+ # gsub("%f", number1 || "").
166
+ # gsub("%l", number2 || "").
167
+ gsub("%e", extension || "")
168
+ end
169
+
170
+ end
171
+
172
+ end
173
+ =begin
174
+ home = PhoneNumber.new('800/555-2468 x012')
175
+ # or
176
+ home = PhoneNumber.new
177
+ home['800/555-2468 x012']
178
+ # or
179
+ # home = PhoneNumber.new
180
+ ({:area_code=>'800', :prefix=>'555', :number=>'2020'})
181
+
182
+ puts home.raw
183
+ puts home.area_code
184
+ puts home.prefix
185
+ puts home.number
186
+ puts home.extension
187
+ puts home.to_s
188
+ puts home.to_s("(%a) %p-%n")
189
+ puts home.pack({:area_code=>'555', :prefix=>'888', :number=>'1212'})
190
+ p home.unpack
191
+ puts home.pack!({:area_code=>'555', :prefix=>'888', :number=>'1212'})
192
+ puts home.formatted
193
+
194
+ =end
195
+
@@ -0,0 +1,243 @@
1
+ require 'test_helper'
2
+
3
+ class PhoneWranglerTest < Test::Unit::TestCase
4
+
5
+ include PhoneWrangler
6
+
7
+ context "Parsing a phone number" do
8
+
9
+ should "accept a string" do
10
+ assert_nothing_raised do
11
+ pn = PhoneNumber.new("1234")
12
+ end
13
+ end
14
+
15
+ should "accept a hash" do
16
+ assert_nothing_raised do
17
+ pn = PhoneNumber.new(:area_code => '256', :prefix => '555', :number => '1234')
18
+ end
19
+ end
20
+
21
+ should "raise ArgumentError when passed silly args" do
22
+ assert_raise(ArgumentError) { PhoneNumber.new(3.77) }
23
+ assert_raise(ArgumentError) { PhoneNumber.new(false) }
24
+ assert_raise(ArgumentError) { PhoneNumber.new(/wicky/) }
25
+ end
26
+
27
+ should "return a PhoneNumber object" do
28
+ pn = PhoneNumber.new("123-456-7685")
29
+ assert_equal PhoneNumber, pn.class
30
+ end
31
+
32
+ should "correctly parse phone number strings" do
33
+ pn = PhoneNumber.new("(256) 555-1234")
34
+ assert_equal '256', pn.area_code
35
+ assert_equal '555', pn.prefix
36
+ assert_equal '1234', pn.number
37
+ assert pn.extension.nil?
38
+ pn = PhoneNumber.new("1-234-567-8901")
39
+ assert_equal '234', pn.area_code
40
+ assert_equal '567', pn.prefix
41
+ assert_equal '8901', pn.number
42
+ assert pn.extension.nil?
43
+ pn = PhoneNumber.new("1-234-567-8901x1234")
44
+ assert_equal '234', pn.area_code
45
+ assert_equal '567', pn.prefix
46
+ assert_equal '8901', pn.number
47
+ assert_equal '1234', pn.extension
48
+ pn = PhoneNumber.new("1-234-567-8901 x1234")
49
+ assert_equal '234', pn.area_code
50
+ assert_equal '567', pn.prefix
51
+ assert_equal '8901', pn.number
52
+ assert_equal '1234', pn.extension
53
+ pn = PhoneNumber.new("1-234-567-8901 ext 1234")
54
+ assert_equal '234', pn.area_code
55
+ assert_equal '567', pn.prefix
56
+ assert_equal '8901', pn.number
57
+ assert_equal '1234', pn.extension
58
+ pn = PhoneNumber.new("1-234-567-8901 ex1234")
59
+ assert_equal '234', pn.area_code
60
+ assert_equal '567', pn.prefix
61
+ assert_equal '8901', pn.number
62
+ assert_equal '1234', pn.extension
63
+ pn = PhoneNumber.new("1(234)567-8901x1234")
64
+ assert_equal '234', pn.area_code
65
+ assert_equal '567', pn.prefix
66
+ assert_equal '8901', pn.number
67
+ assert_equal '1234', pn.extension
68
+ pn = PhoneNumber.new("(234) 567-8901 x1234")
69
+ assert_equal '234', pn.area_code
70
+ assert_equal '567', pn.prefix
71
+ assert_equal '8901', pn.number
72
+ assert_equal '1234', pn.extension
73
+ pn = PhoneNumber.new("1(234) 567.8901 x1234")
74
+ assert_equal '234', pn.area_code
75
+ assert_equal '567', pn.prefix
76
+ assert_equal '8901', pn.number
77
+ assert_equal '1234', pn.extension
78
+ pn = PhoneNumber.new("1.234.567.8901 x1234")
79
+ assert_equal '234', pn.area_code
80
+ assert_equal '567', pn.prefix
81
+ assert_equal '8901', pn.number
82
+ assert_equal '1234', pn.extension
83
+ pn = PhoneNumber.new("234.567.8901 x1234")
84
+ assert_equal '234', pn.area_code
85
+ assert_equal '567', pn.prefix
86
+ assert_equal '8901', pn.number
87
+ assert_equal '1234', pn.extension
88
+ pn = PhoneNumber.new("234/567/8901 xt1234")
89
+ assert_equal '234', pn.area_code
90
+ assert_equal '567', pn.prefix
91
+ assert_equal '8901', pn.number
92
+ assert_equal '1234', pn.extension
93
+ pn = PhoneNumber.new("1 234.567/8901 xt1234")
94
+ assert_equal '234', pn.area_code
95
+ assert_equal '567', pn.prefix
96
+ assert_equal '8901', pn.number
97
+ assert_equal '1234', pn.extension
98
+ pn = PhoneNumber.new("1-234.567/8901 xt1234")
99
+ assert_equal '234', pn.area_code
100
+ assert_equal '567', pn.prefix
101
+ assert_equal '8901', pn.number
102
+ assert_equal '1234', pn.extension
103
+ pn = PhoneNumber.new("1/234.567/8901 xt1234")
104
+ assert_equal '234', pn.area_code
105
+ assert_equal '567', pn.prefix
106
+ assert_equal '8901', pn.number
107
+ assert_equal '1234', pn.extension
108
+ pn = PhoneNumber.new("12345678901x1234")
109
+ assert_equal '234', pn.area_code
110
+ assert_equal '567', pn.prefix
111
+ assert_equal '8901', pn.number
112
+ assert_equal '1234', pn.extension
113
+ end
114
+
115
+ should "correctly parse short phone number strings" do
116
+ pn = PhoneNumber.new("5678901x1234")
117
+ assert pn.area_code.nil?
118
+ assert_equal '567', pn.prefix
119
+ assert_equal '8901', pn.number
120
+ assert_equal '1234', pn.extension
121
+ pn = PhoneNumber.new("567-8901")
122
+ assert pn.area_code.nil?
123
+ assert_equal '567', pn.prefix
124
+ assert_equal '8901', pn.number
125
+ assert pn.extension.nil?
126
+ pn_short = PhoneNumber.new("456-7890")
127
+ pn_parts = PhoneNumber.new(:prefix => '456', :number => '7890')
128
+ assert_equal pn_parts, pn_short
129
+ end
130
+
131
+ should "allow a default area_code to be set for the class" do
132
+ PhoneNumber.default_area_code = "256"
133
+ assert_equal "256", PhoneNumber.default_area_code
134
+ PhoneNumber.default_area_code = nil # put it back, it's a class attr
135
+ end
136
+ end
137
+
138
+ context "With a default_area_code set" do
139
+ setup do
140
+ PhoneNumber.default_area_code = "404"
141
+ end
142
+
143
+ teardown do
144
+ PhoneNumber.default_area_code = nil # put it back, it's a class attr
145
+ end
146
+
147
+ should "use default area_code if one is not provided (String)" do
148
+ pn = PhoneNumber.new("456-7890")
149
+ assert_equal "404", pn.area_code
150
+ end
151
+
152
+ should "use default area_code if one is not provided (Hash)" do
153
+ pn = PhoneNumber.new(:prefix => '456', :number => "7890")
154
+ assert_equal "404", pn.area_code
155
+ end
156
+
157
+ should "ignore default_area_code if one is passed in (String)" do
158
+ pn = PhoneNumber.new("256-456-7890")
159
+ assert_equal "256", pn.area_code
160
+ end
161
+
162
+ should "ignore default_area_code if one is passed in (Hash)" do
163
+ pn = PhoneNumber.new(:area_code => '256', :prefix => '456', :number => "7890")
164
+ assert_equal "256", pn.area_code
165
+ end
166
+
167
+ should "return default area_code if it has one" do
168
+ assert_equal '404', PhoneNumber.default_area_code
169
+ PhoneNumber.default_area_code = nil # put it back, it's a class attr
170
+ assert_equal nil, PhoneNumber.default_area_code
171
+ end
172
+
173
+ should "use default area_code in comparison is needed on either side" do
174
+ pn1 = PhoneNumber.new("456-7890")
175
+ pn2 = PhoneNumber.new("404-456-7890")
176
+ assert pn1 == pn2
177
+ end
178
+ end
179
+
180
+ context "Printing a phone number" do
181
+ setup do
182
+ @phone_hash = {:area_code => '256', :prefix => '555', :number => '1234'}
183
+ @pn = PhoneNumber.new(@phone_hash)
184
+ end
185
+
186
+ should "respond to to_s with a String" do
187
+ assert_equal String, @pn.to_s.class
188
+ end
189
+
190
+ should "format numbers properly with to_s" do
191
+ assert_equal "(256) 555-1234", @pn.to_s
192
+ end
193
+
194
+ should "correctly interpolate format string" do
195
+ assert_equal "256 FF 555--1234", @pn.to_s("%a FF %p--%n")
196
+ assert_equal "256-555-1234", @pn.to_s("%a-%p-%n")
197
+ assert_equal "256/555-1234", @pn.to_s("%a/%p-%n")
198
+ assert_equal "(256) 555-1234", @pn.to_s("(%a) %p-%n")
199
+ assert_equal "256.555.1234", @pn.to_s("%a.%p.%n")
200
+ end
201
+
202
+ should "correctly interpolate format string with missing elements" do
203
+ @pn = PhoneNumber.new("431-4310")
204
+ # Make sure to_s doesn't barf based on the requested elements in the format string
205
+ assert_equal " FF 431--4310", @pn.to_s("%a FF %p--%n")
206
+ end
207
+
208
+
209
+ should "return raw data" do
210
+ assert_equal @phone_hash, @pn.raw
211
+ end
212
+
213
+ should "provide a digits-only version" do
214
+ output = @pn.digits
215
+ assert /^\d+$/ === output
216
+ assert_equal @phone_hash[:area_code]+@phone_hash[:prefix]+@phone_hash[:number], output
217
+ end
218
+ end
219
+
220
+ context "Comparing phone numbers" do
221
+ setup do
222
+ @phone_hash = {:area_code => '256', :prefix => '555', :number => '1234'}
223
+ @pn = PhoneNumber.new(@phone_hash)
224
+ end
225
+
226
+ should "be equal to itself" do
227
+ assert @pn == @pn
228
+ end
229
+
230
+ should "be equal to an identical PhoneNumber" do
231
+ @pn_new = PhoneNumber.new(@phone_hash)
232
+ assert @pn == @pn_new
233
+ end
234
+
235
+ should "should return true when == to an equivalent String" do
236
+ assert @pn == "256-555-1234"
237
+ end
238
+
239
+ should "should return true when == to an equivalent Hash" do
240
+ assert @pn == {:area_code => '256', :prefix => '555', :number => '1234'}
241
+ end
242
+ end
243
+ end
@@ -0,0 +1,10 @@
1
+ require 'rubygems'
2
+ require 'test/unit'
3
+ require 'shoulda'
4
+
5
+ $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
6
+ $LOAD_PATH.unshift(File.dirname(__FILE__))
7
+ require 'phone_wrangler'
8
+
9
+ class Test::Unit::TestCase
10
+ end
metadata ADDED
@@ -0,0 +1,74 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: phone_wrangler
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.0
5
+ platform: ruby
6
+ authors:
7
+ - Ryan Waldron
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+
12
+ date: 2010-01-13 00:00:00 -06:00
13
+ default_executable:
14
+ dependencies:
15
+ - !ruby/object:Gem::Dependency
16
+ name: thoughtbot-shoulda
17
+ type: :development
18
+ version_requirement:
19
+ version_requirements: !ruby/object:Gem::Requirement
20
+ requirements:
21
+ - - ">="
22
+ - !ruby/object:Gem::Version
23
+ version: "0"
24
+ version:
25
+ description: Handle phone numbers intelligently
26
+ email: rew@erebor.com
27
+ executables: []
28
+
29
+ extensions: []
30
+
31
+ extra_rdoc_files:
32
+ - LICENSE
33
+ - README.rdoc
34
+ files:
35
+ - .document
36
+ - .gitignore
37
+ - LICENSE
38
+ - README.rdoc
39
+ - Rakefile
40
+ - VERSION
41
+ - lib/phone_wrangler.rb
42
+ - test/phone_wrangler_test.rb
43
+ - test/test_helper.rb
44
+ has_rdoc: true
45
+ homepage: http://github.com/erebor/phone_wrangler
46
+ licenses: []
47
+
48
+ post_install_message:
49
+ rdoc_options:
50
+ - --charset=UTF-8
51
+ require_paths:
52
+ - lib
53
+ required_ruby_version: !ruby/object:Gem::Requirement
54
+ requirements:
55
+ - - ">="
56
+ - !ruby/object:Gem::Version
57
+ version: "0"
58
+ version:
59
+ required_rubygems_version: !ruby/object:Gem::Requirement
60
+ requirements:
61
+ - - ">="
62
+ - !ruby/object:Gem::Version
63
+ version: "0"
64
+ version:
65
+ requirements: []
66
+
67
+ rubyforge_project:
68
+ rubygems_version: 1.3.5
69
+ signing_key:
70
+ specification_version: 3
71
+ summary: Handle phone numbers intelligently
72
+ test_files:
73
+ - test/phone_wrangler_test.rb
74
+ - test/test_helper.rb