Refactor: Split config objects to Gui and Probe
[mentor.git] / src / utils.py
blobdfcd0ae0a4b56a5dae6a5d1391251f74412e08d7
1 #!/usr/bin/env python
2 # -*- coding: iso-8859-2 -*-
4 # Copyright (C) 2007 Adam Folmert <afolmert@gmail.com>
6 # This file is free software; you can redistribute it and/or modify
7 # it under the terms of the GNU General Public License as published by
8 # the Free Software Foundation; either version 2, or (at your option)
9 # any later version.
11 # This file is distributed in the hope that it will be useful,
12 # but WITHOUT ANY WARRANTY; without even the implied warranty of
13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 # GNU General Public License for more details.
16 # You should have received a copy of the GNU General Public License
17 # along with this program; if not, write to the Free Software
18 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
21 """General utility functions.
23 This is a grab-bag for all useful general-purpose functions I use in the mentor
24 project.
25 These function may be well used in another project.
26 """
28 import exceptions
29 import logging
30 import os
31 import release
32 import sys
33 import time
34 import types
35 import string
36 import subprocess
38 __version__ = release.version
41 #----------------------------------------------------------
42 # Types routines
45 def isdict(obj):
46 """Returns True if object is a Dict."""
47 t = type(obj)
48 return t is types.DictType or \
49 (t is types.InstanceType and isinstance(obj, UserDict))
51 def islist(obj):
52 """Returns True if object is a List."""
53 t = type(obj)
54 return t is types.ListType \
55 or (t is types.InstanceType and isinstance(obj, UserList))
57 def issequence(obj):
58 """Returns True if object is a Sequence."""
59 t = type(obj)
60 return t is types.ListType \
61 or t is types.TupleType \
62 or (t is types.InstanceType and isinstance(obj, UserList))
64 def issequence2(x):
65 "Is x a sequence? We say it is if it has a __getitem__ method."
66 return hasattr(x, '__getitem__')
69 def istuple(obj):
70 """Returns True if object is a Tuple."""
71 t = type(obj)
72 return t is types.TupleType
75 def isstring(obj):
76 """Returns True if obj is a String."""
77 from UserString import UserString
78 t = type(obj)
79 return t in types.StringTypes \
80 or (t is types.InstanceType and isinstance(obj, UserString))
83 def isscalar(obj):
84 """Returns True if object is a scalar type."""
85 return isstring(obj) or (not islist(obj) and not istuple(obj))
88 def isnumber(obj):
89 "Is obj a number? We say it is if it has a __int__ method."
90 return hasattr(obj, '__int__')
93 def extend(seq1, seq2):
94 """Returns a copy of sequence extended with second sequence."""
95 result = list(seq1)[:]
96 result.extend(list(seq2))
97 if isstring(seq1):
98 return "".join([str(r) for r in result])
99 elif istuple(seq1):
100 return tuple(result)
101 else:
102 return result
105 def reverse(obj):
106 """Reverse a copy of object reversed."""
107 if isstring(obj) or istuple(obj) or islist(obj):
108 result = list(obj)[:]
109 result.reverse()
111 if isstring(obj):
112 return "".join(result)
113 elif istuple(obj):
114 return tuple(result)
115 else:
116 return result
117 else:
118 return obj
121 #----------------------------------------------------------
122 # Misc routines and classes
125 def nvl(a, b):
126 """NVL from SQL: return b if a in nil."""
127 if a is None or a == "" or a == [] or a == ():
128 return b
129 else:
130 return a
132 def decode(value, *args):
133 """DECODE-line function from SQL. """
134 i = 0
135 while i < len(args) - 1:
136 if value == args[i]:
137 return args[i + 1]
138 i += 2
139 return args[i]
141 #----------------------------------------------------------
142 # String utils
144 # Here are main classes which use parser to get a parse tree of parse objects
145 # use it to build items
146 # and then use Exporter objects to export those items
148 def ensure_endswith(string, suffix):
149 """Returns string with suffix added. Nothing is added if suffix already exists."""
150 if not string.endswith(suffix):
151 return string + suffix
152 else:
153 return string
156 #----------------------------------------------------------
157 # Enumeration type
160 class EnumException(exceptions.Exception):
161 """Exception used in Enum."""
162 pass
164 class Enumeration:
165 """Enumeration class object Python."""
166 def __init__(self, name, enumList):
167 self.__doc__ = name
168 lookup = { }
169 reverseLookup = { }
170 i = 0
171 uniqueNames = [ ]
172 uniqueValues = [ ]
173 for x in enumList:
174 if type(x) == types.TupleType:
175 x, i = x
176 if type(x) != types.StringType:
177 raise EnumException, "enum name is not a string: " + x
178 if type(i) != types.IntType:
179 raise EnumException, "enum value is not an integer: " + i
180 if x in uniqueNames:
181 raise EnumException, "enum name is not unique: " + x
182 if i in uniqueValues:
183 raise EnumException, "enum value is not unique for " + x
184 uniqueNames.append(x)
185 uniqueValues.append(i)
186 lookup[x] = i
187 reverseLookup[i] = x
188 i = i + 1
189 self.lookup = lookup
190 self.reverseLookup = reverseLookup
191 def __getattr__(self, attr):
192 if not self.lookup.has_key(attr):
193 raise AttributeError
194 return self.lookup[attr]
195 def whatis(self, value):
196 """Does reverse lookup on value."""
197 return self.reverseLookup[value]
200 def testEnumeration():
201 """Test of enumeration function."""
202 Volkswagen = Enumeration("Volkswagen",
203 ["JETTA",
204 "RABBIT",
205 "BEETLE",
206 ("THING", 400),
207 "PASSAT",
208 "GOLF",
209 ("CABRIO", 700),
210 "EURO_VAN",
211 "CLASSIC_BEETLE",
212 "CLASSIC_VAN"
215 Insect = Enumeration("Insect",
216 ["ANT",
217 "APHID",
218 "BEE",
219 "BEETLE",
220 "BUTTERFLY",
221 "MOTH",
222 "HOUSEFLY",
223 "WASP",
224 "CICADA",
225 "GRASSHOPPER",
226 "COCKROACH",
227 "DRAGONFLY"
230 def whatkind(value, enum):
231 """Returns what kind is value."""
232 return enum.__doc__ + "." + enum.whatis(value)
234 class ThingWithType:
235 """ThingWithType"""
236 def __init__(self, type):
237 self.type = type
240 car = ThingWithType(Volkswagen.BEETLE)
241 print whatkind(car.type, Volkswagen)
242 bug = ThingWithType(Insect.BEETLE)
243 print whatkind(bug.type, Insect)
245 # Notice that car's and bug's attributes don't include any of the
246 # enum machinery, because that machinery is all CLASS attributes and
247 # not INSTANCE attributes. So you can generate thousands of cars and
248 # bugs with reckless abandon, never worrying that time or memory will
249 # be wasted on redundant copies of the enum stuff.
251 print car.__dict__
252 print bug.__dict__
253 pprint.pprint(Volkswagen.__dict__)
254 pprint.pprint(Insect.__dict__)
258 #----------------------------------------------------------
259 # String interpolation object
260 # String interpolation for Python (by Ka-Ping Yee, 14 Feb 2000).
261 # Credits: these classes were stolen from IPython sources
263 # This module lets you quickly and conveniently interpolate values into
264 # strings (in the flavour of Perl or Tcl, but with less extraneous
265 # punctuation). You get a bit more power than in the other languages,
266 # because this module allows subscripting, slicing, function calls,
267 # attribute lookup, or arbitrary expressions. Variables and expressions
268 # are evaluated in the namespace of the caller.
270 # The itpl() function returns the result of interpolating a string, and
271 # printpl() prints out an interpolated string. Here are some examples:
273 # from Itpl import printpl
274 # printpl("Here is a $string.")
275 # printpl("Here is a $module.member.")
276 # printpl("Here is an $object.member.")
277 # printpl("Here is a $functioncall(with, arguments).")
278 # printpl("Here is an ${arbitrary + expression}.")
279 # printpl("Here is an $array[3] member.")
280 # printpl("Here is a $dictionary['member'].")
282 # The filter() function filters a file object so that output through it
283 # is interpolated. This lets you produce the illusion that Python knows
284 # how to do interpolation:
286 # import Itpl
287 # sys.stdout = Itpl.filter()
288 # f = "fancy"
289 # print "Isn't this $f?"
290 # print "Standard output has been replaced with a $sys.stdout object."
291 # sys.stdout = Itpl.unfilter()
292 # print "Okay, back $to $normal."
294 # Under the hood, the Itpl class represents a string that knows how to
295 # interpolate values. An instance of the class parses the string once
296 # upon initialization; the evaluation and substitution can then be done
297 # each time the instance is evaluated with str(instance). For example:
299 # from Itpl import Itpl
300 # s = Itpl("Here is $foo.")
301 # foo = 5
302 # print str(s)
303 # foo = "bar"
304 # print str(s)
307 from tokenize import tokenprog
310 class ItplError(ValueError):
311 """Exception defined for Itpl class."""
312 def __init__(self, text, pos):
313 self.text = text
314 self.pos = pos
315 def __str__(self):
316 return "unfinished expression in %s at char %d" % (
317 repr(self.text), self.pos)
319 def match_or_fail(text, pos):
320 """match_or_fail(text, pos)"""
321 match = tokenprog.match(text, pos)
322 if match is None:
323 raise ItplError(text, pos)
324 return match, match.end()
326 class Itpl:
328 Class representing a string with interpolation abilities.
330 Upon creation, an instance works out what parts of the format
331 string are literal and what parts need to be evaluated. The
332 evaluation and substitution happens in the namespace of the
333 caller when str(instance) is called."""
335 def __init__(self, format, codec='utf_8', encoding_errors='backslashreplace'):
336 """The single mandatory argument to this constructor is a format
337 string.
339 The format string is parsed according to the following rules:
341 1. A dollar sign and a name, possibly followed by any of:
342 - an open-paren, and anything up to the matching paren
343 - an open-bracket, and anything up to the matching bracket
344 - a period and a name
345 any number of times, is evaluated as a Python expression.
347 2. A dollar sign immediately followed by an open-brace, and
348 anything up to the matching close-brace, is evaluated as
349 a Python expression.
351 3. Outside of the expressions described in the above two rules,
352 two dollar signs in a row give you one literal dollar sign.
354 Optional arguments:
356 - codec('utf_8'): a string containing the name of a valid Python
357 codec.
359 - encoding_errors('backslashreplace'): a string with a valid error handling
360 policy. See the codecs module documentation for details.
362 These are used to encode the format string if a call to str() fails on
363 the expanded result."""
365 if not isinstance(format, basestring):
366 raise TypeError, "needs string initializer"
367 self.format = format
368 self.codec = codec
369 self.encoding_errors = encoding_errors
371 namechars = "abcdefghijklmnopqrstuvwxyz" \
372 "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_"
373 chunks = []
374 pos = 0
376 while 1:
377 dollar = string.find(format, "$", pos)
378 if dollar < 0:
379 break
380 nextchar = format[dollar+1]
382 if nextchar == "{":
383 chunks.append((0, format[pos:dollar]))
384 pos, level = dollar+2, 1
385 while level:
386 match, pos = match_or_fail(format, pos)
387 tstart, tend = match.regs[3]
388 token = format[tstart:tend]
389 if token == "{":
390 level = level+1
391 elif token == "}":
392 level = level-1
393 chunks.append((1, format[dollar+2:pos-1]))
395 elif nextchar in namechars:
396 chunks.append((0, format[pos:dollar]))
397 match, pos = match_or_fail(format, dollar+1)
398 while pos < len(format):
399 if format[pos] == "." and \
400 pos+1 < len(format) and format[pos+1] in namechars:
401 match, pos = match_or_fail(format, pos+1)
402 elif format[pos] in "([":
403 pos, level = pos+1, 1
404 while level:
405 match, pos = match_or_fail(format, pos)
406 tstart, tend = match.regs[3]
407 token = format[tstart:tend]
408 if token[0] in "([":
409 level = level+1
410 elif token[0] in ")]":
411 level = level-1
412 else:
413 break
414 chunks.append((1, format[dollar+1:pos]))
416 else:
417 chunks.append((0, format[pos:dollar+1]))
418 pos = dollar + 1 + (nextchar == "$")
420 if pos < len(format):
421 chunks.append((0, format[pos:]))
422 self.chunks = chunks
424 def __repr__(self):
425 return "<Itpl %s >" % repr(self.format)
427 def _str(self, glob, loc):
428 """Evaluate to a string in the given globals/locals.
430 The final output is built by calling str(), but if this fails, the
431 result is encoded with the instance's codec and error handling policy,
432 via a call to out.encode(self.codec, self.encoding_errors)"""
433 result = []
434 app = result.append
435 for live, chunk in self.chunks:
436 if live:
437 app(str(eval(chunk, glob, loc)))
438 else: app(chunk)
439 out = ''.join(result)
440 try:
441 return str(out)
442 except UnicodeError:
443 return out.encode(self.codec, self.encoding_errors)
445 def __str__(self):
446 """Evaluate and substitute the appropriate parts of the string."""
448 # We need to skip enough frames to get to the actual caller outside of
449 # Itpl.
450 frame = sys._getframe(1)
451 while frame.f_globals.has_key("__name__") and frame.f_globals["__name__"] == __name__:
452 frame = frame.f_back
453 loc, glob = frame.f_locals, frame.f_globals
455 return self._str(glob, loc)
458 class ItplNS(Itpl):
459 """Class representing a string with interpolation abilities.
461 This inherits from Itpl, but at creation time a namespace is provided
462 where the evaluation will occur. The interpolation becomes a bit more
463 efficient, as no traceback needs to be extracte. It also allows the
464 caller to supply a different namespace for the interpolation to occur than
465 its own."""
467 def __init__(self, format, globals, locals=None,
468 codec='utf_8', encoding_errors='backslashreplace'):
469 """ItplNS(format, globals[, locals]) -> interpolating string instance.
471 This constructor, besides a format string, takes a globals dictionary
472 and optionally a locals (which defaults to globals if not provided).
474 For further details, see the Itpl constructor."""
476 if locals is None:
477 locals = globals
478 self.globals = globals
479 self.locals = locals
480 Itpl.__init__(self, format, codec, encoding_errors)
482 def __str__(self):
483 """Evaluate and substitute the appropriate parts of the string."""
484 return self._str(self.globals, self.locals)
486 def __repr__(self):
487 return "<ItplNS %s >" % repr(self.format)
489 # utilities for fast printing
490 def itpl(text):
491 """Returns interpolated text."""
492 return str(Itpl(text))
494 def printpl(text):
495 """Prints interpolated text."""
496 print itpl(text)
498 # versions with namespace
499 def itplns(text, globals, locals=None):
500 """Returns interpolated text (namespace version)."""
501 return str(ItplNS(text, globals, locals))
503 def printplns(text, globals, locals=None):
504 """Prints interpolated text (namespace version)."""
505 print itplns(text, globals, locals)
507 class ItplFile:
508 """A file object that filters each write() through an interpolator."""
509 def __init__(self, file):
510 self.file = file
511 def __repr__(self):
512 return "<interpolated " + repr(self.file) + ">"
513 def __getattr__(self, attr):
514 return getattr(self.file, attr)
515 def write(self, text):
516 """Overriden write function."""
517 self.file.write(str(Itpl(text)))
520 def itpl_filter(file=sys.stdout):
521 """Return an ItplFile that filters writes to the given file object.
523 'file = filter(file)' replaces 'file' with a filtered object that
524 has a write() method. When called with no argument, this creates
525 a filter to sys.stdout."""
526 return ItplFile(file)
528 def itpl_unfilter(ifile=None):
529 """Return the original file that corresponds to the given ItplFile.
531 'file = unfilter(file)' undoes the effect of 'file = filter(file)'.
532 'sys.stdout = unfilter()' undoes the effect of 'sys.stdout = filter()'."""
533 return ifile and ifile.file or sys.stdout.file
537 class NLprinter:
538 """Print an arbitrarily nested list, indicating index numbers.
540 An instance of this class called nlprint is available and callable as a
541 function.
543 nlprint(list, indent=' ', sep=': ') -> prints indenting each level by 'indent'
544 and using 'sep' to separate the index from the value. """
546 def __init__(self):
547 self.depth = 0
549 def __call__(self, lst, pos='', **kw):
550 """Prints the nested list numbering levels."""
551 kw.setdefault('indent', ' ')
552 kw.setdefault('sep', ': ')
553 kw.setdefault('start', 0)
554 kw.setdefault('stop', len(lst))
555 # we need to remove start and stop from kw so they don't propagate
556 # into a recursive call for a nested list.
557 start = kw['start']
558 del kw['start']
559 stop = kw['stop']
560 del kw['stop']
561 if self.depth == 0 and 'header' in kw.keys():
562 print kw['header']
564 for idx in range(start, stop):
565 elem = lst[idx]
566 if type(elem)==type([]):
567 self.depth += 1
568 self.__call__(elem, itpl('$pos$idx, '), **kw)
569 self.depth -= 1
570 else:
571 printpl(kw['indent']*self.depth+'$pos$idx$kw["sep"]$elem')
573 nlprint = NLprinter()
577 #----------------------------------------------------------
578 # Logging simplified interface
579 # TODO rewrite this basing on Bazaar debugging facilities
581 # logging interface
583 INITIAL_LEVEL = logging.DEBUG
584 # INITIAL_LEVEL = logging.INFO
586 logging.basicConfig(level=INITIAL_LEVEL,
587 format='%(asctime)s %(levelname)-8s %(message)s',
588 datefmt='%H:%M:%S'
592 # TODO change to some windows temp value
593 def set_file_debug(fname = "D:\\Temp\\py.log"):
595 Sets debuglevel of message going to file.
597 l = logging.getLogger()
598 fh = logging.FileHandler(fname)
599 fh.setLevel(INITIAL_LEVEL)
601 fmt = logging.Formatter('%(asctime)s %(levelname)-8s %(message)s')
602 fh.setFormatter(fmt)
604 l.addHandler(fh)
606 # this is broken ??
607 def enable_logging(enable=True):
608 """Enables or disables logging. """
609 if enable:
610 logging.getLogger().setLevel(logging.DEBUG)
611 else:
612 logging.getLogger().setLevel(logging.INFO)
616 def debug(aMesg):
617 """Prints a debug message to logger."""
618 logging.debug(itpl(str(aMesg)))
620 def warn(aMesg):
621 """Prints a warning message to logger."""
622 logging.warn(itpl(str(aMesg)))
625 def info(aMesg):
626 """Prints an info message to logger."""
627 logging.info(itpl(str(aMesg)))
629 def critical(aMesg):
630 """Prints a critical message to logger."""
631 logging.critical(itpl(str(aMesg)))
633 def error(aMesg):
634 """Writes error messages and exits. """
635 sys.stderr.write("Error: " + itpl(str(aMesg)))
636 sys.stderr.write("\n")
637 sys.stderr.flush()
638 sys.exit()
641 log = debug
644 #----------------------------------------------------------
645 # System related functions
648 def run_command(command, outputfilename=None):
649 """Runs a command returning it's code and putting output to file"""
651 print "Running command: " + str(command)
653 outputfile = None
654 if outputfilename is not None:
655 outputfile = open(outputfilename, "wb")
656 p = subprocess.Popen(command, stdout=outputfile)
658 result = p.wait()
660 if outputfile is not None:
661 outputfile.close()
663 return result
665 #----------------------------------------------------------
666 # Build and repo related functions
671 def get_git_root_dir(path):
672 """Returns Git root path for given file, if file is in Git repository."""
673 path = os.path.abspath(path)
674 parent = os.path.dirname(path)
675 # go up recursively until find .git dir or go to top directory
676 while not os.path.isdir(os.path.join(path, '.git')) and path != '' and path != parent:
677 path = parent
678 parent = os.path.dirname(path)
680 if os.path.isdir(os.path.join(path, '.git')) and \
681 os.path.isdir(os.path.join(path, '.git/refs')) and \
682 os.path.isfile(os.path.join(path, '.git/HEAD')):
683 return os.path.join(path)
684 else:
685 return None
689 def generate_buildno(path):
690 """Generates build number in build_stamp.py file, which gets imported to
691 read the current build version.
692 The build number is generated as current date yyyymmdd plus 6 digits of
693 current head git sha1 code.
695 # generate current date
696 builddate = time.strftime('%y%m%d')
698 # try to find .git head tip and use it as a tip
699 # else read embedded number by the build script.
700 buildsha1 = ''
701 git_root_dir = get_git_root_dir(path)
702 if git_root_dir:
703 try:
704 # try to run git to retrieve the name directly
705 # if it's not found then try to run git process
706 import subprocess
707 olddir = os.getcwd()
708 os.chdir(git_root_dir)
709 # check for SHA1
710 output = subprocess.Popen(['git', 'show', '-s'], stdout=subprocess.PIPE).communicate()[0]
711 headsha1 = output[7:]
712 # check for additional info
713 # check if is dirty
714 suffix = ''
715 output = subprocess.Popen(['git', 'diff', 'head'], stdout=subprocess.PIPE).communicate()[0]
716 if output.strip() != '':
717 suffix += '-dirty'
718 os.chdir(olddir)
719 buildsha1 = headsha1[0:6] + suffix
720 except:
721 buildsha1 = '?'
723 return builddate + '.' + buildsha1
726 def load_stamped_buildno():
727 buildno = None
728 try:
729 import build_stamp
730 buildno = build_stamp.buildno
731 except:
732 raise
733 # pass
734 return buildno
737 def save_stamped_buildno():
738 git_root_dir = get_git_root_dir(os.getcwd())
739 assert git_root_dir <> '', 'GIT root dir not found!'
740 f = open(os.path.join(git_root_dir, 'src/build_stamp.py'), 'wt')
741 f.write('#!/bin/env python\n')
742 f.write('# DO NOT EDIT THIS FILE.\n')
743 f.write('# This file is generated automatically by the build.py script.\n')
744 f.write('# All contents here will be overwritten.\n\n')
745 f.write('buildno = "%s"' % generate_buildno(git_root_dir))
746 f.close()
748 def delete_stamped_buildno():
749 git_root_dir = get_git_root_dir(os.getcwd())
750 assert git_root_dir <> '', 'GIT root dir not found!'
751 try:
752 os.remove(os.path.join(git_root_dir, 'src/build_stamp.py'))
753 os.remove(os.path.join(git_root_dir, 'src/build_stamp.pyc'))
754 except:
755 pass