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 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 1AA")
14
- pc.valid? #=> true
15
- pc.full? #=> true
16
- pc.outcode #=> "W1A"
17
- pc.incode #=> "1AA"
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? #=> true
23
- pc.full? #=> false
24
- pc.outcode #=> "W1A"
25
- pc.incode #=> nil
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").to_str #=> "W1A 1AA"
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.1"
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
- RE_OUTCODE = /[A-Z]{1,2}[0-9R][0-9A-Z]?/
3
- RE_INCODE = /[0-9][ABD-HJLNP-UW-Z]{2}/
4
- RE_OUTCODE_ONLY = /\A(#{RE_OUTCODE})\Z/
5
- RE_FULL = /\A(#{RE_OUTCODE}) ?(#{RE_INCODE})\Z/
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.upcase
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
- raw.match(RE_FULL) || raw.match(RE_OUTCODE_ONLY)
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
- raw.match(RE_FULL)
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
- raw[RE_FULL, 1] || raw[RE_OUTCODE_ONLY]
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
- raw[RE_FULL, 2]
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 to_str
71
+ def norm
43
72
  [outcode, incode].compact.join(" ")
44
73
  end
45
- alias_method :to_s, :to_str
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
@@ -5,75 +5,81 @@ require "uk_postcode"
5
5
 
6
6
  class UKPostcodeTest < Test::Unit::TestCase
7
7
 
8
- VALID_SAMPLES = [ "A9 9AA", "A99 9AA", "AA9 9AA", "AA99 9AA", "A9A 9AA", "AA9A 9AA",
9
- "SW1A 0AA", "SW1A 0PW", "SW1A 1AA", "SW1A 2HQ", "W1A 1AA", "W1A 1AB",
10
- "N81 1ER", "EH99 1SP" ]
11
- VALID_OUTCODES = VALID_SAMPLES.map{ |s| s.split(/\s/).first }
12
- VALID_INCODES = VALID_SAMPLES.map{ |s| s.split(/\s/).last }
13
-
14
- context "full samples with spaces" do
15
- setup do
16
- @samples = VALID_SAMPLES
17
- end
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
- should "all be valid" do
20
- @samples.each do |sample|
21
- assert UKPostcode.new(sample).valid?, "'#{sample}' should be valid"
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
- should "all be full" do
26
- @samples.each do |sample|
27
- assert UKPostcode.new(sample).full?, "'#{sample}' should be full"
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
- should "extract outcodes" do
32
- @samples.zip(VALID_OUTCODES).each do |sample, outcode|
33
- assert_equal outcode, UKPostcode.new(sample).outcode
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
- should "extract incodes" do
38
- @samples.zip(VALID_INCODES).each do |sample, incode|
39
- assert_equal incode, UKPostcode.new(sample).incode
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
- context "full samples without spaces" do
45
- setup do
46
- @samples = VALID_SAMPLES.map{ |s| s.sub(/\s/, "") }
47
- end
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
- should "all be valid" do
50
- @samples.each do |sample|
51
- assert UKPostcode.new(sample).valid?, "'#{sample}' should be valid"
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
- should "all be full" do
56
- @samples.each do |sample|
57
- assert UKPostcode.new(sample).full?, "'#{sample}' should be full"
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
- should "extract outcodes" do
62
- @samples.zip(VALID_OUTCODES).each do |sample, outcode|
63
- assert_equal outcode, UKPostcode.new(sample).outcode
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
- should "extract incodes" do
68
- @samples.zip(VALID_INCODES).each do |sample, incode|
69
- assert_equal incode, UKPostcode.new(sample).incode
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 = VALID_OUTCODES
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 for to_str" do
141
- assert_equal "", @postcode.to_str
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").to_str
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").to_str
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").to_str
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 "have same output for to_s and to_str" do
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.to_str, s.to_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.1
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-06 00:00:00 +00:00
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