123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304 |
- # -*- test-case-name: twisted.python.test.test_textattributes -*-
- # Copyright (c) Twisted Matrix Laboratories.
- # See LICENSE for details.
-
- """
- This module provides some common functionality for the manipulation of
- formatting states.
-
- Defining the mechanism by which text containing character attributes is
- constructed begins by subclassing L{CharacterAttributesMixin}.
-
- Defining how a single formatting state is to be serialized begins by
- subclassing L{_FormattingStateMixin}.
-
- Serializing a formatting structure is done with L{flatten}.
-
- @see: L{twisted.conch.insults.helper._FormattingState}
- @see: L{twisted.conch.insults.text._CharacterAttributes}
- @see: L{twisted.words.protocols.irc._FormattingState}
- @see: L{twisted.words.protocols.irc._CharacterAttributes}
- """
-
-
- from typing import ClassVar, List, Sequence
-
- from twisted.python.util import FancyEqMixin
-
-
- class _Attribute(FancyEqMixin):
- """
- A text attribute.
-
- Indexing a text attribute with a C{str} or another text attribute adds that
- object as a child, indexing with a C{list} or C{tuple} adds the elements as
- children; in either case C{self} is returned.
-
- @type children: C{list}
- @ivar children: Child attributes.
- """
-
- compareAttributes: ClassVar[Sequence[str]] = ("children",)
-
- def __init__(self):
- self.children = []
-
- def __repr__(self) -> str:
- return f"<{type(self).__name__} {vars(self)!r}>"
-
- def __getitem__(self, item):
- assert isinstance(item, (list, tuple, _Attribute, str))
- if isinstance(item, (list, tuple)):
- self.children.extend(item)
- else:
- self.children.append(item)
- return self
-
- def serialize(self, write, attrs=None, attributeRenderer="toVT102"):
- """
- Serialize the text attribute and its children.
-
- @param write: C{callable}, taking one C{str} argument, called to output
- a single text attribute at a time.
-
- @param attrs: A formatting state instance used to determine how to
- serialize the attribute children.
-
- @type attributeRenderer: C{str}
- @param attributeRenderer: Name of the method on I{attrs} that should be
- called to render the attributes during serialization. Defaults to
- C{'toVT102'}.
- """
- if attrs is None:
- attrs = DefaultFormattingState()
- for ch in self.children:
- if isinstance(ch, _Attribute):
- ch.serialize(write, attrs.copy(), attributeRenderer)
- else:
- renderMeth = getattr(attrs, attributeRenderer)
- write(renderMeth())
- write(ch)
-
-
- class _NormalAttr(_Attribute):
- """
- A text attribute for normal text.
- """
-
- def serialize(self, write, attrs, attributeRenderer):
- attrs.__init__()
- _Attribute.serialize(self, write, attrs, attributeRenderer)
-
-
- class _OtherAttr(_Attribute):
- """
- A text attribute for text with formatting attributes.
-
- The unary minus operator returns the inverse of this attribute, where that
- makes sense.
-
- @type attrname: C{str}
- @ivar attrname: Text attribute name.
-
- @ivar attrvalue: Text attribute value.
- """
-
- compareAttributes = ("attrname", "attrvalue", "children")
-
- def __init__(self, attrname, attrvalue):
- _Attribute.__init__(self)
- self.attrname = attrname
- self.attrvalue = attrvalue
-
- def __neg__(self):
- result = _OtherAttr(self.attrname, not self.attrvalue)
- result.children.extend(self.children)
- return result
-
- def serialize(self, write, attrs, attributeRenderer):
- attrs = attrs._withAttribute(self.attrname, self.attrvalue)
- _Attribute.serialize(self, write, attrs, attributeRenderer)
-
-
- class _ColorAttr(_Attribute):
- """
- Generic color attribute.
-
- @param color: Color value.
-
- @param ground: Foreground or background attribute name.
- """
-
- compareAttributes = ("color", "ground", "children")
-
- def __init__(self, color, ground):
- _Attribute.__init__(self)
- self.color = color
- self.ground = ground
-
- def serialize(self, write, attrs, attributeRenderer):
- attrs = attrs._withAttribute(self.ground, self.color)
- _Attribute.serialize(self, write, attrs, attributeRenderer)
-
-
- class _ForegroundColorAttr(_ColorAttr):
- """
- Foreground color attribute.
- """
-
- def __init__(self, color):
- _ColorAttr.__init__(self, color, "foreground")
-
-
- class _BackgroundColorAttr(_ColorAttr):
- """
- Background color attribute.
- """
-
- def __init__(self, color):
- _ColorAttr.__init__(self, color, "background")
-
-
- class _ColorAttribute:
- """
- A color text attribute.
-
- Attribute access results in a color value lookup, by name, in
- I{_ColorAttribute.attrs}.
-
- @type ground: L{_ColorAttr}
- @param ground: Foreground or background color attribute to look color names
- up from.
-
- @param attrs: Mapping of color names to color values.
- @type attrs: Dict like object.
- """
-
- def __init__(self, ground, attrs):
- self.ground = ground
- self.attrs = attrs
-
- def __getattr__(self, name):
- try:
- return self.ground(self.attrs[name])
- except KeyError:
- raise AttributeError(name)
-
-
- class CharacterAttributesMixin:
- """
- Mixin for character attributes that implements a C{__getattr__} method
- returning a new C{_NormalAttr} instance when attempting to access
- a C{'normal'} attribute; otherwise a new C{_OtherAttr} instance is returned
- for names that appears in the C{'attrs'} attribute.
- """
-
- def __getattr__(self, name):
- if name == "normal":
- return _NormalAttr()
- if name in self.attrs:
- return _OtherAttr(name, True)
- raise AttributeError(name)
-
-
- class DefaultFormattingState(FancyEqMixin):
- """
- A character attribute that does nothing, thus applying no attributes to
- text.
- """
-
- compareAttributes: ClassVar[Sequence[str]] = ("_dummy",)
-
- _dummy = 0
-
- def copy(self):
- """
- Make a copy of this formatting state.
-
- @return: A formatting state instance.
- """
- return type(self)()
-
- def _withAttribute(self, name, value):
- """
- Add a character attribute to a copy of this formatting state.
-
- @param name: Attribute name to be added to formatting state.
-
- @param value: Attribute value.
-
- @return: A formatting state instance with the new attribute.
- """
- return self.copy()
-
- def toVT102(self):
- """
- Emit a VT102 control sequence that will set up all the attributes this
- formatting state has set.
-
- @return: A string containing VT102 control sequences that mimic this
- formatting state.
- """
- return ""
-
-
- class _FormattingStateMixin(DefaultFormattingState):
- """
- Mixin for the formatting state/attributes of a single character.
- """
-
- def copy(self):
- c = DefaultFormattingState.copy(self)
- c.__dict__.update(vars(self))
- return c
-
- def _withAttribute(self, name, value):
- if getattr(self, name) != value:
- attr = self.copy()
- attr._subtracting = not value
- setattr(attr, name, value)
- return attr
- else:
- return self.copy()
-
-
- def flatten(output, attrs, attributeRenderer="toVT102"):
- """
- Serialize a sequence of characters with attribute information
-
- The resulting string can be interpreted by compatible software so that the
- contained characters are displayed and, for those attributes which are
- supported by the software, the attributes expressed. The exact result of
- the serialization depends on the behavior of the method specified by
- I{attributeRenderer}.
-
- For example, if your terminal is VT102 compatible, you might run
- this for a colorful variation on the \"hello world\" theme::
-
- from twisted.conch.insults.text import flatten, attributes as A
- from twisted.conch.insults.helper import CharacterAttribute
- print(flatten(
- A.normal[A.bold[A.fg.red['He'], A.fg.green['ll'], A.fg.magenta['o'], ' ',
- A.fg.yellow['Wo'], A.fg.blue['rl'], A.fg.cyan['d!']]],
- CharacterAttribute()))
-
- @param output: Object returned by accessing attributes of the
- module-level attributes object.
-
- @param attrs: A formatting state instance used to determine how to
- serialize C{output}.
-
- @type attributeRenderer: C{str}
- @param attributeRenderer: Name of the method on I{attrs} that should be
- called to render the attributes during serialization. Defaults to
- C{'toVT102'}.
-
- @return: A string expressing the text and display attributes specified by
- L{output}.
- """
- flattened: List[str] = []
- output.serialize(flattened.append, attrs, attributeRenderer)
- return "".join(flattened)
-
-
- __all__ = ["flatten", "DefaultFormattingState", "CharacterAttributesMixin"]
|