aboutsummaryrefslogtreecommitdiff
path: root/thcolor/decoders.py
diff options
context:
space:
mode:
authorThomas Touhey <thomas@touhey.fr>2022-07-03 14:24:44 +0200
committerThomas Touhey <thomas@touhey.fr>2022-07-03 14:24:44 +0200
commit42865c468623329f3d9fb08130498d03ab1f77b6 (patch)
tree200b15c715eeb73540fddb8c5c70867cb0ef5524 /thcolor/decoders.py
parent3708f0dd0bc98d9688fcf0dada282c12d0cecd95 (diff)
Fix docstrings and mypy issuesHEADmaster
Diffstat (limited to 'thcolor/decoders.py')
-rw-r--r--thcolor/decoders.py212
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