import operator, builtins
from types import FunctionType, BuiltinFunctionType
from io import StringIO
from .base import generic, method, variadic_method
def _get_operations():
"""Enumerate all operations to implemented as methods/generics."""
answer = []
for name, value in operator.__dict__.items():
if name.startswith("__") and name.endswith("__") and isinstance(
value, (FunctionType, BuiltinFunctionType)):
answer.append((name, value))
answer.append(("__iter__", iter))
answer.append(("__divmod__", divmod))
answer.sort(key=lambda element: element[0])
return answer
operations = {}
__all__ = [
"__spy__", "__out__", "__init__", "__call__", "__del__",
"__float__", "__int__",
"AbstractObject", "Object", "FinalizingMixin",
"Writer", "IndentingWriter",
"spy", "as_string", "as_indented_string", "get_text",
"push", "pop", "indent", "dedent",
"CSep", "csep",
]
_reverse_operation_names = (
"add", "sub", "mul",
"truediv", "floordiv", "mod", "divmod",
"pow",
"lshift", "rshift",
"and", "xor", "or" )
# TODO: Check these sets for completeness
_operations_returning_not_implemented = set((
"__eq__", "__ne__",
"__add__",
"__sub__",
"__mul__",
"__matmul__",
"__truediv__",
"__floordiv__",
"__mod__",
"__divmod__",
"__pow__",
"__lshift__",
"__rshift__",
"__and__",
"__xor__",
"__or_",
))
_operations_returning_not_implemented |= set((
"__i" + name[2:] for name in _operations_returning_not_implemented))
_operations_raising_type_error = set((
"__lt__", "__le__", "__gt__", "__ge__"))
def gni2(generic, name):
"""Generate a method returning `NotImplemented`."""
raise NotImplementedError("TODO")
def gte2(generic, name, operator_symbol=None):
"""Generate a method raising a `TypeError`."""
if operator_symbol is None:
operator_symbol = name
raise NotImplementedError("TODO")
# A specification for default implementations of some operations
default_implementations = dict(
__eq__= gni2,
__ne__= gni2,
__lt__= (gte2, "<"),
__le__= (gte2, "<="),
)
def _install_operations():
"""Install all the operations as generics in this module."""
for name, value in _get_operations():
doc = value.__doc__
if doc is None:
doc = "No doc-string for %r" % name
doc += "\n\n"
doc += ("Called by the :meth:`AbstractObject.%s` special method." %
name)
basic_name = name[2:-2]
if basic_name in _reverse_operation_names:
doc += "\n"
doc += "Also called by :meth:`AbstractObject.__r%s__` "\
"with arguments reversed." % basic_name
operations[name] = globals()[name] = generic(name, doc)
if 1:
if name in _operations_returning_not_implemented:
def operation(o0: object, o1: object):
"""Defaults to not comparable."""
return NotImplemented
operation.__name__ = name
operations[name].method()(operation)
elif name in _operations_raising_type_error:
message = ("%r not supported between instance of %%r and %%r"
% name)
def operation(o0: object, o1: object):
"""Defaults to not comparable."""
raise TypeError(message %
(type(o0).__name__, type(o1).__name__))
operation.__name__ = name
operations[name].method()(operation)
__all__.append(name)
_install_operations()
__init__ = generic("__init__",
""":func:`__init__` initializes instantiates instances of :class:`AbstractObject` and it's subclasses.
It has a multi method for :class:`Object`. This multi-method does not accept any
additional parameters and has no effect.
There is no method for :class:`AbstractObject`, therefore
this class can not be instantiated.""")
__call__ = generic("__call__",
""":func:`__call__` is called when instances of :class:`AbstractObject` are called.""")
__del__ = generic("__del__",
""":func:`__del__` is called when instances of :class:`FinalizingMixin` are about to be destroyed.""")
[docs]class AbstractObject(object):
"""An abstract (mixin) class that maps all the python magic functions to generics."""
[docs] def __init__(self, *arguments):
"Call the :func:`__init__` generic function."""
__init__(self, *arguments)
[docs] def __call__(self, *arguments):
"Call the :func:`__call__` generic function."""
return __call__(self, *arguments)
[docs] def __str__(self):
"Answer the objects print-string by calling :func:`as_string`."""
return as_string(self)
[docs] def __repr__(self):
"Answer the objects debug-string by calling :func:`spy`."""
return spy(self)
[docs] def __float__(self):
"""Convert the object to a `float` by calling the :func:`__float__` generic."""
return __float__(self)
[docs] def __int__(self):
"""Convert the object to an `int` by calling the :func:`__int__` generic."""
return __int__(self)
push = generic("push", "Push the current indent on a stack of indents.")
pop = generic("pop", "Restore the current indent from a stack of indents.")
class AbstractDirective(object):
"""I am the abstract base class of all directives."""
[docs]class CSep(AbstractDirective):
"""A conditional separator"""
csep = CSep()
class IndentDirective(AbstractDirective):
"""I specify an indentation."""
indent = IndentDirective()
class DedentDirective(AbstractDirective):
"""I specify an dedentation."""
dedent = DedentDirective()
[docs]class Writer(AbstractObject):
"""A simple wrapper around a file like object.
:class:`Writer`'s purpose is to simplify the implementation
of the generics :func:`__out__` and :func:`__spy__`.
`Writer` instances are initialised either with a file_like object
or with no arguments. In the later case ab instance of `StringIO`
is used.
Output is done by simply calling the writer with at least one string object.
The first argument acts as a %-template for formating the other arguments.
The class is intended to be sub-classed for formatted output."""
@method()
def __init__(writer: Writer):
"""Initialize the `Write` with a `StringIO` object."""
__init__(writer, StringIO())
@method()
def __init__(writer: Writer, file_like):
"""Initialize the `Write` with a file like object.
:param file_like: A file-like object."""
writer.file = file_like
@method()
def __call__(writer: Writer):
"""Write a newline on the file-like object."""
writer.file.write("\n")
@variadic_method()
def __call__(writer: Writer, directive: AbstractDirective, *directives):
"""Only execute directives."""
execute_and_filter_directives((directive,) + directives, writer)
@variadic_method()
def __call__(writer: Writer, text: str, *arguments):
"""Write text % arguments on the file-like object."""
arguments = execute_and_filter_directives(arguments, writer)
if arguments:
writer.file.write(text % arguments)
else:
writer.file.write(text)
@variadic_method()
def push(writer: Writer, *ignored_arguments):
"""`Push` has no effect on an ordinary `Writer` instance."""
pass
@variadic_method()
def pop(writer: Writer, *ignored_arguments):
"""`Pop` has, like `push`, no effect on an ordinary `Writer` instance."""
pass
get_text = generic("get_text", """Get the text written so far.""")
[docs]@method()
def get_text(writer: Writer):
"""Get the text written so far.
:Note: This method is only supported if the file-like object
implements :class:`StringIO`'s :meth:`getvalue` method."""
return writer.file.getvalue()
[docs]class IndentingWriter(Writer):
"""A writer that maintains a stack of indents."""
@method()
def __init__(writer: IndentingWriter, file_like):
__init__.super(Writer, object)(writer, file_like)
writer.indents = []
writer.indent = ""
writer.indent_width = 4
writer.indent_char = " "
writer.only_newline = False
@method()
def __call__(writer:IndentingWriter):
writer.only_newline = True
__call__.super(Writer)(writer)
[docs]@variadic_method()
def __call__(writer: IndentingWriter, text: str, *arguments):
arguments = execute_and_filter_directives(arguments, writer)
if arguments:
text = text % arguments
text_list = text.split("\n")
base_call = __call__.super(Writer, str)
sub_text = text_list[0]
if writer.only_newline:
base_call(writer, writer.indent)
if sub_text:
base_call(writer, sub_text)
writer.only_newline = False
else:
writer.only_newline = True
for sub_text in text_list[1:]:
writer()
if sub_text:
base_call(writer, writer.indent)
base_call(writer, sub_text)
writer.only_newline = False
@method()
def push(writer: IndentingWriter, steps: int):
"""Push the old indent on the stack and indent.
Indentation is `steps` times the writer's indent width.
"""
writer.indents.append(writer.indent)
indent = writer.indent_char * (writer.indent_width * steps)
writer.indent += indent
if writer.only_newline:
writer.file.write(indent)
@method()
def push(writer: IndentingWriter, indent: str):
"""Indent by using `indent`."""
writer.indents.append(writer.indent)
writer.indent += indent
if writer.only_newline:
writer.file.write(indent)
[docs]@method()
def push(writer: IndentingWriter):
"""Push the old indent and indent one indent width."""
push(writer, 1)
@method()
def pop(writer: IndentingWriter, steps: int):
"""Pop `steps` levels of indentation."""
for count in range(steps):
writer.indent = writer.indents.pop()
[docs]@method()
def pop(writer: IndentingWriter):
"""Pop one level of indentation."""
pop(writer, 1)
execute_directive = generic(
"execute_directive", "Execute formatting directives")
@method()
def execute_directive(some_object: object, write: Writer):
"""Do nothing for ordinary objects.
Answer `True`."""
return True
@method()
def execute_directive(csep: CSep, write: Writer):
"""Insert a space character.
Answer `False`."""
write(" ")
return False
@method()
def execute_directive(csep: CSep, write: IndentingWriter):
write()
return False
@method()
def execute_directive(indent: IndentDirective, write: Writer):
return False
@method()
def execute_directive(dedent: DedentDirective, write: Writer):
return False
@method()
def execute_directive(indent: IndentDirective, write: IndentingWriter):
push(write)
return False
@method()
def execute_directive(dedent: DedentDirective, write: IndentingWriter):
pop(write)
return False
def execute_and_filter_directives(directives, write):
"""Execute and remove all the directives from the list of directives."""
objects = []
for directive in directives:
if execute_directive(directive, write):
objects.append(directive)
return tuple(objects)
[docs]class Object(AbstractObject):
"""Just like `AbstractObject`, but can be instantiated."""
[docs]@method()
def __init__(an_object: Object):
"""Do nothing for :class:`Object`."""
pass
def _install_operations():
"""Install the operations in the class."""
def get_doc(value):
try:
function = getattr(operator, value.__name__)
except AttributeError:
function = getattr(builtins, value.__name__[2:-2])
answer = function.__doc__
if answer is None:
answer = "No doc-string for %r" % value.__name__
return answer
def generate_operation(value):
def operation(*arguments):
#d#print "reg:", arguments
return value(*arguments)
doc = get_doc(value)
doc += "\n\n"
doc += ("Calls the :func:`%s`-generic with its arguments." %
value.__name__)
operation.__doc__ = doc
return operation
def generate_reverse_operation(value):
def operation(argument0, argument1):
#d#print "rev:", argument0, argument1
return value(argument1, argument0)
doc = get_doc(value)
doc += "\n\n"
doc += ("Calls the :func:`%s`-generic with its arguments reversed." %
value.__name__)
operation.__doc__ = doc
return operation
for name, value in _get_operations():
operation = generate_operation(operations[name])
setattr(AbstractObject, name, operation)
for name in _reverse_operation_names:
operation = generate_reverse_operation(operations["__%s__" % name])
setattr(AbstractObject, "__r%s__" % name, operation)
_install_operations()
del _install_operations
del _get_operations
__float__ = generic("__float__", "Convert an :class:`AbstractObject` to a `float`.")
__int__ = generic("__int__", "Convert an :class:`AbstractObject` to an `int`.")
[docs]class FinalizingMixin(object):
"""A mixin class with `__del__` implemented as a generic function.
.. note:: This functionality was separated into mixin class,
because Python's cycle garbage collector does not collect
classes with a :meth:`__del__` method.
Inherit from this class if you really
need :func:`__del__`.
"""
[docs] def __del__(self):
"""Call the :func:`__del__` generic function."""
__del__(self)
[docs]@generic
def as_string(self):
"""Answer an object's print string.
This is done by creating a :class:`Writer` instance
and calling the :func:`__out__` generic with the
object and the writer. The using :meth:`Writer.get_text`
to retrieve the text written."""
writer = Writer()
__out__(self, writer)
return get_text(writer)
[docs]@generic
def as_indented_string(self):
"""Answer an object's indented string.
This is done by creating a :class:`IndentingWriter` instance
and calling the :func:`__out__` generic with the
object and the writer. The using :meth:`Writer.get_text`
to retrieve the text written."""
writer = IndentingWriter()
__out__(self, writer)
return get_text(writer)
__out__ = generic("__out__",
"Create a print string of an object using a :class:`Writer`.")
@method()
def __out__(self, write: Writer):
"""Write a just :func:`str` of self."""
write(str(self))
[docs]@method()
def __out__(self: AbstractObject, write: Writer):
"""Write a just :func:`str` of self by directly calling :meth:`object.__str__`."""
write(object.__str__(self))
[docs]@generic
def spy(self):
"""Answer an object's debug string.
This is done by creating a :class:`Writer` instance
and calling the :func:`__spy__` generic with the
object and the writer. The using :meth:`Writer.get_text`
to retrieve the text written.
.. note:: The function's name was taken from Prolog's
`spy` debugging aid.
.. _spy <http://cis.stvincent.edu/html/tutorials/prolog/spy.html> """
writer = Writer()
__spy__(self, writer)
return get_text(writer)
__spy__ = generic("__spy__",
"""Create a print string of an object using a `Writer`.
.. note:: The function's name was taken from Prolog's `spy` debugging aid.""")
@method()
def __spy__(self, write: Writer):
"""Write a just :func:`repr` of self."""
write(repr(self))
[docs]@method()
def __spy__(self: AbstractObject, write: Writer):
"""Write a just :func:`repr` of self by directly calling :meth:`object.__repr__`."""
write(object.__repr__(self))