uk_postcode 0.0.1 → 0.0.2
Sign up to get free protection for your applications and to get access to all the features.
- data/README.md +25 -11
- data/Rakefile +2 -2
- data/lib/uk_postcode.rb +67 -11
- data/test/samples/wikipedia.list +36 -0
- data/test/test_samples.rb +28 -0
- data/test/test_uk_postcode.rb +93 -54
- metadata +4 -2
data/README.md
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
uk_postcode
|
2
2
|
===========
|
3
3
|
|
4
|
-
UK postcode parsing and validation for Ruby.
|
4
|
+
UK postcode parsing and validation for Ruby. I've checked it against every postcode I can get my hands on: that's about 1.8 million of them.
|
5
5
|
|
6
6
|
Usage
|
7
7
|
-----
|
@@ -10,23 +10,37 @@ Usage
|
|
10
10
|
|
11
11
|
Validate and extract sections of a full postcode:
|
12
12
|
|
13
|
-
pc = UKPostcode.new("W1A
|
14
|
-
pc.valid?
|
15
|
-
pc.full?
|
16
|
-
pc.outcode
|
17
|
-
pc.incode
|
13
|
+
pc = UKPostcode.new("W1A 2AB")
|
14
|
+
pc.valid? #=> true
|
15
|
+
pc.full? #=> true
|
16
|
+
pc.outcode #=> "W1A"
|
17
|
+
pc.incode #=> "2AB"
|
18
|
+
pc.area #=> "W"
|
19
|
+
pc.district #=> "1A"
|
20
|
+
pc.sector #=> "2"
|
21
|
+
pc.unit #=> "AB"
|
18
22
|
|
19
23
|
Or of a partial postcode:
|
20
24
|
|
21
25
|
pc = UKPostcode.new("W1A")
|
22
|
-
pc.valid?
|
23
|
-
pc.full?
|
24
|
-
pc.outcode
|
25
|
-
pc.incode
|
26
|
+
pc.valid? #=> true
|
27
|
+
pc.full? #=> false
|
28
|
+
pc.outcode #=> "W1A"
|
29
|
+
pc.incode #=> nil
|
30
|
+
pc.area #=> "W"
|
31
|
+
pc.district #=> "1A"
|
32
|
+
pc.sector #=> nil
|
33
|
+
pc.unit #=> nil
|
26
34
|
|
27
35
|
Normalise postcodes:
|
28
36
|
|
29
|
-
UKPostcode.new("w1a1aa").
|
37
|
+
UKPostcode.new("w1a1aa").norm #=> "W1A 1AA"
|
38
|
+
|
39
|
+
Fix mistakes with IO/10:
|
40
|
+
|
41
|
+
pc = UKPostcode.new("WIA OAA")
|
42
|
+
pc.outcode #=> "W1A"
|
43
|
+
pc.incode #=> "0AA"
|
30
44
|
|
31
45
|
Gem?
|
32
46
|
----
|
data/Rakefile
CHANGED
@@ -21,14 +21,14 @@ end
|
|
21
21
|
|
22
22
|
spec = Gem::Specification.new do |s|
|
23
23
|
s.name = "uk_postcode"
|
24
|
-
s.version = "0.0.
|
24
|
+
s.version = "0.0.2"
|
25
25
|
s.summary = "UK postcode parsing and validation"
|
26
26
|
s.author = "Paul Battley"
|
27
27
|
s.email = "pbattley@gmail.com"
|
28
28
|
|
29
29
|
s.has_rdoc = true
|
30
30
|
|
31
|
-
s.files = %w(Rakefile README.md) + Dir.glob("{bin,test,lib}/**/*")
|
31
|
+
s.files = %w(Rakefile README.md) + Dir.glob("{bin,test,lib}/**/*") - ["test/samples/postzon.list"]
|
32
32
|
s.executables = FileList["bin/**"].map { |f| File.basename(f) }
|
33
33
|
|
34
34
|
s.require_paths = ["lib"]
|
data/lib/uk_postcode.rb
CHANGED
@@ -1,46 +1,102 @@
|
|
1
1
|
class UKPostcode
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
|
2
|
+
MATCH = /\A
|
3
|
+
( [A-PR-UWYZ][A-Z]? ) # area
|
4
|
+
( [0-9IO][0-9A-HJKMNPR-YIO]? ) # district
|
5
|
+
(?:
|
6
|
+
\s?
|
7
|
+
( [0-9IO] ) # sector
|
8
|
+
( [ABD-HJLNPQ-Z10]{2} ) # unit
|
9
|
+
)?
|
10
|
+
\Z/x
|
6
11
|
|
7
12
|
attr_reader :raw
|
8
13
|
|
9
14
|
# Initialise a new UKPostcode instance from the given postcode string
|
10
15
|
#
|
11
16
|
def initialize(postcode_as_string)
|
12
|
-
@raw = postcode_as_string
|
17
|
+
@raw = postcode_as_string
|
13
18
|
end
|
14
19
|
|
15
20
|
# Returns true if the postcode is a valid full postcode (e.g. W1A 1AA) or outcode (e.g. W1A)
|
16
21
|
#
|
17
22
|
def valid?
|
18
|
-
|
23
|
+
!!outcode
|
19
24
|
end
|
20
25
|
|
21
26
|
# Returns true if the postcode is a valid full postcode (e.g. W1A 1AA)
|
22
27
|
#
|
23
28
|
def full?
|
24
|
-
|
29
|
+
!!(outcode && incode)
|
25
30
|
end
|
26
31
|
|
27
32
|
# The left-hand part of the postcode, e.g. W1A 1AA -> W1A
|
28
33
|
#
|
29
34
|
def outcode
|
30
|
-
|
35
|
+
area && district && [area, district].join
|
31
36
|
end
|
32
37
|
|
33
38
|
# The right-hand part of the postcode, e.g. W1A 1AA -> 1AA
|
34
39
|
#
|
35
40
|
def incode
|
36
|
-
|
41
|
+
sector && unit && [sector, unit].join
|
42
|
+
end
|
43
|
+
|
44
|
+
# The first part of the outcode, e.g. W1A 2AB -> W
|
45
|
+
#
|
46
|
+
def area
|
47
|
+
letters(parts[0])
|
48
|
+
end
|
49
|
+
|
50
|
+
# The second part of the outcode, e.g. W1A 2AB -> 1A
|
51
|
+
#
|
52
|
+
def district
|
53
|
+
digits(parts[1])
|
54
|
+
end
|
55
|
+
|
56
|
+
# The first part of the incode, e.g. W1A 2AB -> 2
|
57
|
+
#
|
58
|
+
def sector
|
59
|
+
digits(parts[2])
|
60
|
+
end
|
61
|
+
|
62
|
+
# The second part of the incode, e.g. W1A 2AB -> AB
|
63
|
+
#
|
64
|
+
def unit
|
65
|
+
letters(parts[3])
|
37
66
|
end
|
38
67
|
|
39
68
|
# Render the postcode as a normalised string, i.e. in upper case and with spacing.
|
40
69
|
# Returns an empty string if the postcode is not valid.
|
41
70
|
#
|
42
|
-
def
|
71
|
+
def norm
|
43
72
|
[outcode, incode].compact.join(" ")
|
44
73
|
end
|
45
|
-
alias_method :
|
74
|
+
alias_method :normalise, :norm
|
75
|
+
alias_method :normalize, :norm
|
76
|
+
|
77
|
+
alias_method :to_s, :raw
|
78
|
+
alias_method :to_str, :raw
|
79
|
+
|
80
|
+
def inspect(*args)
|
81
|
+
"<#{self.class.to_s} #{raw}>"
|
82
|
+
end
|
83
|
+
|
84
|
+
private
|
85
|
+
def parts
|
86
|
+
if @matched
|
87
|
+
@parts
|
88
|
+
else
|
89
|
+
@matched = true
|
90
|
+
matches = raw.upcase.match(MATCH) || []
|
91
|
+
@parts = (1..4).map{ |i| matches[i] }
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
95
|
+
def letters(s)
|
96
|
+
s && s.tr("10", "IO")
|
97
|
+
end
|
98
|
+
|
99
|
+
def digits(s)
|
100
|
+
s && s.tr("IO", "10")
|
101
|
+
end
|
46
102
|
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
# Special postcodes listed in http://en.wikipedia.org/wiki/UK_postcode
|
2
|
+
|
3
|
+
SW1A 0AA
|
4
|
+
SW1A 1AA
|
5
|
+
SW1A 2AA
|
6
|
+
BS98 1TL
|
7
|
+
BX1 1LT
|
8
|
+
BX5 5AT
|
9
|
+
CF99 1NA
|
10
|
+
DH99 1NS
|
11
|
+
E16 1XL
|
12
|
+
E98 1NW
|
13
|
+
E98 1SN
|
14
|
+
E98 1ST
|
15
|
+
E98 1TT
|
16
|
+
EC4Y 0HQ
|
17
|
+
EH99 1SP
|
18
|
+
EN8 9SL
|
19
|
+
G58 1SB
|
20
|
+
GIR 0AA
|
21
|
+
L30 4GB
|
22
|
+
LS98 1FD
|
23
|
+
M2 5BE
|
24
|
+
N81 1ER
|
25
|
+
S2 4SU
|
26
|
+
S6 1SW
|
27
|
+
SE1 8UJ
|
28
|
+
SE9 2UG
|
29
|
+
SN38 1NW
|
30
|
+
SW1A 0PW
|
31
|
+
SW1A 2HQ
|
32
|
+
SW1W 0DT
|
33
|
+
TS1 3BA
|
34
|
+
W1A 1AA
|
35
|
+
W1F 9DJ
|
36
|
+
GIR 0AA
|
@@ -0,0 +1,28 @@
|
|
1
|
+
$:.unshift File.join(File.dirname(__FILE__), "..", "lib")
|
2
|
+
require "test/unit"
|
3
|
+
require "shoulda"
|
4
|
+
require "uk_postcode"
|
5
|
+
|
6
|
+
class UKPostcodeTest < Test::Unit::TestCase
|
7
|
+
|
8
|
+
Dir[File.join(File.dirname(__FILE__), "samples", "*.list")].each do |path|
|
9
|
+
context "in sample file #{File.basename(path, ".list")}" do
|
10
|
+
setup do
|
11
|
+
@file = open(path)
|
12
|
+
end
|
13
|
+
|
14
|
+
teardown do
|
15
|
+
@file.close
|
16
|
+
end
|
17
|
+
|
18
|
+
should "be valid for each line in sample file" do
|
19
|
+
@file.each_line do |line|
|
20
|
+
next if line =~ /^#|^$/
|
21
|
+
sample = line.chomp.sub(/\s+/, "")
|
22
|
+
postcode = UKPostcode.new(sample)
|
23
|
+
assert postcode.valid?, "'#{sample}' should be valid"
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
data/test/test_uk_postcode.rb
CHANGED
@@ -5,75 +5,81 @@ require "uk_postcode"
|
|
5
5
|
|
6
6
|
class UKPostcodeTest < Test::Unit::TestCase
|
7
7
|
|
8
|
-
VALID_SAMPLES = [
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
8
|
+
VALID_SAMPLES = [
|
9
|
+
%w[A 9 9 AA], %w[A 99 9 AA], %w[AA 9 9 AA], %w[AA 99 9 AA], %w[A 9A 9 AA],
|
10
|
+
%w[AA 9A 9 AA], %w[SW 1A 0 AA], %w[SW 1A 0 PW], %w[SW 1A 1 AA], %w[SW 1A 2 HQ],
|
11
|
+
%w[W 1A 1 AA], %w[W 1A 1 AB], %w[N 81 1 ER], %w[EH 99 1 SP], %w[CV 1 1 FL],
|
12
|
+
%w[EX 1 1 AE], %w[TQ 1 1 AG]
|
13
|
+
]
|
14
|
+
|
15
|
+
{ "full samples with spaces" => lambda{ |a,b,c,d| [[a, b, " ", c, d].join, [a, b, c, d]] },
|
16
|
+
"full samples without spaces" => lambda{ |a,b,c,d| [[a, b, c, d].join, [a, b, c, d]] },
|
17
|
+
}.each do |desc, mapping|
|
18
|
+
context desc do
|
19
|
+
setup do
|
20
|
+
@samples = VALID_SAMPLES.map(&mapping)
|
21
|
+
end
|
18
22
|
|
19
|
-
|
20
|
-
|
21
|
-
|
23
|
+
should "all be valid" do
|
24
|
+
@samples.each do |sample, _|
|
25
|
+
assert UKPostcode.new(sample).valid?, "'#{sample}' should be valid"
|
26
|
+
end
|
22
27
|
end
|
23
|
-
end
|
24
28
|
|
25
|
-
|
26
|
-
|
27
|
-
|
29
|
+
should "all be full" do
|
30
|
+
@samples.each do |sample, _|
|
31
|
+
assert UKPostcode.new(sample).full?, "'#{sample}' should be full"
|
32
|
+
end
|
28
33
|
end
|
29
|
-
end
|
30
34
|
|
31
|
-
|
32
|
-
|
33
|
-
|
35
|
+
should "extract outcodes" do
|
36
|
+
@samples.each do |sample, parts|
|
37
|
+
assert_equal parts[0]+parts[1], UKPostcode.new(sample).outcode
|
38
|
+
end
|
34
39
|
end
|
35
|
-
end
|
36
40
|
|
37
|
-
|
38
|
-
|
39
|
-
|
41
|
+
should "extract incodes" do
|
42
|
+
@samples.each do |sample, parts|
|
43
|
+
assert_equal parts[2]+parts[3], UKPostcode.new(sample).incode
|
44
|
+
end
|
40
45
|
end
|
41
|
-
end
|
42
|
-
end
|
43
46
|
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
47
|
+
should "extract area" do
|
48
|
+
@samples.each do |sample, parts|
|
49
|
+
assert_equal parts[0], UKPostcode.new(sample).area
|
50
|
+
end
|
51
|
+
end
|
48
52
|
|
49
|
-
|
50
|
-
|
51
|
-
|
53
|
+
should "extract district" do
|
54
|
+
@samples.each do |sample, parts|
|
55
|
+
assert_equal parts[1], UKPostcode.new(sample).district
|
56
|
+
end
|
52
57
|
end
|
53
|
-
end
|
54
58
|
|
55
|
-
|
56
|
-
|
57
|
-
|
59
|
+
should "extract sector" do
|
60
|
+
@samples.each do |sample, parts|
|
61
|
+
assert_equal parts[2], UKPostcode.new(sample).sector
|
62
|
+
end
|
58
63
|
end
|
59
|
-
end
|
60
64
|
|
61
|
-
|
62
|
-
|
63
|
-
|
65
|
+
should "extract unit" do
|
66
|
+
@samples.each do |sample, parts|
|
67
|
+
assert_equal parts[3], UKPostcode.new(sample).unit
|
68
|
+
end
|
64
69
|
end
|
65
|
-
end
|
66
70
|
|
67
|
-
|
68
|
-
|
69
|
-
|
71
|
+
should "be the same when normalised" do
|
72
|
+
@samples.each do |sample, parts|
|
73
|
+
expected = [parts[0], parts[1], " ", parts[2], parts[3]].join
|
74
|
+
assert_equal expected, UKPostcode.new(sample).norm
|
75
|
+
end
|
70
76
|
end
|
71
77
|
end
|
72
78
|
end
|
73
79
|
|
74
80
|
context "outcode samples" do
|
75
81
|
setup do
|
76
|
-
@samples =
|
82
|
+
@samples = VALID_SAMPLES.map{ |a,b,c,d| [a, b].join }
|
77
83
|
end
|
78
84
|
|
79
85
|
should "all be valid" do
|
@@ -99,6 +105,12 @@ class UKPostcodeTest < Test::Unit::TestCase
|
|
99
105
|
assert_nil UKPostcode.new(sample).incode
|
100
106
|
end
|
101
107
|
end
|
108
|
+
|
109
|
+
should "be the same when normalised" do
|
110
|
+
@samples.each do |sample|
|
111
|
+
assert_equal sample, UKPostcode.new(sample).norm
|
112
|
+
end
|
113
|
+
end
|
102
114
|
end
|
103
115
|
|
104
116
|
context "when the postcode is supplied in lower case" do
|
@@ -137,8 +149,8 @@ class UKPostcodeTest < Test::Unit::TestCase
|
|
137
149
|
assert !@postcode.full?
|
138
150
|
end
|
139
151
|
|
140
|
-
should "return an empty string
|
141
|
-
assert_equal "", @postcode.
|
152
|
+
should "return an empty string when normalised" do
|
153
|
+
assert_equal "", @postcode.norm
|
142
154
|
end
|
143
155
|
|
144
156
|
should "return nil for outcode" do
|
@@ -153,22 +165,49 @@ class UKPostcodeTest < Test::Unit::TestCase
|
|
153
165
|
|
154
166
|
context "when used as a string" do
|
155
167
|
should "normalise spacing" do
|
156
|
-
assert_equal "W1A 1AA", UKPostcode.new("W1A1AA").
|
168
|
+
assert_equal "W1A 1AA", UKPostcode.new("W1A1AA").norm
|
157
169
|
end
|
158
170
|
|
159
171
|
should "convert case" do
|
160
|
-
assert_equal "W1A 1AA", UKPostcode.new("w1a 1aa").
|
172
|
+
assert_equal "W1A 1AA", UKPostcode.new("w1a 1aa").norm
|
161
173
|
end
|
162
174
|
|
163
175
|
should "ignore a missing incode" do
|
164
|
-
assert_equal "W1A", UKPostcode.new("W1A").
|
176
|
+
assert_equal "W1A", UKPostcode.new("W1A").norm
|
177
|
+
end
|
178
|
+
end
|
179
|
+
|
180
|
+
should "return original input for to_s" do
|
181
|
+
["W1A1AA", "w1a 1aa", "W1A"].each do |s|
|
182
|
+
postcode = UKPostcode.new(s)
|
183
|
+
assert_equal s, postcode.to_s
|
165
184
|
end
|
166
185
|
end
|
167
186
|
|
168
|
-
should "
|
187
|
+
should "return original input for to_str" do
|
169
188
|
["W1A1AA", "w1a 1aa", "W1A"].each do |s|
|
170
189
|
postcode = UKPostcode.new(s)
|
171
|
-
assert_equal s
|
190
|
+
assert_equal s, postcode.to_str
|
191
|
+
end
|
192
|
+
end
|
193
|
+
|
194
|
+
context "when letters are used instead of digits" do
|
195
|
+
context "in a full postcode" do
|
196
|
+
setup do
|
197
|
+
@postcode = UKPostcode.new("SWIA OPW")
|
198
|
+
end
|
199
|
+
|
200
|
+
should "be valid" do
|
201
|
+
assert @postcode.valid?
|
202
|
+
end
|
203
|
+
|
204
|
+
should "be full" do
|
205
|
+
assert @postcode.full?
|
206
|
+
end
|
207
|
+
|
208
|
+
should "normalise to digits" do
|
209
|
+
assert_equal "SW1A 0PW", @postcode.norm
|
210
|
+
end
|
172
211
|
end
|
173
212
|
end
|
174
213
|
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: uk_postcode
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0.
|
4
|
+
version: 0.0.2
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Paul Battley
|
@@ -9,7 +9,7 @@ autorequire:
|
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
11
|
|
12
|
-
date: 2010-01-
|
12
|
+
date: 2010-01-09 00:00:00 +00:00
|
13
13
|
default_executable:
|
14
14
|
dependencies:
|
15
15
|
- !ruby/object:Gem::Dependency
|
@@ -33,6 +33,8 @@ extra_rdoc_files: []
|
|
33
33
|
files:
|
34
34
|
- Rakefile
|
35
35
|
- README.md
|
36
|
+
- test/samples/wikipedia.list
|
37
|
+
- test/test_samples.rb
|
36
38
|
- test/test_uk_postcode.rb
|
37
39
|
- lib/uk_postcode.rb
|
38
40
|
has_rdoc: true
|