506 lines
17 KiB
Python
506 lines
17 KiB
Python
# -*- coding: utf-8 -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8
|
|
|
|
"""Fonctions de base
|
|
"""
|
|
|
|
__all__ = ('myself', 'mydir', 'myname',
|
|
'Undef',
|
|
'updated', 'updatem', 'odict', 'ncdict',
|
|
'required', 'nlistf', 'snlistf',
|
|
'isnum', 'isflt', 'isbool', 'isbytes', 'isunicode', 'isstr',
|
|
'isseq', 'seqof', 'listof', 'firstof',
|
|
'all_matches', 'one_match',
|
|
'strip_nl', 'norm_nl',
|
|
'make_getter', 'make_setter', 'make_deleter', 'make_prop',
|
|
'getattrs', 'setattrs', 'delattrs', 'make_delegate',
|
|
)
|
|
|
|
import os, sys, re
|
|
from os import path
|
|
from types import IntType, LongType, FloatType, BooleanType
|
|
from types import StringType, UnicodeType, StringTypes
|
|
|
|
# Enlever le répertoire courant de sys.path
|
|
try: from nutools_config import CLEAN_SYSPATH
|
|
except ImportError: CLEAN_SYSPATH = True
|
|
if CLEAN_SYSPATH:
|
|
def __clean_syspath():
|
|
cwd = os.getcwd()
|
|
sys.path = filter(lambda p: p not in ('', '.', cwd), sys.path)
|
|
__clean_syspath()
|
|
del __clean_syspath
|
|
|
|
# emplacement du script courant
|
|
myself = path.abspath(sys.argv[0])
|
|
mydir, myname = path.split(myself)
|
|
|
|
# Fonctions diverses
|
|
|
|
_undef = object()
|
|
class Undef(object):
|
|
def sa(self, value, kw, name, default=_undef):
|
|
"""si value est Undef, récupérer la valeur avec le nom court name dans kw
|
|
"""
|
|
if default is _undef: default = self
|
|
if value is self and name is not None: value = kw.pop(name, self)
|
|
if value is self: value = default
|
|
return value
|
|
|
|
def __nonzero__(self):
|
|
return False
|
|
def __len__(self):
|
|
return 0
|
|
def __lt__(self, other):
|
|
if other: return True
|
|
else: return False
|
|
def __le__(self, other):
|
|
return True
|
|
def __eq__(self, other):
|
|
if other: return False
|
|
else: return True
|
|
def __ne__(self, other):
|
|
if other: return True
|
|
else: return False
|
|
def __gt__(self, other):
|
|
if other: return False
|
|
else: return True
|
|
def __ge__(self, other):
|
|
return True
|
|
def __repr__(self):
|
|
return 'Undef'
|
|
def __call__(self):
|
|
"""créer une nouvelle instance de Undef, pratique pour un module qui veut
|
|
utiliser sa propre valeur différente de la valeur globale
|
|
"""
|
|
return self.__class__()
|
|
Undef = Undef()
|
|
|
|
def updated(dict=None, **kw):
|
|
"""Retourner une copie de dict mise à jour avec les éléments de kw
|
|
"""
|
|
if dict is None: dict = {}
|
|
else: dict = dict.copy()
|
|
dict.update(kw)
|
|
return dict
|
|
|
|
def updatem(dict=None, *dicts):
|
|
"""Mets à jour dict avec les dictionnaires dicts, et retourner dict
|
|
"""
|
|
if dict is None: dict = {}
|
|
for kw in dicts: dict.update(kw)
|
|
return dict
|
|
|
|
class odict(dict):
|
|
"""dictionnaire qui supporte aussi l'accès aux propriétés comme des attributs
|
|
"""
|
|
def __init__(self, dict=None, **kw):
|
|
super(odict, self).__init__(**updated(dict, **kw))
|
|
|
|
def __getattr__(self, name):
|
|
try: return self[name]
|
|
except KeyError: raise AttributeError(name)
|
|
|
|
def __setattr__(self, name, value):
|
|
if name in self.__dict__: self.__dict__[name] = value
|
|
else: self[name] = value
|
|
|
|
def __delattr__(self, name):
|
|
try: del self[name]
|
|
except KeyError: raise AttributeError(name)
|
|
|
|
def copy(self):
|
|
return self.__class__(super(odict, self).copy())
|
|
|
|
_none = object()
|
|
class ncdict(odict):
|
|
"""dictionnaire dont les clés sont insensibles à la casse
|
|
"""
|
|
def __init__(self, dict=None, **kw):
|
|
super(ncdict, self).__init__(**updated(dict, **kw))
|
|
|
|
def __getitem__(self, key):
|
|
if isstr(key): key = key.lower()
|
|
return super(ncdict, self).__getitem__(key)
|
|
|
|
def __setitem__(self, key, value):
|
|
if isstr(key): key = key.lower()
|
|
return super(ncdict, self).__setitem__(key, value)
|
|
|
|
def __delitem__(self, key):
|
|
if isstr(key): key = key.lower()
|
|
return super(ncdict, self).__delitem__(key)
|
|
|
|
def __getattr__(self, key):
|
|
if isstr(key): key = key.lower()
|
|
return super(ncdict, self).__getattr__(key)
|
|
|
|
def __setattr__(self, key, value):
|
|
if isstr(key): key = key.lower()
|
|
return super(ncdict, self).__setattr__(key, value)
|
|
|
|
def __delattr__(self, key):
|
|
if isstr(key): key = key.lower()
|
|
return super(ncdict, self).__delattr__(key)
|
|
|
|
def has_key(self, key):
|
|
if isstr(key): key = key.lower()
|
|
return super(ncdict, self).has_key(key)
|
|
|
|
def get(self, key, default=_none):
|
|
if isstr(key): key = key.lower()
|
|
if default is _none: return super(ncdict, self).get(key)
|
|
else: return super(ncdict, self).get(key, default)
|
|
|
|
def _itemprop(i, name):
|
|
def getter(self):
|
|
return self._values[i]
|
|
def setter(self, value):
|
|
validator = self.VALIDATORS.get(name, None)
|
|
if validator is not None: value = validator(value)
|
|
self._values[i] = value
|
|
return property(getter, setter)
|
|
|
|
def _fix_module(cls):
|
|
try: cls.__module__ = sys._getframe(1).f_globals.get('__name__', '__main__')
|
|
except (AttributeError, ValueError): pass
|
|
return cls
|
|
|
|
def required(validator, name=None):
|
|
if name is None: name = "The value"
|
|
def wrapper(value):
|
|
value = validator(value)
|
|
if value is None: raise ValueError("%s is required" % name)
|
|
else: return value
|
|
return wrapper
|
|
|
|
def nlistf(name, *attrs, **kw):
|
|
name = str(name)
|
|
# attributs
|
|
if len(attrs) == 1 and isstr(attrs[0]):
|
|
attrs = attrs[0].replace(',', ' ').split()
|
|
attrs = tuple(map(str, attrs))
|
|
# validateurs
|
|
validators = {}
|
|
for attr, validator in kw.iteritems():
|
|
if attr not in attrs:
|
|
raise ValueError("Invalid validator attribute: %s" % attr)
|
|
validators[attr] = validator
|
|
|
|
template = ["""class %(name)s(object):
|
|
__slots__ = ('_values')
|
|
ATTRS = None
|
|
VALIDATORS = None
|
|
def reset(self):
|
|
"Reinitialiser toutes les valeurs a None"
|
|
self._values = [None] * len(self.ATTRS)
|
|
return self
|
|
def replace(self, *values, **kw):
|
|
"Modifier des valeurs specifiques"
|
|
for i, attr in enumerate(self.ATTRS[:len(values)]): setattr(self, attr, values[i])
|
|
for attr, value in kw.iteritems(): setattr(self, attr, value)
|
|
return self
|
|
def init(self, *values, **kw):
|
|
"Modifier toutes les valeurs de cet objet. Les valeurs non specifiees recoivent None."
|
|
return self.reset().replace(*values, **kw)
|
|
def __init__(self, *values, **kw): self.init(*values, **kw)
|
|
def inito(self, o):
|
|
"Modifier toutes les valeurs de cet objet en les prenant depuis les attributs de l'objet o."
|
|
for attr in self.ATTRS: setattr(self, attr, getattr(o, attr, None))
|
|
def update(self, d):
|
|
"Mettre a jour le dictionnaire d avec les valeurs de cet objet"
|
|
for attr in self.ATTRS: d[attr] = getattr(self, attr)
|
|
def updateo(self, o):
|
|
"Mettre a jour les attributs de l'objet o avec les valeurs de cet objet."
|
|
for attr in self.ATTRS: setattr(o, attr, getattr(self, attr))
|
|
def asdict(self): return dict(zip(self.ATTRS, self._values))
|
|
def __repr__(self): return repr(self.asdict())
|
|
def __len__(self): return len(self._values)
|
|
def __getitem__(self, key): return self._values.__getitem__(key)
|
|
def __setitem__(self, key, value): self._values.__setitem__(key, value)
|
|
def __iter__(self): return self._values.__iter__()
|
|
def __contains__(self, item): return self._values.__contains__(item)"""]
|
|
for i, attr in enumerate(attrs):
|
|
template.append(" %s = itemprop(%i, '%s')" % (attr, i, attr))
|
|
template = "\n".join(template) % locals()
|
|
namespace = dict(itemprop=_itemprop)
|
|
try: exec template in namespace
|
|
except SyntaxError, e: raise SyntaxError('%s:\n%s' % (e.message, template))
|
|
|
|
cls = namespace[name]
|
|
cls.ATTRS = attrs
|
|
cls.VALIDATORS = validators
|
|
return _fix_module(cls)
|
|
|
|
def snlistf(base, name, *attrs, **kw):
|
|
name = str(name)
|
|
# attributs
|
|
if len(attrs) == 1 and isstr(attrs[0]):
|
|
attrs = attrs[0].replace(',', ' ').split()
|
|
attrs = tuple(map(str, attrs))
|
|
allattrs = base.ATTRS + attrs
|
|
# validateurs
|
|
validators = base.VALIDATORS.copy()
|
|
for attr, validator in kw.iteritems():
|
|
if attr not in allattrs:
|
|
raise ValueError("Invalid validator attribute: %s" % attr)
|
|
validators[attr] = validator
|
|
|
|
template = ["""class %(name)s(base):
|
|
__slots__ = ()
|
|
ATTRS = None
|
|
VALIDATORS = None"""]
|
|
basei = len(base.ATTRS)
|
|
for i, attr in enumerate(attrs):
|
|
template.append(" %s = itemprop(%i, '%s')" % (attr, basei + i, attr))
|
|
template = "\n".join(template) % locals()
|
|
namespace = dict(base=base, itemprop=_itemprop)
|
|
try: exec template in namespace
|
|
except SyntaxError, e: raise SyntaxError('%s:\n%s' % (e.message, template))
|
|
|
|
cls = namespace[name]
|
|
cls.ATTRS = allattrs
|
|
cls.VALIDATORS = validators
|
|
return _fix_module(cls)
|
|
|
|
def isnum(i):
|
|
"""Tester si i est une valeur numérique (int ou long)
|
|
"""
|
|
return type(i) in (IntType, LongType)
|
|
def isflt(f):
|
|
"""Tester si f est une valeur numérique flottante (float)
|
|
"""
|
|
return type(f) is FloatType
|
|
def isbool(b):
|
|
"""Tester si b est une valeur booléenne
|
|
"""
|
|
return type(b) is BooleanType
|
|
def isseq(t):
|
|
"""Tester si t est une séquence (list ou tuple)
|
|
"""
|
|
return isinstance(t, list) or isinstance(t, tuple)
|
|
def seqof(seq, ifNone=Undef, nocopy=False):
|
|
"""Retourner une séquence.
|
|
Si seq est une séquence, retourner une copie de l'objet si nocopy==False,
|
|
sinon l'objet lui-même.
|
|
Si seq==None: si ifNone est défini, retourner ifNone, sinon un tuple vide.
|
|
Sinon, retourner le tuple (seq,)
|
|
"""
|
|
if isseq(seq):
|
|
if nocopy: return seq
|
|
else: return seq[:]
|
|
elif seq is None:
|
|
if ifNone is Undef: return ()
|
|
else: return ifNone
|
|
else: return (seq,)
|
|
def listof(seq, ifNone=Undef):
|
|
"""Retourner une liste.
|
|
Si seq est une séquence, retourner la liste correspondante
|
|
Si seq==None: si ifNone est défini, retourner ifNone, sinon une liste vide.
|
|
Sinon, retourner la liste [seq]
|
|
"""
|
|
if seq is None:
|
|
if ifNone is Undef: return []
|
|
else: return ifNone
|
|
elif isseq(seq): return list(seq)
|
|
else: return [seq]
|
|
def firstof(seq):
|
|
"""Retourner le premier élément de la séquence.
|
|
Si seq n'est pas une séquence, retourner l'objet lui-même.
|
|
Si seq est une séquence vide, retourner None.
|
|
"""
|
|
if isseq(seq): return seq[0:1] and seq[0] or None
|
|
else: return seq
|
|
def isbytes(s):
|
|
"""Tester si s est une valeur chaine (str)
|
|
"""
|
|
return type(s) is StringType
|
|
def isunicode(s):
|
|
"""Tester si s est une valeur chaine (unicode)
|
|
"""
|
|
return type(s) is UnicodeType
|
|
def isstr(s):
|
|
"""Tester si s est une valeur chaine (str ou unicode)
|
|
"""
|
|
return type(s) in StringTypes
|
|
|
|
def all_matches(func, seq):
|
|
"""Tester si tous les éléments de seq sont matchés par la fonction func.
|
|
"""
|
|
for item in seqof(seq):
|
|
if not func(item): return False
|
|
return True
|
|
|
|
def one_match(func, seq):
|
|
"""Tester si au moins un des éléments de seq est matché par la fonction
|
|
func.
|
|
"""
|
|
for item in seqof(seq):
|
|
if func(item): return True
|
|
return False
|
|
|
|
def strip_nl(s):
|
|
"""Enlever le caractère de fin de ligne de s: soit \\n, soit \\r, soit \\r\\n
|
|
"""
|
|
if s is None: return None
|
|
elif s.endswith("\r\n"): s = s[: - 2]
|
|
elif s.endswith("\n"): s = s[: - 1]
|
|
elif s.endswith("\r"): s = s[: - 1]
|
|
return s
|
|
|
|
RE_NL = re.compile(r'(?:\r?\n|\r)')
|
|
def norm_nl(s, nl="\\n"):
|
|
"""Transformer tous les caractères de fin de ligne en \\n
|
|
"""
|
|
if s is None: return None
|
|
else: return RE_NL.sub(nl, s)
|
|
|
|
def make_getter(name):
|
|
return lambda self: getattr(self, name)
|
|
def make_setter(name, validator=None):
|
|
if validator is None:
|
|
return lambda self, value: setattr(self, name, value)
|
|
else:
|
|
return lambda self, value: setattr(self, name, validator(value))
|
|
|
|
def make_deleter(name):
|
|
return lambda self: delattr(self, name)
|
|
|
|
def make_prop(name, value=None, getter=True, setter=True, deleter=False, validator=None):
|
|
"""Retourne un tuple facilitant la création d'une propriété protégée par
|
|
des accesseurs.
|
|
|
|
Voici un exemple d'usage:
|
|
|
|
class C:
|
|
_name, name, get_name, set_name = make_prop('_name', 'Default value')
|
|
|
|
@return: (value, property, getter_func, setter_func, deleter_func)
|
|
"""
|
|
accessors = {}
|
|
if getter in (False, None): pass
|
|
elif getter is True: getter = make_getter(name)
|
|
if getter: accessors['fget'] = getter
|
|
if setter in (False, None): pass
|
|
elif setter is True: setter = make_setter(name, validator)
|
|
elif validator is not None:
|
|
_setter = setter
|
|
setter = lambda self, value: _setter(self, validator(value))
|
|
if setter: accessors['fset'] = setter
|
|
if deleter in (False, None): pass
|
|
elif deleter is True: deleter = make_deleter(name)
|
|
if deleter: accessors['fdel'] = deleter
|
|
result = [value, property(**accessors)]
|
|
if getter: result.append(accessors['fget'])
|
|
if setter: result.append(accessors['fset'])
|
|
if deleter: result.append(accessors['fdel'])
|
|
return tuple(result)
|
|
|
|
def __check_names(names):
|
|
if not names: raise AttributeError("The attribute name is required")
|
|
|
|
def getattrs(obj, names, strict=False):
|
|
u"""Soit un objet obj, et un nom de la forme "attr0.attr1....",
|
|
retourner l'objet obtenu avec l'expression obj.attr0.attr1....
|
|
|
|
@param strict: on requière que toute l'expression soit parcouru jusqu'à la
|
|
fin. Sinon, arrêter dès que le résultat de l'expression est None.
|
|
"""
|
|
if not names: return obj
|
|
if not isseq(names): names = names.split(".")
|
|
__check_names(names)
|
|
value = obj
|
|
for i in range(len(names)):
|
|
name = names[i]
|
|
if value is None:
|
|
if strict:
|
|
if i > 0: path = "obj." + ".".join(names[:i])
|
|
else: path = "None"
|
|
raise AttributeError("%s instance has no value '%s'" % (path, name))
|
|
else: break
|
|
value = getattr(value, name)
|
|
return value
|
|
|
|
def setattrs(obj, names, value):
|
|
u"""Soit un objet obj, et un nom de la forme "attr0.attr1....",
|
|
effectuer l'équivalent de l'opération:
|
|
|
|
obj.attr0.attr1.... = value
|
|
"""
|
|
if not isseq(names): names = names.split(".")
|
|
__check_names(names)
|
|
obj = getattrs(obj, names[:-1], True)
|
|
setattr(obj, names[-1], value)
|
|
|
|
def delattrs(obj, names):
|
|
u"""Soit un objet obj, et un nom de la forme "attr0.attr1....",
|
|
effectuer l'équivalent de l'opération:
|
|
|
|
del obj.attr0.attr1....
|
|
"""
|
|
if not isseq(names): names = names.split(".")
|
|
__check_names(names)
|
|
obj = getattrs(obj, names[:-1], True)
|
|
delattr(obj, names[-1])
|
|
|
|
def make_delegate(names, getter=True, setter=True, deleter=False):
|
|
if getter is True:
|
|
def getter(self):
|
|
return getattrs(self, names, True)
|
|
if setter is True:
|
|
def setter(self, value):
|
|
setattrs(self, names, value)
|
|
if deleter is True:
|
|
def deleter(self):
|
|
delattrs(self, names)
|
|
|
|
accessors = {}
|
|
if getter: accessors['fget'] = getter
|
|
if setter: accessors['fset'] = setter
|
|
if deleter: accessors['fdel'] = deleter
|
|
return property(**accessors)
|
|
|
|
def get__all__(module):
|
|
"""Retourner la valeur __all__ d'un module, ou la construire si cette
|
|
valeur n'est pas définie.
|
|
|
|
@rtype: tuple
|
|
"""
|
|
all = getattr(module, '__all__', None)
|
|
if all is None:
|
|
all = []
|
|
for key in module.__dict__.keys():
|
|
if key[0] != '_': all.append(key)
|
|
return tuple(all)
|
|
|
|
def import__module__(module_name, globals, locals=None, name=None):
|
|
"""Importer dans globals le module nommé module_name, en le nommant name.
|
|
|
|
Par défaut, name est le nom de base du module. par exemple, le module
|
|
"a.b.c" sera importé sous le nom "c"
|
|
"""
|
|
module = __import__(module_name, globals, locals)
|
|
basenames = module_name.split('.')
|
|
for basename in basenames[1:]:
|
|
module = getattr(module, basename)
|
|
|
|
if name is None: name = basenames[-1]
|
|
globals[name] = module
|
|
return [name]
|
|
|
|
def import__all__(module_name, globals, locals=None, *names):
|
|
"""Importer dans globals tous les objets du module nommé module_name
|
|
mentionnés dans names. Si names est vides, tous les objets sont importés
|
|
comme avec 'from module import *'
|
|
"""
|
|
module = __import__(module_name, globals, locals)
|
|
basenames = module_name.split('.')
|
|
for basename in basenames[1:]:
|
|
module = getattr(module, basename)
|
|
|
|
if not names: names = get__all__(module)
|
|
__all__ = []
|
|
for name in names:
|
|
globals[name] = getattr(module, name, None)
|
|
__all__.append(name)
|
|
return __all__
|