# Copyright (C) 2005-2012 Aaron Bentley # Chris Ball # Gianluca Montecchi # W. Trevor King # # This file is part of Bugs Everywhere. # # Bugs Everywhere is free software: you can redistribute it and/or modify it # under the terms of the GNU General Public License as published by the Free # Software Foundation, either version 2 of the License, or (at your option) any # later version. # # Bugs Everywhere is distributed in the hope that it will be useful, but # WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or # FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for # more details. # # You should have received a copy of the GNU General Public License along with # Bugs Everywhere. If not, see . """ Assorted utility functions that don't fit in anywhere else. """ import calendar import codecs import os import shutil import tempfile import time import types try: # import core module, Python >= 2.5 from xml.etree import ElementTree except ImportError: # look for non-core module from elementtree import ElementTree import libbe if libbe.TESTING == True: import doctest class InvalidXML(ValueError): """Invalid XML while parsing for a `*.from_xml()` method. Parameters ---------- type : str String identifying `*`, e.g. "bug", "comment", ... element : :py:class:`ElementTree.Element` ElementTree.Element instance which caused the error. error : str Error description. """ def __init__(self, type, element, error): msg = 'Invalid %s xml: %s\n %s\n' \ % (type, error, ElementTree.tostring(element)) ValueError.__init__(self, msg) self.type = type self.element = element self.error = error def search_parent_directories(path, filename): """ Find the file (or directory) named filename in path or in any of path's parents. For example:: search_parent_directories("/a/b/c", ".be") will return the path to the first existing file from:: /a/b/c/.be /a/b/.be /a/.be /.be or `None` if none of those files exist. """ path = os.path.realpath(path) assert os.path.exists(path) old_path = None while True: check_path = os.path.join(path, filename) if os.path.exists(check_path): return check_path if path == old_path: return None old_path = path path = os.path.dirname(path) class Dir (object): """A temporary directory for testing use. Make sure you run :py:meth:`cleanup` after you're done using the directory. """ def __init__(self): self.path = tempfile.mkdtemp(prefix="BEtest") self.removed = False def cleanup(self): if self.removed == False: shutil.rmtree(self.path) self.removed = True def __call__(self): return self.path RFC_2822_TIME_FMT = "%a, %d %b %Y %H:%M:%S +0000" """RFC 2822 [#]_ format string for :py:func:`time.strftime` and :py:func:`time.strptime`. .. [#] See `RFC 2822`_, sections 3.3 and A.1.1. .. _RFC 2822: http://www.faqs.org/rfcs/rfc2822.html """ def time_to_str(time_val): """Convert a time number into an RFC 2822-formatted string. Parameters ---------- time_val : float Float seconds since the Epoc, see :py:func:`time.time`. Note that while `time_val` may contain sub-second data, the output string will not. Examples -------- >>> time_to_str(0) 'Thu, 01 Jan 1970 00:00:00 +0000' See Also -------- str_to_time : inverse handy_time : localtime string """ return time.strftime(RFC_2822_TIME_FMT, time.gmtime(time_val)) def str_to_time(str_time): """Convert an RFC 2822-fomatted string into a time value. Parameters ---------- str_time : str An RFC 2822-formatted string. Examples -------- >>> str_to_time("Thu, 01 Jan 1970 00:00:00 +0000") 0 >>> q = time.time() >>> str_to_time(time_to_str(q)) == int(q) True >>> str_to_time("Thu, 01 Jan 1970 00:00:00 -1000") 36000 See Also -------- time_to_str : inverse """ timezone_str = str_time[-5:] if timezone_str != "+0000": str_time = str_time.replace(timezone_str, "+0000") time_val = calendar.timegm(time.strptime(str_time, RFC_2822_TIME_FMT)) timesign = -int(timezone_str[0]+"1") # "+" -> time_val ahead of GMT timezone_tuple = time.strptime(timezone_str[1:], "%H%M") timezone = timezone_tuple.tm_hour*3600 + timezone_tuple.tm_min*60 return time_val + timesign*timezone def handy_time(time_val): """Convert a time number into a useful localtime. Where :py:func:`time_to_str` returns GMT +0000, `handy_time` returns a string in local time. This may be more accessible for the user. Parameters ---------- time_val : float Float seconds since the Epoc, see :py:func:`time.time`. """ return time.strftime("%a, %d %b %Y %H:%M", time.localtime(time_val)) def time_to_gmtime(str_time): """Convert an RFC 2822-fomatted string to a GMT string. Parameters ---------- str_time : str An RFC 2822-formatted string. Examples -------- >>> time_to_gmtime("Thu, 01 Jan 1970 00:00:00 -1000") 'Thu, 01 Jan 1970 10:00:00 +0000' """ time_val = str_to_time(str_time) return time_to_str(time_val) def iterable_full_of_strings(value, alternative=None): """Require an iterable full of strings. This is useful, for example, in validating `*.extra_strings`. See :py:attr:`libbe.bugdir.BugDir.extra_strings` Parameters ---------- value : list or None The potential list of strings. alternative Allow a default (e.g. `None`), such that:: iterable_full_of_strings(value=x, alternative=x) -> True Examples -------- >>> iterable_full_of_strings([]) True >>> iterable_full_of_strings(["abc", "def", u"hij"]) True >>> iterable_full_of_strings(["abc", None, u"hij"]) False >>> iterable_full_of_strings(None, alternative=None) True """ if value == alternative: return True elif not hasattr(value, '__iter__'): return False for x in value: if type(x) not in types.StringTypes: return False return True def underlined(string, char='='): """Produces a version of a string that is underlined. Parameters ---------- string : str The string to underline char : str The character to use for the underlining. Examples -------- >>> underlined("Underlined String") 'Underlined String\\n=================' """ assert len(char) == 1, char return '%s\n%s' % (string, char*len(string)) if libbe.TESTING == True: suite = doctest.DocTestSuite()