uk_postcode 1.0.1 → 2.0.0.alpha

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: fd5d3327b3f2fff64453bb32cbf18941c6e668ed
4
- data.tar.gz: 9e8d9284064d002eb6b004ed1fbd0d5779b9a9aa
3
+ metadata.gz: a2918ca3cc4b63e92b62146b63e57897b31166f0
4
+ data.tar.gz: c6689e38f4f8e4583d74f9bf06c5c617a16f66b9
5
5
  SHA512:
6
- metadata.gz: efa1121b17ba18e5d1c33ddd6a8d0ee7a239e1c1baa35a1699400e5e2f3892542636d585fe597bd66be3fa7a4f0148ec824d5a335430a1dd06daf13b6887325e
7
- data.tar.gz: 26f43a092dce9d1f706cf36eee76af39d076b988af4d08ff581ee7ac21c84465f8a2f8d34b570e7021b68038f9e1558bf6c28e21b3c42bd18c221a7526122b31
6
+ metadata.gz: ea95753c01bc600400b6223b5da95957170db8d1e39f10b80f045e5379f248235a33e38b558c64f91b9c3548d616414dfac2f35172cc18b1d29da372418df458
7
+ data.tar.gz: d34d691408d759e967cf626aecec25f92bbff1a10507bff46a082ca96a5ab37e44a2ad185116998a6f4d040c68bcdea1863cbf7487541dbf6928f40ec2b3a8f4
data/COPYING.txt ADDED
@@ -0,0 +1,25 @@
1
+ Copyright (c) 2010-2014 Paul Battley
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining a copy
4
+ of this software and associated documentation files (the "Software"), to deal
5
+ in the Software without restriction, including without limitation the rights
6
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7
+ copies of the Software, and to permit persons to whom the Software is
8
+ furnished to do so, subject to the following conditions:
9
+
10
+ The above copyright notice and this permission notice shall be included in all
11
+ copies or substantial portions of the Software.
12
+
13
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
19
+ SOFTWARE.
20
+
21
+ The regular expressions in lookup.rb are generated from the ONS Postcode
22
+ Directory under the terms of the Open Government Licence (OGL). The following
23
+ source accreditation is given to fulfil the terms of this licence.
24
+
25
+ http://www.ons.gov.uk/ons/guide-method/geography/products/postcode-directories/-nspp-/index.html
data/README.md CHANGED
@@ -11,6 +11,7 @@ Features:
11
11
  * Allows extraction of fields within postcode.
12
12
  * Validated against 2.5 million postcodes in England, Wales, Scotland, Northern
13
13
  Ireland, the Channel Islands, and the Isle of Man.
14
+ * Finds the country corresponding to a postcode, where possible.
14
15
 
15
16
  ## Usage
16
17
 
@@ -18,52 +19,106 @@ Features:
18
19
  require "uk_postcode"
19
20
  ```
20
21
 
21
- Validate and extract sections of a full postcode:
22
+ Parse and extract sections of a full postcode:
22
23
 
23
24
  ```ruby
24
- pc = UKPostcode.new("W1A 2AB")
25
- pc.valid? #=> true
26
- pc.full? #=> true
27
- pc.outcode #=> "W1A"
28
- pc.incode #=> "2AB"
29
- pc.area #=> "W"
30
- pc.district #=> "1A"
31
- pc.sector #=> "2"
32
- pc.unit #=> "AB"
25
+ pc = UKPostcode.parse("W1A 2AB")
26
+ pc.valid? # => true
27
+ pc.full? # => true
28
+ pc.outcode # => "W1A"
29
+ pc.incode # => "2AB"
30
+ pc.area # => "W"
31
+ pc.district # => "1A"
32
+ pc.sector # => "2"
33
+ pc.unit # => "AB"
33
34
  ```
34
35
 
35
36
  Or of a partial postcode:
36
37
 
37
38
  ```ruby
38
- pc = UKPostcode.new("W1A")
39
- pc.valid? #=> true
40
- pc.full? #=> false
41
- pc.outcode #=> "W1A"
42
- pc.incode #=> nil
43
- pc.area #=> "W"
44
- pc.district #=> "1A"
45
- pc.sector #=> nil
46
- pc.unit #=> nil
39
+ pc = UKPostcode.parse("W1A")
40
+ pc.valid? # => true
41
+ pc.full? # => false
42
+ pc.outcode # => "W1A"
43
+ pc.incode # => nil
44
+ pc.area # => "W"
45
+ pc.district # => "1A"
46
+ pc.sector # => nil
47
+ pc.unit # => nil
47
48
  ```
48
49
 
49
- Normalise postcodes with `normalize` (or just `norm`):
50
+ Postcodes are converted to a normal or canonical form:
50
51
 
51
52
  ```ruby
52
- UKPostcode.new("w1a1aa").normalize #=> "W1A 1AA"
53
+ pc = UKPostcode.parse("w1a1aa")
54
+ pc.valid? # => true
55
+ pc.area # => "W"
56
+ pc.district # => "1A"
57
+ pc.sector # => "1"
58
+ pc.unit # => "AA
59
+ pc.to_s # => "W1A 1AA"
53
60
  ```
54
61
 
55
- Fix mistakes with IO/10:
62
+ And mistakes with I/1 and O/0 are corrected:
56
63
 
57
64
  ```ruby
58
- pc = UKPostcode.new("WIA OAA")
59
- pc.outcode #=> "W1A"
60
- pc.incode #=> "0AA"
65
+ pc = UKPostcode.parse("WIA OAA")
66
+ pc.valid? # => true
67
+ pc.area # => "W"
68
+ pc.district # => "1A"
69
+ pc.sector # => "0"
70
+ pc.unit # => "AA
71
+ pc.to_s # => "W1A 0AA"
61
72
  ```
62
73
 
63
- ## Gem?
74
+ Find the country of a full or partial postcode (if possible: some outcodes span
75
+ countries):
64
76
 
65
- ```sh
66
- $ gem install uk_postcode
77
+ ```ruby
78
+ UKPostcode.parse("W1A 1AA").country # => :england
79
+ UKPostcode.parse("BT4").country # => :northern_ireland
80
+ UKPostcode.parse("CA6").country # => :unknown
81
+ UKPostcode.parse("CA6 5HS").country # => :scotland
82
+ UKPostcode.parse("CA6 5HT").country # => :england
83
+ ```
84
+
85
+ The country returned for a postcode is derived from the [ONS Postcode
86
+ Directory][onspd] and might not always be correct in a border region:
87
+
88
+ > Users should note that postcodes that straddle two geographic areas will be
89
+ > assigned to the area where the mean grid reference of all the addresses
90
+ > within the postcode falls.
91
+
92
+ Invalid postcodes:
93
+
94
+ ```ruby
95
+ pc = UKPostcode.parse("Not Valid")
96
+ pc.valid? # => false
97
+ pc.full? # => false
98
+ pc.area # => nil
99
+ pc.to_s # => "Not valid"
100
+ pc.country # => :unknown
101
+ ```
102
+
103
+ ## For users of version 1.x
104
+
105
+ The interface has changed significantly, so code that worked with version 1.x
106
+ will not work with version 2.x without changes.
107
+
108
+ Specifically:
109
+
110
+ * Use `UKPostcode.parse(str)` where you previously used `UKPostcode.new(str)`.
111
+ * `parse` will return either a `GeographicPostcode`, a `GiroPostcode`, or an
112
+ `InvalidPostcode`.
113
+ * You may prefer to use `GeographicPostcode.parse` directly if you wish to
114
+ exclude `GIR 0AA` and invalid postcodes.
115
+
116
+ ## As a gem
117
+
118
+ In your `Gemfile`:
119
+
120
+ ```ruby
121
+ gem "uk_postcode", "~> 2.0.0.alpha"
67
122
  ```
68
123
 
69
124
  ## Testing
@@ -71,44 +126,39 @@ $ gem install uk_postcode
71
126
  To run the test suite:
72
127
 
73
128
  ```sh
74
- $ rake
129
+ $ make
75
130
  ```
76
131
 
77
132
  The full list of UK postcodes is not included in the repository due to its
78
- size.
79
- Additional sample files under `test/samples` will automatically be used.
133
+ size, but will be fetched automatically from [mySociety][mys].
80
134
 
81
- The format of each line in a sample file must be `OOOOIII` or `OOO III` (where
82
- `O` = outcode, `I` = incode), e.g.:
135
+ If you are running an automatic build process, please find a way to cache these
136
+ files and run the tests via Rake instead:
83
137
 
84
138
  ```
85
- AB1 0AA
86
- BT109AH
139
+ $ rake
87
140
  ```
88
141
 
89
- You can obtain lists of postcodes by processing various datasets available
90
- from [mySociety][mys].
142
+ ## Licensing
91
143
 
92
- ### Code-Point Open
144
+ You may use this library according to the terms of the MIT License; see
145
+ COPYING.txt for details.
93
146
 
94
- This does not include postcodes for Northern Ireland, the Channel Islands,
95
- or the Isle of Man.
147
+ The regular expressions in `lookup.rb` are derived from the ONS Postcode
148
+ Directory according to the terms of the [Open Government
149
+ Licence][onspd-lic].
96
150
 
97
- ```sh
98
- $ cut -c 2-8 Data/CSV/*.csv | \
99
- sort -uV > test/samples/large/code_point_open.list
100
- ```
101
-
102
- ### ONS Postcode Directory
151
+ > Under the terms of the Open Government Licence (OGL) […] anyone wishing to
152
+ > use or re-use ONS material, whether commercially or privately, may do so
153
+ > freely without a specific application for a licence, subject to the
154
+ > conditions of the OGL and the Framework. Users reproducing ONS content must
155
+ > include a source accreditation to ONS.
103
156
 
104
- This includes postcodes for the whole UK as well as the Channel Islands and the
105
- Isle of Man.
106
- It also includes some defunct postcodes, most notably the NPT outcode, which
107
- must be filtered out.
108
-
109
- ```sh
110
- $ cut -c 2-8 ONSPD_*.csv | grep '[A-Z]' | grep -v ^NPT | \
111
- sort -uV > test/samples/large/onspd.list
112
- ```
157
+ In order to avoid the restrictive commercial terms of the Northern Ireland
158
+ data in the ONSPD, this is not used to generate the regular expressions.
159
+ Fortunately, Northern Ireland postcodes are very simple: they all start with
160
+ `BT`!
113
161
 
114
162
  [mys]: http://parlvid.mysociety.org/os/
163
+ [onspd]: http://www.ons.gov.uk/ons/guide-method/geography/products/postcode-directories/-nspp-/index.html
164
+ [onspd-lic]: http://www.ons.gov.uk/ons/guide-method/geography/beginner-s-guide/licences/index.html
@@ -0,0 +1,15 @@
1
+ require 'uk_postcode/country_lookup'
2
+
3
+ module UKPostcode
4
+ module CountryFinder
5
+ module_function
6
+
7
+ def country(postcode)
8
+ normalized = [postcode.outcode, postcode.incode].compact.join
9
+ COUNTRY_LOOKUP.each do |name, regexp|
10
+ return name if normalized.match(regexp)
11
+ end
12
+ :unknown
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,10 @@
1
+ module UKPostcode
2
+ COUNTRY_LOOKUP = {
3
+ england: /^(?:AL|B[123456789ABDHLNRS]|C(?:A(?:1|2|3|4|5|6(?:4|5(?:G|H[TU]|L[GHJLNPQRSTUWXYZ]|N|P|Q|R|S|T|U|W|X|Y)|6|7)|7|8|9)|B|H(?:1(?:1|2|3|4(?:A|B|D|E|F|G|H|J|L|N|P|Q[ABDFGHJNPQRSTUWX]|R|S|W|X|Y|Z)|5|6(?:A|B[DEFGHJU]|D[ABEFGHLNPQRSTUWXYZ]|E[ADEFGHJLNPQRSTUWXYZ]|F|G|H[DEFGJLNQSTUWXYZ]|J|L|N|P|Q[AP]|R|W|X|Y|Z)|9)|2(?:1|2|3|4|5|6|7|89(?:A[ABEFGHJLNPQRSTUWXYZ]|B)|99(?:A[BDEFGHJLNPQRSTUWXYZ]|B|D|E))|3(?:5|6|7|8|9|0|1|2|3|49(?:A[ABDEFGHJLNQRSTUWXYZ]|B|D|E|S|T|Z))|4(?:0(?:EJ|W[BDFGHRU]|Y[HJ]|Z[ADEFGHJLPTUWY])|7|8(?:A|B|D|E|H|J|L[BDEFLNPSUXYZ]|N[DEFGLNPQRSTUWXYZ]|P[ADEFGHJLNPQRSTUWXZ]|Q[ADU]|R[AD]|W[ABDEFGHJLPQRSTUWXYZ]|X|Y|Z|0|1|2|3|4|5|6|7|8|9)|9(?:A[DEFG]|BW|D[EFGHJLNPQRSTUW]|E[BDEFGJLNPQRSTUWXYZ]|F|G[ABJ]|H|J|L|N|P[XZ]|Q|R|T|U|W|X[SUY]|Y|Z|0|1|2|3|4|5|6|7|8|9)|1|2|3|4|5|6)|6(?:5[0123456789]|6[0123456789]|9AB|0|1|2|3|4)|708(?:A[ADEGHJLNPQRSTUWXYZ]|B|D)|883|9)|M|O|R|T|V|W)|D(?:A|E|G165(?:H[TUZ]|J[AB])|H|L|N|T|Y)|E[123456789CNX]|FY|G(?:I|L(?:1(?:1|2|3|4|5|9|0|6(?:7|8(?:A|B|D|E|H|J|L|N|P[ADEFGHJLNPQRSTUWXYZ]|Q|R|S|T|W|Y|Z)|9)|7|8)|2|3|4|5|6|7|8|9)|U)|H(?:A|D|G|P|R(?:1|2|3(?:5(?:AL|B[BDERZ]|E[NPRSTUWXYZ]|FE|H|Q[XYZ]|R[ABDFGNQW]|S[TUXY]|T[ABDEFGP]|W[NUX]|XA|Y[ADFJNQRSTWX])|6(?:A|B|D|E[ABDEFGHJLNPQRSTUWZ]|H[EFGHJPQSTUWXYZ]|J[ABDEFGHLNPQRSTZ]|L|N|P|Q|R|S|W|Y[ABDEFHJLNPQU]|Z))|4|5(?:3(?:A|B|D|E[ABDEFGHJLNQRSTUWXYZ]|F|G|H|J|L|N[ABEFGHJLNQ]|P[BFGHQTUXYZ]|QA|R[BDEFGHJLNPQRSTUWXYZ]|S|T|U|W|X|Y|Z)|8)|6|7|8|9)|U|X)|I[GP]|KT|L(?:1|2|3|4|5|6|7|8|9|A|D(?:71(?:DY|EA|N[BDEF]|T[NPRSUWY]|U[ABDEFW]|Y[SUY]|ZJ)|82(?:H[HLNPRSTUWY]|L[EGHLNPRSTUWY]|N[ABD]|P[RS]))|E|L145(?:B[UY]|D[ABDEFGLNPRSUWY]|E[ABDEFHLNW]|L[JP])|N|S|U)|M[123456789EK]|N(?:1|2|3|4|5|6|7|8|9|E|G|N|P(?:167(?:A|B|D|E|F|H|J|L|N[AFGHJLNPQRSTUW]|P|Q|R|WA|Y[BDEFGJLN]|Z[AB])|25(?:3S[RSTUX]|4(?:AS|L[DHJLNPQRTXYZ]|N[ABDE])|5(?:Q[JL]|R[DGHJLNPQRSTWY]))|5(?:3(?:Q[JL]|R[GHJLNPQRSTWY]|S[RSTUX])|4(?:L[HJLNPQRSTUWXYZ]|N[AB]))|6(?:6SG|7(?:A|B|D|E|F|H|J|L|N[AFGJLNPQRSTUW]|P|Q|R|Y|Z))|78(?:EB|HH))|R|W)|O|P[ELOR]|R|S(?:1|2|3|4|5|6|7|8|9|E|G|K|L|M|N|O|P|R|S|T|W|Y(?:1(?:1|2|3(?:A|B|D|E|F|G|H|J|L|N|P|Q|R|S|T|W|X|Y|Z|1|2(?:A|B|D|E|F|G|H|J[ABDEFGL]|L[FGT]|N|P[ADEFGHPQRSTUXYZ]|Q|R|S|T|W|X|Y|Z)|3(?:A[AB]|J[GHJLNPQRSTUWXYZ]|L|N[EFGHJLNPQRSTUWXY]|P[FG]|W[ADFGHJXYZ]|X|Y)|4(?:A|B|D|E|H|J|L|N|P|Q|R[ABDFG]|W)|9)|4(?:A|B|D|E|H|J|L|N|P|Q|R|S|T|U|W|Y|7(?:A[ABDEFGHJLPQRSTUWXY]|B|D|E|H[ABDEFGHJLNPQRSTUWXZ]|J[ABDEFJLNPRSWXY]|N[EFGHJQ]|P|U|W|Y)|8)|9[ABDEFGHJLSTUWX]|0(?:0(?:W[AHJQZ]|Z)|1|7(?:A|B|D|E|G|H|J[ABDPRSUXYZ]|L|N[ABDGHJLNPQRW]|P[BDEHJLNPQRW]|R|S|T|U|W|X|Y|Z)|8|9(?:A|B[ABDEFGHJLNQSTUXYZ]|D|E|F|H[ABDEFGHJLNPQRSTUWXY]|J[ABDEFQ]|L[SUXYZ]|N|P[BDEFGHJLNPQRSTUWXYZ]|Q|R|W|Y))|56(?:A[XY]|B[DEGHJLNPQSTUWXYZ]|D[ABDEFGPR]|E[DEP]|H[TUY]|S[PYZ]|T[RZ]|U[BDEFGHQ]|Z))|2(?:2(?:P|5WD|6(?:E[ABDEFGHJLQ]|H[BD]|JX|L[GLNQUWXYZ]|N[TW]|W[ABEHJXYZ]))|5[ABDEGHJLNPQRSTUWXYZ]|6|18(?:D[LN]|E[PRW]|J[LNPQRSTUWXY]|LB))|3|4|5(?:0(?:A|B|D|E|F|H|J[ABDEFGHPQWXYZ]|L|N|P|Q|R|S|T|U|W|X|Y|Z)|6|7|8|9(?:A[ABDEFGHJLNPQ]|B[XYZ]|D|E[ABDEFGHJLNPQRSTWXYZ]|FA|G[ABDEFGHJLQXYZ]|H|J|L[ABEFGHJLNPQRSTUWXYZ]|N|P|Q|R|SA|W|X|Y|Z))|6|7(?:0|7|8(?:A|B|D|E|H|J|L|N|P[ABDEFGHJLNPQRW]|Q|W)|9)|8|9(?:5(?:A|B|D|E|F|H|J[GHJLNQRSTUXY]|L|N|P|W)|9)))|T(?:A|D(?:1(?:24(?:Q[ABDEFGHJLNPQRSTW]|R|S|T|U|WB|X[ABDFGHJLNQ])|5(?:1(?:A|B[ABEFGHJLNPQSUWXYZ]|D|E|H|J|L|N|P|Q|R|S[ABDEJLNPWXY]|T[ABQUX]|U[ABDENPQRTUXY]|W[FX]|X[XY]|Y|Z)|2|9))|90T[JPRSTW])|F|N|Q|R|S|W)|U|W|Y)/,
4
+ scotland: /^(?:AB|CA65(?:A|B|D|E|H[ABDEFGHJLNPQRSWXYZ]|J|L[ABDE])|D(?:D|G(?:1(?:1|2|3|4|9|0|65(?:A|B|D|E|F|G|H[ABDEFGHJLNPQRSWXY]|J[DEFGHJLNPQRSTUWXYZ]|L|N|Q|U|W|X|Y))|2|3|4|5|6|7|8|9))|EH|FK|G[123459678]|HS|IV|K[AWY]|ML|P[AH]|TD(?:1(?:1|2(?:A|B|D|E|H|J|L|N|P|Q|R|S|T|U|W|Y|4(?:A|B|D|E|F|H|J|L|N|QU|W[ADX]|XE|Y)|9)|3|9|0|4|51(?:BT|S[UZ]|T[DEFGHJLNPRSTW]|U[FGHJLSWZ]|W[YZ]|X[ABGHJLNQTUWZ]))|2|3|4|5|6|7|8|9(?:0(?:A|B|D|E|H|J|L|N|P|Q|R|S|T[ABDEFGHLNQUXY]|W|Y|Z)|1|7|8|9))|Z)/,
5
+ wales: /^(?:C(?:F|H(?:1(?:4QL|6(?:B[ABLPQRSTWXYZ]|D[DJ]|EB|H[ABH]|Q[BEFGQ]))|2(?:89AD|99AA)|349AP|4(?:0(?:A|B|D|E[ABDEFGHLNPQRSTUWXYZ]|F|G|H|J|L|N|P|Q|R|S|T|W[AE]|X|Y[PRSTUXY]|Z[BNQRS])|8(?:G|L[AGHJQRTW]|N[ABH]|P[BY]|Q[BEFGHJLNPQRSTWYZ]|R[BEFGHJLNPQRSTUWXYZ]|S|T|U|WN)|9(?:A[ABHJLNPQRSTUWXYZ]|B[ABDEFGHJLNPQRSTUXYZ]|D[ABDXYZ]|E[AH]|GD|P[ABDFG]|XA))|5|6(?:5[ABDEFGHJLNPQRSTUWXYZ]|6[ABDEFHJLZ]|9(?:A[DEFGJLNPQRSTUWXYZ]|B|D))|7(?:1|2|3|4|5|6|9|08A[BF])|8(?:7|8[ABDEHJLNPQRSTWYZ]|9)))|GL168PB|HR(?:3(?:5(?:A[ABDEFGHJNPQRSTUWXYZ]|B[AGHJLNPQSTUWXY]|D|E[ABDEFGHJLQ]|F[AD]|J|L|N|P|Q[ABDEFGHJLNPQRSTUW]|R[EHJLPRSTUXYZ]|S[ABDEFGHJLNPQRSWZ]|T[HRSTUX]|W[ALPQRSTWYZ]|XZ|Y[BEGHLPU]|Z)|6(?:E[XY]|H[ALN]|J[JUWXY]|YG))|53(?:EP|N[DPRSTUWXYZ]|P[ADEJLNPRW]|Q[BDEFGHJLNPQRSTUWXZ]|RA))|L(?:D(?:1|2|3|4|5|6|7(?:1(?:A|B|D[ABDEFGHJLNPQRSTUW]|E[BDEFGHNPRSTUWY]|H|L|N[AGHJLNPQRSTUWY]|P|R|S|T[ABDET]|U[GHPRTUY]|W|Y[ABDEFGHLNPRTW]|ZY)|9)|82(?:A|B|D|E|G|H[ABDEFG]|L[ABDF]|N[EFGHJLNPQRSTUWXY]|P[ABDEFGHLNPTUWY]|R|S|T|U|W|Y|Z))|L(?:1(?:1|2|3|4(?:1|2|3|4|5(?:A|B[ABDEFGHJLNPQSTWXZ]|D[HJ]|E[PQRSTUYZ]|H|L[ABDEFGHLNQRSTUWY]|N|P|Q|R|S|W|Y|Z)|6)|5|6|7|8|9)|2|3|4|5|6|7))|NP(?:1(?:0|2|4|5|6(?:A|B|D|E|F|G|H|J|L|N|P|Q|R|X|Y|Z|5|6|7(?:NX|W[EJLNPQRSTUWXYZ]|Y[AHPQRSTUWY]|Z[SX])|9)|7|8|9|1|3)|2(?:0|1|2|3|4|5(?:A|B|D|E|H|J|L|N|P|Q|R|X|Y|3(?:A|B|D|E|G|H|J|L|N|P|Q|S[ABDEFGHJLNPQWYZ]|T|U|W|X|Y|Z)|4(?:A[BDEFGHJLNPQRTW]|B|D|E|H|J|L[EGUW]|N[YZ]|P|Q|R|S|T|U|W|X|Y|Z)|5(?:A|B|D|E|F|G|H|J|L|N|P|Q[ABDEFGHQXYZ]|R[ABEFZ]|S|T|U|W|X|Y|Z)|9)|6)|3|4|5(?:1|2|3(?:A|B|D|E|G|H|J|L|N|P|Q[ABDEFGHQXYZ]|R[ABDEFZ]|S[ABDEFGHJLNPQWZ]|T|U|X|Y|Z)|4(?:A|B|D|E|F|H|J|L[ABDEFG]|N[FGHJLNPQRSTUWXYZ]|P|Q|R|S|T|U|X|Y))|6(?:1|2|3|4|5|6(?:A|B|D|E|H|J|L|N|P|Q|R|S[ABDEFHJLNPQRSTUWXYZ]|T|U|X|Y)|7N[HX])|7(?:0|1|5|6|7|8(?:A|B|D|E[ADEFGHLNPQRSTWXY]|H[ABDEFGLNPRSTUWY]|L|N|P|R|S|T|U|W|Y|Z)|9)|8|9)|S(?:A|Y(?:1(?:3(?:2(?:J[HJNPQRSTUWXYZ]|L[ABDEHJLNPQRSUWX]|PB)|3(?:A[DEFGHJLNPQRSTUWXYZ]|B|D|E|G|H|J[AB]|NZ|P[ABDE]|W[BE])|4RE)|47(?:A[NZ]|HY|J[HTUZ]|L|N[ABD])|97|0(?:0(?:A|B|D|E|G|H|J|L|N|P|W[BDEFG])|7(?:J[EFGHJLNQW]|N[EFSTUXYZ]|P[AGSTUXYZ]|Q)|9(?:B[PW]|HZ|J[GHJLNPRSTUWXYZ]|L[ABDEFGHJLNPQRTW]|PA))|56(?:A[ABDEFGHJLNPQRSTUWZ]|B[AB]|D[HJLNQSTUWXYZ]|E[ABFGHJLNQRSTUWXYZ]|H[ABDEFGHJLNPQRSWXZ]|J|L|N|P|Q|R|S[ABDEFGHQRSTUX]|T[ABDEFGHJLNPQWY]|U[AJLNPR]|W|Y)|6|7|8)|2(?:2(?:5(?:A|B|D|E|H|J|L|N|W[ABPZ])|6(?:A|B|D|E[NPRSTUWXYZ]|F|G|H[AEFLNPRSTUWXYZ]|J[ABDEFGHJLNPQRSTWYZ]|L[ABDEFHJPRST]|N[ABDEFGHJLNPQRSXYZ]|P|Q|R|S|T|U|W[DFG]|X|Y))|56|0|1(?:0|1|7|8(?:A|B|D[ABDEFGHJPQRSTUWXYZ]|E[ABDEFGHJLNQSTUXYZ]|F|G|H|J[ABDEFGHJZ]|L[ADEFGHJLNPQRSTUWXYZ]|N|P|Q|R|S|T|W|X|Z)|9)|3|4)|5(?:0J[JLN]|9(?:A[RSTUWXYZ]|B[ABDEFGHJLNPQSTUW]|EU|FB|GN|LD|SB))|78PS|95J[PW])))/,
6
+ channel_islands: /^(?:GY|J)/,
7
+ isle_of_man: /^IM/,
8
+ northern_ireland: /^BT/
9
+ }
10
+ end
@@ -0,0 +1,98 @@
1
+ require "uk_postcode/country_finder"
2
+
3
+ module UKPostcode
4
+
5
+ # GeographicPostcode models the majority of postcodes, containing an area,
6
+ # a district, a sector, and a unit.
7
+ #
8
+ # Despite the name, it also handles non-geographic postcodes that follow the
9
+ # geographic format.
10
+ #
11
+ class GeographicPostcode
12
+ PATTERN = %r{
13
+ \A ( [A-PR-UWYZ01][A-HJ-Z0]? ) # area
14
+ (?: ( [0-9IO][0-9A-HJKMNPR-YIO]? ) # district
15
+ (?: \s* ( [0-9IO] ) # sector
16
+ ( [ABD-HJLNPQ-Z]{2} )? )? )? # unit
17
+ \Z
18
+ }ix
19
+
20
+ # Attempts to parse the postcode given in str, and returns an instance of
21
+ # GeographicPostcode on success, or nil on failure.
22
+ #
23
+ def self.parse(str)
24
+ matched = PATTERN.match(str.strip)
25
+ if matched
26
+ new(*(1..4).map { |i| matched[i] })
27
+ else
28
+ nil
29
+ end
30
+ end
31
+
32
+ # Initialise a new GeographicPostcode instance with the given area,
33
+ # district, sector, and unit. Only area is required.
34
+ #
35
+ def initialize(area, district = nil, sector = nil, unit = nil)
36
+ @area = letters(area)
37
+ @district = digits(district)
38
+ @sector = digits(sector)
39
+ @unit = letters(unit)
40
+ end
41
+
42
+ attr_reader :area, :district, :sector, :unit
43
+
44
+ # The left-hand part of the postcode, e.g. W1A 1AA -> W1A
45
+ #
46
+ def outcode
47
+ return nil unless district
48
+ [area, district].join('')
49
+ end
50
+
51
+ # The right-hand part of the postcode, e.g. W1A 1AA -> 1AA
52
+ #
53
+ def incode
54
+ return nil unless sector && unit
55
+ [sector, unit].join('')
56
+ end
57
+
58
+ # Returns the canonical string representation of the postcode.
59
+ #
60
+ def to_s
61
+ [area, district, " ", sector, unit].compact.join('').strip
62
+ end
63
+
64
+ # Returns true if the postcode is a valid full postcode (e.g. W1A 1AA)
65
+ #
66
+ def full?
67
+ area && district && sector && unit && true
68
+ end
69
+
70
+ # Any geographic postcode is assumed to be valid
71
+ #
72
+ def valid?
73
+ true
74
+ end
75
+
76
+ # Find the country associated with the postcode. Possible values are
77
+ # :england, :scotland, :wales, :northern_ireland, :isle_of_man,
78
+ # :channel_islands, or :unknown.
79
+ #
80
+ # Note that, due to limitations in the underlying data, the country might
81
+ # not always be correct in border regions.
82
+ #
83
+ def country
84
+ CountryFinder.country(self)
85
+ end
86
+
87
+ private
88
+ def letters(str)
89
+ return nil unless str
90
+ str.upcase.tr("10", "IO")
91
+ end
92
+
93
+ def digits(str)
94
+ return nil unless str
95
+ str.upcase.tr("IO", "10")
96
+ end
97
+ end
98
+ end
@@ -0,0 +1,61 @@
1
+ require "singleton"
2
+
3
+ module UKPostcode
4
+
5
+ # GiroPostcode models the single, peculiar GIR 0AA postcode originally used
6
+ # by Girobank.
7
+ #
8
+ # area, district, sector, and unit all return nil, because this postcode
9
+ # does not meaningfully possess these distinctions.
10
+ #
11
+ class GiroPostcode
12
+ include Singleton
13
+
14
+ PATTERN = /\A G[I1]R \s* [0O]AA \z/ix
15
+
16
+ # Returns an instance of GiroPostcode if str represents GIR 0AA, and nil
17
+ # otherwise.
18
+ #
19
+ def self.parse(str)
20
+ PATTERN.match(str.strip) ? instance : nil
21
+ end
22
+
23
+ attr_reader :area, :district, :sector, :unit
24
+
25
+ # The left-hand part of the postcode, always "GIR".
26
+ #
27
+ def outcode
28
+ "GIR"
29
+ end
30
+
31
+ # The right-hand part of the postcode, always "0AA".
32
+ #
33
+ def incode
34
+ "0AA"
35
+ end
36
+
37
+ # The canonical string representation of the postcode, i.e. "GIR 0AA".
38
+ #
39
+ def to_s
40
+ "GIR 0AA"
41
+ end
42
+
43
+ # GIR 0AA is always valid.
44
+ #
45
+ def valid?
46
+ true
47
+ end
48
+
49
+ # GIR 0AA is always full.
50
+ #
51
+ def full?
52
+ true
53
+ end
54
+
55
+ # GIR 0AA is in England. (In Bootle, in fact.)
56
+ #
57
+ def country
58
+ :england
59
+ end
60
+ end
61
+ end
@@ -0,0 +1,38 @@
1
+ module UKPostcode
2
+
3
+ # InvalidPostcode is a singleton null object returned by UKPostcode.parse
4
+ # when it is unable to parse the supplied postcode.
5
+ #
6
+ # The sub-fields of the postcode (outcode, area, etc.) are all nil.
7
+ #
8
+ class InvalidPostcode
9
+ def self.parse(str)
10
+ new(str)
11
+ end
12
+
13
+ def initialize(input)
14
+ @input = input
15
+ end
16
+
17
+ attr_reader :area, :district, :sector, :unit, :incode, :outcode
18
+
19
+ # Returns the literal string supplied at initialisation. This may be
20
+ # helpful when returning erroneous input to the user.
21
+ #
22
+ def to_s
23
+ @input
24
+ end
25
+
26
+ def full?
27
+ false
28
+ end
29
+
30
+ def valid?
31
+ false
32
+ end
33
+
34
+ def country
35
+ :unknown
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,75 @@
1
+ module UKPostcode
2
+ class Tree
3
+ def initialize(tree = {})
4
+ @root = tree
5
+ end
6
+
7
+ def insert(path, value)
8
+ path[0..-2].inject(@root) { |n, p| n[p] ||= {} }[path.last] = value
9
+ end
10
+
11
+ def compress
12
+ self.class.new(compress_node(@root))
13
+ end
14
+
15
+ def filter(leaf_value)
16
+ self.class.new(filter_node(@root, leaf_value))
17
+ end
18
+
19
+ def regexp
20
+ Regexp.new('^' + node_regexp(@root))
21
+ end
22
+
23
+ def to_h
24
+ @root
25
+ end
26
+
27
+ private
28
+
29
+ def leaf?(node)
30
+ node.is_a?(Symbol)
31
+ end
32
+
33
+ def contains_identical_leaves?(node)
34
+ node.values.all? { |t| leaf?(t) } && node.values.uniq.length == 1
35
+ end
36
+
37
+ def compress_node(node)
38
+ if leaf?(node)
39
+ node
40
+ else
41
+ comp = Hash[node.map { |k, v| [k, compress_node(v)] }]
42
+ contains_identical_leaves?(comp) ? comp.values.first : comp
43
+ end
44
+ end
45
+
46
+ def filter_node(node, leaf_value)
47
+ if node == leaf_value
48
+ node
49
+ elsif leaf?(node)
50
+ nil
51
+ else
52
+ h = Hash[node.map { |k, v| [k, filter_node(v, leaf_value)] }.
53
+ select { |_, v| v }]
54
+ h.empty? ? nil : h
55
+ end
56
+ end
57
+
58
+ def node_regexp(node)
59
+ if leaf?(node)
60
+ ''
61
+ else
62
+ segments = node.map { |k, v| k + node_regexp(v) }
63
+ if segments.length > 1
64
+ if segments.all? { |s| s.length == 1 }
65
+ '[' + segments.join('') + ']'
66
+ else
67
+ '(?:' + segments.join('|') + ')'
68
+ end
69
+ else
70
+ segments.first
71
+ end
72
+ end
73
+ end
74
+ end
75
+ end
@@ -1,3 +1,3 @@
1
- class UKPostcode
2
- VERSION = "1.0.1"
1
+ module UKPostcode
2
+ VERSION = "2.0.0.alpha"
3
3
  end