diff options
author | Robin H. Johnson <robbat2@gentoo.org> | 2010-01-26 11:52:02 +0000 |
---|---|---|
committer | Robin H. Johnson <robbat2@gentoo.org> | 2010-01-26 11:52:02 +0000 |
commit | a1ac49f3f18e6086f87f7c963c9ee3d621439c48 (patch) | |
tree | 28eb9b63348a18bf6cec78db0527c3caf5e50cb6 | |
parent | ferringb found a workaround. (diff) | |
download | packages-3-a1ac49f3f18e6086f87f7c963c9ee3d621439c48.tar.gz packages-3-a1ac49f3f18e6086f87f7c963c9ee3d621439c48.tar.bz2 packages-3-a1ac49f3f18e6086f87f7c963c9ee3d621439c48.zip |
Use system snakeoil now.
-rw-r--r-- | snakeoil/__init__.py | 4 | ||||
-rw-r--r-- | snakeoil/caching.py | 86 | ||||
-rw-r--r-- | snakeoil/compatibility.py | 32 | ||||
-rw-r--r-- | snakeoil/containers.py | 207 | ||||
-rw-r--r-- | snakeoil/currying.py | 129 | ||||
-rwxr-xr-x | snakeoil/debug_imports.py | 99 | ||||
-rw-r--r-- | snakeoil/demandload.py | 226 | ||||
-rw-r--r-- | snakeoil/dependant_methods.py | 86 | ||||
-rw-r--r-- | snakeoil/descriptors.py | 28 | ||||
-rw-r--r-- | snakeoil/fileutils.py | 284 | ||||
-rw-r--r-- | snakeoil/fix_copy.py | 74 | ||||
-rw-r--r-- | snakeoil/formatters.py | 495 | ||||
-rw-r--r-- | snakeoil/iterables.py | 202 | ||||
-rw-r--r-- | snakeoil/klass.py | 95 | ||||
-rw-r--r-- | snakeoil/lists.py | 171 | ||||
-rw-r--r-- | snakeoil/mappings.py | 579 | ||||
-rw-r--r-- | snakeoil/modules.py | 53 | ||||
-rw-r--r-- | snakeoil/obj.py | 206 | ||||
-rw-r--r-- | snakeoil/osutils/__init__.py | 340 | ||||
-rw-r--r-- | snakeoil/osutils/native_readdir.py | 60 | ||||
-rw-r--r-- | snakeoil/pickling.py | 18 | ||||
-rw-r--r-- | snakeoil/tar.py | 35 | ||||
-rw-r--r-- | snakeoil/version.py | 39 | ||||
-rw-r--r-- | snakeoil/weakrefs.py | 12 | ||||
-rw-r--r-- | snakeoil/xml/__init__.py | 46 | ||||
-rw-r--r-- | snakeoil/xml/bundled_elementtree.py | 1254 |
26 files changed, 0 insertions, 4860 deletions
diff --git a/snakeoil/__init__.py b/snakeoil/__init__.py deleted file mode 100644 index 9ff5a09..0000000 --- a/snakeoil/__init__.py +++ /dev/null @@ -1,4 +0,0 @@ -# Copyright: 2005 Brian Harring <ferringb@gmail.com> -# License: GPL2 - -"""misc. utility functions""" diff --git a/snakeoil/caching.py b/snakeoil/caching.py deleted file mode 100644 index daced2b..0000000 --- a/snakeoil/caching.py +++ /dev/null @@ -1,86 +0,0 @@ -# Copyright: 2006 Brian Harring <ferringb@gmail.com> -# License: GPL2 - -""" -instance caching metaclass -""" - -from snakeoil.demandload import demandload -demandload( - globals(), - 'warnings', - 'weakref:WeakValueDictionary', - ) - -class native_WeakInstMeta(type): - """metaclass for instance caching, resulting in reuse of unique instances - - few notes- - - instances must be immutable (or effectively so). - Since creating a new instance may return a preexisting instance, - this requirement B{must} be honored. - - due to the potential for mishap, each subclass of a caching class must - assign __inst_caching__ = True to enable caching for the derivative. - - conversely, __inst_caching__ = False does nothing - (although it's useful as a sign of - I{do not enable caching for this class} - - instance caching can be disabled per instantiation via passing - disabling_inst_caching=True into the class constructor. - - Being a metaclass, the voodoo used doesn't require modification of - the class itself. - - Examples of usage is the restrictions subsystem for - U{pkgcore project<http://pkgcore.org>} - """ - def __new__(cls, name, bases, d): - if d.get("__inst_caching__", False): - d["__inst_caching__"] = True - d["__inst_dict__"] = WeakValueDictionary() - else: - d["__inst_caching__"] = False - slots = d.get('__slots__') - if slots is not None: - for base in bases: - if getattr(base, '__weakref__', False): - break - else: - d['__slots__'] = tuple(slots) + ('__weakref__',) - return type.__new__(cls, name, bases, d) - - def __call__(cls, *a, **kw): - """disable caching via disable_inst_caching=True""" - if cls.__inst_caching__ and not kw.pop("disable_inst_caching", False): - kwlist = kw.items() - kwlist.sort() - key = (a, tuple(kwlist)) - try: - instance = cls.__inst_dict__.get(key) - except (NotImplementedError, TypeError), t: - warnings.warn( - "caching keys for %s, got %s for a=%s, kw=%s" % ( - cls, t, a, kw)) - del t - key = instance = None - - if instance is None: - instance = super(native_WeakInstMeta, cls).__call__(*a, **kw) - - if key is not None: - cls.__inst_dict__[key] = instance - else: - instance = super(native_WeakInstMeta, cls).__call__(*a, **kw) - - return instance - -# "Invalid name" -# pylint: disable-msg=C0103 - -try: - # No name in module - # pylint: disable-msg=E0611 - from snakeoil._caching import WeakInstMeta - cpy_WeakInstMeta = WeakInstMeta -except ImportError: - cpy_WeakInstMeta = None - WeakInstMeta = native_WeakInstMeta diff --git a/snakeoil/compatibility.py b/snakeoil/compatibility.py deleted file mode 100644 index fbdbb87..0000000 --- a/snakeoil/compatibility.py +++ /dev/null @@ -1,32 +0,0 @@ -# Copyright: 2006 Brian Harring <ferringb@gmail.com> -# License: GPL2 - -""" -Compatibility module providing native reimplementations of python2.5 functionality. - -Uses the native implementation from C{__builtins__} if available. -""" - -def native_any(iterable): - for x in iterable: - if x: - return True - return False - -def native_all(iterable): - for x in iterable: - if not x: - return False - return True - -# using variable before assignment -# pylint: disable-msg=E0601 - -if "any" in __builtins__: - any = any - all = all -else: - try: - from snakeoil._compatibility import any, all - except ImportError: - any, all = native_any, native_all diff --git a/snakeoil/containers.py b/snakeoil/containers.py deleted file mode 100644 index 4ec3f2b..0000000 --- a/snakeoil/containers.py +++ /dev/null @@ -1,207 +0,0 @@ -# Copyright: 2005-2007 Brian Harring <ferringb@gmail.com> -# License: GPL2 - -""" -collection of container classes -""" - -from snakeoil.demandload import demandload -demandload( - globals(), - 'itertools:chain,ifilterfalse', -) - -class InvertedContains(set): - - """Set that inverts all contains lookup results. - - Mainly useful in conjuection with LimitedChangeSet for converting - from blacklist to whitelist. - - Cannot be iterated over. - """ - - def __contains__(self, key): - return not set.__contains__(self, key) - - def __iter__(self): - # infinite set, non iterable. - raise TypeError("InvertedContains cannot be iterated over") - - -class SetMixin(object): - """ - A mixin providing set methods. - - Subclasses should provide __init__, __iter__ and __contains__. - """ - - def __and__(self, other, kls=None): - # Note: for these methods we don't bother to filter dupes from this - # list - since the subclasses __init__ should already handle this, - # there's no point doing it twice. - return (kls or self.__class__)(x for x in self if x in other) - - def __rand__(self, other): - return self.__and__(other, kls=other.__class__) - - def __or__(self, other, kls=None): - return (kls or self.__class__)(chain(self, other)) - - def __ror__(self, other): - return self.__or__(other, kls=other.__class__) - - def __xor__(self, other, kls=None): - return (kls or self.__class__)(chain((x for x in self if x not in other), - (x for x in other if x not in self))) - - def __rxor__(self, other): - return self.__xor__(other, kls=other.__class__) - - def __sub__(self, other): - return self.__class__(x for x in self if x not in other) - - def __rsub__(self, other): - return other.__class__(x for x in other if x not in self) - - __add__ = __or__ - __radd__ = __ror__ - - -class LimitedChangeSet(SetMixin): - - """Set used to limit the number of times a key can be removed/added. - - specifically deleting/adding a key only once per commit, - optionally blocking changes to certain keys. - """ - - _removed = 0 - _added = 1 - - def __init__(self, initial_keys, unchangable_keys=None): - self._new = set(initial_keys) - if unchangable_keys is None: - self._blacklist = [] - else: - if isinstance(unchangable_keys, (list, tuple)): - unchangable_keys = set(unchangable_keys) - self._blacklist = unchangable_keys - self._changed = set() - self._change_order = [] - self._orig = frozenset(self._new) - - def add(self, key): - if key in self._changed or key in self._blacklist: - # it's been del'd already once upon a time. - if key in self._new: - return - raise Unchangable(key) - - self._new.add(key) - self._changed.add(key) - self._change_order.append((self._added, key)) - - def remove(self, key): - if key in self._changed or key in self._blacklist: - if key not in self._new: - raise KeyError(key) - raise Unchangable(key) - - if key in self._new: - self._new.remove(key) - self._changed.add(key) - self._change_order.append((self._removed, key)) - - def __contains__(self, key): - return key in self._new - - def changes_count(self): - return len(self._change_order) - - def commit(self): - self._orig = frozenset(self._new) - self._changed.clear() - self._change_order = [] - - def rollback(self, point=0): - l = self.changes_count() - if point < 0 or point > l: - raise TypeError( - "%s point must be >=0 and <= changes_count()" % point) - while l > point: - change, key = self._change_order.pop(-1) - self._changed.remove(key) - if change == self._removed: - self._new.add(key) - else: - self._new.remove(key) - l -= 1 - - def __str__(self): - return str(self._new).replace("set(", "LimitedChangeSet(", 1) - - def __iter__(self): - return iter(self._new) - - def __len__(self): - return len(self._new) - - def __eq__(self, other): - if isinstance(other, LimitedChangeSet): - return self._new == other._new - elif isinstance(other, (frozenset, set)): - return self._new == other - return False - - def __ne__(self, other): - return not (self == other) - - -class Unchangable(Exception): - - def __init__(self, key): - Exception.__init__(self, "key '%s' is unchangable" % (key,)) - self.key = key - - -class ProtectedSet(SetMixin): - - """ - Wraps a set pushing all changes into a secondary set. - """ - def __init__(self, orig_set): - self._orig = orig_set - self._new = set() - - def __contains__(self, key): - return key in self._orig or key in self._new - - def __iter__(self): - return chain(iter(self._new), - ifilterfalse(self._new.__contains__, self._orig)) - - def __len__(self): - return len(self._orig.union(self._new)) - - def add(self, key): - if key not in self._orig: - self._new.add(key) - - -class RefCountingSet(dict): - - def __init__(self, iterable=None): - if iterable is not None: - dict.__init__(self, ((x, 1) for x in iterable)) - - def add(self, item): - count = self.get(item, 0) - self[item] = count + 1 - - def remove(self, item): - count = self[item] - if count == 1: - del self[item] - else: - self[item] = count - 1 diff --git a/snakeoil/currying.py b/snakeoil/currying.py deleted file mode 100644 index 33be84f..0000000 --- a/snakeoil/currying.py +++ /dev/null @@ -1,129 +0,0 @@ -# Copyright: 2005 Brian Harring <ferringb@gmail.com> -# License: GPL2 - -""" -Function currying, generating a functor with a set of args/defaults pre bound. - -L{pre_curry} and L{post_curry} return "normal" python functions. -L{partial} returns a callable object. The difference between -L{pre_curry} and L{partial} is this:: - - >>> def func(arg=None, self=None): - ... return arg, self - >>> curry = pre_curry(func, True) - >>> part = partial(func, True) - >>> class Test(object): - ... curry = pre_curry(func, True) - ... part = partial(func, True) - ... def __repr__(self): - ... return '<Test object>' - >>> curry() - (True, None) - >>> Test().curry() - (True, <Test object>) - >>> part() - (True, None) - >>> Test().part() - (True, None) - -If your curried function is not used as a class attribute the results -should be identical. Because L{partial} has an implementation in c -while L{pre_curry} is python you should use L{partial} if possible. -""" - -from operator import attrgetter - -__all__ = ("pre_curry", "partial", "post_curry", "pretty_docs", - "alias_class_method") - -def pre_curry(func, *args, **kwargs): - """passed in args are prefixed, with further args appended""" - - if not kwargs: - def callit(*moreargs, **morekwargs): - return func(*(args + moreargs), **morekwargs) - elif not args: - def callit(*moreargs, **morekwargs): - kw = kwargs.copy() - kw.update(morekwargs) - return func(*moreargs, **kw) - else: - def callit(*moreargs, **morekwargs): - kw = kwargs.copy() - kw.update(morekwargs) - return func(*(args+moreargs), **kw) - - callit.func = func - return callit - - -class native_partial(object): - - """Like pre_curry, but does not get turned into an instance method.""" - - def __init__(self, func, *args, **kwargs): - self.func = func - self.args = args - self.kwargs = kwargs - - def __call__(self, *moreargs, **morekwargs): - kw = self.kwargs.copy() - kw.update(morekwargs) - return self.func(*(self.args + moreargs), **kw) - -# Unused import, unable to import -# pylint: disable-msg=W0611,F0401 -try: - from functools import partial -except ImportError: - try: - from snakeoil._compatibility import partial - except ImportError: - partial = native_partial - - -def post_curry(func, *args, **kwargs): - """passed in args are appended to any further args supplied""" - - if not kwargs: - def callit(*moreargs, **morekwargs): - return func(*(moreargs+args), **morekwargs) - elif not args: - def callit(*moreargs, **morekwargs): - kw = morekwargs.copy() - kw.update(kwargs) - return func(*moreargs, **kw) - else: - def callit(*moreargs, **morekwargs): - kw = morekwargs.copy() - kw.update(kwargs) - return func(*(moreargs+args), **kw) - - callit.func = func - return callit - -def pretty_docs(wrapped, extradocs=None): - wrapped.__module__ = wrapped.func.__module__ - doc = wrapped.func.__doc__ - if extradocs is None: - wrapped.__doc__ = doc - else: - wrapped.__doc__ = extradocs - return wrapped - - -def alias_class_method(attr): - """at runtime, redirect to another method - - attr is the desired attr name to lookup, and supply all later passed in - args/kws to - - Useful for when setting has_key to __contains__ for example, and - __contains__ may be overriden. - """ - grab_attr = attrgetter(attr) - - def _asecond_level_call(self, *a, **kw): - return grab_attr(self)(*a, **kw) - - return _asecond_level_call diff --git a/snakeoil/debug_imports.py b/snakeoil/debug_imports.py deleted file mode 100755 index dc137ed..0000000 --- a/snakeoil/debug_imports.py +++ /dev/null @@ -1,99 +0,0 @@ -#!/usr/bin/python -# Copyright: 2007 Brian Harring <ferringb@gmail.com> -# License: GPL2 - -import __builtin__ - -class intercept_import(object): - - def __init__(self, callback): - self.callback = callback - self.stack = [] - self.seen = set() - - def __call__(self, *args): - if args[0] not in self.seen: - self.disable() - self.callback(self.stack, args) - self.enable() - self.stack.append(args[0]) - self.seen.add(args[0]) - try: - return self.orig_import(*args) - finally: - self.stack.pop() - - def enable(self): - cur_import = __builtin__.__import__ - if isinstance(cur_import, intercept_import): - raise RuntimeError("an intercept is already active") - self.orig_import = cur_import - __builtin__.__import__ = self - - def disable(self): - if __builtin__.__import__ != self: - raise RuntimeError("either not active, or a different intercept " - "is in use") - __builtin__.__import__ = self.orig_import - del self.orig_import - - -if __name__ == "__main__": - import __main__ - orig = dict(__main__.__dict__.iteritems()) - del orig["intercept_import"] - del orig["__builtin__"] - del orig["__main__"] - - import sys, imp - - usage = "debug_imports.py [-o output_file_path || -i] scriptfile [arg] ..." - if not sys.argv[1:]: - print "Usage: ", usage - sys.exit(2) - - # yes, at first thought, this should use getopt or optparse. - # problem is, folks may want to spot that import, thus we can't. - - import traceback, pdb - - args = sys.argv[1:] - if args[0] == '-o': - if not len(args) > 2: - print "Usage: ", usage - sys.exit(2) - f = open(args[1], 'w') - def callback(modules, key, val): - f.write("adding %s\n" % key) - traceback.print_stack(file=f) - args = args[2:] - elif args[0] == '-i': - def callback(args): - pdb.set_trace() - args = args[1:] - else: - import time - def callback(stack, args): - if stack: - print "in: %s" % ', '.join(stack) - if len(args) == 4 and args[3] is not None: - print "from %s import %s" % (args[0], ', '.join(args[3])) - else: - print "import %s " % args[0] - print time.time() -# traceback.print_stack(file=sys.stdout) - print - - - path = args[0] - - sys.argv = args[:] - i = intercept_import(callback) - i.enable() - print "starting\n",time.time(),"\n" - try: - imp.load_module("__main__", open(args[0]), args[0], ("", "r", imp.PY_SOURCE)) - finally: - i.disable() - print "\nfinished\n",time.time(),"\n" - sys.exit(0) diff --git a/snakeoil/demandload.py b/snakeoil/demandload.py deleted file mode 100644 index a515b90..0000000 --- a/snakeoil/demandload.py +++ /dev/null @@ -1,226 +0,0 @@ -# Copyright: 2006 Vadim Gelfer <vadim.gelfer@gmail.com> -# Copyright: 2007 Marien Zwart <marienz@gentoo.org> -# License: GPL2 - -"""Demand load things when used. - -This uses L{Placeholder} objects which create an actual object on -first use and know how to replace themselves with that object, so -there is no performance penalty after first use. - -This trick is *mostly* transparent, but there are a few things you -have to be careful with: - - - You may not bind a second name to a placeholder object. Specifically, - if you demandload C{bar} in module C{foo}, you may not - C{from foo import bar} in a third module. The placeholder object - does not "know" it gets imported, so this does not trigger the - demandload: C{bar} in the third module is the placeholder object. - When that placeholder gets used it replaces itself with the actual - module in C{foo} but not in the third module. - Because this is normally unwanted (it introduces a small - performance hit) the placeholder object will raise an exception if - it detects this. But if the demandload gets triggered before the - third module is imported you do not get that exception, so you - have to be careful not to import or otherwise pass around the - placeholder object without triggering it. - - Not all operations on the placeholder object trigger demandload. - The most common problem is that C{except ExceptionClass} does not - work if C{ExceptionClass} is a placeholder. - C{except module.ExceptionClass} with C{module} a placeholder does - work. You can normally avoid this by always demandloading the - module, not something in it. -""" - -# TODO: the use of a curried func instead of subclassing needs more thought. - -# the replace_func used by Placeholder is currently passed in as an -# external callable, with "partial" used to provide arguments to it. -# This works, but has the disadvantage that calling -# demand_compile_regexp needs to import re (to hand re.compile to -# partial). One way to avoid that would be to add a wrapper function -# that delays the import (well, triggers the demandload) at the time -# the regexp is used, but that's a bit convoluted. A different way is -# to make replace_func a method of Placeholder implemented through -# subclassing instead of a callable passed to its __init__. The -# current version does not do this because getting/setting attributes -# of Placeholder is annoying because of the -# __getattribute__/__setattr__ override. - - -from snakeoil.modules import load_any -from snakeoil.currying import partial - -# There are some demandloaded imports below the definition of demandload. - -_allowed_chars = "".join((x.isalnum() or x in "_.") and " " or "a" - for x in map(chr, xrange(256))) - -def parse_imports(imports): - """Parse a sequence of strings describing imports. - - For every input string it returns a tuple of (import, targetname). - Examples:: - - 'foo' -> ('foo', 'foo') - 'foo:bar' -> ('foo.bar', 'bar') - 'foo:bar,baz@spork' -> ('foo.bar', 'bar'), ('foo.baz', 'spork') - 'foo@bar' -> ('foo', 'bar') - - Notice 'foo.bar' is not a valid input. This simplifies the code, - but if it is desired it can be added back. - - @type imports: sequence of C{str} objects. - @rtype: iterable of tuples of two C{str} objects. - """ - for s in imports: - fromlist = s.split(':', 1) - if len(fromlist) == 1: - # Not a "from" import. - if '.' in s: - raise ValueError('dotted imports unsupported.') - split = s.split('@', 1) - for s in split: - if not s.translate(_allowed_chars).isspace(): - raise ValueError("bad target: %s" % s) - if len(split) == 2: - yield tuple(split) - else: - yield split[0], split[0] - else: - # "from" import. - base, targets = fromlist - if not base.translate(_allowed_chars).isspace(): - raise ValueError("bad target: %s" % base) - for target in targets.split(','): - split = target.split('@', 1) - for s in split: - if not s.translate(_allowed_chars).isspace(): - raise ValueError("bad target: %s" % s) - yield base + '.' + split[0], split[-1] - - -class Placeholder(object): - - """Object that knows how to replace itself when first accessed. - - See the module docstring for common problems with its use. - """ - - def __init__(self, scope, name, replace_func): - """Initialize. - - @param scope: the scope we live in, normally the result of - C{globals()}. - @param name: the name we have in C{scope}. - @param replace_func: callable returning the object to replace us with. - """ - object.__setattr__(self, '_scope', scope) - object.__setattr__(self, '_name', name) - object.__setattr__(self, '_replace_func', replace_func) - - def _already_replaced(self): - name = object.__getattribute__(self, '_name') - raise ValueError('Placeholder for %r was triggered twice' % (name,)) - - def _replace(self): - """Replace ourself in C{scope} with the result of our C{replace_func}. - - @returns: the result of calling C{replace_func}. - """ - replace_func = object.__getattribute__(self, '_replace_func') - scope = object.__getattribute__(self, '_scope') - name = object.__getattribute__(self, '_name') - # Paranoia, explained in the module docstring. - already_replaced = object.__getattribute__(self, '_already_replaced') - object.__setattr__(self, '_replace_func', already_replaced) - - # Cleanup, possibly unnecessary. - object.__setattr__(self, '_scope', None) - - result = replace_func() - scope[name] = result - return result - - # Various methods proxied to our replacement. - - def __str__(self): - return self.__getattribute__('__str__')() - - def __getattribute__(self, attr): - result = object.__getattribute__(self, '_replace')() - return getattr(result, attr) - - def __setattr__(self, attr, value): - result = object.__getattribute__(self, '_replace')() - setattr(result, attr, value) - - def __call__(self, *args, **kwargs): - result = object.__getattribute__(self, '_replace')() - return result(*args, **kwargs) - - -def demandload(scope, *imports): - """Import modules into scope when each is first used. - - scope should be the value of C{globals()} in the module calling - this function. (using C{locals()} may work but is not recommended - since mutating that is not safe). - - Other args are strings listing module names. - names are handled like this:: - - foo import foo - foo@bar import foo as bar - foo:bar from foo import bar - foo:bar,quux from foo import bar, quux - foo.bar:quux from foo.bar import quux - foo:baz@quux from foo import baz as quux - """ - for source, target in parse_imports(imports): - scope[target] = Placeholder(scope, target, partial(load_any, source)) - - -demandload(globals(), 're') - -# Extra name to make undoing monkeypatching demandload with -# disabled_demandload easier. -enabled_demandload = demandload - - -def disabled_demandload(scope, *imports): - """Exactly like L{demandload} but does all imports immediately.""" - for source, target in parse_imports(imports): - scope[target] = load_any(source) - - -class RegexPlaceholder(Placeholder): - """ - Compiled Regex object that knows how to replace itself when first accessed. - - See the module docstring for common problems with its use; used by - L{demand_compile_regexp}. - """ - - def _replace(self): - args, kwargs = object.__getattribute__(self, '_replace_func') - object.__setattr__(self, '_replace_func', - partial(re.compile, *args, **kwargs)) - return Placeholder._replace(self) - - - -def demand_compile_regexp(scope, name, *args, **kwargs): - """Demandloaded version of L{re.compile}. - - Extra arguments are passed unchanged to L{re.compile}. - - This returns the placeholder, which you *must* bind to C{name} in - the scope you pass as C{scope}. It is done this way to prevent - confusing code analysis tools like pylint. - - @param scope: the scope, just like for L{demandload}. - @param name: the name of the compiled re object in that scope. - @returns: the placeholder object. - """ - return RegexPlaceholder(scope, name, (args, kwargs)) diff --git a/snakeoil/dependant_methods.py b/snakeoil/dependant_methods.py deleted file mode 100644 index b7597b5..0000000 --- a/snakeoil/dependant_methods.py +++ /dev/null @@ -1,86 +0,0 @@ -# Copyright: 2005-2006 Brian Harring <ferringb@gmail.com> -# License: GPL2 - -"""Metaclass to inject dependencies into method calls. - -Essentially, method a must be run prior to method b, invoking method a -if b is called first. -""" - -from snakeoil.lists import iflatten_instance -from snakeoil.currying import partial - -__all__ = ["ForcedDepends"] - -def ensure_deps(self, name, *a, **kw): - ignore_deps = "ignore_deps" in kw - if ignore_deps: - del kw["ignore_deps"] - s = [name] - else: - s = yield_deps(self, self.stage_depends, name) - - r = True - for dep in s: - if dep not in self._stage_state: - r = getattr(self, dep).raw_func(*a, **kw) - if r: - self._stage_state.add(dep) - else: - return r - return r - -def yield_deps(inst, d, k): - # While at first glance this looks like should use expandable_chain, - # it shouldn't. --charlie - if k not in d: - yield k - return - s = [k, iflatten_instance(d.get(k, ()))] - while s: - if isinstance(s[-1], basestring): - yield s.pop(-1) - continue - exhausted = True - for x in s[-1]: - v = d.get(x) - if v: - s.append(x) - s.append(iflatten_instance(v)) - exhausted = False - break - yield x - if exhausted: - s.pop(-1) - - -class ForcedDepends(type): - """ - Metaclass forcing methods to run in a certain order. - - Dependencies are controlled by the existance of a stage_depends - dict in the class namespace. Its keys are method names, values are - either a string (name of preceeding method), or list/tuple - (proceeding methods). - - U{pkgcore projects pkgcore.intefaces.format.build_base is an example consumer<http://pkgcore.org>} - to look at for usage. - """ - def __call__(cls, *a, **kw): - o = super(ForcedDepends, cls).__call__(*a, **kw) - if not getattr(cls, "stage_depends"): - return o - - if not hasattr(o, "_stage_state"): - o._stage_state = set() - - # wrap the funcs - - for x in set(x for x in iflatten_instance(o.stage_depends.iteritems()) - if x): - f = getattr(o, x) - f2 = partial(ensure_deps, o, x) - f2.raw_func = f - setattr(o, x, f2) - - return o diff --git a/snakeoil/descriptors.py b/snakeoil/descriptors.py deleted file mode 100644 index bf31b9f..0000000 --- a/snakeoil/descriptors.py +++ /dev/null @@ -1,28 +0,0 @@ -# Copyright: 2006 Marien Zwart <marienz@gentoo.org> -# License: GPL2 - - -"""Classes implementing the descriptor protocol.""" - - -class classproperty(object): - - """Like the builtin C{property} but takes a single classmethod. - - Used like this: - - class Example(object): - - @classproperty - def test(cls): - # Do stuff with cls here (it is Example or a subclass). - - Now both C{Example.test} and C{Example().test} invoke the getter. - A "normal" property only works on instances. - """ - - def __init__(self, getter): - self.getter = getter - - def __get__(self, instance, owner): - return self.getter(owner) diff --git a/snakeoil/fileutils.py b/snakeoil/fileutils.py deleted file mode 100644 index 9c9478e..0000000 --- a/snakeoil/fileutils.py +++ /dev/null @@ -1,284 +0,0 @@ -# Copyright: 2005-2006 Brian Harring <ferringb@gmail.com> -# License: GPL2 - -""" -file related operations, mainly reading -""" - -import re, os -from shlex import shlex -from snakeoil.mappings import ProtectedDict -from snakeoil.osutils import readlines - -class AtomicWriteFile(file): - - """File class that stores the changes in a tempfile. - - Upon close call, uses rename to replace the destination. - - Similar to file protocol behaviour, except for the C{__init__}, and - that close *must* be called for the changes to be made live, - - if C{__del__} is triggered it's assumed that an exception occured, - thus the changes shouldn't be made live. - """ - def __init__(self, fp, binary=False, perms=None, uid=-1, gid=-1, **kwds): - self.is_finalized = False - if binary: - file_mode = "wb" - else: - file_mode = "w" - fp = os.path.realpath(fp) - self.original_fp = fp - self.temp_fp = os.path.join( - os.path.dirname(fp), ".update.%s" % os.path.basename(fp)) - old_umask = None - if perms: - # give it just write perms - old_umask = os.umask(0200) - try: - file.__init__(self, self.temp_fp, mode=file_mode, **kwds) - finally: - if old_umask is not None: - os.umask(old_umask) - if perms: - os.chmod(self.temp_fp, perms) - if (gid, uid) != (-1, -1): - os.chown(self.temp_fp, uid, gid) - - def close(self): - file.close(self) - os.rename(self.temp_fp, self.original_fp) - self.is_finalized = True - - def __del__(self): - file.close(self) - if not self.is_finalized: - os.unlink(self.temp_fp) - - -def iter_read_bash(bash_source): - """ - Read file honoring bash commenting rules. - - Note that it's considered good behaviour to close filehandles, as - such, either iterate fully through this, or use read_bash instead. - Once the file object is no longer referenced the handle will be - closed, but be proactive instead of relying on the garbage - collector. - - @param bash_source: either a file to read from - or a string holding the filename to open. - """ - if isinstance(bash_source, basestring): - bash_source = readlines(bash_source, True) - for s in bash_source: - s = s.strip() - if s and s[0] != "#": - yield s - - -def read_bash(bash_source): - return list(iter_read_bash(bash_source)) -read_bash.__doc__ = iter_read_bash.__doc__ - - -def read_dict(bash_source, splitter="=", source_isiter=False): - """ - read key value pairs, ignoring bash-style comments. - - @param splitter: the string to split on. Can be None to - default to str.split's default - @param bash_source: either a file to read from, - or a string holding the filename to open. - """ - d = {} - if not source_isiter: - filename = bash_source - i = iter_read_bash(bash_source) - else: - # XXX what to do? - filename = '<unknown>' - i = bash_source - line_count = 1 - try: - for k in i: - line_count += 1 - try: - k, v = k.split(splitter, 1) - except ValueError: - raise ParseError(filename, line_count) - if len(v) > 2 and v[0] == v[-1] and v[0] in ("'", '"'): - v = v[1:-1] - d[k] = v - finally: - del i - return d - -def read_bash_dict(bash_source, vars_dict=None, sourcing_command=None): - """ - read bash source, yielding a dict of vars - - @param bash_source: either a file to read from - or a string holding the filename to open - @param vars_dict: initial 'env' for the sourcing. - Is protected from modification. - @type vars_dict: dict or None - @param sourcing_command: controls whether a source command exists. - If one does and is encountered, then this func is called. - @type sourcing_command: callable - @raise ParseError: thrown if invalid syntax is encountered. - @return: dict representing the resultant env if bash executed the source. - """ - - # quite possibly I'm missing something here, but the original - # portage_util getconfig/varexpand seemed like it only went - # halfway. The shlex posix mode *should* cover everything. - - if vars_dict is not None: - d, protected = ProtectedDict(vars_dict), True - else: - d, protected = {}, False - if isinstance(bash_source, basestring): - f = open(bash_source, "r") - else: - f = bash_source - s = bash_parser(f, sourcing_command=sourcing_command, env=d) - - try: - tok = "" - try: - while tok is not None: - key = s.get_token() - if key is None: - break - elif key.isspace(): - # we specifically have to check this, since we're - # screwing with the whitespace filters below to - # detect empty assigns - continue - eq = s.get_token() - if eq != '=': - raise ParseError(bash_source, s.lineno, - "got token %r, was expecting '='" % eq) - val = s.get_token() - if val is None: - val = '' - # look ahead to see if we just got an empty assign. - next_tok = s.get_token() - if next_tok == '=': - # ... we did. - # leftmost insertions, thus reversed ordering - s.push_token(next_tok) - s.push_token(val) - val = '' - else: - s.push_token(next_tok) - d[key] = val - except ValueError, e: - raise ParseError(bash_source, s.lineno, str(e)) - finally: - del f - if protected: - d = d.new - return d - - -var_find = re.compile(r'\\?(\${\w+}|\$\w+)') -backslash_find = re.compile(r'\\.') -def nuke_backslash(s): - s = s.group() - if s == "\\\n": - return "\n" - try: - return chr(ord(s)) - except TypeError: - return s[1] - -class bash_parser(shlex): - def __init__(self, source, sourcing_command=None, env=None): - self.__dict__['state'] = ' ' - shlex.__init__(self, source, posix=True) - self.wordchars += "@${}/.-+/:~^" - self.wordchars = frozenset(self.wordchars) - if sourcing_command is not None: - self.source = sourcing_command - if env is None: - env = {} - self.env = env - self.__pos = 0 - - def __setattr__(self, attr, val): - if attr == "state": - if (self.state, val) in ( - ('"', 'a'), ('a', '"'), ('a', ' '), ("'", 'a')): - strl = len(self.token) - if self.__pos != strl: - self.changed_state.append( - (self.state, self.token[self.__pos:])) - self.__pos = strl - self.__dict__[attr] = val - - def sourcehook(self, newfile): - try: - return shlex.sourcehook(self, newfile) - except IOError, ie: - raise ParseError(newfile, 0, str(ie)) - - def read_token(self): - self.changed_state = [] - self.__pos = 0 - tok = shlex.read_token(self) - if tok is None: - return tok - self.changed_state.append((self.state, self.token[self.__pos:])) - tok = '' - for s, t in self.changed_state: - if s in ('"', "a"): - tok += self.var_expand(t).replace("\\\n", '') - else: - tok += t - return tok - - def var_expand(self, val): - prev, pos = 0, 0 - l = [] - match = var_find.search(val) - while match is not None: - pos = match.start() - if val[pos] == '\\': - # it's escaped. either it's \\$ or \\${ , either way, - # skipping two ahead handles it. - pos += 2 - else: - var = val[match.start():match.end()].strip("${}") - if prev != pos: - l.append(val[prev:pos]) - if var in self.env: - if not isinstance(self.env[var], basestring): - raise ValueError( - "env key %r must be a string, not %s: %r" % ( - var, type(self.env[var]), self.env[var])) - l.append(self.env[var]) - else: - l.append("") - prev = pos = match.end() - match = var_find.search(val, pos) - - # do \\ cleansing, collapsing val down also. - val = backslash_find.sub(nuke_backslash, ''.join(l) + val[prev:]) - return val - - -class ParseError(Exception): - - def __init__(self, filename, line, errmsg=None): - if errmsg is not None: - Exception.__init__(self, - "error parsing '%s' on or before %i: err %s" % - (filename, line, errmsg)) - else: - Exception.__init__(self, - "error parsing '%s' on or before %i" % - (filename, line)) - self.file, self.line, self.errmsg = filename, line, errmsg diff --git a/snakeoil/fix_copy.py b/snakeoil/fix_copy.py deleted file mode 100644 index 3a73d5b..0000000 --- a/snakeoil/fix_copy.py +++ /dev/null @@ -1,74 +0,0 @@ -# Copyright (C) 2005, 2006 Canonical Ltd -# -# This program 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. -# -# This program 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 this program; if not, write to the Free Software -# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA - -"""A version of inspect that includes what 'copy' needs. - -Importing the python standard module 'copy' is far more expensive than it -needs to be, because copy imports 'inspect' which imports 'tokenize'. -And 'copy' only needs 2 small functions out of 'inspect', but has to -load all of 'tokenize', which makes it horribly slow. - -This module is designed to use tricky hacks in import rules, to avoid this -overhead. -""" - - -#### -# These are the only 2 functions that 'copy' needs from 'inspect' -# As you can see, they are quite trivial, and don't justify the -# 40ms spent to import 'inspect' because it is importing 'tokenize' -# These are copied verbatim from the python standard library. - -# ----------------------------------------------------------- class helpers -def _searchbases(cls, accum): - # Simulate the "classic class" search order. - if cls in accum: - return - accum.append(cls) - for base in cls.__bases__: - _searchbases(base, accum) - - -def getmro(cls): - "Return tuple of base classes (including cls) in method resolution order." - if hasattr(cls, "__mro__"): - return cls.__mro__ - else: - result = [] - _searchbases(cls, result) - return tuple(result) - - -def inject_copy(): - """Import the 'copy' module with a hacked 'inspect' module""" - # We don't actually care about 'getmro' but we need to pass - # something in the list so that we get the direct module, - # rather than getting the base module - import sys - - # Don't hack around if 'inspect' already exists - if 'inspect' in sys.modules: - import copy - return - - mod = __import__('snakeoil.fix_copy', - globals(), locals(), ['getmro']) - - sys.modules['inspect'] = mod - try: - import copy - finally: - del sys.modules['inspect'] diff --git a/snakeoil/formatters.py b/snakeoil/formatters.py deleted file mode 100644 index b327bb4..0000000 --- a/snakeoil/formatters.py +++ /dev/null @@ -1,495 +0,0 @@ -# Copyright: 2006 Marien Zwart <marienz@gentoo.org> -# License: GPL2 - -"""Classes wrapping a file-like object to do fancy output on it.""" - -import os -import errno - -from snakeoil.klass import GetAttrProxy -from snakeoil.demandload import demandload -demandload(globals(), 'locale') - - -class native_StreamClosed(KeyboardInterrupt): - """Raised by L{Formatter.write} if the stream it prints to was closed. - - This inherits from C{KeyboardInterrupt} because it should usually - be handled the same way: a common way of triggering this exception - is by closing a pager before the script finished outputting, which - should be handled like control+c, not like an error. - """ - - -# "Invalid name" (for fg and bg methods, too short) -# pylint: disable-msg=C0103 - - -class Formatter(object): - - """Abstract formatter base class. - - The types of most of the instance attributes is undefined (depends - on the implementation of the particular Formatter subclass). - - @ivar bold: object to pass to L{write} to switch to bold mode. - @ivar underline: object to pass to L{write} to switch to underlined mode. - @ivar reset: object to pass to L{write} to turn off bold and underline. - @ivar wrap: boolean indicating we auto-linewrap (defaults to off). - @ivar autoline: boolean indicating we are in auto-newline mode - (defaults to on). - """ - - def __init__(self): - self.autoline = True - self.wrap = False - - def write(self, *args, **kwargs): - """Write something to the stream. - - Acceptable arguments are: - - Strings are simply written to the stream. - - C{None} is ignored. - - Functions are called with the formatter as argument. - Their return value is then used the same way as the other - arguments. - - Formatter subclasses might special-case certain objects. - - Accepts wrap and autoline as keyword arguments. Effect is - the same as setting them before the write call and resetting - them afterwards. - - Accepts first_prefixes and later_prefixes as keyword - arguments. They should be sequences that are temporarily - appended to the first_prefix and later_prefix attributes. - - Accepts prefixes as a keyword argument. Effect is the same as - setting first_prefixes and later_prefixes to the same value. - - Accepts first_prefix, later_prefix and prefix as keyword - argument. Effect is the same as setting first_prefixes, - later_prefixes or prefixes to a one-element tuple. - - The formatter has a couple of attributes that are useful as argument - to write. - """ - - def fg(self, color=None): - """Change foreground color. - - @type color: a string or C{None}. - @param color: color to change to. A default is used if omitted. - C{None} resets to the default color. - """ - - def bg(self, color=None): - """Change background color. - - @type color: a string or C{None}. - @param color: color to change to. A default is used if omitted. - C{None} resets to the default color. - """ - - def error(self, message): - """Format a string as an error message.""" - self.write(message, prefixes=( - self.fg('red'), self.bold, '!!! ', self.reset)) - - def warn(self, message): - """Format a string as a warning message.""" - self.write(message, prefixes=( - self.fg('yellow'), self.bold, '*** ', self.reset)) - - def title(self, string): - """Set the title to string""" - pass - - -class native_PlainTextFormatter(Formatter): - - """Formatter writing plain text to a file-like object. - - @ivar width: contains the current maximum line length. - @ivar encoding: the encoding unicode strings should be converted to. - @ivar first_prefix: prefixes to output at the beginning of every write. - @ivar later_prefix: prefixes to output on each line after the first of - every write. - """ - - bold = underline = reset = '' - - def __init__(self, stream, width=79, encoding=None): - """Initialize. - - @type stream: file-like object. - @param stream: stream to output to. - @param width: maximum line width (defaults to 79). - @param encoding: encoding unicode strings are converted to. - """ - Formatter.__init__(self) - self.stream = stream - if encoding is None: - encoding = getattr(self.stream, 'encoding', None) - if encoding is None: - try: - encoding = locale.getpreferredencoding() - except locale.Error: - encoding = 'ascii' - self.encoding = encoding - self.width = width - self._pos = 0 - self._in_first_line = True - self._wrote_something = False - self.first_prefix = [] - self.later_prefix = [] - - - def _write_prefix(self, wrap): - if self._in_first_line: - prefix = self.first_prefix - else: - prefix = self.later_prefix - # This is a bit braindead since it duplicates a lot of code - # from write. Avoids fun things like word wrapped prefix though. - - for thing in prefix: - while callable(thing): - thing = thing(self) - if thing is None: - continue - if not isinstance(thing, basestring): - thing = str(thing) - self._pos += len(thing) - if isinstance(thing, unicode): - thing = thing.encode(self.encoding, 'replace') - self.stream.write(thing) - if wrap and self._pos >= self.width: - # XXX What to do? Our prefix does not fit. - # This makes sure we still output something, - # but it is completely arbitrary. - self._pos = self.width - 10 - - - def write(self, *args, **kwargs): - wrap = kwargs.get('wrap', self.wrap) - autoline = kwargs.get('autoline', self.autoline) - prefixes = kwargs.get('prefixes') - first_prefixes = kwargs.get('first_prefixes') - later_prefixes = kwargs.get('later_prefixes') - if prefixes is not None: - if first_prefixes is not None or later_prefixes is not None: - raise TypeError( - 'do not pass first_prefixes or later_prefixes ' - 'if prefixes is passed') - first_prefixes = later_prefixes = prefixes - prefix = kwargs.get('prefix') - first_prefix = kwargs.get('first_prefix') - later_prefix = kwargs.get('later_prefix') - if prefix is not None: - if first_prefix is not None or later_prefix is not None: - raise TypeError( - 'do not pass first_prefix or later_prefix with prefix') - first_prefix = later_prefix = prefix - if first_prefix is not None: - if first_prefixes is not None: - raise TypeError( - 'do not pass both first_prefix and first_prefixes') - first_prefixes = (first_prefix,) - if later_prefix is not None: - if later_prefixes is not None: - raise TypeError( - 'do not pass both later_prefix and later_prefixes') - later_prefixes = (later_prefix,) - if first_prefixes is not None: - self.first_prefix.extend(first_prefixes) - if later_prefixes is not None: - self.later_prefix.extend(later_prefixes) - # Remove this nested try block once we depend on python 2.5 - try: - try: - for arg in args: - # If we're at the start of the line, write our prefix. - # There is a deficiency here: if neither our arg nor our - # prefix affect _pos (both are escape sequences or empty) - # we will write prefix more than once. This should not - # matter. - if not self._pos: - self._write_prefix(wrap) - while callable(arg): - arg = arg(self) - if arg is None: - continue - if not isinstance(arg, basestring): - arg = str(arg) - is_unicode = isinstance(arg, unicode) - while wrap and self._pos + len(arg) > self.width: - # We have to split. - maxlen = self.width - self._pos - space = arg.rfind(' ', 0, maxlen) - if space == -1: - # No space to split on. - - # If we are on the first line we can simply go to - # the next (this helps if the "later" prefix is - # shorter and should not really matter if not). - - # If we are on the second line and have already - # written something we can also go to the next - # line. - if self._in_first_line or self._wrote_something: - bit = '' - else: - # Forcibly split this as far to the right as - # possible. - bit = arg[:maxlen] - arg = arg[maxlen:] - else: - bit = arg[:space] - # Omit the space we split on. - arg = arg[space+1:] - if is_unicode: - bit = bit.encode(self.encoding, 'replace') - self.stream.write(bit) - self.stream.write('\n') - self._pos = 0 - self._in_first_line = False - self._wrote_something = False - self._write_prefix(wrap) - - # This fits. - self._wrote_something = True - self._pos += len(arg) - if is_unicode: - arg = arg.encode(self.encoding, 'replace') - self.stream.write(arg) - if autoline: - self.stream.write('\n') - self._wrote_something = False - self._pos = 0 - self._in_first_line = True - except IOError, e: - if e.errno == errno.EPIPE: - raise StreamClosed(e) - raise - finally: - if first_prefixes is not None: - self.first_prefix = self.first_prefix[:-len(first_prefixes)] - if later_prefixes is not None: - self.later_prefix = self.later_prefix[:-len(later_prefixes)] - - def fg(self, color=None): - return '' - - def bg(self, color=None): - return '' - -try: - from snakeoil._formatters import PlainTextFormatter, StreamClosed - class PlainTextFormatter(PlainTextFormatter, Formatter): - __doc__ = native_PlainTextFormatter.__doc__ - __slots__ = () - def fg(self, color=None): - return '' - bg = fg - -except ImportError: - PlainTextFormatter = native_PlainTextFormatter - StreamClosed = native_StreamClosed - -# This is necessary because the curses module is optional (and we -# should run on a very minimal python for bootstrapping). -try: - import curses -except ImportError: - TerminfoColor = None -else: - class TerminfoColor(object): - - def __init__(self, mode, color): - self.mode = mode - self.color = color - - def __call__(self, formatter): - if self.color is None: - formatter._current_colors[self.mode] = None - res = formatter._color_reset - # slight abuse of boolean True/False and 1/0 equivalence - other = formatter._current_colors[not self.mode] - if other is not None: - res = res + other - else: - if self.mode == 0: - default = curses.COLOR_WHITE - else: - default = curses.COLOR_BLACK - color = formatter._colors.get(self.color, default) - # The curses module currently segfaults if handed a - # bogus template so check explicitly. - template = formatter._set_color[self.mode] - if template: - res = curses.tparm(template, color) - else: - res = '' - formatter._current_colors[self.mode] = res - formatter.stream.write(res) - - - class TerminfoCode(object): - def __init__(self, value): - self.value = value - - class TerminfoMode(TerminfoCode): - def __call__(self, formatter): - formatter._modes.add(self) - formatter.stream.write(self.value) - - class TerminfoReset(TerminfoCode): - def __call__(self, formatter): - formatter._modes.clear() - formatter.stream.write(self.value) - - - class TerminfoFormatter(PlainTextFormatter): - - """Formatter writing to a tty, using terminfo to do colors.""" - - _colors = dict( - black = curses.COLOR_BLACK, - red = curses.COLOR_RED, - green = curses.COLOR_GREEN, - yellow = curses.COLOR_YELLOW, - blue = curses.COLOR_BLUE, - magenta = curses.COLOR_MAGENTA, - cyan = curses.COLOR_CYAN, - white = curses.COLOR_WHITE) - - # Remapping of TERM setting to more capable equivalent. - # Mainly used to force on the hardstatus (aka title bar updates) - # capability for terminals that do not support this by default. - term_alternates = { - 'xterm': 'xterm+sl', - 'screen': 'screen-s', - } - - def __init__(self, stream, term=None, forcetty=False, encoding=None): - """Initialize. - - @type stream: file-like object. - @param stream: stream to output to, defaulting to C{sys.stdout}. - @type term: string. - @param term: terminal type, pulled from the environment if omitted. - @type forcetty: bool - @param forcetty: force output of colors even if the wrapped stream - is not a tty. - """ - PlainTextFormatter.__init__(self, stream, encoding=encoding) - fd = stream.fileno() - if term is None: - # We only apply the remapping if we are guessing the - # terminal type from the environment. If we get a term - # type passed explicitly we just use it as-is (if the - # caller wants the remap just doing the - # term_alternates lookup there is easy enough.) - term_env = os.environ.get('TERM') - term_alt = self.term_alternates.get(term_env) - for term in (term_alt, term_env, 'dumb'): - if term is not None: - try: - curses.setupterm(fd=fd, term=term) - except curses.error: - pass - else: - break - else: - raise ValueError( - 'no terminfo entries, not even for "dumb"?') - else: - # TODO maybe do something more useful than raising curses.error - # if term is not in the terminfo db here? - curses.setupterm(fd=fd, term=term) - self.width = curses.tigetnum('cols') - self.reset = TerminfoReset(curses.tigetstr('sgr0')) - self.bold = TerminfoMode(curses.tigetstr('bold')) - self.underline = TerminfoMode(curses.tigetstr('smul')) - self._color_reset = curses.tigetstr('op') - self._set_color = ( - curses.tigetstr('setaf'), curses.tigetstr('setab')) - # [fg, bg] - self._current_colors = [None, None] - self._modes = set() - self._pos = 0 - - def fg(self, color=None): - return TerminfoColor(0, color) - - def bg(self, color=None): - return TerminfoColor(1, color) - - def write(self, *args, **kwargs): - PlainTextFormatter.write(self, *args, **kwargs) - try: - if self._modes: - self.reset(self) - if self._current_colors != [None, None]: - self._current_colors = [None, None] - self.stream.write(self._color_reset) - except IOError, e: - if e.errno == errno.EPIPE: - raise StreamClosed(e) - raise - - def title(self, string): - # I want to use curses.tigetflag('hs') here but at least - # the screen-s entry defines a tsl and fsl string but does - # not set the hs flag. So just check for the ability to - # jump to and out of the status line, without checking if - # the status line we're using exists. - if curses.tigetstr('tsl') and curses.tigetstr('fsl'): - self.stream.write( - curses.tigetstr('tsl') + string + curses.tigetstr('fsl')) - self.stream.flush() - - -class ObserverFormatter(object): - - def __init__(self, real_formatter): - self._formatter = real_formatter - - def write(self, *args): - self._formatter.write(autoline=False, *args) - - __getattr__ = GetAttrProxy("_formatter") - - -def get_formatter(stream): - """TerminfoFormatter if the stream is a tty, else PlainTextFormatter.""" - if TerminfoColor is None: - return PlainTextFormatter(stream) - try: - fd = stream.fileno() - except AttributeError: - pass - else: - # We do this instead of stream.isatty() because TerminfoFormatter - # needs an fd to pass to curses, not just a filelike talking to a tty. - if os.isatty(fd): - try: - return TerminfoFormatter(stream) - except curses.error: - # This happens if TERM is unset and possibly in more cases. - # Just fall back to the PlainTextFormatter. - pass - return PlainTextFormatter(stream) - - -def decorate_forced_wrapping(setting=True): - def wrapped_func(func): - def f(out, *args, **kwds): - oldwrap = out.wrap - out.wrap = setting - try: - return func(out, *args, **kwds) - finally: - out.wrap = oldwrap - return f - return wrapped_func diff --git a/snakeoil/iterables.py b/snakeoil/iterables.py deleted file mode 100644 index e6f05bf..0000000 --- a/snakeoil/iterables.py +++ /dev/null @@ -1,202 +0,0 @@ -# Copyright: 2006 Brian Harring <ferringb@gmail.com> -# License: GPL2 - -from collections import deque - -class expandable_chain(object): - """ - chained iterables, with the ability to add new iterables to the chain - as long as the instance hasn't raised StopIteration already. - """ - - __slot__ = ("iterables", "__weakref__") - - def __init__(self, *iterables): - """ - accepts N iterables, must have at least one specified - """ - self.iterables = deque() - self.extend(iterables) - - def __iter__(self): - return self - - def next(self): - if self.iterables is not None: - while self.iterables: - try: - return self.iterables[0].next() - except StopIteration: - self.iterables.popleft() - self.iterables = None - raise StopIteration() - - def append(self, iterable): - """append an iterable to the chain to be consumed""" - if self.iterables is None: - raise StopIteration() - self.iterables.append(iter(iterable)) - - def appendleft(self, iterable): - """prepend an iterable to the chain to be consumed""" - if self.iterables is None: - raise StopIteration() - self.iterables.appendleft(iter(iterable)) - - def extend(self, iterables): - """extend multiple iterables to the chain to be consumed""" - if self.iterables is None: - raise StopIteration() - self.iterables.extend(iter(x) for x in iterables) - - def extendleft(self, iterables): - """prepend multiple iterables to the chain to be consumed""" - if self.iterables is None: - raise StopIteration() - self.iterables.extendleft(iter(x) for x in iterables) - - -class caching_iter(object): - """ - On demand consumes from an iterable so as to appear like a tuple - """ - __slots__ = ("iterable", "__weakref__", "cached_list", "sorter") - - def __init__(self, iterable, sorter=None): - self.sorter = sorter - self.iterable = iter(iterable) - self.cached_list = [] - - def __setitem__(self, key, val): - raise TypeError("non modifiable") - - def __getitem__(self, index): - existing_len = len(self.cached_list) - if self.iterable is not None and self.sorter: - self.cached_list.extend(self.iterable) - self.cached_list = tuple(self.sorter(self.cached_list)) - self.iterable = self.sorter = None - existing_len = len(self.cached_list) - - if index < 0: - if self.iterable is not None: - self.cached_list = tuple(self.cached_list + list(self.iterable)) - self.iterable = None - existing_len = len(self.cached_list) - - index = existing_len + index - if index < 0: - raise IndexError("list index out of range") - - elif index >= existing_len - 1: - if self.iterable is not None: - try: - self.cached_list.extend(self.iterable.next() - for i in xrange(existing_len - index + 1)) - except StopIteration: - # consumed, baby. - self.iterable = None - self.cached_list = tuple(self.cached_list) - raise IndexError("list index out of range") - - return self.cached_list[index] - - def __cmp__(self, other): - if self.iterable is not None: - if self.sorter: - self.cached_list.extend(self.iterable) - self.cached_list = tuple(self.sorter(self.cached_list)) - self.sorter = None - else: - self.cached_list = tuple(self.cached_list + list(self.iterable)) - self.iterable = None - return cmp(self.cached_list, other) - - def __nonzero__(self): - if self.cached_list: - return True - - if self.iterable: - for x in self.iterable: - self.cached_list.append(x) - return True - # if we've made it here... then nothing more in the iterable. - self.iterable = self.sorter = None - self.cached_list = () - return False - - def __len__(self): - if self.iterable is not None: - self.cached_list.extend(self.iterable) - if self.sorter: - self.cached_list = tuple(self.sorter(self.cached_list)) - self.sorter = None - else: - self.cached_list = tuple(self.cached_list) - self.iterable = None - return len(self.cached_list) - - def __iter__(self): - if (self.sorter is not None and - self.iterable is not None and - len(self.cached_list) == 0): - self.cached_list = tuple(self.sorter(self.iterable)) - self.iterable = self.sorter = None - - for x in self.cached_list: - yield x - if self.iterable is not None: - for x in self.iterable: - self.cached_list.append(x) - yield x - else: - return - self.iterable = None - self.cached_list = tuple(self.cached_list) - - def __hash__(self): - if self.iterable is not None: - self.cached_list.extend(self.iterable) - self.cached_list = tuple(self.cached_list) - self.iterable = None - return hash(self.cached_list) - - def __str__(self): - return "iterable(%s), cached: %s" % ( - self.iterable, str(self.cached_list)) - -def iter_sort(sorter, *iterables): - """Merge a number of sorted iterables into a single sorted iterable. - - @type sorter: callable. - @param sorter: function, passed a list of [element, iterable]. - @param iterables: iterables to consume from. - B{Required} to yield in presorted order. - """ - l = [] - for x in iterables: - try: - x = iter(x) - l.append([x.next(), x]) - except StopIteration: - pass - if len(l) == 1: - yield l[0][0] - for x in l[0][1]: - yield x - return - l = sorter(l) - while l: - yield l[0][0] - for y in l[0][1]: - l[0][0] = y - break - else: - del l[0] - if len(l) == 1: - yield l[0][0] - for x in l[0][1]: - yield x - break - continue - l = sorter(l) diff --git a/snakeoil/klass.py b/snakeoil/klass.py deleted file mode 100644 index 6763c1f..0000000 --- a/snakeoil/klass.py +++ /dev/null @@ -1,95 +0,0 @@ -# Copyright: 2006 Brian Harring <ferringb@gmail.com> -# License: GPL2 - -from operator import attrgetter -from snakeoil.caching import WeakInstMeta -from collections import deque - -def native_GetAttrProxy(target): - def reflected_getattr(self, attr): - return getattr(getattr(self, target), attr) - return reflected_getattr - -def native_contains(self, key): - try: - self[key] - return True - except KeyError: - return False - -def native_get(self, key, default=None): - try: - return self[key] - except KeyError: - return default - - -attrlist_getter = attrgetter("__attr_comparison__") -def native_generic_eq(inst1, inst2, sentinel=object()): - if inst1 is inst2: - return True - for attr in attrlist_getter(inst1): - if getattr(inst1, attr, sentinel) != \ - getattr(inst2, attr, sentinel): - return False - return True - -def native_generic_ne(inst1, inst2, sentinel=object()): - if inst1 is inst2: - return False - for attr in attrlist_getter(inst1): - if getattr(inst1, attr, sentinel) != \ - getattr(inst2, attr, sentinel): - return True - return False - -try: - from snakeoil._klass import (GetAttrProxy, contains, get, - generic_eq, generic_ne) -except ImportError: - GetAttrProxy = native_GetAttrProxy - contains = native_contains - get = native_get - generic_eq = native_generic_eq - generic_ne = native_generic_ne - - -def generic_equality(name, bases, scope, real_type=type, - eq=generic_eq, ne=generic_ne): - attrlist = scope.pop("__attr_comparison__", None) - if attrlist is None: - raise TypeError("__attr_comparison__ must be in the classes scope") - for x in attrlist: - if not isinstance(x, str): - raise TypeError("all members of attrlist must be strings- " - " got %r %s" % (type(x), repr(x))) - - scope["__attr_comparison__"] = tuple(attrlist) - scope.setdefault("__eq__", eq) - scope.setdefault("__ne__", ne) - return real_type(name, bases, scope) - - -class chained_getter(object): - def __metaclass__(name, bases, scope): - return generic_equality(name, bases, scope, real_type=WeakInstMeta) - __slots__ = ('namespace', 'chain') - __fifo_cache__ = deque() - __inst_caching__ = True - __attr_comparison__ = ("namespace",) - - def __init__(self, namespace): - self.namespace = namespace - self.chain = map(attrgetter, namespace.split(".")) - if len(self.__fifo_cache__) > 10: - self.__fifo_cache__.popleft() - self.__fifo_cache__.append(self) - - def __hash__(self): - return hash(self.namespace) - - def __call__(self, obj): - o = obj - for f in self.chain: - o = f(o) - return o diff --git a/snakeoil/lists.py b/snakeoil/lists.py deleted file mode 100644 index 3cc8d4c..0000000 --- a/snakeoil/lists.py +++ /dev/null @@ -1,171 +0,0 @@ -# Copyright: 2005 Brian Harring <ferringb@gmail.com> -# License: GPL2 - -""" -sequence related operations -""" - -from snakeoil.iterables import expandable_chain - -def unstable_unique(sequence): - """ - lifted from python cookbook, credit: Tim Peters - Return a list of the elements in s in arbitrary order, sans duplicates - """ - - n = len(sequence) - # assume all elements are hashable, if so, it's linear - try: - return list(set(sequence)) - except TypeError: - pass - - # so much for linear. abuse sort. - try: - t = sorted(sequence) - except TypeError: - pass - else: - assert n > 0 - last = t[0] - lasti = i = 1 - while i < n: - if t[i] != last: - t[lasti] = last = t[i] - lasti += 1 - i += 1 - return t[:lasti] - - # blah. back to original portage.unique_array - u = [] - for x in sequence: - if x not in u: - u.append(x) - return u - -def stable_unique(iterable): - """ - return unique list from iterable, preserving ordering - """ - return list(iter_stable_unique(iterable)) - -def iter_stable_unique(iterable): - """ - generator yielding unique elements from iterable, preserving ordering - """ - s = set() - for x in iterable: - if x not in s: - yield x - s.add(x) - -def native_iflatten_instance(l, skip_flattening=(basestring,)): - """ - collapse [[1],2] into [1,2] - - @param skip_flattening: list of classes to not descend through - """ - if isinstance(l, skip_flattening): - yield l - return - iters = expandable_chain(l) - try: - while True: - x = iters.next() - if hasattr(x, '__iter__') and not isinstance(x, skip_flattening): - iters.appendleft(x) - else: - yield x - except StopIteration: - pass - -def native_iflatten_func(l, skip_func): - """ - collapse [[1],2] into [1,2] - - @param skip_func: a callable that returns True when iflatten_func should - descend no further - """ - if skip_func(l): - yield l - return - iters = expandable_chain(l) - try: - while True: - x = iters.next() - if hasattr(x, '__iter__') and not skip_func(x): - iters.appendleft(x) - else: - yield x - except StopIteration: - pass - - -try: - # No name "readdir" in module osutils - # pylint: disable-msg=E0611 - from snakeoil._lists import iflatten_instance, iflatten_func - cpy_builtin = True -except ImportError: - cpy_builtin = False - cpy_iflatten_instance = cpy_iflatten_func = None - iflatten_instance = native_iflatten_instance - iflatten_func = native_iflatten_func - - -class ChainedLists(object): - """ - sequences chained together, without collapsing into a list - """ - __slots__ = ("_lists", "__weakref__") - - def __init__(self, *lists): - """ - all args must be sequences - """ - # ensure they're iterable - for x in lists: - iter(x) - - if isinstance(lists, tuple): - lists = list(lists) - self._lists = lists - - def __len__(self): - return sum(len(l) for l in self._lists) - - def __getitem__(self, idx): - if idx < 0: - idx += len(self) - if idx < 0: - raise IndexError - for l in self._lists: - l2 = len(l) - if idx < l2: - return l[idx] - idx -= l2 - else: - raise IndexError - - def __setitem__(self, idx, val): - raise TypeError("not mutable") - - def __delitem__(self, idx): - raise TypeError("not mutable") - - def __iter__(self): - for l in self._lists: - for x in l: - yield x - - def __contains__(self, obj): - return obj in iter(self) - - def __str__(self): - return "[ %s ]" % ", ".join(str(l) for l in self._lists) - - def append(self, item): - self._lists.append(item) - - def extend(self, items): - self._lists.extend(items) diff --git a/snakeoil/mappings.py b/snakeoil/mappings.py deleted file mode 100644 index 7847c8c..0000000 --- a/snakeoil/mappings.py +++ /dev/null @@ -1,579 +0,0 @@ -# Copyright: 2005-2007 Brian Harring <ferringb@gmail.com> -# License: GPL2 - -""" -miscellanious mapping/dict related classes -""" - -import operator -from itertools import imap, chain, ifilterfalse, izip -from snakeoil.klass import get, contains -from collections import deque - - -class DictMixin(object): - """ - new style class replacement for L{UserDict.DictMixin} - designed around iter* methods rather then forcing lists as DictMixin does - """ - - __slots__ = () - - __externally_mutable__ = True - - def __init__(self, iterable=None, **kwargs): - if iterable is not None: - self.update(iterable) - - if kwargs: - self.update(kwargs.iteritems()) - - def __iter__(self): - return self.iterkeys() - - def keys(self): - return list(self.iterkeys()) - - def values(self): - return list(self.itervalues()) - - def items(self): - return list(self.iteritems()) - - def update(self, iterable): - for k, v in iterable: - self[k] = v - - get = get - __contains__ = contains - - # default cmp actually operates based on key len comparison, oddly enough - def __cmp__(self, other): - for k1, k2 in izip(sorted(self), sorted(other)): - c = cmp(k1, k2) - if c != 0: - return c - c = cmp(self[k1], other[k2]) - if c != 0: - return c - c = cmp(len(self), len(other)) - return c - - def __eq__(self, other): - return self.__cmp__(other) == 0 - - def __ne__(self, other): - return self.__cmp__(other) != 0 - - def pop(self, key, default=None): - if not self.__externally_mutable__: - raise AttributeError(self, "pop") - try: - val = self[key] - del self[key] - except KeyError: - if default is not None: - return default - raise - return val - - def setdefault(self, key, default=None): - if not self.__externally_mutable__: - raise AttributeError(self, "setdefault") - if key in self: - return self[key] - self[key] = default - return default - - def has_key(self, key): - return key in self - - def iterkeys(self): - raise NotImplementedError(self, "iterkeys") - - def itervalues(self): - return imap(self.__getitem__, self) - - def iteritems(self): - for k in self: - yield k, self[k] - - def __getitem__(self, key): - raise NotImplementedError(self, "__getitem__") - - def __setitem__(self, key, val): - if not self.__externally_mutable__: - raise AttributeError(self, "__setitem__") - raise NotImplementedError(self, "__setitem__") - - def __delitem__(self, key): - if not self.__externally_mutable__: - raise AttributeError(self, "__delitem__") - raise NotImplementedError(self, "__delitem__") - - def clear(self): - if not self.__externally_mutable__: - raise AttributeError(self, "clear") - # crappy, override if faster method exists. - map(self.__delitem__, self.keys()) - - def __len__(self): - c = 0 - for x in self: - c += 1 - return c - - def popitem(self): - if not self.__externally_mutable__: - raise AttributeError(self, "popitem") - # do it this way so python handles the stopiteration; faster - for key, val in self.iteritems(): - del self[key] - return key, val - raise KeyError("container is empty") - - -class LazyValDict(DictMixin): - - """ - Mapping that loads values via a callable - - given a function to get keys, and to look up the val for those keys, it'll - lazily load key definitions and values as requested - """ - __slots__ = ("_keys", "_keys_func", "_vals", "_val_func") - __externally_mutable__ = False - - def __init__(self, get_keys_func, get_val_func): - """ - @param get_keys_func: either a container, or func to call to get keys. - @param get_val_func: a callable that is JIT called - with the key requested. - """ - if not callable(get_val_func): - raise TypeError("get_val_func isn't a callable") - if hasattr(get_keys_func, "__iter__"): - self._keys = get_keys_func - self._keys_func = None - else: - if not callable(get_keys_func): - raise TypeError( - "get_keys_func isn't iterable or callable") - self._keys_func = get_keys_func - self._val_func = get_val_func - self._vals = {} - - def __getitem__(self, key): - if self._keys_func is not None: - self._keys = set(self._keys_func()) - self._keys_func = None - if key in self._vals: - return self._vals[key] - if key in self._keys: - v = self._vals[key] = self._val_func(key) - return v - raise KeyError(key) - - def keys(self): - if self._keys_func is not None: - self._keys = set(self._keys_func()) - self._keys_func = None - return list(self._keys) - - def iterkeys(self): - if self._keys_func is not None: - self._keys = set(self._keys_func()) - self._keys_func = None - return iter(self._keys) - - def itervalues(self): - return imap(self.__getitem__, self.iterkeys()) - - def iteritems(self): - return ((k, self[k]) for k in self.iterkeys()) - - def __contains__(self, key): - if self._keys_func is not None: - self._keys = set(self._keys_func()) - self._keys_func = None - return key in self._keys - - def __len__(self): - if self._keys_func is not None: - self._keys = set(self._keys_func()) - self._keys_func = None - return len(self._keys) - - -class LazyFullValLoadDict(LazyValDict): - - __slots__ = () - - def __getitem__(self, key): - if self._keys_func is not None: - self._keys = set(self._keys_func()) - self._keys_func = None - if key in self._vals: - return self._vals[key] - if key in self._keys: - if self._val_func is not None: - self._vals.update(self._val_func(self._keys)) - return self._vals[key] - raise KeyError(key) - - -class ProtectedDict(DictMixin): - - """ - Mapping wrapper storing changes to a dict without modifying the original. - - Changes are stored in a secondary dict, protecting the underlying - mapping from changes. - """ - - __slots__ = ("orig", "new", "blacklist") - - def __init__(self, orig): - self.orig = orig - self.new = {} - self.blacklist = {} - - def __setitem__(self, key, val): - self.new[key] = val - if key in self.blacklist: - del self.blacklist[key] - - def __getitem__(self, key): - if key in self.new: - return self.new[key] - if key in self.blacklist: - raise KeyError(key) - return self.orig[key] - - def __delitem__(self, key): - if key in self.new: - del self.new[key] - self.blacklist[key] = True - return - elif key in self.orig: - if key not in self.blacklist: - self.blacklist[key] = True - return - raise KeyError(key) - - def iterkeys(self): - for k in self.new.iterkeys(): - yield k - for k in self.orig.iterkeys(): - if k not in self.blacklist and k not in self.new: - yield k - - def __contains__(self, key): - return key in self.new or (key not in self.blacklist and - key in self.orig) - - -class ImmutableDict(dict): - - """Immutable Dict, non changable after instantiating""" - - _hash_key_grabber = operator.itemgetter(0) - - def __delitem__(self, *args): - raise TypeError("non modifiable") - - __setitem__ = clear = update = pop = popitem = setdefault = __delitem__ - - def __hash__(self): - k = self.items() - k.sort(key=self._hash_key_grabber) - return hash(tuple(k)) - - __delattr__ = __setitem__ - __setattr__ = __setitem__ - - -class IndeterminantDict(object): - - """A wrapped dict with constant defaults, and a function for other keys.""" - - __slots__ = ("__initial", "__pull") - - def __init__(self, pull_func, starter_dict=None): - object.__init__(self) - if starter_dict is None: - self.__initial = {} - else: - self.__initial = starter_dict - self.__pull = pull_func - - def __getitem__(self, key): - if key in self.__initial: - return self.__initial[key] - else: - return self.__pull(key) - - def get(self, key, val=None): - try: - return self[key] - except KeyError: - return val - - def __hash__(self): - raise TypeError("non hashable") - - def __delitem__(self, *args): - raise TypeError("non modifiable") - - pop = get - - clear = update = popitem = setdefault = __setitem__ = __delitem__ - __iter__ = keys = values = items = __len__ = __delitem__ - iteritems = iterkeys = itervalues = __delitem__ - - -class StackedDict(DictMixin): - - """A non modifiable dict that makes multiple dicts appear as one""" - - def __init__(self, *dicts): - self._dicts = dicts - - def __getitem__(self, key): - for x in self._dicts: - if key in x: - return x[key] - raise KeyError(key) - - def iterkeys(self): - s = set() - for k in ifilterfalse(s.__contains__, chain(*map(iter, self._dicts))): - s.add(k) - yield k - - def __contains__(self, key): - for x in self._dicts: - if key in x: - return True - return False - - def __setitem__(self, *a): - raise TypeError("non modifiable") - - __delitem__ = clear = __setitem__ - - -class OrderedDict(DictMixin): - - """Dict that preserves insertion ordering which is used for iteration ops""" - - __slots__ = ("_data", "_order") - - def __init__(self, iterable=()): - self._order = deque() - self._data = {} - for k, v in iterable: - self[k] = v - - def __setitem__(self, key, val): - if key not in self: - self._order.append(key) - self._data[key] = val - - def __delitem__(self, key): - del self._data[key] - - for idx, o in enumerate(self._order): - if o == key: - del self._order[idx] - break - else: - raise AssertionError("orderdict lost its internal ordering") - - def __getitem__(self, key): - return self._data[key] - - def __len__(self): - return len(self._order) - - def iterkeys(self): - return iter(self._order) - - def clear(self): - self._order = deque() - self._data = {} - - def __contains__(self, key): - return key in self._data - - -class ListBackedDict(DictMixin): - - __slots__ = ("_data") - _kls = list - _key_grabber = operator.itemgetter(0) - _value_grabber = operator.itemgetter(1) - - def __init__(self, iterables=()): - self._data = self._kls((k, v) for k, v in iterables) - - def __setitem__(self, key, val): - for idx, vals in enumerate(self._data): - if vals[0] == key: - self._data[idx] = (key, val) - break - else: - self._data.append((key, val)) - - def __getitem__(self, key): - for existing_key, val in self._data: - if key == existing_key: - return val - raise KeyError(key) - - def __delitem__(self, key): - l = self._kls((k, v) for k, v in self._data if k != key) - if len(l) == len(self._data): - # no match. - raise KeyError(key) - self._data = l - - def iterkeys(self): - return imap(self._key_grabber, self._data) - - def itervalues(self): - return imap(self._value_grabber, self._data) - - def iteritems(self): - return iter(self._data) - - def __contains__(self, key): - for k, v in self._data: - if k == key: - return True - return False - - def __len__(self): - return len(self._data) - -class TupleBackedDict(ListBackedDict): - __slots__ = () - _kls = tuple - - def __setitem__(self, key, val): - self._data = self._kls( - chain((x for x in self.iteritems() if x[0] != key), ((key, val),))) - - -class PreservingFoldingDict(DictMixin): - - """dict that uses a 'folder' function when looking up keys. - - The most common use for this is to implement a dict with - case-insensitive key values (by using C{str.lower} as folder - function). - - This version returns the original 'unfolded' key. - """ - - def __init__(self, folder, sourcedict=None): - self._folder = folder - # dict mapping folded keys to (original key, value) - self._dict = {} - if sourcedict is not None: - self.update(sourcedict) - - def copy(self): - return PreservingFoldingDict(self._folder, self.iteritems()) - - def refold(self, folder=None): - """Use the remembered original keys to update to a new folder. - - If folder is C{None}, keep the current folding function (this - is useful if the folding function uses external data and that - data changed). - """ - if folder is not None: - self._folder = folder - oldDict = self._dict - self._dict = {} - for key, value in oldDict.itervalues(): - self._dict[self._folder(key)] = (key, value) - - def __getitem__(self, key): - return self._dict[self._folder(key)][1] - - def __setitem__(self, key, value): - self._dict[self._folder(key)] = (key, value) - - def __delitem__(self, key): - del self._dict[self._folder(key)] - - def iteritems(self): - return self._dict.itervalues() - - def iterkeys(self): - for val in self._dict.itervalues(): - yield val[0] - - def itervalues(self): - for val in self._dict.itervalues(): - yield val[1] - - def __contains__(self, key): - return self._folder(key) in self._dict - - def __len__(self): - return len(self._dict) - - def clear(self): - self._dict = {} - - -class NonPreservingFoldingDict(DictMixin): - - """dict that uses a 'folder' function when looking up keys. - - The most common use for this is to implement a dict with - case-insensitive key values (by using C{str.lower} as folder - function). - - This version returns the 'folded' key. - """ - - def __init__(self, folder, sourcedict=None): - self._folder = folder - # dict mapping folded keys to values. - self._dict = {} - if sourcedict is not None: - self.update(sourcedict) - - def copy(self): - return NonPreservingFoldingDict(self._folder, self.iteritems()) - - def __getitem__(self, key): - return self._dict[self._folder(key)] - - def __setitem__(self, key, value): - self._dict[self._folder(key)] = value - - def __delitem__(self, key): - del self._dict[self._folder(key)] - - def iterkeys(self): - return iter(self._dict) - - def itervalues(self): - return self._dict.itervalues() - - def iteritems(self): - return self._dict.iteritems() - - def __contains__(self, key): - return self._folder(key) in self._dict - - def __len__(self): - return len(self._dict) - - def clear(self): - self._dict = {} diff --git a/snakeoil/modules.py b/snakeoil/modules.py deleted file mode 100644 index d685001..0000000 --- a/snakeoil/modules.py +++ /dev/null @@ -1,53 +0,0 @@ -# Copyright: 2005 Brian Harring <ferringb@gmail.com> -# License: GPL2 - -""" -dynamic import functionality -""" - -import sys - -class FailedImport(ImportError): - def __init__(self, trg, e): - ImportError.__init__( - self, "Failed importing target '%s': '%s'" % (trg, e)) - self.trg, self.e = trg, e - - -def load_module(name): - """load 'name' module, throwing a FailedImport if __import__ fails""" - if name in sys.modules: - return sys.modules[name] - try: - m = __import__(name) - # __import__('foo.bar') returns foo, so... - for bit in name.split('.')[1:]: - m = getattr(m, bit) - return m - except (KeyboardInterrupt, SystemExit): - raise - except Exception, e: - raise FailedImport(name, e) - - -def load_attribute(name): - """load a specific attribute, rather then a module""" - chunks = name.rsplit(".", 1) - if len(chunks) == 1: - raise FailedImport(name, "it isn't an attribute, it's a module") - try: - m = load_module(chunks[0]) - m = getattr(m, chunks[1]) - return m - except (AttributeError, ImportError), e: - raise FailedImport(name, e) - - -def load_any(name): - """Load a module or attribute.""" - try: - return load_module(name) - except FailedImport, fi: - if not isinstance(fi.e, ImportError): - raise - return load_attribute(name) diff --git a/snakeoil/obj.py b/snakeoil/obj.py deleted file mode 100644 index 9101280..0000000 --- a/snakeoil/obj.py +++ /dev/null @@ -1,206 +0,0 @@ -# Copyright: 2006 Brian Harring <ferringb@gmail.com> -# License: GPL2 - -from operator import attrgetter -from snakeoil.currying import pre_curry -from snakeoil.mappings import DictMixin - -def alias_method(getter, self, *a, **kwd): - return getter(self.__obj__)(*a, **kwd) - -def instantiate(inst): - delayed = object.__getattribute__(inst, "__delayed__") - obj = delayed[1](*delayed[2], **delayed[3]) - object.__setattr__(inst, "__obj__", obj) - object.__delattr__(inst, "__delayed__") - return obj - - -# we exempt __getattribute__ since we cover it already, same -# for __new__ and __init__ -base_kls_descriptors = frozenset( - ('__delattr__', '__doc__', '__hash__', '__reduce__', - '__reduce_ex__', '__repr__', '__setattr__', '__str__')) - -class BaseDelayedObject(object): - """ - delay actual instantiation - """ - - def __new__(cls, desired_kls, func, *a, **kwd): - o = object.__new__(cls) - object.__setattr__(o, "__delayed__", (desired_kls, func, a, kwd)) - object.__setattr__(o, "__obj__", None) - return o - - def __getattribute__(self, attr): - obj = object.__getattribute__(self, "__obj__") - if obj is None: - if attr == "__class__": - return object.__getattribute__(self, "__delayed__")[0] - - obj = instantiate(self) - # now we grow some attributes. - - if attr == "__obj__": - # special casing for alias_method - return obj - return getattr(obj, attr) - - # special case the normal descriptors - for x in base_kls_descriptors: - locals()[x] = pre_curry(alias_method, attrgetter(x)) - del x - - -# note that we ignore __getattribute__; we already handle it. -kls_descriptors = frozenset([ - # simple comparison protocol... - '__cmp__', - # rich comparison protocol... - '__le__', '__lt__', '__eq__', '__ne__', '__gt__', '__ge__', - # unicode conversion - '__unicode__', - # truth... - '__nonzero__', - # container protocol... - '__len__', '__getitem__', '__setitem__', '__delitem__', - '__iter__', '__contains__', - # deprecated sequence protocol bits... - '__getslice__', '__setslice__', '__delslice__', - # numeric... - '__add__', '__sub__', '__mul__', '__floordiv__', '__mod__', - '__divmod__', '__pow__', '__lshift__', '__rshift__', - '__and__', '__xor__', '__or__', '__div__', '__truediv__', - '__rad__', '__rsub__', '__rmul__', '__rdiv__', '__rtruediv__', - '__rfloordiv__', '__rmod__', '__rdivmod__', '__rpow__', - '__rlshift__', '__rrshift__', '__rand__', '__rxor__', '__ror__', - '__iadd__', '__isub__', '__imul__', '__idiv__', '__itruediv__', - '__ifloordiv__', '__imod__', '__ipow__', '__ilshift__', - '__irshift__', '__iand__', '__ixor__', '__ior__', - '__neg__', '__pos__', '__abs__', '__invert__', '__complex__', - '__int__', '__long__', '__float__', '__oct__', '__hex__', - '__coerce__', - # remaining... - '__call__']) - -descriptor_overrides = dict((k, pre_curry(alias_method, attrgetter(k))) - for k in kls_descriptors) - -method_cache = {} -def make_kls(kls): - special_descriptors = tuple(sorted(kls_descriptors.intersection(dir(kls)))) - if not special_descriptors: - return BaseDelayedObject - o = method_cache.get(special_descriptors, None) - if o is None: - class CustomDelayedObject(BaseDelayedObject): - locals().update((k, descriptor_overrides[k]) - for k in special_descriptors) - - o = CustomDelayedObject - method_cache[special_descriptors] = o - return o - -def DelayedInstantiation_kls(kls, *a, **kwd): - return DelayedInstantiation(kls, kls, *a, **kwd) - -class_cache = {} -def DelayedInstantiation(resultant_kls, func, *a, **kwd): - """Generate an objects that does not get initialized before it is used. - - The returned object can be passed around without triggering - initialization. The first time it is actually used (an attribute - is accessed) it is initialized once. - - The returned "fake" object cannot completely reliably mimic a - builtin type. It will usually work but some corner cases may fail - in confusing ways. Make sure to test if DelayedInstantiation has - no unwanted side effects. - - @param resultant_kls: type object to fake an instance of. - @param func: callable, the return value is used as initialized object. - """ - o = class_cache.get(resultant_kls, None) - if o is None: - o = make_kls(resultant_kls) - class_cache[resultant_kls] = o - return o(resultant_kls, func, *a, **kwd) - - -slotted_dict_cache = {} -def make_SlottedDict_kls(keys): - new_keys = tuple(sorted(keys)) - o = slotted_dict_cache.get(new_keys, None) - if o is None: - class SlottedDict(DictMixin): - __slots__ = new_keys - __externally_mutable__ = True - - def __init__(self, iterables=()): - if iterables: - self.update(iterables) - - __setitem__ = object.__setattr__ - - def __getitem__(self, key): - try: - return getattr(self, key) - except AttributeError: - raise KeyError(key) - - def __delitem__(self, key): - # Python does not raise anything if you delattr an - # unset slot (works ok if __slots__ is not involved). - try: - getattr(self, key) - except AttributeError: - raise KeyError(key) - delattr(self, key) - - def __iter__(self): - for k in self.__slots__: - if hasattr(self, k): - yield k - - def iterkeys(self): - return iter(self) - - def itervalues(self): - for k in self: - yield self[k] - - def get(self, key, default=None): - return getattr(self, key, default) - - def pop(self, key, *a): - # faster then the exception form... - l = len(a) - if l > 1: - raise TypeError("pop accepts 1 or 2 args only") - if hasattr(self, key): - o = getattr(self, key) - object.__delattr__(self, key) - elif l: - o = a[0] - else: - raise KeyError(key) - return o - - def clear(self): - for k in self: - del self[k] - - def update(self, iterable): - for k, v in iterable: - setattr(self, k, v) - - def __len__(self): - return len(self.keys()) - - def __contains__(self, key): - return hasattr(self, key) - - o = SlottedDict - slotted_dict_cache[new_keys] = o - return o diff --git a/snakeoil/osutils/__init__.py b/snakeoil/osutils/__init__.py deleted file mode 100644 index 62c1b28..0000000 --- a/snakeoil/osutils/__init__.py +++ /dev/null @@ -1,340 +0,0 @@ -# Copyright 2004-2007 Brian Harring <ferringb@gmail.com> -# Copyright 2006 Marien Zwart <marienz@gentoo.org> -# Distributed under the terms of the GNU General Public License v2 - -""" -os specific utilities, FS access mainly - -""" - -import os, stat -import fcntl -import errno - -__all__ = ['abspath', 'abssymlink', 'ensure_dirs', 'join', 'pjoin', - 'listdir_files', 'listdir_dirs', 'listdir', 'readlines', 'readfile', - 'readdir'] - - -# No name '_readdir' in module osutils -# pylint: disable-msg=E0611 - -try: - from snakeoil.osutils import _readdir as module -except ImportError: - from snakeoil.osutils import native_readdir as module - -listdir = module.listdir -listdir_dirs = module.listdir_dirs -listdir_files = module.listdir_files -readdir = module.readdir - -del module - - -def ensure_dirs(path, gid=-1, uid=-1, mode=0777, minimal=True): - """ - ensure dirs exist, creating as needed with (optional) gid, uid, and mode. - - be forewarned- if mode is specified to a mode that blocks the euid - from accessing the dir, this code *will* try to create the dir. - """ - - try: - st = os.stat(path) - except OSError: - base = os.path.sep - try: - um = os.umask(0) - # if the dir perms would lack +wx, we have to force it - force_temp_perms = ((mode & 0300) != 0300) - resets = [] - apath = normpath(os.path.abspath(path)) - sticky_parent = False - - for directory in apath.split(os.path.sep): - base = join(base, directory) - try: - st = os.stat(base) - if not stat.S_ISDIR(st.st_mode): - return False - - # if it's a subdir, we need +wx at least - if apath != base: - if ((st.st_mode & 0300) != 0300): - try: - os.chmod(base, (st.st_mode | 0300)) - except OSError: - return False - resets.append((base, st.st_mode)) - sticky_parent = (st.st_gid & stat.S_ISGID) - - except OSError: - # nothing exists. - try: - if force_temp_perms: - os.mkdir(base, 0700) - resets.append((base, mode)) - else: - os.mkdir(base, mode) - if base == apath and sticky_parent: - resets.append((base, mode)) - if gid != -1 or uid != -1: - os.chown(base, uid, gid) - except OSError: - return False - - try: - for base, m in reversed(resets): - os.chmod(base, m) - if uid != -1 or gid != -1: - os.chown(base, uid, gid) - except OSError: - return False - - finally: - os.umask(um) - return True - else: - try: - if ((gid != -1 and gid != st.st_gid) or - (uid != -1 and uid != st.st_uid)): - os.chown(path, uid, gid) - if minimal: - if mode != (st.st_mode & mode): - os.chmod(path, st.st_mode | mode) - elif mode != (st.st_mode & 07777): - os.chmod(path, mode) - except OSError: - return False - return True - - -def abssymlink(symlink): - """ - Read a symlink, resolving if it is relative, returning the absolute. - If the path doesn't exist, OSError is thrown. - - @param symlink: filepath to resolve - @return: resolve path. - """ - mylink = os.readlink(symlink) - if mylink[0] != '/': - mydir = os.path.dirname(symlink) - mylink = mydir+"/"+mylink - return os.path.normpath(mylink) - - -def abspath(path): - """ - resolve a path absolutely, including symlink resolving. - Throws OSError if the path doesn't exist - - Note that if it's a symlink and the target doesn't exist, it'll still - return the target. - - @param path: filepath to resolve. - @return: resolve path - """ - path = os.path.abspath(path) - try: - return abssymlink(path) - except OSError, e: - if e.errno == errno.EINVAL: - return path - raise - - -def native_normpath(mypath): - """ - normalize path- //usr/bin becomes /usr/bin - """ - newpath = os.path.normpath(mypath) - if newpath.startswith('//'): - return newpath[1:] - return newpath - -native_join = os.path.join - -def native_readfile(mypath, none_on_missing=False): - """ - read a file, returning the contents - - @param mypath: fs path for the file to read - @param none_on_missing: whether to return None if the file is missing, - else through the exception - """ - try: - return open(mypath, "r").read() - except IOError, oe: - if none_on_missing and oe.errno == errno.ENOENT: - return None - raise - - -class readlines_iter(object): - __slots__ = ("iterable", "mtime") - def __init__(self, iterable, mtime): - self.iterable = iterable - self.mtime = mtime - - def __iter__(self): - return self.iterable - - -def native_readlines(mypath, strip_newlines=True, swallow_missing=False, - none_on_missing=False): - """ - read a file, yielding each line - - @param mypath: fs path for the file to read - @param strip_newlines: strip trailing newlines? - @param swallow_missing: throw an IOError if missing, or swallow it? - @param none_on_missing: if the file is missing, return None, else - if the file is missing return an empty iterable - """ - try: - f = open(mypath, "r") - except IOError, ie: - if ie.errno != errno.ENOENT or not swallow_missing: - raise - if none_on_missing: - return None - return readlines_iter(iter([]), None) - - if not strip_newlines: - return readlines_iter(f, os.fstat(f.fileno()).st_mtime) - - return readlines_iter((x.strip("\n") for x in f), os.fstat(f.fileno()).st_mtime) - - -try: - from snakeoil.osutils._posix import normpath, join, readfile, readlines -except ImportError: - normpath = native_normpath - join = native_join - readfile = native_readfile - readlines = native_readlines - -# convenience. importing join into a namespace is ugly, pjoin less so -pjoin = join - -class LockException(Exception): - """Base lock exception class""" - def __init__(self, path, reason): - Exception.__init__(self, path, reason) - self.path, self.reason = path, reason - -class NonExistant(LockException): - """Missing file/dir exception""" - def __init__(self, path, reason=None): - LockException.__init__(self, path, reason) - def __str__(self): - return ( - "Lock action for '%s' failed due to not being a valid dir/file %s" - % (self.path, self.reason)) - -class GenericFailed(LockException): - """The fallback lock exception class. - - Covers perms, IOError's, and general whackyness. - """ - def __str__(self): - return "Lock action for '%s' failed due to '%s'" % ( - self.path, self.reason) - - -# should the fd be left open indefinitely? -# IMO, it shouldn't, but opening/closing everytime around is expensive - - -class FsLock(object): - - """ - fnctl based locks - """ - - __slots__ = ("path", "fd", "create") - def __init__(self, path, create=False): - """ - @param path: fs path for the lock - @param create: controls whether the file will be created - if the file doesn't exist. - If true, the base dir must exist, and it will create a file. - If you want to lock via a dir, you have to ensure it exists - (create doesn't suffice). - @raise NonExistant: if no file/dir exists for that path, - and cannot be created - """ - self.path = path - self.fd = None - self.create = create - if not create: - if not os.path.exists(path): - raise NonExistant(path) - - def _acquire_fd(self): - if self.create: - try: - self.fd = os.open(self.path, os.R_OK|os.O_CREAT) - except OSError, oe: - raise GenericFailed(self.path, oe) - else: - try: - self.fd = os.open(self.path, os.R_OK) - except OSError, oe: - raise NonExistant(self.path, oe) - - def _enact_change(self, flags, blocking): - if self.fd is None: - self._acquire_fd() - # we do it this way, due to the fact try/except is a bit of a hit - if not blocking: - try: - fcntl.flock(self.fd, flags|fcntl.LOCK_NB) - except IOError, ie: - if ie.errno == errno.EAGAIN: - return False - raise GenericFailed(self.path, ie) - else: - fcntl.flock(self.fd, flags) - return True - - def acquire_write_lock(self, blocking=True): - """ - Acquire an exclusive lock - - Note if you have a read lock, it implicitly upgrades atomically - - @param blocking: if enabled, don't return until we have the lock - @return: True if lock is acquired, False if not. - """ - return self._enact_change(fcntl.LOCK_EX, blocking) - - def acquire_read_lock(self, blocking=True): - """ - Acquire a shared lock - - Note if you have a write lock, it implicitly downgrades atomically - - @param blocking: if enabled, don't return until we have the lock - @return: True if lock is acquired, False if not. - """ - return self._enact_change(fcntl.LOCK_SH, blocking) - - def release_write_lock(self): - """Release an write/exclusive lock if held""" - self._enact_change(fcntl.LOCK_UN, False) - - def release_read_lock(self): - """Release an shared/read lock if held""" - self._enact_change(fcntl.LOCK_UN, False) - - def __del__(self): - # alright, it's 5:45am, yes this is weird code. - try: - if self.fd is not None: - self.release_read_lock() - finally: - if self.fd is not None: - os.close(self.fd) diff --git a/snakeoil/osutils/native_readdir.py b/snakeoil/osutils/native_readdir.py deleted file mode 100644 index c09eb1d..0000000 --- a/snakeoil/osutils/native_readdir.py +++ /dev/null @@ -1,60 +0,0 @@ -# Copyright: 2006-2007 Brian Harring <ferringb@gmail.com> -# Copyright: 2006 Marien Zwart <marienz@gentoo.org> -# License: GPL2 - -"""Wrapper for readdir which grabs file type from d_type.""" - - -import os, errno -from stat import (S_IFDIR, S_IFREG, S_IFCHR, S_IFBLK, S_IFIFO, S_IFLNK, S_IFSOCK, - S_IFMT, S_ISDIR, S_ISREG) - -listdir = os.listdir - -# we can still use the cpy pjoin here, just need to do something about the -# import cycle. -pjoin = os.path.join - -def stat_swallow_enoent(path, check, default=False, stat=os.stat): - try: - return check(stat(path).st_mode) - except OSError, oe: - if oe.errno == errno.ENOENT: - return default - raise - -def listdir_dirs(path, followSymlinks=True): - scheck = S_ISDIR - pjf = pjoin - lstat = os.lstat - if followSymlinks: - return [x for x in os.listdir(path) if - stat_swallow_enoent(pjf(path, x), scheck)] - lstat = os.lstat - return [x for x in os.listdir(path) if - scheck(lstat(pjf(path, x)).st_mode)] - -def listdir_files(path, followSymlinks=True): - scheck = S_ISREG - pjf = pjoin - if followSymlinks: - return [x for x in os.listdir(path) if - stat_swallow_enoent(pjf(path, x), scheck)] - lstat = os.lstat - return [x for x in os.listdir(path) if - scheck(lstat(pjf(path, x)).st_mode)] - -def readdir(path): - pjf = pjoin - assocs = { - S_IFREG: "file", - S_IFDIR: "directory", - S_IFLNK: "symlink", - S_IFCHR: "chardev", - S_IFBLK: "block", - S_IFSOCK: "socket", - S_IFIFO: "fifo", - } - things = listdir(path) - lstat = os.lstat - return [(name, assocs[S_IFMT(lstat(pjf(path, name)).st_mode)]) for name in things] diff --git a/snakeoil/pickling.py b/snakeoil/pickling.py deleted file mode 100644 index fe1b39f..0000000 --- a/snakeoil/pickling.py +++ /dev/null @@ -1,18 +0,0 @@ -# Copyright: 2007 Brian Harring <ferringb@gmail.com> -# License: GPL2 - -""" -convenience module using cPickle if available, else failing back to pickle -""" - -try: - from cPickle import * -except ImportError: - from pickle import * - -def iter_stream(stream): - try: - while True: - yield load(stream) - except EOFError: - pass diff --git a/snakeoil/tar.py b/snakeoil/tar.py deleted file mode 100644 index 8f581b1..0000000 --- a/snakeoil/tar.py +++ /dev/null @@ -1,35 +0,0 @@ -# Copyright: 2006 Brian Harring <ferringb@gmail.com> -# License: GPL2 - -""" -tar file access - -monkey patching of stdlib tarfile to reduce mem usage (33% reduction). - -note this is also racey; N threads trying an import, if they're after -the *original* tarfile, they may inadvertantly get ours. -""" - -import sys -t = sys.modules.pop("tarfile", None) -tarfile = __import__("tarfile") -if t is not None: - sys.modules["tarfile"] = t -else: - del sys.modules["tarfile"] -del t -# ok, we now have our own local copy to monkey patch - -class TarInfo(tarfile.TarInfo): - __slots__ = ( - "name", "mode", "uid", "gid", "size", "mtime", "chksum", "type", - "linkname", "uname", "gname", "devmajor", "devminor", "prefix", - "offset", "offset_data", "buf", "sparse", "_link_target") - -tarfile.TarInfo = TarInfo -# finished monkey patching. now to lift things out of our tarfile -# module into this scope so from/import behaves properly. - -for x in tarfile.__all__: - locals()[x] = getattr(tarfile, x) -del x diff --git a/snakeoil/version.py b/snakeoil/version.py deleted file mode 100644 index 4c48c5f..0000000 --- a/snakeoil/version.py +++ /dev/null @@ -1,39 +0,0 @@ -# Copyright: 2006 Marien Zwart <marienz@gentoo.org> -# License: GPL2 - - -"""Version information (tied to bzr).""" - -import os - -__version__ = '0.1_rc2' - -_ver = None - -def get_version(): - """@returns: a string describing the snakeoil version.""" - global _ver - if _ver is not None: - return _ver - - try: - from snakeoil.bzr_verinfo import version_info - except ImportError: - try: - from bzrlib import branch, errors - except ImportError: - ver = 'unknown (not from an sdist tarball, bzr unavailable)' - else: - try: - # Returns a (branch, relpath) tuple, ignore relpath. - b = branch.Branch.open_containing(os.path.realpath(__file__))[0] - except errors.NotBranchError: - ver = 'unknown (not from an sdist tarball, not a bzr branch)' - else: - ver = '%s:%s %s' % (b.nick, b.revno(), b.last_revision()) - else: - ver = '%(branch_nick)s:%(revno)s %(revision_id)s' % version_info - - _ver = 'snakeoil %s\n(bzr rev %s)' % (__version__, ver) - - return _ver diff --git a/snakeoil/weakrefs.py b/snakeoil/weakrefs.py deleted file mode 100644 index 16336be..0000000 --- a/snakeoil/weakrefs.py +++ /dev/null @@ -1,12 +0,0 @@ -# Copyright: 2006 Brian Harring <ferringb@gmail.com> -# License: GPL2 - -# Unused import -# pylint: disable-msg=W0611 - -try: - # No name in module - # pylint: disable-msg=E0611 - from snakeoil._caching import WeakValCache -except ImportError: - from weakref import WeakValueDictionary as WeakValCache diff --git a/snakeoil/xml/__init__.py b/snakeoil/xml/__init__.py deleted file mode 100644 index 4091f02..0000000 --- a/snakeoil/xml/__init__.py +++ /dev/null @@ -1,46 +0,0 @@ -# Copyright: 2006 Brian Harring <ferringb@gmail.com> -# License: GPL2 - -""" -indirection to load ElementTree -""" -# essentially... prefer cElementTree, then 2.5 bundled, then -# elementtree, then 2.5 bundled, then our own bundled - -# "No name etree in module xml", "Reimport cElementTree" -# pylint: disable-msg=E0611,W0404 - -gotit = True -try: - import cElementTree as etree -except ImportError: - gotit = False -if not gotit: - try: - from xml.etree import cElementTree as etree - gotit = True - except ImportError: - pass -if not gotit: - try: - from elementtree import ElementTree as etree - gotit = True - except ImportError: - pass -if not gotit: - try: - from xml.etree import ElementTree as etree - gotit = True - except ImportError: - pass - -if not gotit: - from snakeoil.xml import bundled_elementtree as etree -del gotit - -def escape(string): - """ - simple escaping of &, <, and > - """ - return string.replace("&", "&").replace("<", "<").replace(">", - ">") diff --git a/snakeoil/xml/bundled_elementtree.py b/snakeoil/xml/bundled_elementtree.py deleted file mode 100644 index 5d8b1d3..0000000 --- a/snakeoil/xml/bundled_elementtree.py +++ /dev/null @@ -1,1254 +0,0 @@ -# -# ElementTree -# $Id: ElementTree.py 2326 2005-03-17 07:45:21Z fredrik $ -# -# light-weight XML support for Python 1.5.2 and later. -# -# history: -# 2001-10-20 fl created (from various sources) -# 2001-11-01 fl return root from parse method -# 2002-02-16 fl sort attributes in lexical order -# 2002-04-06 fl TreeBuilder refactoring, added PythonDoc markup -# 2002-05-01 fl finished TreeBuilder refactoring -# 2002-07-14 fl added basic namespace support to ElementTree.write -# 2002-07-25 fl added QName attribute support -# 2002-10-20 fl fixed encoding in write -# 2002-11-24 fl changed default encoding to ascii; fixed attribute encoding -# 2002-11-27 fl accept file objects or file names for parse/write -# 2002-12-04 fl moved XMLTreeBuilder back to this module -# 2003-01-11 fl fixed entity encoding glitch for us-ascii -# 2003-02-13 fl added XML literal factory -# 2003-02-21 fl added ProcessingInstruction/PI factory -# 2003-05-11 fl added tostring/fromstring helpers -# 2003-05-26 fl added ElementPath support -# 2003-07-05 fl added makeelement factory method -# 2003-07-28 fl added more well-known namespace prefixes -# 2003-08-15 fl fixed typo in ElementTree.findtext (Thomas Dartsch) -# 2003-09-04 fl fall back on emulator if ElementPath is not installed -# 2003-10-31 fl markup updates -# 2003-11-15 fl fixed nested namespace bug -# 2004-03-28 fl added XMLID helper -# 2004-06-02 fl added default support to findtext -# 2004-06-08 fl fixed encoding of non-ascii element/attribute names -# 2004-08-23 fl take advantage of post-2.1 expat features -# 2005-02-01 fl added iterparse implementation -# 2005-03-02 fl fixed iterparse support for pre-2.2 versions -# -# Copyright (c) 1999-2005 by Fredrik Lundh. All rights reserved. -# -# fredrik@pythonware.com -# http://www.pythonware.com -# -# -------------------------------------------------------------------- -# The ElementTree toolkit is -# -# Copyright (c) 1999-2005 by Fredrik Lundh -# -# By obtaining, using, and/or copying this software and/or its -# associated documentation, you agree that you have read, understood, -# and will comply with the following terms and conditions: -# -# Permission to use, copy, modify, and distribute this software and -# its associated documentation for any purpose and without fee is -# hereby granted, provided that the above copyright notice appears in -# all copies, and that both that copyright notice and this permission -# notice appear in supporting documentation, and that the name of -# Secret Labs AB or the author not be used in advertising or publicity -# pertaining to distribution of the software without specific, written -# prior permission. -# -# SECRET LABS AB AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD -# TO THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANT- -# ABILITY AND FITNESS. IN NO EVENT SHALL SECRET LABS AB OR THE AUTHOR -# BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY -# DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, -# WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS -# ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE -# OF THIS SOFTWARE. -# -------------------------------------------------------------------- - -__all__ = [ - # public symbols - "Comment", - "dump", - "Element", "ElementTree", - "fromstring", - "iselement", "iterparse", - "parse", - "PI", "ProcessingInstruction", - "QName", - "SubElement", - "tostring", - "TreeBuilder", - "VERSION", "XML", - "XMLTreeBuilder", - ] - -## -# The <b>Element</b> type is a flexible container object, designed to -# store hierarchical data structures in memory. The type can be -# described as a cross between a list and a dictionary. -# <p> -# Each element has a number of properties associated with it: -# <ul> -# <li>a <i>tag</i>. This is a string identifying what kind of data -# this element represents (the element type, in other words).</li> -# <li>a number of <i>attributes</i>, stored in a Python dictionary.</li> -# <li>a <i>text</i> string.</li> -# <li>an optional <i>tail</i> string.</li> -# <li>a number of <i>child elements</i>, stored in a Python sequence</li> -# </ul> -# -# To create an element instance, use the {@link #Element} or {@link -# #SubElement} factory functions. -# <p> -# The {@link #ElementTree} class can be used to wrap an element -# structure, and convert it from and to XML. -## - -import string, sys, re - -class _SimpleElementPath: - # emulate pre-1.2 find/findtext/findall behaviour - def find(self, element, tag): - for elem in element: - if elem.tag == tag: - return elem - return None - def findtext(self, element, tag, default=None): - for elem in element: - if elem.tag == tag: - return elem.text or "" - return default - def findall(self, element, tag): - if tag[:3] == ".//": - return element.getiterator(tag[3:]) - result = [] - for elem in element: - if elem.tag == tag: - result.append(elem) - return result - -try: - import ElementPath -except ImportError: - # FIXME: issue warning in this case? - ElementPath = _SimpleElementPath() - -# TODO: add support for custom namespace resolvers/default namespaces -# TODO: add improved support for incremental parsing - -VERSION = "1.2.6" - -## -# Internal element class. This class defines the Element interface, -# and provides a reference implementation of this interface. -# <p> -# You should not create instances of this class directly. Use the -# appropriate factory functions instead, such as {@link #Element} -# and {@link #SubElement}. -# -# @see Element -# @see SubElement -# @see Comment -# @see ProcessingInstruction - -class _ElementInterface: - # <tag attrib>text<child/>...</tag>tail - - ## - # (Attribute) Element tag. - - tag = None - - ## - # (Attribute) Element attribute dictionary. Where possible, use - # {@link #_ElementInterface.get}, - # {@link #_ElementInterface.set}, - # {@link #_ElementInterface.keys}, and - # {@link #_ElementInterface.items} to access - # element attributes. - - attrib = None - - ## - # (Attribute) Text before first subelement. This is either a - # string or the value None, if there was no text. - - text = None - - ## - # (Attribute) Text after this element's end tag, but before the - # next sibling element's start tag. This is either a string or - # the value None, if there was no text. - - tail = None # text after end tag, if any - - def __init__(self, tag, attrib): - self.tag = tag - self.attrib = attrib - self._children = [] - - def __repr__(self): - return "<Element %s at %x>" % (self.tag, id(self)) - - ## - # Creates a new element object of the same type as this element. - # - # @param tag Element tag. - # @param attrib Element attributes, given as a dictionary. - # @return A new element instance. - - def makeelement(self, tag, attrib): - return Element(tag, attrib) - - ## - # Returns the number of subelements. - # - # @return The number of subelements. - - def __len__(self): - return len(self._children) - - ## - # Returns the given subelement. - # - # @param index What subelement to return. - # @return The given subelement. - # @exception IndexError If the given element does not exist. - - def __getitem__(self, index): - return self._children[index] - - ## - # Replaces the given subelement. - # - # @param index What subelement to replace. - # @param element The new element value. - # @exception IndexError If the given element does not exist. - # @exception AssertionError If element is not a valid object. - - def __setitem__(self, index, element): - assert iselement(element) - self._children[index] = element - - ## - # Deletes the given subelement. - # - # @param index What subelement to delete. - # @exception IndexError If the given element does not exist. - - def __delitem__(self, index): - del self._children[index] - - ## - # Returns a list containing subelements in the given range. - # - # @param start The first subelement to return. - # @param stop The first subelement that shouldn't be returned. - # @return A sequence object containing subelements. - - def __getslice__(self, start, stop): - return self._children[start:stop] - - ## - # Replaces a number of subelements with elements from a sequence. - # - # @param start The first subelement to replace. - # @param stop The first subelement that shouldn't be replaced. - # @param elements A sequence object with zero or more elements. - # @exception AssertionError If a sequence member is not a valid object. - - def __setslice__(self, start, stop, elements): - for element in elements: - assert iselement(element) - self._children[start:stop] = list(elements) - - ## - # Deletes a number of subelements. - # - # @param start The first subelement to delete. - # @param stop The first subelement to leave in there. - - def __delslice__(self, start, stop): - del self._children[start:stop] - - ## - # Adds a subelement to the end of this element. - # - # @param element The element to add. - # @exception AssertionError If a sequence member is not a valid object. - - def append(self, element): - assert iselement(element) - self._children.append(element) - - ## - # Inserts a subelement at the given position in this element. - # - # @param index Where to insert the new subelement. - # @exception AssertionError If the element is not a valid object. - - def insert(self, index, element): - assert iselement(element) - self._children.insert(index, element) - - ## - # Removes a matching subelement. Unlike the <b>find</b> methods, - # this method compares elements based on identity, not on tag - # value or contents. - # - # @param element What element to remove. - # @exception ValueError If a matching element could not be found. - # @exception AssertionError If the element is not a valid object. - - def remove(self, element): - assert iselement(element) - self._children.remove(element) - - ## - # Returns all subelements. The elements are returned in document - # order. - # - # @return A list of subelements. - # @defreturn list of Element instances - - def getchildren(self): - return self._children - - ## - # Finds the first matching subelement, by tag name or path. - # - # @param path What element to look for. - # @return The first matching element, or None if no element was found. - # @defreturn Element or None - - def find(self, path): - return ElementPath.find(self, path) - - ## - # Finds text for the first matching subelement, by tag name or path. - # - # @param path What element to look for. - # @param default What to return if the element was not found. - # @return The text content of the first matching element, or the - # default value no element was found. Note that if the element - # has is found, but has no text content, this method returns an - # empty string. - # @defreturn string - - def findtext(self, path, default=None): - return ElementPath.findtext(self, path, default) - - ## - # Finds all matching subelements, by tag name or path. - # - # @param path What element to look for. - # @return A list or iterator containing all matching elements, - # in document order. - # @defreturn list of Element instances - - def findall(self, path): - return ElementPath.findall(self, path) - - ## - # Resets an element. This function removes all subelements, clears - # all attributes, and sets the text and tail attributes to None. - - def clear(self): - self.attrib.clear() - self._children = [] - self.text = self.tail = None - - ## - # Gets an element attribute. - # - # @param key What attribute to look for. - # @param default What to return if the attribute was not found. - # @return The attribute value, or the default value, if the - # attribute was not found. - # @defreturn string or None - - def get(self, key, default=None): - return self.attrib.get(key, default) - - ## - # Sets an element attribute. - # - # @param key What attribute to set. - # @param value The attribute value. - - def set(self, key, value): - self.attrib[key] = value - - ## - # Gets a list of attribute names. The names are returned in an - # arbitrary order (just like for an ordinary Python dictionary). - # - # @return A list of element attribute names. - # @defreturn list of strings - - def keys(self): - return self.attrib.keys() - - ## - # Gets element attributes, as a sequence. The attributes are - # returned in an arbitrary order. - # - # @return A list of (name, value) tuples for all attributes. - # @defreturn list of (string, string) tuples - - def items(self): - return self.attrib.items() - - ## - # Creates a tree iterator. The iterator loops over this element - # and all subelements, in document order, and returns all elements - # with a matching tag. - # <p> - # If the tree structure is modified during iteration, the result - # is undefined. - # - # @param tag What tags to look for (default is to return all elements). - # @return A list or iterator containing all the matching elements. - # @defreturn list or iterator - - def getiterator(self, tag=None): - nodes = [] - if tag == "*": - tag = None - if tag is None or self.tag == tag: - nodes.append(self) - for node in self._children: - nodes.extend(node.getiterator(tag)) - return nodes - -# compatibility -_Element = _ElementInterface - -## -# Element factory. This function returns an object implementing the -# standard Element interface. The exact class or type of that object -# is implementation dependent, but it will always be compatible with -# the {@link #_ElementInterface} class in this module. -# <p> -# The element name, attribute names, and attribute values can be -# either 8-bit ASCII strings or Unicode strings. -# -# @param tag The element name. -# @param attrib An optional dictionary, containing element attributes. -# @param **extra Additional attributes, given as keyword arguments. -# @return An element instance. -# @defreturn Element - -def Element(tag, attrib={}, **extra): - attrib = attrib.copy() - attrib.update(extra) - return _ElementInterface(tag, attrib) - -## -# Subelement factory. This function creates an element instance, and -# appends it to an existing element. -# <p> -# The element name, attribute names, and attribute values can be -# either 8-bit ASCII strings or Unicode strings. -# -# @param parent The parent element. -# @param tag The subelement name. -# @param attrib An optional dictionary, containing element attributes. -# @param **extra Additional attributes, given as keyword arguments. -# @return An element instance. -# @defreturn Element - -def SubElement(parent, tag, attrib={}, **extra): - attrib = attrib.copy() - attrib.update(extra) - element = parent.makeelement(tag, attrib) - parent.append(element) - return element - -## -# Comment element factory. This factory function creates a special -# element that will be serialized as an XML comment. -# <p> -# The comment string can be either an 8-bit ASCII string or a Unicode -# string. -# -# @param text A string containing the comment string. -# @return An element instance, representing a comment. -# @defreturn Element - -def Comment(text=None): - element = Element(Comment) - element.text = text - return element - -## -# PI element factory. This factory function creates a special element -# that will be serialized as an XML processing instruction. -# -# @param target A string containing the PI target. -# @param text A string containing the PI contents, if any. -# @return An element instance, representing a PI. -# @defreturn Element - -def ProcessingInstruction(target, text=None): - element = Element(ProcessingInstruction) - element.text = target - if text: - element.text = element.text + " " + text - return element - -PI = ProcessingInstruction - -## -# QName wrapper. This can be used to wrap a QName attribute value, in -# order to get proper namespace handling on output. -# -# @param text A string containing the QName value, in the form {uri}local, -# or, if the tag argument is given, the URI part of a QName. -# @param tag Optional tag. If given, the first argument is interpreted as -# an URI, and this argument is interpreted as a local name. -# @return An opaque object, representing the QName. - -class QName: - def __init__(self, text_or_uri, tag=None): - if tag: - text_or_uri = "{%s}%s" % (text_or_uri, tag) - self.text = text_or_uri - def __str__(self): - return self.text - def __hash__(self): - return hash(self.text) - def __cmp__(self, other): - if isinstance(other, QName): - return cmp(self.text, other.text) - return cmp(self.text, other) - -## -# ElementTree wrapper class. This class represents an entire element -# hierarchy, and adds some extra support for serialization to and from -# standard XML. -# -# @param element Optional root element. -# @keyparam file Optional file handle or name. If given, the -# tree is initialized with the contents of this XML file. - -class ElementTree: - - def __init__(self, element=None, file=None): - assert element is None or iselement(element) - self._root = element # first node - if file: - self.parse(file) - - ## - # Gets the root element for this tree. - # - # @return An element instance. - # @defreturn Element - - def getroot(self): - return self._root - - ## - # Replaces the root element for this tree. This discards the - # current contents of the tree, and replaces it with the given - # element. Use with care. - # - # @param element An element instance. - - def _setroot(self, element): - assert iselement(element) - self._root = element - - ## - # Loads an external XML document into this element tree. - # - # @param source A file name or file object. - # @param parser An optional parser instance. If not given, the - # standard {@link XMLTreeBuilder} parser is used. - # @return The document root element. - # @defreturn Element - - def parse(self, source, parser=None): - if not hasattr(source, "read"): - source = open(source, "rb") - if not parser: - parser = XMLTreeBuilder() - while 1: - data = source.read(32768) - if not data: - break - parser.feed(data) - self._root = parser.close() - return self._root - - ## - # Creates a tree iterator for the root element. The iterator loops - # over all elements in this tree, in document order. - # - # @param tag What tags to look for (default is to return all elements) - # @return An iterator. - # @defreturn iterator - - def getiterator(self, tag=None): - assert self._root is not None - return self._root.getiterator(tag) - - ## - # Finds the first toplevel element with given tag. - # Same as getroot().find(path). - # - # @param path What element to look for. - # @return The first matching element, or None if no element was found. - # @defreturn Element or None - - def find(self, path): - assert self._root is not None - if path[:1] == "/": - path = "." + path - return self._root.find(path) - - ## - # Finds the element text for the first toplevel element with given - # tag. Same as getroot().findtext(path). - # - # @param path What toplevel element to look for. - # @param default What to return if the element was not found. - # @return The text content of the first matching element, or the - # default value no element was found. Note that if the element - # has is found, but has no text content, this method returns an - # empty string. - # @defreturn string - - def findtext(self, path, default=None): - assert self._root is not None - if path[:1] == "/": - path = "." + path - return self._root.findtext(path, default) - - ## - # Finds all toplevel elements with the given tag. - # Same as getroot().findall(path). - # - # @param path What element to look for. - # @return A list or iterator containing all matching elements, - # in document order. - # @defreturn list of Element instances - - def findall(self, path): - assert self._root is not None - if path[:1] == "/": - path = "." + path - return self._root.findall(path) - - ## - # Writes the element tree to a file, as XML. - # - # @param file A file name, or a file object opened for writing. - # @param encoding Optional output encoding (default is US-ASCII). - - def write(self, file, encoding="us-ascii"): - assert self._root is not None - if not hasattr(file, "write"): - file = open(file, "wb") - if not encoding: - encoding = "us-ascii" - elif encoding != "utf-8" and encoding != "us-ascii": - file.write("<?xml version='1.0' encoding='%s'?>\n" % encoding) - self._write(file, self._root, encoding, {}) - - def _write(self, file, node, encoding, namespaces): - # write XML to file - tag = node.tag - if tag is Comment: - file.write("<!-- %s -->" % _escape_cdata(node.text, encoding)) - elif tag is ProcessingInstruction: - file.write("<?%s?>" % _escape_cdata(node.text, encoding)) - else: - items = node.items() - xmlns_items = [] # new namespaces in this scope - try: - if isinstance(tag, QName) or tag[:1] == "{": - tag, xmlns = fixtag(tag, namespaces) - if xmlns: xmlns_items.append(xmlns) - except TypeError: - _raise_serialization_error(tag) - file.write("<" + _encode(tag, encoding)) - if items or xmlns_items: - items.sort() # lexical order - for k, v in items: - try: - if isinstance(k, QName) or k[:1] == "{": - k, xmlns = fixtag(k, namespaces) - if xmlns: xmlns_items.append(xmlns) - except TypeError: - _raise_serialization_error(k) - try: - if isinstance(v, QName): - v, xmlns = fixtag(v, namespaces) - if xmlns: xmlns_items.append(xmlns) - except TypeError: - _raise_serialization_error(v) - file.write(" %s=\"%s\"" % (_encode(k, encoding), - _escape_attrib(v, encoding))) - for k, v in xmlns_items: - file.write(" %s=\"%s\"" % (_encode(k, encoding), - _escape_attrib(v, encoding))) - if node.text or len(node): - file.write(">") - if node.text: - file.write(_escape_cdata(node.text, encoding)) - for n in node: - self._write(file, n, encoding, namespaces) - file.write("</" + _encode(tag, encoding) + ">") - else: - file.write(" />") - for k, v in xmlns_items: - del namespaces[v] - if node.tail: - file.write(_escape_cdata(node.tail, encoding)) - -# -------------------------------------------------------------------- -# helpers - -## -# Checks if an object appears to be a valid element object. -# -# @param An element instance. -# @return A true value if this is an element object. -# @defreturn flag - -def iselement(element): - # FIXME: not sure about this; might be a better idea to look - # for tag/attrib/text attributes - return isinstance(element, _ElementInterface) or hasattr(element, "tag") - -## -# Writes an element tree or element structure to sys.stdout. This -# function should be used for debugging only. -# <p> -# The exact output format is implementation dependent. In this -# version, it's written as an ordinary XML file. -# -# @param elem An element tree or an individual element. - -def dump(elem): - # debugging - if not isinstance(elem, ElementTree): - elem = ElementTree(elem) - elem.write(sys.stdout) - tail = elem.getroot().tail - if not tail or tail[-1] != "\n": - sys.stdout.write("\n") - -def _encode(s, encoding): - try: - return s.encode(encoding) - except AttributeError: - return s # 1.5.2: assume the string uses the right encoding - -if sys.version[:3] == "1.5": - _escape = re.compile(r"[&<>\"\x80-\xff]+") # 1.5.2 -else: - _escape = re.compile(eval(r'u"[&<>\"\u0080-\uffff]+"')) - -_escape_map = { - "&": "&", - "<": "<", - ">": ">", - '"': """, -} - -_namespace_map = { - # "well-known" namespace prefixes - "http://www.w3.org/XML/1998/namespace": "xml", - "http://www.w3.org/1999/xhtml": "html", - "http://www.w3.org/1999/02/22-rdf-syntax-ns#": "rdf", - "http://schemas.xmlsoap.org/wsdl/": "wsdl", -} - -def _raise_serialization_error(text): - raise TypeError( - "cannot serialize %r (type %s)" % (text, type(text).__name__) - ) - -def _encode_entity(text, pattern=_escape): - # map reserved and non-ascii characters to numerical entities - def escape_entities(m, map=_escape_map): - out = [] - append = out.append - for char in m.group(): - text = map.get(char) - if text is None: - text = "&#%d;" % ord(char) - append(text) - return string.join(out, "") - try: - return _encode(pattern.sub(escape_entities, text), "ascii") - except TypeError: - _raise_serialization_error(text) - -# -# the following functions assume an ascii-compatible encoding -# (or "utf-16") - -def _escape_cdata(text, encoding=None, replace=string.replace): - # escape character data - try: - if encoding: - try: - text = _encode(text, encoding) - except UnicodeError: - return _encode_entity(text) - text = replace(text, "&", "&") - text = replace(text, "<", "<") - text = replace(text, ">", ">") - return text - except (TypeError, AttributeError): - _raise_serialization_error(text) - -def _escape_attrib(text, encoding=None, replace=string.replace): - # escape attribute value - try: - if encoding: - try: - text = _encode(text, encoding) - except UnicodeError: - return _encode_entity(text) - text = replace(text, "&", "&") - text = replace(text, "'", "'") # FIXME: overkill - text = replace(text, "\"", """) - text = replace(text, "<", "<") - text = replace(text, ">", ">") - return text - except (TypeError, AttributeError): - _raise_serialization_error(text) - -def fixtag(tag, namespaces): - # given a decorated tag (of the form {uri}tag), return prefixed - # tag and namespace declaration, if any - if isinstance(tag, QName): - tag = tag.text - namespace_uri, tag = string.split(tag[1:], "}", 1) - prefix = namespaces.get(namespace_uri) - if prefix is None: - prefix = _namespace_map.get(namespace_uri) - if prefix is None: - prefix = "ns%d" % len(namespaces) - namespaces[namespace_uri] = prefix - if prefix == "xml": - xmlns = None - else: - xmlns = ("xmlns:%s" % prefix, namespace_uri) - else: - xmlns = None - return "%s:%s" % (prefix, tag), xmlns - -## -# Parses an XML document into an element tree. -# -# @param source A filename or file object containing XML data. -# @param parser An optional parser instance. If not given, the -# standard {@link XMLTreeBuilder} parser is used. -# @return An ElementTree instance - -def parse(source, parser=None): - tree = ElementTree() - tree.parse(source, parser) - return tree - -## -# Parses an XML document into an element tree incrementally, and reports -# what's going on to the user. -# -# @param source A filename or file object containing XML data. -# @param events A list of events to report back. If omitted, only "end" -# events are reported. -# @return A (event, elem) iterator. - -class iterparse: - - def __init__(self, source, events=None): - if not hasattr(source, "read"): - source = open(source, "rb") - self._file = source - self._events = [] - self._index = 0 - self.root = self._root = None - self._parser = XMLTreeBuilder() - # wire up the parser for event reporting - parser = self._parser._parser - append = self._events.append - if events is None: - events = ["end"] - for event in events: - if event == "start": - try: - parser.ordered_attributes = 1 - parser.specified_attributes = 1 - def handler(tag, attrib_in, event=event, append=append, - start=self._parser._start_list): - append((event, start(tag, attrib_in))) - parser.StartElementHandler = handler - except AttributeError: - def handler(tag, attrib_in, event=event, append=append, - start=self._parser._start): - append((event, start(tag, attrib_in))) - parser.StartElementHandler = handler - elif event == "end": - def handler(tag, event=event, append=append, - end=self._parser._end): - append((event, end(tag))) - parser.EndElementHandler = handler - elif event == "start-ns": - def handler(prefix, uri, event=event, append=append): - try: - uri = _encode(uri, "ascii") - except UnicodeError: - pass - append((event, (prefix or "", uri))) - parser.StartNamespaceDeclHandler = handler - elif event == "end-ns": - def handler(prefix, event=event, append=append): - append((event, None)) - parser.EndNamespaceDeclHandler = handler - - def next(self): - while 1: - try: - item = self._events[self._index] - except IndexError: - if self._parser is None: - self.root = self._root - try: - raise StopIteration - except NameError: - raise IndexError - # load event buffer - del self._events[:] - self._index = 0 - data = self._file.read(16384) - if data: - self._parser.feed(data) - else: - self._root = self._parser.close() - self._parser = None - else: - self._index = self._index + 1 - return item - - try: - iter - def __iter__(self): - return self - except NameError: - def __getitem__(self, index): - return self.next() - -## -# Parses an XML document from a string constant. This function can -# be used to embed "XML literals" in Python code. -# -# @param source A string containing XML data. -# @return An Element instance. -# @defreturn Element - -def XML(text): - parser = XMLTreeBuilder() - parser.feed(text) - return parser.close() - -## -# Parses an XML document from a string constant, and also returns -# a dictionary which maps from element id:s to elements. -# -# @param source A string containing XML data. -# @return A tuple containing an Element instance and a dictionary. -# @defreturn (Element, dictionary) - -def XMLID(text): - parser = XMLTreeBuilder() - parser.feed(text) - tree = parser.close() - ids = {} - for elem in tree.getiterator(): - id = elem.get("id") - if id: - ids[id] = elem - return tree, ids - -## -# Parses an XML document from a string constant. Same as {@link #XML}. -# -# @def fromstring(text) -# @param source A string containing XML data. -# @return An Element instance. -# @defreturn Element - -fromstring = XML - -## -# Generates a string representation of an XML element, including all -# subelements. -# -# @param element An Element instance. -# @return An encoded string containing the XML data. -# @defreturn string - -def tostring(element, encoding=None): - class dummy: - pass - data = [] - file = dummy() - file.write = data.append - ElementTree(element).write(file, encoding) - return string.join(data, "") - -## -# Generic element structure builder. This builder converts a sequence -# of {@link #TreeBuilder.start}, {@link #TreeBuilder.data}, and {@link -# #TreeBuilder.end} method calls to a well-formed element structure. -# <p> -# You can use this class to build an element structure using a custom XML -# parser, or a parser for some other XML-like format. -# -# @param element_factory Optional element factory. This factory -# is called to create new Element instances, as necessary. - -class TreeBuilder: - - def __init__(self, element_factory=None): - self._data = [] # data collector - self._elem = [] # element stack - self._last = None # last element - self._tail = None # true if we're after an end tag - if element_factory is None: - element_factory = _ElementInterface - self._factory = element_factory - - ## - # Flushes the parser buffers, and returns the toplevel documen - # element. - # - # @return An Element instance. - # @defreturn Element - - def close(self): - assert len(self._elem) == 0, "missing end tags" - assert self._last is not None, "missing toplevel element" - return self._last - - def _flush(self): - if self._data: - if self._last is not None: - text = string.join(self._data, "") - if self._tail: - assert self._last.tail is None, "internal error (tail)" - self._last.tail = text - else: - assert self._last.text is None, "internal error (text)" - self._last.text = text - self._data = [] - - ## - # Adds text to the current element. - # - # @param data A string. This should be either an 8-bit string - # containing ASCII text, or a Unicode string. - - def data(self, data): - self._data.append(data) - - ## - # Opens a new element. - # - # @param tag The element name. - # @param attrib A dictionary containing element attributes. - # @return The opened element. - # @defreturn Element - - def start(self, tag, attrs): - self._flush() - self._last = elem = self._factory(tag, attrs) - if self._elem: - self._elem[-1].append(elem) - self._elem.append(elem) - self._tail = 0 - return elem - - ## - # Closes the current element. - # - # @param tag The element name. - # @return The closed element. - # @defreturn Element - - def end(self, tag): - self._flush() - self._last = self._elem.pop() - assert self._last.tag == tag,\ - "end tag mismatch (expected %s, got %s)" % ( - self._last.tag, tag) - self._tail = 1 - return self._last - -## -# Element structure builder for XML source data, based on the -# <b>expat</b> parser. -# -# @keyparam target Target object. If omitted, the builder uses an -# instance of the standard {@link #TreeBuilder} class. -# @keyparam html Predefine HTML entities. This flag is not supported -# by the current implementation. -# @see #ElementTree -# @see #TreeBuilder - -class XMLTreeBuilder: - - def __init__(self, html=0, target=None): - try: - from xml.parsers import expat - except ImportError: - raise ImportError( - "No module named expat; use SimpleXMLTreeBuilder instead" - ) - self._parser = parser = expat.ParserCreate(None, "}") - if target is None: - target = TreeBuilder() - self._target = target - self._names = {} # name memo cache - # callbacks - parser.DefaultHandlerExpand = self._default - parser.StartElementHandler = self._start - parser.EndElementHandler = self._end - parser.CharacterDataHandler = self._data - # let expat do the buffering, if supported - try: - self._parser.buffer_text = 1 - except AttributeError: - pass - # use new-style attribute handling, if supported - try: - self._parser.ordered_attributes = 1 - self._parser.specified_attributes = 1 - parser.StartElementHandler = self._start_list - except AttributeError: - pass - encoding = None - if not parser.returns_unicode: - encoding = "utf-8" - # target.xml(encoding, None) - self._doctype = None - self.entity = {} - - def _fixtext(self, text): - # convert text string to ascii, if possible - try: - return _encode(text, "ascii") - except UnicodeError: - return text - - def _fixname(self, key): - # expand qname, and convert name string to ascii, if possible - try: - name = self._names[key] - except KeyError: - name = key - if "}" in name: - name = "{" + name - self._names[key] = name = self._fixtext(name) - return name - - def _start(self, tag, attrib_in): - fixname = self._fixname - tag = fixname(tag) - attrib = {} - for key, value in attrib_in.items(): - attrib[fixname(key)] = self._fixtext(value) - return self._target.start(tag, attrib) - - def _start_list(self, tag, attrib_in): - fixname = self._fixname - tag = fixname(tag) - attrib = {} - if attrib_in: - for i in xrange(0, len(attrib_in), 2): - attrib[fixname(attrib_in[i])] = self._fixtext(attrib_in[i+1]) - return self._target.start(tag, attrib) - - def _data(self, text): - return self._target.data(self._fixtext(text)) - - def _end(self, tag): - return self._target.end(self._fixname(tag)) - - def _default(self, text): - prefix = text[:1] - if prefix == "&": - # deal with undefined entities - try: - self._target.data(self.entity[text[1:-1]]) - except KeyError: - from xml.parsers import expat - raise expat.error( - "undefined entity %s: line %d, column %d" % - (text, self._parser.ErrorLineNumber, - self._parser.ErrorColumnNumber) - ) - elif prefix == "<" and text[:9] == "<!DOCTYPE": - self._doctype = [] # inside a doctype declaration - elif self._doctype is not None: - # parse doctype contents - if prefix == ">": - self._doctype = None - return - text = string.strip(text) - if not text: - return - self._doctype.append(text) - n = len(self._doctype) - if n > 2: - type = self._doctype[1] - if type == "PUBLIC" and n == 4: - name, type, pubid, system = self._doctype - elif type == "SYSTEM" and n == 3: - name, type, system = self._doctype - pubid = None - else: - return - if pubid: - pubid = pubid[1:-1] - self.doctype(name, pubid, system[1:-1]) - self._doctype = None - - ## - # Handles a doctype declaration. - # - # @param name Doctype name. - # @param pubid Public identifier. - # @param system System identifier. - - def doctype(self, name, pubid, system): - pass - - ## - # Feeds data to the parser. - # - # @param data Encoded data. - - def feed(self, data): - self._parser.Parse(data, 0) - - ## - # Finishes feeding data to the parser. - # - # @return An element structure. - # @defreturn Element - - def close(self): - self._parser.Parse("", 1) # end of data - tree = self._target.close() - del self._target, self._parser # get rid of circular references - return tree |