/distribution/libraries/Babel-1.0dev-py3.2/babel/util.py
Python | 377 lines | 326 code | 10 blank | 41 comment | 13 complexity | 393700d9e11a263b72ae155d5ecf724f MD5 | raw file
Possible License(s): BSD-3-Clause, GPL-3.0, GPL-2.0
- # -*- coding: utf-8 -*-
- #
- # Copyright (C) 2007-2011 Edgewall Software
- # All rights reserved.
- #
- # This software is licensed as described in the file COPYING, which
- # you should have received as part of this distribution. The terms
- # are also available at http://babel.edgewall.org/wiki/License.
- #
- # This software consists of voluntary contributions made by many
- # individuals. For the exact contribution history, see the revision
- # history and logs, available at http://babel.edgewall.org/log/.
- """Various utility classes and functions."""
- import codecs
- from datetime import timedelta, tzinfo
- import os
- import re
- import sys
- import textwrap
- import time
- from babel.compat import PY3, binary_type, text_type, b
- missing = object()
- __all__ = ['distinct', 'pathmatch', 'relpath', 'wraptext', 'odict', 'UTC',
- 'LOCALTZ']
- __docformat__ = 'restructuredtext en'
- def distinct(iterable):
- """Yield all items in an iterable collection that are distinct.
- Unlike when using sets for a similar effect, the original ordering of the
- items in the collection is preserved by this function.
- >>> print(list(distinct([1, 2, 1, 3, 4, 4])))
- [1, 2, 3, 4]
- >>> print(list(distinct('foobar')))
- ['f', 'o', 'b', 'a', 'r']
- :param iterable: the iterable collection providing the data
- :return: the distinct items in the collection
- :rtype: ``iterator``
- """
- seen = set()
- for item in iter(iterable):
- if item not in seen:
- yield item
- seen.add(item)
- # Regexp to match python magic encoding line
- PYTHON_MAGIC_COMMENT_re = re.compile(
- b(r'[ \t\f]* \# .* coding[=:][ \t]*([-\w.]+)'), re.VERBOSE)
- def parse_encoding(fp):
- """Deduce the encoding of a source file from magic comment.
- It does this in the same way as the `Python interpreter`__
- .. __: http://docs.python.org/ref/encodings.html
- The ``fp`` argument should be a seekable file object.
- (From Jeff Dairiki)
- """
- pos = fp.tell()
- fp.seek(0)
- try:
- line1 = fp.readline()
- if isinstance(line1, text_type):
- line1 = line1.encode()
- has_bom = line1.startswith(codecs.BOM_UTF8)
- if has_bom:
- line1 = line1[len(codecs.BOM_UTF8):]
- m = PYTHON_MAGIC_COMMENT_re.match(line1)
- if not m:
- try:
- import parser
- parser.suite(line1.decode())
- except (ImportError, SyntaxError):
- # Either it's a real syntax error, in which case the source is
- # not valid python source, or line2 is a continuation of line1,
- # in which case we don't want to scan line2 for a magic
- # comment.
- pass
- else:
- line2 = fp.readline()
- if isinstance(line2, text_type):
- line2 = line2.encode()
- m = PYTHON_MAGIC_COMMENT_re.match(line2)
- if has_bom:
- if m:
- raise SyntaxError(
- "python refuses to compile code with both a UTF8 "
- "byte-order-mark and a magic encoding comment")
- return 'utf_8'
- elif m:
- return m.group(1)
- else:
- return None
- finally:
- fp.seek(pos)
- def pathmatch(pattern, filename):
- """Extended pathname pattern matching.
-
- This function is similar to what is provided by the ``fnmatch`` module in
- the Python standard library, but:
-
- * can match complete (relative or absolute) path names, and not just file
- names, and
- * also supports a convenience pattern ("**") to match files at any
- directory level.
-
- Examples:
-
- >>> pathmatch('**.py', 'bar.py')
- True
- >>> pathmatch('**.py', 'foo/bar/baz.py')
- True
- >>> pathmatch('**.py', 'templates/index.html')
- False
-
- >>> pathmatch('**/templates/*.html', 'templates/index.html')
- True
- >>> pathmatch('**/templates/*.html', 'templates/foo/bar.html')
- False
-
- :param pattern: the glob pattern
- :param filename: the path name of the file to match against
- :return: `True` if the path name matches the pattern, `False` otherwise
- :rtype: `bool`
- """
- symbols = {
- '?': '[^/]',
- '?/': '[^/]/',
- '*': '[^/]+',
- '*/': '[^/]+/',
- '**/': '(?:.+/)*?',
- '**': '(?:.+/)*?[^/]+',
- }
- buf = []
- for idx, part in enumerate(re.split('([?*]+/?)', pattern)):
- if idx % 2:
- buf.append(symbols[part])
- elif part:
- buf.append(re.escape(part))
- match = re.match(''.join(buf) + '$', filename.replace(os.sep, '/'))
- return match is not None
- class TextWrapper(textwrap.TextWrapper):
- wordsep_re = re.compile(
- r'(\s+|' # any whitespace
- r'(?<=[\w\!\"\'\&\.\,\?])-{2,}(?=\w))' # em-dash
- )
- def wraptext(text, width=70, initial_indent='', subsequent_indent=''):
- """Simple wrapper around the ``textwrap.wrap`` function in the standard
- library. This version does not wrap lines on hyphens in words.
-
- :param text: the text to wrap
- :param width: the maximum line width
- :param initial_indent: string that will be prepended to the first line of
- wrapped output
- :param subsequent_indent: string that will be prepended to all lines save
- the first of wrapped output
- :return: a list of lines
- :rtype: `list`
- """
- wrapper = TextWrapper(width=width, initial_indent=initial_indent,
- subsequent_indent=subsequent_indent,
- break_long_words=False)
- return wrapper.wrap(text)
- if sys.version_info >= (3, 1):
- import collections
- class odict(collections.OrderedDict):
- """
- Ordered dict implementation.
- As per PEP 372, an ordered dict was added to Python 3.1 and above
- Use that here, as the odict below did not work for py3.2
- """
- def __init__(self, data=None):
- collections.OrderedDict.__init__(self, data or {})
-
- else:
- class odict(dict):
- """Ordered dict implementation.
-
- :see: http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/107747
- """
- def __init__(self, data=None):
- dict.__init__(self, data or {})
- self._keys = dict.keys(self)
- def __delitem__(self, key):
- dict.__delitem__(self, key)
- self._keys.remove(key)
- def __setitem__(self, key, item):
- dict.__setitem__(self, key, item)
- if key not in self._keys:
- self._keys.append(key)
- def __iter__(self):
- return iter(self._keys)
- iterkeys = __iter__
- def clear(self):
- dict.clear(self)
- self._keys = []
- def copy(self):
- d = odict()
- d.update(self)
- return d
- def items(self):
- if PY3:
- return list(zip(self._keys, list(self.values())))
- else:
- return zip(self._keys, self.values())
- def iteritems(self):
- if PY3:
- return zip(self._keys, iter(self.values()))
- else:
- from itertools import izip
- return izip(self._keys, self.itervalues())
- def keys(self):
- return self._keys[:]
- def pop(self, key, default=missing):
- if default is missing:
- return dict.pop(self, key)
- elif key not in self:
- return default
- self._keys.remove(key)
- return dict.pop(self, key, default)
- def popitem(self, key):
- self._keys.remove(key)
- return dict.popitem(key)
- def setdefault(self, key, failobj = None):
- dict.setdefault(self, key, failobj)
- if key not in self._keys:
- self._keys.append(key)
- def update(self, dict):
- for (key, val) in dict.items():
- self[key] = val
- def values(self):
- if PY3:
- return list(map(self.get, self._keys))
- else:
- return map(self.get, self._keys)
- def itervalues(self):
- if PY3:
- return map(self.get, self._keys)
- else:
- from itertools import imap
- return imap(self.get, self._keys)
- try:
- relpath = os.path.relpath
- except AttributeError:
- def relpath(path, start='.'):
- """Compute the relative path to one path from another.
-
- >>> relpath('foo/bar.txt', '').replace(os.sep, '/')
- 'foo/bar.txt'
- >>> relpath('foo/bar.txt', 'foo').replace(os.sep, '/')
- 'bar.txt'
- >>> relpath('foo/bar.txt', 'baz').replace(os.sep, '/')
- '../foo/bar.txt'
-
- :return: the relative path
- :rtype: `basestring`
- """
- start_list = os.path.abspath(start).split(os.sep)
- path_list = os.path.abspath(path).split(os.sep)
- # Work out how much of the filepath is shared by start and path.
- i = len(os.path.commonprefix([start_list, path_list]))
- rel_list = [os.path.pardir] * (len(start_list) - i) + path_list[i:]
- return os.path.join(*rel_list)
- ZERO = timedelta(0)
- class FixedOffsetTimezone(tzinfo):
- """Fixed offset in minutes east from UTC."""
- def __init__(self, offset, name=None):
- self._offset = timedelta(minutes=offset)
- if name is None:
- name = 'Etc/GMT+%d' % offset
- self.zone = name
- def __str__(self):
- return self.zone
- def __repr__(self):
- return '<FixedOffset "%s" %s>' % (self.zone, self._offset)
- def utcoffset(self, dt):
- return self._offset
- def tzname(self, dt):
- return self.zone
- def dst(self, dt):
- return ZERO
- try:
- from pytz import UTC
- except ImportError:
- UTC = FixedOffsetTimezone(0, 'UTC')
- """`tzinfo` object for UTC (Universal Time).
-
- :type: `tzinfo`
- """
- STDOFFSET = timedelta(seconds = -time.timezone)
- if time.daylight:
- DSTOFFSET = timedelta(seconds = -time.altzone)
- else:
- DSTOFFSET = STDOFFSET
- DSTDIFF = DSTOFFSET - STDOFFSET
- class LocalTimezone(tzinfo):
- def utcoffset(self, dt):
- if self._isdst(dt):
- return DSTOFFSET
- else:
- return STDOFFSET
- def dst(self, dt):
- if self._isdst(dt):
- return DSTDIFF
- else:
- return ZERO
- def tzname(self, dt):
- return time.tzname[self._isdst(dt)]
- def _isdst(self, dt):
- tt = (dt.year, dt.month, dt.day,
- dt.hour, dt.minute, dt.second,
- dt.weekday(), 0, -1)
- stamp = time.mktime(tt)
- tt = time.localtime(stamp)
- return tt.tm_isdst > 0
- LOCALTZ = LocalTimezone()
- """`tzinfo` object for local time-zone.
- :type: `tzinfo`
- """