# Copyright (C) 2005-2012 Aaron Bentley <abentley@panoramicfeedback.com>
# Chris Ball <cjb@laptop.org>
# Gianluca Montecchi <gian@grys.it>
# W. Trevor King <wking@tremily.us>
#
# 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 <http://www.gnu.org/licenses/>.
"""
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 (str,):
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()