uk_postcode 0.0.1 → 0.0.2
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.
- 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
|