-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathname_utils.py
executable file
·275 lines (234 loc) · 9.37 KB
/
name_utils.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
# Copyright 2007,2008,2009,2011 Everyblock LLC, OpenPlans, and contributors
#
# This file is part of ebpub
#
# ebpub is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# ebpub is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with ebpub. If not, see <http://www.gnu.org/licenses/>.
#
"""
Utility functions for munging address/block/street names.
"""
import re
from ebpub.utils.text import smart_title, slugify
def make_street_pretty_name(prefix, street, suffix):
"""
>>> make_street_pretty_name(None, 'whee', None)
u'Whee'
>>> make_street_pretty_name('oh', 'boy', None)
u'Oh Boy'
>>> make_street_pretty_name('', 'YES', 'nO')
u'Yes No'
>>> make_street_pretty_name(' US hWy ', '101', 'C')
u'US Hwy 101 C'
>>> make_street_pretty_name(' I- ', '40', '')
u'I-40'
"""
prefix = make_pretty_prefix(prefix or u'')
suffix = smart_title(suffix or u'').strip()
street = smart_title(street or u'').strip()
#assert street
if prefix == u'I':
# Special case to avoid "I- 40", the standard is apparently "I-40"
prefix = u''
street = u'I-%s' % street
street_name = u' '.join((prefix, street, suffix)).strip()
return street_name
def make_block_number(left_from_num, left_to_num, right_from_num, right_to_num):
"""
Given 4 numbers (left low, left high, right low, right high),
returns a string indicating the range of lowest to highest.
"lowest" and "highest" are derived as per the make_block_numbers() function.
>>> make_block_number(1, 9, 2, 3)
u'1-9'
>>> make_block_number(1, 1, 1, 1)
u'1'
>>> make_block_number(9, 8, 7, 6)
u'6-9'
Zero is not considered part of a range:
>>> make_block_number(0, 1, 2, 3)
u'1-3'
None is ignored, but one non-zero number must be provided:
>>> make_block_number(None, None, 1, None)
u'1'
>>> make_block_number(None, None, None, None)
Traceback (most recent call last):
...
ValueError: No non-None addresses provided
>>> make_block_number(0, 0, 0, 0)
Traceback (most recent call last):
...
ValueError: No non-zero numeric addresses provided in [0, 0, 0, 0]
"""
lo_num, hi_num = make_block_numbers(left_from_num, left_to_num,
right_from_num, right_to_num)
if lo_num == hi_num:
number = unicode(lo_num)
elif lo_num and not hi_num:
number = unicode(lo_num)
elif hi_num and not lo_num:
number = unicode(hi_num)
else:
number = u'%s-%s' % (lo_num, hi_num)
return number
def make_block_numbers(left_from_num, left_to_num, right_from_num, right_to_num):
"""
Given four numbers, or strings containing numbers, returns the min
and max as a pair.
Because the input is possibly messy and quirky, there are some
subtleties in what's considered the min and max, see below. In
all cases, the motivation is to assume that the input is spelled
correctly for human reading, no matter how unlikely; but for
sorting we assume we want a single non-negative number.
>>> make_block_numbers(10,9,8,7)
(7, 10)
>>> make_block_numbers(1,1,1,1)
(1, 1)
The first quirk is that zero is ignored:
>>> make_block_numbers(0,1,2,3)
(1, 3)
Another quirk is that negative numbers are compared as if positive:
>>> make_block_numbers(1000, 0, -9999, 0)
(1000, -9999)
None is ignored, but at least one number must be provided:
>>> make_block_numbers(None, None, None, None)
Traceback (most recent call last):
...
ValueError: No non-None addresses provided
>>> make_block_numbers(None, None, None, 1)
(1, 1)
Handles strings that look like integers too. Note that they
are returned unchanged:
>>> make_block_numbers('1000', '0', u'9999', u'')
('1000', u'9999')
It also, *for sorting purposes*, tries to ignore any non-numeric
characters, and if one looks like an address range (like "10-20"),
it compares only the absolute value of the first numeric part -
but again, returns them unchanged. For example, this sorts them
as if they were 99 and 33 respectively:
>>> make_block_numbers('blah 99 blah', '33-44-55', '', '')
('33-44-55', 'blah 99 blah')
This also sorts them as if they were 33 and 99 (not -99):
>>> make_block_numbers('33-44-55', '-99-123', '', '')
('33-44-55', '-99-123')
>>> make_block_numbers('a', 'b', 'c', 'd')
Traceback (most recent call last):
...
ValueError: No non-zero numeric addresses provided in ['a', 'b', 'c', 'd']
>>> make_block_numbers('a', 'b', 'c', 'd9d')
('d9d', 'd9d')
"""
nums = [x for x in (left_from_num, left_to_num, right_from_num, right_to_num)
if x not in (None, '', u'')]
if not nums:
# This used to raise ValueError, maybe accidentally, because
# min([]) does so. Preserving that for backward compatibility,
# not sure if it matters.
raise ValueError("No non-None addresses provided")
# Note that we may get passed strings with non-numeric junk.
# In that case, try to grab out the numbers for sorting.
sortable = []
for x in nums:
if isinstance(x, basestring):
maybe = re.search('(\d+)', x)
if maybe:
sortkey = int(maybe.group(1))
if sortkey:
sortable.append((sortkey, x))
else:
if x:
sortable.append((abs(x), x))
if sortable:
sortable.sort()
return (sortable[0][1], sortable[-1][1])
else:
raise ValueError("No non-zero numeric addresses provided in %s" % nums)
def make_pretty_directional(directional):
"""
Returns a formatted directional.
e.g.:
N -> N.
NW -> N.W.
"""
return "".join(u"%s." % c for c in directional)
def make_pretty_name(left_from_num, left_to_num, right_from_num, right_to_num,
predir, prefix, street, suffix, postdir=None):
"""
Returns a tuple of (street_pretty_name, block_pretty_name) for the
given address bits.
>>> make_pretty_name(1, 29, 2, 30, 'NW', 'STATE RT', '101', 'DRIVE', 'SE')
(u'State Route 101 Drive', u'1-30 N.W. State Route 101 Drive S.E.')
"""
prefix_part = make_pretty_prefix(prefix or u'')
street_name = make_street_pretty_name(prefix_part, street, suffix)
num_part = make_block_number(left_from_num, left_to_num, right_from_num, right_to_num)
predir_part = predir and make_pretty_directional(predir) or u''
postdir_part = postdir and make_pretty_directional(postdir) or u''
block_name = u'%s %s %s %s' % (num_part, predir_part, street_name, postdir_part)
block_name = re.sub(u'\s+', u' ', block_name).strip()
return street_name, block_name
def make_pretty_prefix(prefix):
"""
>>> make_pretty_prefix('US Hwy')
u'US Highway'
>>> make_pretty_prefix('State Rt ')
u'State Route'
>>> make_pretty_prefix(' I- ')
u'I'
>>> make_pretty_prefix(' Anything Else ')
u'Anything Else'
"""
prefix = unicode(prefix).strip()
if prefix.upper().endswith(u'HWY'):
return prefix[:-3] + u'Highway'
if prefix.upper().endswith(u'RT'):
return prefix[:-2] + u'Route'
prefix = prefix.strip().strip('-').strip()
prefix = smart_title(prefix, exceptions=['US'])
return prefix
def make_dir_street_name(block):
"""
Returns a street name from a block with the directional included.
If the block has a ``predir``, the directional is prepended:
"W. Diversey Ave."
If the block has a ``postdir``, the directional is appended:
"18th St. N.W."
"""
name = make_street_pretty_name(block.prefix, block.street, block.suffix)
if block.predir:
name = u"%s %s" % (make_pretty_directional(block.predir), name)
if block.postdir:
name = u"%s %s" % (name, make_pretty_directional(block.postdir))
return name
def pretty_name_from_blocks(block_a, block_b):
return u"%s & %s" % (make_dir_street_name(block_a), make_dir_street_name(block_b))
def slug_from_blocks(block_a, block_b):
slug = u"%s-and-%s" % (slugify(make_dir_street_name(block_a)),
slugify(make_dir_street_name(block_b)))
# If it's too long for the slug field, drop the directionals
if len(slug) > 64:
slug = u"%s-and-%s" % (
slugify(make_street_pretty_name(block_a.prefix, block_a.street, block_a.suffix)),
slugify(make_street_pretty_name(block_b.prefix, block_b.street, block_b.suffix)))
# If it's still too long, drop the suffixes
if len(slug) > 64:
slug = u"%s-and-%s" % (
slugify(make_street_pretty_name(block_a.prefix, block_a.street, u'')),
slugify(make_street_pretty_name(block_b.prefix, block_b.street, u'')))
# If it's *still* too long, drop the prefixes too
if len(slug) > 64:
slug = u"%s-and-%s" % (
slugify(block_a.street),
slugify(block_b.street),
)
slug = slug[:64]
return slug