diff options
author | Thomas Touhey <thomas@touhey.fr> | 2022-07-03 14:24:44 +0200 |
---|---|---|
committer | Thomas Touhey <thomas@touhey.fr> | 2022-07-03 14:24:44 +0200 |
commit | 42865c468623329f3d9fb08130498d03ab1f77b6 (patch) | |
tree | 200b15c715eeb73540fddb8c5c70867cb0ef5524 /thcolor/decoders.py | |
parent | 3708f0dd0bc98d9688fcf0dada282c12d0cecd95 (diff) |
Diffstat (limited to 'thcolor/decoders.py')
-rw-r--r-- | thcolor/decoders.py | 212 |
1 files changed, 91 insertions, 121 deletions
diff --git a/thcolor/decoders.py b/thcolor/decoders.py index f8383be..b29f149 100644 --- a/thcolor/decoders.py +++ b/thcolor/decoders.py @@ -3,10 +3,9 @@ # Copyright (C) 2019-2022 Thomas "Cakeisalie5" Touhey <thomas@touhey.fr> # This file is part of the thcolor project, which is MIT-licensed. # ***************************************************************************** -""" Function and data reference. """ +"""Function and data reference.""" import re as _re - from abc import ABCMeta as _ABCMeta from collections.abc import Mapping as _Mapping from enum import Enum as _Enum, auto as _auto @@ -37,21 +36,20 @@ _NO_DEFAULT_VALUE = type('_NO_DEFAULT_VALUE_TYPE', (), {})() def _canonicalkey(x): - """ Get a canonical key for the decoder mapping. """ + """Get a canonical key for the decoder mapping.""" return str(x).replace('-', '_').casefold() def _issubclass(cls, types): - """ Check if ``cls`` is a subclass of ``types``. + """Check if ``cls`` is a subclass of ``types``. - Until Python 3.10, this function is not aware of the special - types available in the standard ``typing`` module; this function - aims at fixing this for Python <= 3.9. + Until Python 3.10, this function is not aware of the special + types available in the standard ``typing`` module; this function + aims at fixing this for Python <= 3.9. """ # Check if is a special case from typing that we know of. - try: origin = types.__origin__ except AttributeError: @@ -64,7 +62,6 @@ def _issubclass(cls, types): # If ``types`` is any iterable type (tuple, list, you name it), # then we check if the class is a subclass of at least one element # in the iterable. - try: types_iter = iter(types) except TypeError: @@ -73,7 +70,6 @@ def _issubclass(cls, types): return any(_issubclass(cls, type_) for type_ in types_iter) # We default on the base ``issubclass`` from here. - try: return issubclass(cls, types) except TypeError: @@ -81,31 +77,33 @@ def _issubclass(cls, types): def _isinstance(value, types): - """ Check if ``cls`` is a subclass of ``types``. + """Check if ``cls`` is a subclass of ``types``. - Until Python 3.10, this function is not aware of the special - types available in the standard ``typing`` module; this function - aims at fixing this for Python <= 3.9. + Until Python 3.10, this function is not aware of the special + types available in the standard ``typing`` module; this function + aims at fixing this for Python <= 3.9. """ return _issubclass(type(value), types) def _get_args(func) -> _Sequence[_Tuple[str, _Any, _Any]]: - """ Get the arguments definition from a function. + """Get the arguments definition from a function. - Each argument is returned as a tuple containing: + Each argument is returned as a tuple containing: - * The name of the argument. - * The type of the argument. - * The default value of the argument; - ``_NO_DEFAULT_VALUE`` if the argument has no default - value. + * The name of the argument. + * The type of the argument. + * The default value of the argument; + ``_NO_DEFAULT_VALUE`` if the argument has no default value. - In case an argument is keyword-only with no default - value, the function will raise an exception. + In case an argument is keyword-only with no default + value, the function will raise an exception. """ + if isinstance(func, staticmethod): + func = func.__func__ + argspec = _getfullargspec(func) try: kwarg = next( @@ -141,14 +139,14 @@ def _get_args(func) -> _Sequence[_Tuple[str, _Any, _Any]]: def _make_function(func, name: str, args: _Optional[_Sequence[_Any]]): - """ Create a function calling another. + """Create a function calling another. - This function will rearrange the given positional - arguments with the given argument order ``args``. + This function will rearrange the given positional + arguments with the given argument order ``args``. - It will also set the default value and annotations of - the previous function, rearranged using the given - function. + It will also set the default value and annotations of + the previous function, rearranged using the given + function. """ # Here, we get the variables for the base function call. @@ -156,7 +154,6 @@ def _make_function(func, name: str, args: _Optional[_Sequence[_Any]]): # # * ``proxy_args``: the proxy arguments as (name, type, defvalue) tuples. # * ``args_index``: a args index -> proxy index correspondance. - args_spec = _get_args(func) args_index: _List[_Optional[int]] @@ -182,7 +179,6 @@ def _make_function(func, name: str, args: _Optional[_Sequence[_Any]]): # We want to check that no value without a default value was # left unspecified. - for (aname, _, adefvalue), proxy_index in zip(args_spec, args_index): if proxy_index is None and adefvalue is _NO_DEFAULT_VALUE: raise ValueError( @@ -190,10 +186,7 @@ def _make_function(func, name: str, args: _Optional[_Sequence[_Any]]): ) # Produce the function code. - - locals_ = { - '__func': func, - } + locals_ = {'__func': func} def _iter_proxy_args(d, args): # First, obtain the index of the last optional argument from the @@ -278,7 +271,7 @@ def _ncol_func( class _ColorExpressionTokenType(_Enum): - """ Token type. """ + """Token type.""" NAME = _auto() ANGLE = _auto() @@ -296,7 +289,7 @@ class _ColorExpressionTokenType(_Enum): class _ColorExpressionToken: - """ A token as expressed by the color expression lexer. """ + """A token as expressed by the color expression lexer.""" __slots__ = ('_type', '_name', '_value', '_column', '_rawtext') @@ -343,31 +336,31 @@ class _ColorExpressionToken: @property def type_(self): - """ Token type. """ + """Token type.""" return self._type @property def name(self): - """ Token name. """ + """Token name.""" return self._name @property def value(self): - """ Token value. """ + """Token value.""" return self._value @property def column(self): - """ Column at which the token is located. """ + """Column at which the token is located.""" return self._column @property def rawtext(self): - """ Raw text for decoding. """ + """Raw text for decoding.""" return self._rawtext @@ -406,11 +399,11 @@ _colorexpressionpattern = _re.compile( def _get_color_tokens(string: str, extended_hex: bool = False): - """ Get color tokens. + """Get color tokens. - :param string: The string to get the color tokens from. - :param extended_hex: Whether 4 or 8-digit hex colors are - allowed (``True``) or not (``False``). + :param string: The string to get the color tokens from. + :param extended_hex: Whether 4 or 8-digit hex colors are + allowed (``True``) or not (``False``). """ start: int = 0 @@ -439,20 +432,22 @@ def _get_color_tokens(string: str, extended_hex: bool = False): elif result['agl'] is not None: value = float(result['agl_sign'] + result['agl_val']) typ = result['agl_typ'] + + ang: _Angle if typ == 'deg': - value = _DegreesAngle(value) + ang = _DegreesAngle(value) elif typ == 'rad': - value = _RadiansAngle(value) + ang = _RadiansAngle(value) elif typ == 'grad': - value = _GradiansAngle(value) + ang = _GradiansAngle(value) elif typ in ('turn', 'turns'): - value = _TurnsAngle(value) + ang = _TurnsAngle(value) else: raise NotImplementedError yield _ColorExpressionToken( _ColorExpressionToken.TYPE_ANGLE, - value=value, + value=ang, column=column, rawtext=result['agl'], ) @@ -538,7 +533,6 @@ def _get_color_tokens(string: str, extended_hex: bool = False): # # Note that ``func(a, b) / / c`` will still contain an empty # argument before name 'c'. - if not was_call_end: yield _ColorExpressionToken( _ColorExpressionToken.TYPE_EMPTY, @@ -573,14 +567,14 @@ def _get_color_tokens(string: str, extended_hex: bool = False): # --- def fallback(value: _Union[_Color, _Angle, int, float]): - """ Decorator for setting a fallback value on a function. + """Set a fallback value on a function, if used as a variable. - When a function is used as a symbol instead of a function, - by default, it yields that it is callable and should be - accompanied with arguments. + When a function is used as a symbol instead of a function, + by default, it yields that it is callable and should be + accompanied with arguments. - Using this decorator on a function makes it to be evaluated - as a color in this case. + Using this decorator on a function makes it to be evaluated + as a color in this case. """ if not isinstance(value, (_Color, _Angle, int, float)): @@ -597,7 +591,7 @@ def fallback(value: _Union[_Color, _Angle, int, float]): class alias: - """ Define an alias for a function. """ + """Define an alias for a function.""" __slots__ = ('_name', '_args') @@ -612,47 +606,43 @@ class alias: @property def name(self): - """ Get the name of the function the current alias targets. """ + """Get the name of the function the current alias targets.""" return self._name @property def args(self) -> _Sequence[str]: - """ Get the arguments' order of the alias. """ + """Get the arguments' order of the alias.""" return self._args class ColorDecoder(_Mapping): - """ Base color decoder. - - This color decoder behaves as a mapping returning syntax elements, - with the additional properties controlling its behaviour. - - The properties defined at class definition time are the following: - - ``__mapping__`` + """Base color decoder. - Defines the base mapping that is copied at the - instanciation of each class. + This color decoder behaves as a mapping returning syntax elements, + with the additional properties controlling its behaviour. - ``__ncol_support__`` + The properties defined at class definition time are the following: - Defines whether natural colors (NCol) are - supported while decoding or not. + ``__mapping__`` + Defines the base mapping that is copied at the + instanciation of each class. - ``__extended_hex_support__`` + ``__ncol_support__`` + Defines whether natural colors (NCol) are + supported while decoding or not. - Defines whether 4 or 8-digit - hexadecimal colors (starting with a '#') are allowed or not. + ``__extended_hex_support__`` + Defines whether 4 or 8-digit + hexadecimal colors (starting with a '#') are allowed or not. - ``__defaults_to_netscape_color`` + ``__defaults_to_netscape_color`` + Defines whether color decoding + defaults to Netscape color parsing or not. - Defines whether color decoding - defaults to Netscape color parsing or not. - - These properties cannot be changed at runtime, although they might - be in a future version of thcolor. + These properties cannot be changed at runtime, although they might + be in a future version of thcolor. """ __slots__ = ('_mapping',) @@ -699,17 +689,17 @@ class ColorDecoder(_Mapping): prefer_colors: bool = False, prefer_angles: bool = False, ) -> _Sequence[_Optional[_Union[_Color, _Angle, int, float]]]: - """ Decode a color expression. + """Decode a color expression. - When top-level result(s) are not colors and colors are - actually expected if possible to obtain, the caller should - set ``prefer_colors`` to ``True`` in order for top-level - conversions to take place. + When top-level result(s) are not colors and colors are + actually expected if possible to obtain, the caller should + set ``prefer_colors`` to ``True`` in order for top-level + conversions to take place. - Otherwise, when top-level result(s) are not angles and angles - are actually expected if possible to obtain, the caller should - set ``prefer_angles`` to ``True`` in order for top-level - conversions to take place. + Otherwise, when top-level result(s) are not angles and angles + are actually expected if possible to obtain, the caller should + set ``prefer_angles`` to ``True`` in order for top-level + conversions to take place. """ global _color_pattern @@ -729,14 +719,13 @@ class ColorDecoder(_Mapping): # * ``current``: the current list of elements, which is put on # top of the stack when a call is started and is popped out of # the top of the stack when a call is ended. - stack: _List[_List[_ColorExpressionToken]] = [] func_stack: _List[_ColorExpressionToken] = [] implicit_stack: _List[int] = [] current: _List[_ColorExpressionToken] = [] def _get_parent_func(): - """ Get the parent function name for exceptions. """ + """Get the parent function name for exceptions.""" if not func_stack: return None @@ -774,7 +763,6 @@ class ColorDecoder(_Mapping): _ColorExpressionToken.TYPE_EMPTY, ): # Current token is a value, we add it. - current.append(token) elif token.type_ == _ColorExpressionToken.TYPE_CALL_START: if func_stack and ( @@ -812,7 +800,6 @@ class ColorDecoder(_Mapping): # We pop the function out of the stack with the # arguments we have. - implicit_stack.pop(0) old_current = stack.pop(0) old_current.append(_ColorExpressionToken( @@ -824,7 +811,6 @@ class ColorDecoder(_Mapping): current = old_current # We have a function name and it is not implicit. - old_current = stack.pop(0) old_current.append(_ColorExpressionToken( _ColorExpressionTokenType.CALL, @@ -850,7 +836,6 @@ class ColorDecoder(_Mapping): # We have the required number of tokens for this # to work. - old_current = stack.pop(0) old_current.append(_ColorExpressionToken( type_=_ColorExpressionToken.TYPE_CALL, @@ -863,7 +848,6 @@ class ColorDecoder(_Mapping): # We want to pop out all implicit functions we have. # If we still have an explicit function in the function stack, # that means a parenthesis was omitted. - while func_stack: try: name_token = func_stack.pop(0) @@ -879,7 +863,6 @@ class ColorDecoder(_Mapping): # We pop the function out of the stack with the # arguments we have. - implicit_stack.pop(0) old_current = stack.pop(0) old_current.append(_ColorExpressionToken( @@ -891,16 +874,15 @@ class ColorDecoder(_Mapping): current = old_current # Evaluating stage. - def evaluate(element, parent_func=None): - """ Evaluate the element in the current context. + """Evaluate the element in the current context. - Always returns a tuple where: + Always returns a tuple where: - * The first element is the real answer. - * The second element is the string which can be used - in the case of a color fallback (when Netscape color - defaulting is on). + * The first element is the real answer. + * The second element is the string which can be used + in the case of a color fallback (when Netscape color + defaulting is on). """ if element.type_ == _ColorExpressionTokenType.NAME: @@ -962,7 +944,6 @@ class ColorDecoder(_Mapping): ) # Check the function. - try: args_spec = _get_args(func) except ValueError as exc: @@ -978,7 +959,6 @@ class ColorDecoder(_Mapping): # We remove empty arguments at the end of calls so that # even calls such as ``func(a, b, , , , , ,)`` will only # have arguments 'a' and 'b'. - unevaluated_args = element.value while ( unevaluated_args and unevaluated_args[-1].type_ @@ -996,7 +976,6 @@ class ColorDecoder(_Mapping): # Now we should evaluate subtokens and check the types of # the function. - new_args = [] for token, (argname, argtype, argdefvalue) in _zip_longest( @@ -1049,7 +1028,6 @@ class ColorDecoder(_Mapping): new_args.append(arg) # Get the result. - result = func(*new_args) if result is None: raise _ColorExpressionSyntaxError( @@ -1086,7 +1064,7 @@ class ColorDecoder(_Mapping): class _MetaColorDecoderType(_ABCMeta): - """ The decoder type. """ + """The decoder type.""" def __new__(mcls, clsname, superclasses, attributedict): elements = {} @@ -1097,7 +1075,6 @@ class _MetaColorDecoderType(_ABCMeta): } # Explore the parents. - for supercls in reversed(superclasses): if not issubclass(supercls, ColorDecoder): continue @@ -1112,7 +1089,6 @@ class _MetaColorDecoderType(_ABCMeta): options[option] = attributedict.get(option, value) # Instanciate the class. - clsattrs = { '__doc__': attributedict.get('__doc__', None), '__mapping__': elements, @@ -1123,7 +1099,6 @@ class _MetaColorDecoderType(_ABCMeta): del clsattrs # Get the elements and aliases from the current attribute dictionary. - aliases = {} childelements = {} for key, value in attributedict.items(): @@ -1157,7 +1132,6 @@ class _MetaColorDecoderType(_ABCMeta): # # This is not optimized. However, given the cases we have, # it'll be enough. - aliaselements = {} while aliases: resolved = len(aliases) @@ -1169,14 +1143,12 @@ class _MetaColorDecoderType(_ABCMeta): # Check if the current alias still depends on an alias # that hasn't been resolved yet; in that case, ignore # it for now. - if funcname in aliases: resolved -= 1 nextaliases[key] = alias_value continue # Check the function name and arguments. - try: func = childelements[funcname] except KeyError: @@ -1207,12 +1179,10 @@ class _MetaColorDecoderType(_ABCMeta): ) # Add all of the elements in order of importance. - elements.update(aliaselements) elements.update(childelements) # Check the types of the annotations, just in case. - for key, element in elements.items(): try: args_spec = _get_args(element) @@ -1262,7 +1232,7 @@ class _MetaColorDecoderType(_ABCMeta): class MetaColorDecoder(ColorDecoder, metaclass=_MetaColorDecoderType): - """ Base meta color decoder, which gets the function and things. """ + """Base meta color decoder, which gets the function and things.""" pass |