Nektar++
utilities.py
Go to the documentation of this file.
1"""Copyright (c) 2005-2016, University of Oxford.
2All rights reserved.
3
4University of Oxford means the Chancellor, Masters and Scholars of the
5University of Oxford, having an administrative office at Wellington
6Square, Oxford OX1 2JD, UK.
7
8This file is part of Chaste.
9
10Redistribution and use in source and binary forms, with or without
11modification, are permitted provided that the following conditions are met:
12 * Redistributions of source code must retain the above copyright notice,
13 this list of conditions and the following disclaimer.
14 * Redistributions in binary form must reproduce the above copyright notice,
15 this list of conditions and the following disclaimer in the documentation
16 and/or other materials provided with the distribution.
17 * Neither the name of the University of Oxford nor the names of its
18 contributors may be used to endorse or promote products derived from this
19 software without specific prior written permission.
20
21THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
22AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
23IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
24ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
25LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
26CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
27GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
28HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
29LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
30OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
31"""
32
33"""
34Various helpful utility functions/classes used by PyCml.
35"""
36
37import logging
38import sys
39from xml.dom import Node # For nodeType values
40
41import amara
42
43from enum import Enum
44
45__all__ = ['OnlyWarningsFilter', 'OnlyDebugFilter', 'OnlyTheseSourcesFilter', 'NotifyHandler',
46 'DEBUG', 'LOG',
47 'Colourable', 'DFS', 'Sentinel', 'unitary_iterator',
48 'amara_parse', 'element_path', 'element_path_cmp', 'element_xpath', 'brief_xml',
49 'call_if', 'max_i', 'prid', 'add_dicts',
50 'open_output_stream', 'close_output_stream']
51
52################################################################################
53# #
54# Logging #
55# #
56################################################################################
57
58class OnlyWarningsFilter(logging.Filter):
59 """A filter that only passes warning messages."""
60 def filter(self, rec):
61 return (logging.WARNING <= rec.levelno < logging.ERROR)
62
63
64class OnlyDebugFilter(logging.Filter):
65 """A filter that only passes debug messages."""
66 def filter(self, rec):
67 return (logging.DEBUG <= rec.levelno < logging.INFO)
68
69
70class OnlyTheseSourcesFilter(logging.Filter):
71 """A filter that only emits messages from the given sources."""
72 def __init__(self, sources):
73 logging.Filter.__init__(self)
74 self.__sources = sources
75 def filter(self, rec):
76 return rec.name in self.__sources
77
78
79class NotifyHandler(logging.Handler):
80 """
81 A logging handler that just notes if any messages are logged.
82 """
83 def __init__(self, level=logging.NOTSET):
84 logging.Handler.__init__(self, level=level)
85 self.reset()
86
87 def emit(self, record):
88 self.messages = True
89
90 def reset(self):
91 """Reset the handler, as if no messages have occurred."""
92 self.messages = False
93
94
95def DEBUG(facility, *args):
96 """Log a debug message to facility.
97
98 Arguments are treated as for the print statement.
99 """
100 logger = logging.getLogger(facility)
101 if logger.isEnabledFor(logging.DEBUG):
102 logger.debug(' '.join(map(str, args)))
103
104
105def LOG(facility, level, *args):
106 """Log a message to facility with the given level.
107
108 Arguments are treated as for the print statement.
109 """
110 logger = logging.getLogger(facility)
111 if logger.isEnabledFor(level):
112 logger.log(level, ' '.join(map(str, args)))
113
114################################################################################
115# #
116# Miscellaneous classes #
117# #
118################################################################################
119
120# Useful constants for depth-first search
121DFS = Enum('White', 'Gray', 'Black')
122
123class Colourable(object):
124 """
125 A mixin class for objects that have a colour attribute, and so support
126 a depth-first search.
127 """
128 def __init__(self, *args, **kwargs):
129 super(Colourable, self).__init__(*args, **kwargs)
130 self.clear_colour()
131
132 def set_colour(self, colour):
133 self._cml_colour = colour
134
135 def get_colour(self):
136 return self._cml_colour
137
138 def clear_colour(self):
139 self._cml_colour = DFS.White
140
141
142class Sentinel(object):
143 """A simple true/false store that can be used as a default parameter value in recursive calls.
144
145 This provides a slightly nicer looking alternative to code such as:
146
147 def f(done=[False]):
148 if not done[0]:
149 # do some stuff
150 if xyz: done[0] = True
151 f(done)
152
153 The list can be replaced with a Sentinel instance.
154 """
155 def __init__(self, tf=False):
156 """Create a new, unset sentinel."""
157 self._tf = tf
158
159 def __nonzero__(self):
160 """Test whether the sentinel has been set."""
161 return self._tf
162
163 def set(self, tf=True):
164 """Set the sentinel."""
165 self._tf = tf
166
167
168class unitary_iterator(object):
169 """An iterator over a single item."""
170 def __init__(self, start):
171 self.curr = start
172 return
173
174 def __iter__(self):
175 return self
176
177 def next(self):
178 if not self.curr:
179 raise StopIteration()
180 result = self.curr
181 self.curr = None
182 return result
183
184
185################################################################################
186# #
187# XML-related functions #
188# #
189################################################################################
190
191def amara_parse(source, uri=None, rules=None, binderobj=None,
192 prefixes=None):
193 """Convenience function for parsing XML.
194
195 Works just as amara.parse, except that if source is '-' then
196 it reads from standard input.
197 """
198 if source == '-':
199 return amara.parse(sys.stdin, uri=uri, rules=rules,
200 binderobj=binderobj, prefixes=prefixes)
201 else:
202 return amara.parse(source, uri=uri, rules=rules,
203 binderobj=binderobj, prefixes=prefixes)
204
205
207 """Find the path from the root element to this element."""
208 if hasattr(elt, 'xml_parent'):
209 idx = 0
210 for child in elt.xml_parent.xml_children:
211 if getattr(child, 'nodeType', None) == Node.ELEMENT_NODE:
212 idx += 1
213 if child is elt:
214 break
215 return element_path(elt.xml_parent) + [idx]
216 else:
217 return []
218
219
221 """Compare 2 elements by comparing their paths from the root element."""
222 return cmp(element_path(e1), element_path(e2))
223
224
226 """Return an xpath expression that will select this element."""
227 indices = element_path(elt)
228 xpath = u'/*[' + u']/*['.join(map(str, indices)) + u']'
229 return xpath
230
231
232def brief_xml(elt):
233 """Print a more concise version of elt.xml() that omits all attributes."""
234 s = ''
235 if getattr(elt, 'nodeType', None) == Node.ELEMENT_NODE:
236 children = getattr(elt, 'xml_children', [])
237 if children:
238 s += '<' + elt.localName + '>'
239 for child in children:
240 s += brief_xml(child)
241 s += '</' + elt.localName + '>'
242 else:
243 s += '<' + elt.localName + '/>'
244 else:
245 s += str(elt)
246 return s
247
248
249################################################################################
250# #
251# Miscellaneous functions #
252# #
253################################################################################
254
255def call_if(tf, callable, *args, **kwargs):
256 """Call the given callable with the given arguments iff tf is True."""
257 if tf:
258 return callable(*args, **kwargs)
259
260
261def max_i(it):
262 """Find the maximum entry of an iterable, and return it with its index.
263
264 Returns (i, m) where i is the index of m, the maximum entry of `it`.
265 """
266 idx, m = None, None
267 for i, val in enumerate(it):
268 if m is None or val > m:
269 m, idx = val, i
270 return idx, m
271
272
273def prid(obj, show_cls=False):
274 """Get the id of an object as a hex string, optionally with its class/type."""
275 if obj is None:
276 s = 'None'
277 else:
278 s = hex(id(obj))
279 if show_cls:
280 s += str(type(obj))
281 return s
282
283
284def add_dicts(r, *ds):
285 """Add multiple dictionaries together.
286
287 Updates the first input dictionary by adding values from
288 subsequent inputs. Produces a dictionary with keys taken from the
289 input dictionaries, and values being the sum of the corresponding
290 values in all inputs.
291 Assumes values are numeric.
292 """
293 for d in ds:
294 for k, v in d.iteritems():
295 r[k] = r.get(k, 0) + v
296
297
299 """Open fname for output.
300
301 fname should be a local filename, or '-' for standard output.
302 Additionally, the names 'stdout' and 'stderr' have the usual
303 special meanings.
304 """
305 if fname == '-' or fname == 'stdout':
306 stream = sys.stdout
307 elif fname == 'stderr':
308 stream = sys.stderr
309 else:
310 stream = open(fname, 'w')
311 return stream
312
313
315 """
316 Close the given output stream, unless it's one of the standard streams
317 (i.e. sys.stdout or sys.stderr).
318 Note that closing a stream multiple times is safe.
319 """
320 if not stream is sys.stdout and not stream is sys.stderr:
321 stream.close()
def __init__(self, *args, **kwargs)
Definition: utilities.py:128
def __init__(self, level=logging.NOTSET)
Definition: utilities.py:83
def __init__(self, tf=False)
Definition: utilities.py:155
def Enum(*names)
Definition: enum.py:8
def open_output_stream(fname)
Definition: utilities.py:298
def amara_parse(source, uri=None, rules=None, binderobj=None, prefixes=None)
Definition: utilities.py:192
def LOG(facility, level, *args)
Definition: utilities.py:105
def prid(obj, show_cls=False)
Definition: utilities.py:273
def call_if(tf, callable, *args, **kwargs)
Definition: utilities.py:255
def close_output_stream(stream)
Definition: utilities.py:314
def DEBUG(facility, *args)
Definition: utilities.py:95
def element_path_cmp(e1, e2)
Definition: utilities.py:220