Source code for parce.standardaction

# -*- coding: utf-8 -*-
#
# This file is part of the parce Python package.
#
# Copyright © 2019-2020 by Wilbert Berendsen <info@wilbertberendsen.nl>
#
# This module 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 3 of the License, or
# (at your option) any later version.
#
# This module 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, see <https://www.gnu.org/licenses/>.


"""
This module defines StandardAction.

A StandardAction is a singleton object. Instantiate a StandardAction with a
string name. The same name actually returns the same object::

    >>> from parce.standardaction import StandardAction
    >>> StandardAction('test') is StandardAction('test')
    True

A StandardAction stores its name in the ``_name`` attribute. Accessing any
attribute (*without underscore*) creates that attribute as a new instance, with
the current instance in the ``_parent`` attribute::

    >>> Name = StandardAction('Name')
    >>> Name.Function is Name.Function
    True
    >>> Name.Function._parent
    Name

This way a new action type can be created that shares its parent with other
types, a concept borrowed from pygments. An example::

    >>> Comment = StandardAction("Comment")
    >>> Literal = StandardAction("Literal")
    >>> String = Literal.String
    >>> String.DoubleQuotedString
    Literal.String.DoubleQuotedString
    >>> String.SingleQuotedString
    Literal.String.SingleQuotedString

StandardAction instances support iteration and membership methods.
Iteration yields the instance ifself and then the parents::

    >>> for i in String.DoubleQuoted:
    ...     print(i)
    ...
    Literal.String.DoubleQuoted
    Literal.String
    Literal

And the :keyword:`in` operator returns True when a standard action belongs to
another one, i.e. the other one is one of the ancestors of the current action::

    >>> String.DoubleQuotedString in String
    True
    >>> Literal in String
    False

The :keyword:`in` operator also works with a string::

    >>> 'String' in Literal.String.DoubleQuoted
    True
    >>> 'Literal' in String
    True

The last one could be surprising, but String is defined as ``Literal.String``::

    >>> String
    Literal.String

Finally, the `&` operator returns the common ancestor, if any::

    >>> String & Number
    Literal
    >>> String & Text
    >>>

See for the full list of pre-defined standard actions :doc:`stdactions`.


"""


import threading

# we use a global lock for standardaction creation, it seems overkill
# to me to equip every instance with one.
_lock = threading.Lock()

_toplevel_actions = {}       # store the "root" actions


[docs]class StandardAction: """Factory for standard action singletons.""" def __new__(cls, name, parent=None): d = parent.__dict__ if parent else _toplevel_actions with _lock: try: return d[name] except KeyError: new = d[name] = object.__new__(cls) new._name = name new._parent = parent return new def __getattr__(self, name): if name.startswith('_'): raise AttributeError("{} has no attribute {}".format(self, repr(name))) return type(self)(name, self) def __repr__(self): return ".".join(reversed([n._name for n in self])) def __iter__(self): node = self while node: yield node node = node._parent def __contains__(self, other): if isinstance(other, str): return any(t._name == other for t in self) return any(t is self for t in other) def __and__(self, other): ancestors = frozenset(other) for t in self: if t in ancestors: return t def __copy__(self): return self def __deepcopy__(self, memo): return self