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