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)
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
25 These function may be well used in another project.
38 __version__
= release
.version
41 #----------------------------------------------------------
46 """Returns True if object is a Dict."""
48 return t
is types
.DictType
or \
49 (t
is types
.InstanceType
and isinstance(obj
, UserDict
))
52 """Returns True if object is a List."""
54 return t
is types
.ListType \
55 or (t
is types
.InstanceType
and isinstance(obj
, UserList
))
58 """Returns True if object is a Sequence."""
60 return t
is types
.ListType \
61 or t
is types
.TupleType \
62 or (t
is types
.InstanceType
and isinstance(obj
, UserList
))
65 "Is x a sequence? We say it is if it has a __getitem__ method."
66 return hasattr(x
, '__getitem__')
70 """Returns True if object is a Tuple."""
72 return t
is types
.TupleType
76 """Returns True if obj is a String."""
77 from UserString
import UserString
79 return t
in types
.StringTypes \
80 or (t
is types
.InstanceType
and isinstance(obj
, UserString
))
84 """Returns True if object is a scalar type."""
85 return isstring(obj
) or (not islist(obj
) and not istuple(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
))
98 return "".join([str(r
) for r
in result
])
106 """Reverse a copy of object reversed."""
107 if isstring(obj
) or istuple(obj
) or islist(obj
):
108 result
= list(obj
)[:]
112 return "".join(result
)
121 #----------------------------------------------------------
122 # Misc routines and classes
126 """NVL from SQL: return b if a in nil."""
127 if a
is None or a
== "" or a
== [] or a
== ():
132 def decode(value
, *args
):
133 """DECODE-line function from SQL. """
135 while i
< len(args
) - 1:
141 #----------------------------------------------------------
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
156 #----------------------------------------------------------
160 class EnumException(exceptions
.Exception):
161 """Exception used in Enum."""
165 """Enumeration class object Python."""
166 def __init__(self
, name
, enumList
):
174 if type(x
) == types
.TupleType
:
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
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
)
190 self
.reverseLookup
= reverseLookup
191 def __getattr__(self
, attr
):
192 if not self
.lookup
.has_key(attr
):
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",
215 Insect
= Enumeration("Insect",
230 def whatkind(value
, enum
):
231 """Returns what kind is value."""
232 return enum
.__doc
__ + "." + enum
.whatis(value
)
236 def __init__(self
, 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.
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:
287 # sys.stdout = Itpl.filter()
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.")
307 from tokenize
import tokenprog
310 class ItplError(ValueError):
311 """Exception defined for Itpl class."""
312 def __init__(self
, text
, pos
):
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
)
323 raise ItplError(text
, pos
)
324 return match
, match
.end()
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
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
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.
356 - codec('utf_8'): a string containing the name of a valid Python
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"
369 self
.encoding_errors
= encoding_errors
371 namechars
= "abcdefghijklmnopqrstuvwxyz" \
372 "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_"
377 dollar
= string
.find(format
, "$", pos
)
380 nextchar
= format
[dollar
+1]
383 chunks
.append((0, format
[pos
:dollar
]))
384 pos
, level
= dollar
+2, 1
386 match
, pos
= match_or_fail(format
, pos
)
387 tstart
, tend
= match
.regs
[3]
388 token
= format
[tstart
:tend
]
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
405 match
, pos
= match_or_fail(format
, pos
)
406 tstart
, tend
= match
.regs
[3]
407 token
= format
[tstart
:tend
]
410 elif token
[0] in ")]":
414 chunks
.append((1, format
[dollar
+1:pos
]))
417 chunks
.append((0, format
[pos
:dollar
+1]))
418 pos
= dollar
+ 1 + (nextchar
== "$")
420 if pos
< len(format
):
421 chunks
.append((0, format
[pos
:]))
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)"""
435 for live
, chunk
in self
.chunks
:
437 app(str(eval(chunk
, glob
, loc
)))
439 out
= ''.join(result
)
443 return out
.encode(self
.codec
, self
.encoding_errors
)
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
450 frame
= sys
._getframe
(1)
451 while frame
.f_globals
.has_key("__name__") and frame
.f_globals
["__name__"] == __name__
:
453 loc
, glob
= frame
.f_locals
, frame
.f_globals
455 return self
._str
(glob
, loc
)
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
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."""
478 self
.globals = globals
480 Itpl
.__init
__(self
, format
, codec
, encoding_errors
)
483 """Evaluate and substitute the appropriate parts of the string."""
484 return self
._str
(self
.globals, self
.locals)
487 return "<ItplNS %s >" % repr(self
.format
)
489 # utilities for fast printing
491 """Returns interpolated text."""
492 return str(Itpl(text
))
495 """Prints interpolated 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)
508 """A file object that filters each write() through an interpolator."""
509 def __init__(self
, file):
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
538 """Print an arbitrarily nested list, indicating index numbers.
540 An instance of this class called nlprint is available and callable as a
543 nlprint(list, indent=' ', sep=': ') -> prints indenting each level by 'indent'
544 and using 'sep' to separate the index from the value. """
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.
561 if self
.depth
== 0 and 'header' in kw
.keys():
564 for idx
in range(start
, stop
):
566 if type(elem
)==type([]):
568 self
.__call
__(elem
, itpl('$pos$idx, '), **kw
)
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
583 INITIAL_LEVEL
= logging
.DEBUG
584 # INITIAL_LEVEL = logging.INFO
586 logging
.basicConfig(level
=INITIAL_LEVEL
,
587 format
='%(asctime)s %(levelname)-8s %(message)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')
607 def enable_logging(enable
=True):
608 """Enables or disables logging. """
610 logging
.getLogger().setLevel(logging
.DEBUG
)
612 logging
.getLogger().setLevel(logging
.INFO
)
617 """Prints a debug message to logger."""
618 logging
.debug(itpl(str(aMesg
)))
621 """Prints a warning message to logger."""
622 logging
.warn(itpl(str(aMesg
)))
626 """Prints an info message to logger."""
627 logging
.info(itpl(str(aMesg
)))
630 """Prints a critical message to logger."""
631 logging
.critical(itpl(str(aMesg
)))
634 """Writes error messages and exits. """
635 sys
.stderr
.write("Error: " + itpl(str(aMesg
)))
636 sys
.stderr
.write("\n")
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
)
654 if outputfilename
is not None:
655 outputfile
= open(outputfilename
, "wb")
656 p
= subprocess
.Popen(command
, stdout
=outputfile
)
660 if outputfile
is not None:
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
:
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
)
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.
701 git_root_dir
= get_git_root_dir(path
)
704 # try to run git to retrieve the name directly
705 # if it's not found then try to run git process
708 os
.chdir(git_root_dir
)
710 output
= subprocess
.Popen(['git', 'show', '-s'], stdout
=subprocess
.PIPE
).communicate()[0]
711 headsha1
= output
[7:]
712 # check for additional info
715 output
= subprocess
.Popen(['git', 'diff', 'head'], stdout
=subprocess
.PIPE
).communicate()[0]
716 if output
.strip() != '':
719 buildsha1
= headsha1
[0:6] + suffix
723 return builddate
+ '.' + buildsha1
726 def load_stamped_buildno():
730 buildno
= build_stamp
.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
))
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!'
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'))