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 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