My favorites | Sign in
Project Home Downloads Wiki Issues Source
Repository:
Checkout   Browse   Changes   Clones  
Changes to /src/lepl/support/lib.py
e200243f0586 vs. 5b14542ff43b Compare: vs.  Format:
Revision 5b14542ff43b
Go to: 
Project members, sign in to write a code review
/src/lepl/support/lib.py   e200243f0586 /src/lepl/support/lib.py   5b14542ff43b
1 # The contents of this file are subject to the Mozilla Public License 1 # The contents of this file are subject to the Mozilla Public License
2 # (MPL) Version 1.1 (the "License"); you may not use this file except 2 # (MPL) Version 1.1 (the "License"); you may not use this file except
3 # in compliance with the License. You may obtain a copy of the License 3 # in compliance with the License. You may obtain a copy of the License
4 # at http://www.mozilla.org/MPL/ 4 # at http://www.mozilla.org/MPL/
5 # 5 #
6 # Software distributed under the License is distributed on an "AS IS" 6 # Software distributed under the License is distributed on an "AS IS"
7 # basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See 7 # basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
8 # the License for the specific language governing rights and 8 # the License for the specific language governing rights and
9 # limitations under the License. 9 # limitations under the License.
10 # 10 #
11 # The Original Code is LEPL (http://www.acooke.org/lepl) 11 # The Original Code is LEPL (http://www.acooke.org/lepl)
12 # The Initial Developer of the Original Code is Andrew Cooke. 12 # The Initial Developer of the Original Code is Andrew Cooke.
13 # Portions created by the Initial Developer are Copyright (C) 2009-2010 13 # Portions created by the Initial Developer are Copyright (C) 2009-2010
14 # Andrew Cooke (andrew@acooke.org). All Rights Reserved. 14 # Andrew Cooke (andrew@acooke.org). All Rights Reserved.
15 # 15 #
16 # Alternatively, the contents of this file may be used under the terms 16 # Alternatively, the contents of this file may be used under the terms
17 # of the LGPL license (the GNU Lesser General Public License, 17 # of the LGPL license (the GNU Lesser General Public License,
18 # http://www.gnu.org/licenses/lgpl.html), in which case the provisions 18 # http://www.gnu.org/licenses/lgpl.html), in which case the provisions
19 # of the LGPL License are applicable instead of those above. 19 # of the LGPL License are applicable instead of those above.
20 # 20 #
21 # If you wish to allow use of your version of this file only under the 21 # If you wish to allow use of your version of this file only under the
22 # terms of the LGPL License and not to allow others to use your version 22 # terms of the LGPL License and not to allow others to use your version
23 # of this file under the MPL, indicate your decision by deleting the 23 # of this file under the MPL, indicate your decision by deleting the
24 # provisions above and replace them with the notice and other provisions 24 # provisions above and replace them with the notice and other provisions
25 # required by the LGPL License. If you do not delete the provisions 25 # required by the LGPL License. If you do not delete the provisions
26 # above, a recipient may use your version of this file under either the 26 # above, a recipient may use your version of this file under either the
27 # MPL or the LGPL License. 27 # MPL or the LGPL License.
28 28
29 ''' 29 '''
30 Library routines / utilities (some unused). 30 Library routines / utilities (some unused).
31 ''' 31 '''
32 32
33 from logging import getLogger 33 from logging import getLogger
34 34
35 # this is an attempt to make 2.6 and 3 function equally with strings 35 # this is an attempt to make 2.6 and 3 function equally with strings
36 36
37 try: 37 try:
38 unicode
38 #noinspection PyUnresolvedReferences 39 #noinspection PyUnresolvedReferences
39 chr = unichr 40 chr = unichr
40 #noinspection PyUnresolvedReferences 41 #noinspection PyUnresolvedReferences
41 str = unicode 42 str = unicode
43 bytes = bytes
42 #noinspection PyUnresolvedReferences,PyUnboundLocalVariable 44 #noinspection PyUnresolvedReferences,PyUnboundLocalVariable
43 basestring = basestring 45 basestring = basestring
44 #noinspection PyUnresolvedReferences,PyUnboundLocalVariable 46 #noinspection PyUnresolvedReferences,PyUnboundLocalVariable
45 file = file 47 file = file
46 #noinspection PyUnresolvedReferences 48 #noinspection PyUnresolvedReferences
47 from StringIO import StringIO 49 from StringIO import StringIO
48 except NameError: 50 except NameError:
49 from io import IOBase, StringIO 51 from io import IOBase, StringIO
50 #noinspection PyUnboundLocalVariable 52 #noinspection PyUnboundLocalVariable
51 chr = chr 53 chr = chr
52 #noinspection PyUnboundLocalVariable 54 #noinspection PyUnboundLocalVariable
53 str = str 55 str = str
56 bytes = bytes
54 basestring = str 57 basestring = str
55 file = IOBase 58 file = IOBase
56 59
57 60
58 def assert_type(name, value, type_, none_ok=False): 61 def assert_type(name, value, type_, none_ok=False):
59 ''' 62 '''
60 If the value is not of the given type, raise a syntax error. 63 If the value is not of the given type, raise a syntax error.
61 ''' 64 '''
62 if none_ok and value is None: 65 if none_ok and value is None:
63 return 66 return
64 if isinstance(value, type_): 67 if isinstance(value, type_):
65 return 68 return
66 raise TypeError(fmt('{0} (value {1}) must be of type {2}.', 69 raise TypeError(fmt('{0} (value {1}) must be of type {2}.',
67 name, repr(value), type_.__name__)) 70 name, repr(value), type_.__name__))
68 71
69 72
70 class CircularFifo(object): 73 class CircularFifo(object):
71 ''' 74 '''
72 A FIFO queue with a fixed maximum size that silently discards data on 75 A FIFO queue with a fixed maximum size that silently discards data on
73 overflow. It supports iteration for reading current contents and so 76 overflow. It supports iteration for reading current contents and so
74 can be used for a "latest window". 77 can be used for a "latest window".
75 78
76 Might be able to use deque instead? This may be more efficient 79 Might be able to use deque instead? This may be more efficient
77 if the entire contents are read often (as is the case when depth gets 80 if the entire contents are read often (as is the case when depth gets
78 deeper)? 81 deeper)?
79 ''' 82 '''
80 83
81 def __init__(self, size): 84 def __init__(self, size):
82 ''' 85 '''
83 Stores up to size entries. Once full, appending a further value 86 Stores up to size entries. Once full, appending a further value
84 will discard (and return) the oldest still present. 87 will discard (and return) the oldest still present.
85 ''' 88 '''
86 self.__size = 0 89 self.__size = 0
87 self.__next = 0 90 self.__next = 0
88 self.__buffer = [None] * size 91 self.__buffer = [None] * size
89 92
90 def append(self, value): 93 def append(self, value):
91 ''' 94 '''
92 This returns a value on overflow, otherwise None. 95 This returns a value on overflow, otherwise None.
93 ''' 96 '''
94 capacity = len(self.__buffer) 97 capacity = len(self.__buffer)
95 if self.__size == capacity: 98 if self.__size == capacity:
96 dropped = self.__buffer[self.__next] 99 dropped = self.__buffer[self.__next]
97 else: 100 else:
98 dropped = None 101 dropped = None
99 self.__size += 1 102 self.__size += 1
100 self.__buffer[self.__next] = value 103 self.__buffer[self.__next] = value
101 self.__next = (self.__next + 1) % capacity 104 self.__next = (self.__next + 1) % capacity
102 return dropped 105 return dropped
103 106
104 def pop(self, index=0): 107 def pop(self, index=0):
105 ''' 108 '''
106 Remove and return the next item. 109 Remove and return the next item.
107 ''' 110 '''
108 if index: 111 if index:
109 raise IndexError('FIFO is only a FIFO') 112 raise IndexError('FIFO is only a FIFO')
110 if self.__size < 1: 113 if self.__size < 1:
111 raise IndexError('FIFO empty') 114 raise IndexError('FIFO empty')
112 popped = self.__buffer[(self.__next - self.__size) % len(self.__buffer)] 115 popped = self.__buffer[(self.__next - self.__size) % len(self.__buffer)]
113 self.__size -= 1 116 self.__size -= 1
114 return popped 117 return popped
115 118
116 def __len__(self): 119 def __len__(self):
117 return len(self.__buffer) 120 return len(self.__buffer)
118 121
119 def __iter__(self): 122 def __iter__(self):
120 capacity = len(self.__buffer) 123 capacity = len(self.__buffer)
121 index = (self.__next - self.__size) % capacity 124 index = (self.__next - self.__size) % capacity
122 for _ in range(self.__size): 125 for _ in range(self.__size):
123 yield self.__buffer[index] 126 yield self.__buffer[index]
124 index = (index + 1) % capacity 127 index = (index + 1) % capacity
125 128
126 def clear(self): 129 def clear(self):
127 ''' 130 '''
128 Clear the data (we just set the size to zero - this doesn't release 131 Clear the data (we just set the size to zero - this doesn't release
129 any references). 132 any references).
130 ''' 133 '''
131 self.__size = 0 134 self.__size = 0
132 135
133 136
134 def open_stop(spec): 137 def open_stop(spec):
135 ''' 138 '''
136 In Python 2.6 open [] appears to use maxint or similar, which is not 139 In Python 2.6 open [] appears to use maxint or similar, which is not
137 available in Python 3. This uses a minimum value for maxint I found 140 available in Python 3. This uses a minimum value for maxint I found
138 somewhere; hopefully no-one ever wants finite repeats larger than this. 141 somewhere; hopefully no-one ever wants finite repeats larger than this.
139 ''' 142 '''
140 return spec.stop == None or spec.stop > 2147483647 143 return spec.stop == None or spec.stop > 2147483647
141 144
142 145
143 def lmap(function, values): 146 def lmap(function, values):
144 ''' 147 '''
145 A map that returns a list rather than an iterator. 148 A map that returns a list rather than an iterator.
146 ''' 149 '''
147 return list(map(function, values)) 150 return list(map(function, values))
148 151
149 152
150 def compose(fun_a, fun_b): 153 def compose(fun_a, fun_b):
151 ''' 154 '''
152 Functional composition (assumes fun_a takes a single argument). 155 Functional composition (assumes fun_a takes a single argument).
153 ''' 156 '''
154 def fun(*args, **kargs): 157 def fun(*args, **kargs):
155 ''' 158 '''
156 This assumes fun_a takes a single argument. 159 This assumes fun_a takes a single argument.
157 ''' 160 '''
158 return fun_a(fun_b(*args, **kargs)) 161 return fun_a(fun_b(*args, **kargs))
159 return fun 162 return fun
160 163
161 164
162 def compose_tuple(fun_a, fun_b): 165 def compose_tuple(fun_a, fun_b):
163 ''' 166 '''
164 Functional composition (assumes fun_b returns a sequence which is supplied 167 Functional composition (assumes fun_b returns a sequence which is supplied
165 to fun_a via *args). 168 to fun_a via *args).
166 ''' 169 '''
167 def fun(*args, **kargs): 170 def fun(*args, **kargs):
168 ''' 171 '''
169 Supply result from fun_b as *arg. 172 Supply result from fun_b as *arg.
170 ''' 173 '''
171 return fun_a(*fun_b(*args, **kargs)) 174 return fun_a(*fun_b(*args, **kargs))
172 return fun 175 return fun
173 176
174 177
175 def empty(): 178 def empty():
176 ''' 179 '''
177 An empty generator. 180 An empty generator.
178 ''' 181 '''
179 if False: 182 if False:
180 yield None 183 yield None
181 184
182 185
183 def count(value=0, step=1): 186 def count(value=0, step=1):
184 ''' 187 '''
185 Identical to itertools.count for recent Python, but 2.6 lacks the step. 188 Identical to itertools.count for recent Python, but 2.6 lacks the step.
186 ''' 189 '''
187 while True: 190 while True:
188 yield value 191 yield value
189 value += step 192 value += step
190 193
191 194
192 class LogMixin(object): 195 class LogMixin(object):
193 ''' 196 '''
194 Add standard Python logging to a class. 197 Add standard Python logging to a class.
195 ''' 198 '''
196 199
197 def __init__(self, *args, **kargs): 200 def __init__(self, *args, **kargs):
198 #noinspection PyArgumentList 201 #noinspection PyArgumentList
199 super(LogMixin, self).__init__(*args, **kargs) 202 super(LogMixin, self).__init__(*args, **kargs)
200 #noinspection PyUnresolvedReferences 203 #noinspection PyUnresolvedReferences
201 self._log = getLogger(self.__module__ + '.' + self.__class__.__name__) 204 self._log = getLogger(self.__module__ + '.' + self.__class__.__name__)
202 self._debug = self._log.debug 205 self._debug = self._log.debug
203 self._info = self._log.info 206 self._info = self._log.info
204 self._warn = self._log.warn 207 self._warn = self._log.warn
205 self._error = self._log.error 208 self._error = self._log.error
206 209
207 210
208 def safe_in(value, container, default=False): 211 def safe_in(value, container, default=False):
209 ''' 212 '''
210 Test for membership without an error for unhashable items. 213 Test for membership without an error for unhashable items.
211 ''' 214 '''
212 try: 215 try:
213 return value in container 216 return value in container
214 except TypeError: 217 except TypeError:
215 log = getLogger('lepl.support.safe_in') 218 log = getLogger('lepl.support.safe_in')
216 log.debug(fmt('Cannot test for {0!r} in collection', value)) 219 log.debug(fmt('Cannot test for {0!r} in collection', value))
217 return default 220 return default
218 221
219 222
220 def safe_add(container, value): 223 def safe_add(container, value):
221 ''' 224 '''
222 Add items to a container, if they are hashable. 225 Add items to a container, if they are hashable.
223 ''' 226 '''
224 try: 227 try:
225 container.add(value) 228 container.add(value)
226 except TypeError: 229 except TypeError:
227 log = getLogger('lepl.support.safe_add') 230 log = getLogger('lepl.support.safe_add')
228 log.warn(fmt('Cannot add {0!r} to collection', value)) 231 log.warn(fmt('Cannot add {0!r} to collection', value))
229 232
230 233
231 def fallback_add(container, value): 234 def fallback_add(container, value):
232 ''' 235 '''
233 Add items to a container. Call initially with a set, but accept the 236 Add items to a container. Call initially with a set, but accept the
234 returned collection, which will fallback to a list of necessary (if the 237 returned collection, which will fallback to a list of necessary (if the
235 contents are unhashable). 238 contents are unhashable).
236 ''' 239 '''
237 try: 240 try:
238 container.add(value) 241 container.add(value)
239 return container 242 return container
240 except AttributeError: 243 except AttributeError:
241 container.append(value) 244 container.append(value)
242 return container 245 return container
243 except TypeError: 246 except TypeError:
244 if isinstance(container, list): 247 if isinstance(container, list):
245 raise 248 raise
246 else: 249 else:
247 container = list(container) 250 container = list(container)
248 return fallback_add(container, value) 251 return fallback_add(container, value)
249 252
250 253
251 def fold(fun, start, sequence): 254 def fold(fun, start, sequence):
252 ''' 255 '''
253 Fold over a sequence. 256 Fold over a sequence.
254 ''' 257 '''
255 for value in sequence: 258 for value in sequence:
256 start = fun(start, value) 259 start = fun(start, value)
257 return start 260 return start
258 261
259 262
260 def sample(prefix, rest, size=40): 263 def sample(prefix, rest, size=40):
261 ''' 264 '''
262 Provide a small sample of a string. 265 Provide a small sample of a string.
263 ''' 266 '''
264 text = prefix + rest 267 text = prefix + rest
265 if len(text) > size: 268 if len(text) > size:
266 text = prefix + rest[0:size-len(prefix)-3] + '...' 269 text = prefix + rest[0:size-len(prefix)-3] + '...'
267 return text 270 return text
268 271
269 272
270 __SINGLETONS = {} 273 __SINGLETONS = {}
271 ''' 274 '''
272 Map from factory (constructor/class) to singleton. 275 Map from factory (constructor/class) to singleton.
273 ''' 276 '''
274 277
275 def singleton(key, factory=None): 278 def singleton(key, factory=None):
276 ''' 279 '''
277 Manage singletons for various types. 280 Manage singletons for various types.
278 ''' 281 '''
279 if key not in __SINGLETONS: 282 if key not in __SINGLETONS:
280 if not factory: 283 if not factory:
281 factory = key 284 factory = key
282 __SINGLETONS[key] = factory() 285 __SINGLETONS[key] = factory()
283 return __SINGLETONS[key] 286 return __SINGLETONS[key]
284 287
285 288
286 def fmt(template, *args, **kargs): 289 def fmt(template, *args, **kargs):
287 ''' 290 '''
288 Guarantee that template is always unicode, as embedding unicode in ascii 291 Guarantee that template is always unicode, as embedding unicode in ascii
289 can cause errors. 292 can cause errors.
290 ''' 293 '''
291 return str(template).format(*args, **kargs) 294 return str(template).format(*args, **kargs)
292 295
293 296
294 def identity(x): 297 def identity(x):
295 return x 298 return x
296 299
297 300
298 def document(destination, source, text=None): 301 def document(destination, source, text=None):
299 ''' 302 '''
300 Copy function name and docs. 303 Copy function name and docs.
301 ''' 304 '''
302 if text: 305 if text:
303 destination.__name__ = text 306 destination.__name__ = text
304 else: 307 else:
305 destination.__name__ = source.__name__ 308 destination.__name__ = source.__name__
306 destination.__doc__ = source.__doc__ 309 destination.__doc__ = source.__doc__
307 # module used in auto-linking for docs 310 # module used in auto-linking for docs
308 destination.__module__ = source.__module__ 311 destination.__module__ = source.__module__
309 return destination 312 return destination
310 313
311 314
312 def add_defaults(original, defaults, prefix=''): 315 def add_defaults(original, defaults, prefix=''):
313 ''' 316 '''
314 Add defaults to original dict if not already present. 317 Add defaults to original dict if not already present.
315 ''' 318 '''
316 for (name, value) in defaults.items(): 319 for (name, value) in defaults.items():
317 if prefix + name not in original: 320 if prefix + name not in original:
318 original[prefix + name] = value 321 original[prefix + name] = value
319 return original 322 return original
320 323
321 324
322 class UnimplementedMethod(Exception): 325 class UnimplementedMethod(Exception):
323 ''' 326 '''
324 Raised when an "abstract" method is not implemented. 327 Raised when an "abstract" method is not implemented.
325 ''' 328 '''
326 329
327 330
328 def unimplemented(method): 331 def unimplemented(method):
329 ''' 332 '''
330 A decorator that raise an error if the (abstract) method is called. 333 A decorator that raise an error if the (abstract) method is called.
331 ''' 334 '''
332 #noinspection PyUnusedLocal 335 #noinspection PyUnusedLocal
333 def replacement(*args, **kargs): 336 def replacement(*args, **kargs):
334 raise UnimplementedMethod(method) 337 raise UnimplementedMethod(method)
335 return replacement 338 return replacement
Powered by Google Project Hosting