mirror of
https://https.git.savannah.gnu.org/git/gnulib.git
synced 2026-08-23 01:26:41 +00:00
258 lines
7.3 KiB
Python
258 lines
7.3 KiB
Python
#!/usr/bin/python
|
|
# encoding: UTF-8
|
|
"""miscellaneous tools and traits"""
|
|
|
|
|
|
|
|
import os as _os
|
|
import subprocess as _sp
|
|
from collections import OrderedDict as _OrderedDict
|
|
|
|
|
|
|
|
class Property(property):
|
|
"""generic property"""
|
|
@staticmethod
|
|
def __fget(*args, **kwargs):
|
|
raise AttributeError("unreadable attribute")
|
|
|
|
@staticmethod
|
|
def __fset(*args, **kwargs):
|
|
raise AttributeError("can't set attribute")
|
|
|
|
|
|
def __init__(self, fget=None, fset=None, doc=None, check=None):
|
|
"""
|
|
doc : any arbitrary string
|
|
fget : function to get the value
|
|
fset : function to set the value
|
|
|
|
def fget(self):
|
|
return self.value
|
|
|
|
def fset(self, value):
|
|
self.value = value
|
|
"""
|
|
fget = Property.__fget if fget is None else fget
|
|
fset = Property.__fset if fset is None else fset
|
|
super().__init__(fget=fget, fset=fset, doc=doc)
|
|
self.__check = check
|
|
self.__fget = fget
|
|
self.__fset = fset
|
|
self.__doc__ = doc
|
|
|
|
|
|
def __set__(self, obj, val):
|
|
if self.__check:
|
|
if not self.__check(val):
|
|
raise ValueError("value check failed")
|
|
return super().__set__(obj, val)
|
|
|
|
|
|
class PathProperty(Property):
|
|
"""file system path property"""
|
|
def __init__(self, fget=None, fset=None, doc=None):
|
|
"""
|
|
doc : any arbitrary string
|
|
fget : function to get the value
|
|
fset : function to set the value
|
|
|
|
def fget(self):
|
|
return self.path
|
|
|
|
def fset(self, path):
|
|
self.path = path
|
|
"""
|
|
super().__init__(fget=fget, fset=fset, doc=doc)
|
|
|
|
|
|
def __set__(self, obj, val):
|
|
if not isinstance(val, str):
|
|
raise TypeError("value: str expected")
|
|
if not val:
|
|
raise ValueError("value: empty path")
|
|
val = _os.path.normpath(val)
|
|
return super().__set__(obj, val)
|
|
|
|
|
|
class OptionalPathProperty(PathProperty):
|
|
def __init__(self, fget=None, fset=None, doc=None):
|
|
"""
|
|
doc : any arbitrary string
|
|
fget : function to get the value
|
|
fset : function to set the value
|
|
|
|
def fget(self):
|
|
return self.path
|
|
|
|
def fset(self, path):
|
|
self.path = path
|
|
"""
|
|
super().__init__(fget=fget, fset=fset, doc=doc)
|
|
|
|
|
|
def __set__(self, obj, val):
|
|
if not (val is None or isinstance(val, str)):
|
|
raise TypeError("value: str or None expected")
|
|
val = _os.path.normpath(val) if val else None
|
|
return super(PathProperty, self).__set__(obj, val)
|
|
|
|
|
|
|
|
class BitwiseProperty(Property):
|
|
"""bitwise flag property"""
|
|
def __init__(self, mask, fget=None, fset=None, doc=None):
|
|
"""
|
|
mask : int bitwise mask
|
|
doc : any arbitrary string
|
|
fget : function to get the value
|
|
fset : function to set the value
|
|
|
|
def fget(self, mask):
|
|
return bool(self.mask & mask)
|
|
|
|
def fset(self, mask, state):
|
|
if state:
|
|
self.mask |= mask
|
|
else:
|
|
self.mask &= ~mask
|
|
"""
|
|
if not isinstance(mask, int):
|
|
raise TypeError("mask: int expected")
|
|
|
|
def _fget(obj):
|
|
return self.__fget(obj, self.__mask)
|
|
|
|
def _fset(obj, state):
|
|
return self.__fset(obj, self.__mask, state)
|
|
|
|
super().__init__(doc=doc, fget=_fget, fset=_fset)
|
|
self.__mask = mask
|
|
self.__fget = fget
|
|
self.__fset = fset
|
|
|
|
|
|
@property
|
|
def mask(self):
|
|
return self.__mask
|
|
|
|
|
|
def __set__(self, obj, val):
|
|
if not isinstance(val, bool):
|
|
raise TypeError("value: bool expected")
|
|
return super().__set__(obj, val)
|
|
|
|
|
|
|
|
class StringListProperty(Property):
|
|
"""string sequence property"""
|
|
__TYPES = (list, tuple, set, frozenset, type({}.keys()), type({}.values()))
|
|
|
|
def __init__(self, sorted=False, unique=False, fget=None, fset=None, doc=None):
|
|
if not isinstance(sorted, bool):
|
|
raise TypeError("sorted: bool expected")
|
|
if not isinstance(unique, bool):
|
|
raise TypeError("unique: bool expected")
|
|
super().__init__(fget=fget, fset=fset, doc=doc)
|
|
self.__sorted = sorted
|
|
self.__unique = unique
|
|
|
|
|
|
def __set__(self, obj, val):
|
|
if not isinstance(val, StringListProperty.__TYPES):
|
|
raise TypeError("value: iterable expected")
|
|
res = list()
|
|
for item in val:
|
|
if not isinstance(item, str):
|
|
raise TypeError("item: str expected")
|
|
res.append(item)
|
|
res = tuple(_OrderedDict.fromkeys(res) if self.__unique else res)
|
|
val = tuple(sorted(res) if self.__sorted else res)
|
|
return super().__set__(obj, val)
|
|
|
|
|
|
|
|
class PathListProperty(StringListProperty):
|
|
"""path sequence property"""
|
|
__TYPES = (list, tuple, set, frozenset, type({}.keys()), type({}.values()))
|
|
|
|
|
|
def __init__(self, sorted=False, unique=False, fget=None, fset=None, doc=None):
|
|
super().__init__(sorted=sorted, unique=unique, fget=fget, fset=fset, doc=doc)
|
|
|
|
|
|
def __set__(self, obj, val):
|
|
if not isinstance(val, PathListProperty.__TYPES):
|
|
raise TypeError("value: iterable expected")
|
|
res = list()
|
|
for item in val:
|
|
if not isinstance(item, str):
|
|
raise TypeError("item: str expected")
|
|
if not item:
|
|
raise TypeError("item: empty path")
|
|
item = _os.path.normpath(item)
|
|
res.append(item)
|
|
return super().__set__(obj, res)
|
|
|
|
|
|
|
|
class _PipeMeta(type):
|
|
__INSTANCE = None
|
|
def __call__(cls, *args, **kwargs):
|
|
if _PipeMeta.__INSTANCE is None:
|
|
_PipeMeta.__INSTANCE = super(_PipeMeta, cls).__call__(*args, **kwargs)
|
|
return _PipeMeta.__INSTANCE
|
|
|
|
|
|
class Pipe(metaclass=_PipeMeta):
|
|
"""pipe handle singleton"""
|
|
def __repr__(self):
|
|
module = self.__class__.__module__
|
|
name = self.__class__.__name__
|
|
return f"{module}.{name}"
|
|
|
|
|
|
|
|
class Executable:
|
|
"""command-line program or script"""
|
|
def __init__(self, path=None, encoding=None):
|
|
if not isinstance(path, str):
|
|
raise TypeError("path: str expected")
|
|
if not path:
|
|
raise ValueError("path: empty string")
|
|
if encoding is not None and not isinstance(encoding, str):
|
|
raise TypeError("encoding: str or None expected")
|
|
self.__path = path
|
|
self.__encoding = encoding
|
|
|
|
|
|
path = Property(
|
|
fget=lambda self: self.__path,
|
|
doc="executable path",
|
|
)
|
|
encoding = Property(
|
|
fget=lambda self: self.__encoding,
|
|
doc="stdin/stdout/stderr encoding",
|
|
)
|
|
|
|
|
|
def __repr__(self):
|
|
module = self.__class__.__module__
|
|
name = self.__class__.__name__
|
|
path = self.__path
|
|
encoding = self.__encoding
|
|
return f"{module}.{name}(path={path}, encoding={encoding})"
|
|
|
|
|
|
def __call__(self, *args, **kwargs):
|
|
"""
|
|
Invoke command-line tool with the given arguments.
|
|
Upon execution subprocess.Popen instance is returned.
|
|
"""
|
|
args = ([self.path] + list(args))
|
|
for key in ("stdin", "stdout", "stderr"):
|
|
if isinstance(kwargs.get(key, Pipe()), Pipe):
|
|
kwargs[key] = _sp.PIPE
|
|
kwargs.setdefault("encoding", self.__encoding)
|
|
return _sp.Popen(args, **kwargs)
|