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