Source code for xdoctest.directive_facade

"""
Bridge between stdlib :mod:`doctest`-style optionflags and xdoctest's
structured :class:`~xdoctest.directive.RuntimeState`.

Adopters use :func:`register_optionflag` to register a checker-only flag
(returning a stable ``int`` bit, identical to what
:func:`doctest.register_optionflag` returns) and may then enable it via
xdoctest directives like ``# xdoctest: +MY_FLAG``. Inside the runner the bit
is carried on :class:`~xdoctest.directive.RuntimeState` and ultimately
delivered to the active output checker as the standard ``flags`` argument.

The bridge is two-way:

- :func:`runtime_state_to_optionflags` packs the structured runtime state
  into a stdlib-shaped optionflags ``int``.
- :func:`optionflags_to_runtime_state` unpacks an optionflags ``int`` back
  into a :class:`RuntimeState`.

Stdlib defaults (``ELLIPSIS``, ``NORMALIZE_WHITESPACE``, ...) are mapped to
their corresponding xdoctest runtime keys at import time. Custom flags do
not touch :class:`RuntimeState` keys; they are carried through as
checker-only optionflag bits.
"""

from __future__ import annotations

import doctest
from collections.abc import Callable, Mapping
from typing import cast

from xdoctest import directive

BLANKLINE_MARKER = doctest.BLANKLINE_MARKER
ELLIPSIS_MARKER = doctest.ELLIPSIS_MARKER

DONT_ACCEPT_BLANKLINE = doctest.DONT_ACCEPT_BLANKLINE
NORMALIZE_WHITESPACE = doctest.NORMALIZE_WHITESPACE
ELLIPSIS = doctest.ELLIPSIS
SKIP = doctest.SKIP
IGNORE_EXCEPTION_DETAIL = doctest.IGNORE_EXCEPTION_DETAIL
REPORT_UDIFF = doctest.REPORT_UDIFF
REPORT_CDIFF = doctest.REPORT_CDIFF
REPORT_NDIFF = doctest.REPORT_NDIFF


class RuntimeFlagFacade:
    """
    Registry mapping optionflag names <-> ``int`` bits, and (optionally)
    optionflag bits <-> :class:`~xdoctest.directive.RuntimeState` keys.

    Two kinds of registrations exist:

    - Builtin flags (``ELLIPSIS``, ``NORMALIZE_WHITESPACE``, ...): these are
      bidirectionally bound to a runtime state key so xdoctest's structured
      state and stdlib-style ``flags`` round-trip cleanly.
    - Plain registered flags (``FIX``, ``FLOAT_CMP``, ...): these have no
      structured runtime state representation and are carried as
      checker-only bits inside the runtime state.

    Example:
        >>> facade = RuntimeFlagFacade()
        >>> facade.register_builtin_optionflag('ELLIPSIS', 1, 'ELLIPSIS')
        1
        >>> facade.register_builtin_optionflag('CUSTOM', 2)
        2
        >>> state = facade.optionflags_to_runtime_state(3)
        >>> state['ELLIPSIS']
        True
        >>> state.get_output_checker_flags()
        2
        >>> facade.runtime_state_to_optionflags(state)
        3
    """

    def __init__(self) -> None:
        self._optionflags_by_name: dict[str, int] = {}
        self._optionflag_names_by_value: dict[int, str] = {}
        self._runtime_key_to_optionflag: dict[str, int] = {}
        self._optionflag_to_runtime_key: dict[int, str] = {}

    def _bind_runtime_key(self, flag: int, runtime_state_key: str) -> None:
        self._runtime_key_to_optionflag[runtime_state_key] = flag
        self._optionflag_to_runtime_key[flag] = runtime_state_key

    def register_builtin_optionflag(
        self,
        name: str,
        flag: int,
        runtime_state_key: str | None = None,
    ) -> int:
        """
        Bind a stdlib-doctest builtin flag to its runtime state key.
        """
        self._optionflags_by_name[name] = flag
        self._optionflag_names_by_value[flag] = name
        if runtime_state_key is not None:
            self._bind_runtime_key(flag, runtime_state_key)
        return flag

    def register_optionflag(
        self,
        name: str,
        runtime_state_key: str | None = None,
    ) -> int:
        """
        Register an optionflag name and return its ``int`` bit.

        The returned ``int`` is exactly the value that
        :func:`doctest.register_optionflag` would return for this name, so
        third-party tooling that wires the same name through stdlib doctest
        will see consistent bits.
        """
        if name in self._optionflags_by_name:
            flag = self._optionflags_by_name[name]
            if runtime_state_key is not None:
                self._bind_runtime_key(flag, runtime_state_key)
            return flag
        flag = doctest.register_optionflag(name)
        return self.register_builtin_optionflag(name, flag, runtime_state_key)

    def is_registered_optionflag(self, name: str) -> bool:
        return name in self._optionflags_by_name

    def get_optionflag(self, name: str) -> int:
        return self._optionflags_by_name[name]

    def get_registered_optionflags(self) -> dict[str, int]:
        return dict(self._optionflags_by_name)

    def _runtime_bound_optionflag_mask(self) -> int:
        """Return the mask represented by structured runtime-state keys."""
        mask = 0
        for flag in self._optionflag_to_runtime_key:
            mask |= flag
        return mask

    def runtime_state_to_optionflags(
        self,
        runstate: directive.RuntimeState | Mapping[str, object] | None,
    ) -> int:
        """
        Pack a :class:`~xdoctest.directive.RuntimeState` (or a plain mapping)
        into a stdlib-shaped optionflags ``int``.

        Includes:

        - Any checker-only flags carried via
          :meth:`RuntimeState.get_output_checker_flags`
        - Any builtin flags whose mapped runtime key is currently ``True``
        """
        runtime_bound_mask = self._runtime_bound_optionflag_mask()
        flags = 0
        lookup: Callable[[str], object]
        if isinstance(runstate, directive.RuntimeState):
            flags |= runstate.get_output_checker_flags() & ~runtime_bound_mask
            lookup = runstate.__getitem__
        elif runstate is None:
            default_runstate = directive.RuntimeState()
            flags |= (
                default_runstate.get_output_checker_flags() & ~runtime_bound_mask
            )
            lookup = default_runstate.__getitem__
        else:
            raw_flags = runstate.get('_optionflags', 0)
            if not isinstance(raw_flags, int):
                raise TypeError('_optionflags must be an integer')
            flags |= raw_flags & ~runtime_bound_mask
            lookup = runstate.__getitem__

        for runtime_key, flag in self._runtime_key_to_optionflag.items():
            try:
                value = lookup(runtime_key)
            except KeyError:
                continue
            if isinstance(value, bool) and value:
                flags |= flag
        return flags

    def optionflags_to_runtime_state(
        self,
        optionflags: int,
        default_state: directive.RuntimeStateDict | None = None,
    ) -> directive.RuntimeState:
        """
        Unpack an optionflags ``int`` into a fresh
        :class:`~xdoctest.directive.RuntimeState`.

        Builtin flag bits are reflected onto the structured runtime state.
        Any unregistered bits are preserved verbatim as checker-only
        optionflags so they can be delivered to a custom checker.
        """
        base_state = {} if default_state is None else dict(default_state)
        for runtime_key in self._runtime_key_to_optionflag:
            if runtime_key == 'REQUIRES':
                continue
            base_state.setdefault(runtime_key, False)
        runstate = directive.RuntimeState(
            cast(directive.RuntimeStateDict, base_state)
        )
        runtime_bound_mask = self._runtime_bound_optionflag_mask()
        checker_only_flags = optionflags & ~runtime_bound_mask
        runstate.set_output_checker_flags(checker_only_flags)
        for flag, runtime_key in self._optionflag_to_runtime_key.items():
            value = runstate[runtime_key]
            if isinstance(value, bool) and (optionflags & flag):
                runstate[runtime_key] = True
        return runstate


_RUNTIME_FLAGS = RuntimeFlagFacade()


[docs] def register_optionflag(name: str, runtime_state_key: str | None = None) -> int: """ Register an optionflag with xdoctest. See :meth:`RuntimeFlagFacade.register_optionflag`. Calling this with an existing name is idempotent. Example: >>> register_optionflag('FLOAT_CMP') == FLOAT_CMP True """ return _RUNTIME_FLAGS.register_optionflag(name, runtime_state_key)
[docs] def runtime_state_to_optionflags( runstate: directive.RuntimeState | Mapping[str, object] | None, ) -> int: """Pack structured runtime state into a stdlib optionflag mask. Example: >>> state = directive.RuntimeState({'ELLIPSIS': True}) >>> bool(runtime_state_to_optionflags(state) & ELLIPSIS) True """ return _RUNTIME_FLAGS.runtime_state_to_optionflags(runstate)
[docs] def optionflags_to_runtime_state( optionflags: int, default_state: directive.RuntimeStateDict | None = None, ) -> directive.RuntimeState: """Unpack a complete stdlib optionflag mask into runtime state. Example: >>> state = optionflags_to_runtime_state(0) >>> state['ELLIPSIS'], state['NORMALIZE_WHITESPACE'] (False, False) >>> optionflags_to_runtime_state(SKIP)['SKIP'] True """ return _RUNTIME_FLAGS.optionflags_to_runtime_state(optionflags, default_state)
def is_registered_optionflag(name: str) -> bool: """Check whether an optionflag name is known to the facade. Example: >>> is_registered_optionflag('ELLIPSIS') True """ return _RUNTIME_FLAGS.is_registered_optionflag(name) def get_optionflag(name: str) -> int: """Look up the integer bit assigned to a registered optionflag. Example: >>> get_optionflag('ELLIPSIS') == ELLIPSIS True """ return _RUNTIME_FLAGS.get_optionflag(name) def get_registered_optionflags() -> dict[str, int]: """Return a copy of the optionflag registry. Example: >>> flags = get_registered_optionflags() >>> flags['FLOAT_CMP'] == FLOAT_CMP True """ return _RUNTIME_FLAGS.get_registered_optionflags() _RUNTIME_FLAGS.register_builtin_optionflag( 'DONT_ACCEPT_BLANKLINE', DONT_ACCEPT_BLANKLINE, 'DONT_ACCEPT_BLANKLINE' ) _RUNTIME_FLAGS.register_builtin_optionflag( 'NORMALIZE_WHITESPACE', NORMALIZE_WHITESPACE, 'NORMALIZE_WHITESPACE' ) _RUNTIME_FLAGS.register_builtin_optionflag('ELLIPSIS', ELLIPSIS, 'ELLIPSIS') _RUNTIME_FLAGS.register_builtin_optionflag('SKIP', SKIP, 'SKIP') _RUNTIME_FLAGS.register_builtin_optionflag( 'IGNORE_EXCEPTION_DETAIL', IGNORE_EXCEPTION_DETAIL, 'IGNORE_EXCEPTION_DETAIL', ) _RUNTIME_FLAGS.register_builtin_optionflag('REPORT_UDIFF', REPORT_UDIFF, 'REPORT_UDIFF') _RUNTIME_FLAGS.register_builtin_optionflag('REPORT_CDIFF', REPORT_CDIFF, 'REPORT_CDIFF') _RUNTIME_FLAGS.register_builtin_optionflag('REPORT_NDIFF', REPORT_NDIFF, 'REPORT_NDIFF') IGNORE_WANT = register_optionflag('IGNORE_WANT', 'IGNORE_WANT') IGNORE_OUTPUT = register_optionflag('IGNORE_OUTPUT', 'IGNORE_OUTPUT') IGNORE_WHITESPACE = register_optionflag('IGNORE_WHITESPACE', 'IGNORE_WHITESPACE') FLOAT_CMP = register_optionflag('FLOAT_CMP', 'FLOAT_CMP') NORMALIZE_REPR = register_optionflag('NORMALIZE_REPR', 'NORMALIZE_REPR') IGNORE_WARNINGS = register_optionflag('IGNORE_WARNINGS', 'IGNORE_WARNINGS') SHOW_WARNINGS = register_optionflag('SHOW_WARNINGS', 'SHOW_WARNINGS') __all__ = [ 'BLANKLINE_MARKER', 'ELLIPSIS_MARKER', 'DONT_ACCEPT_BLANKLINE', 'NORMALIZE_WHITESPACE', 'ELLIPSIS', 'SKIP', 'IGNORE_EXCEPTION_DETAIL', 'REPORT_UDIFF', 'REPORT_CDIFF', 'REPORT_NDIFF', 'IGNORE_WANT', 'IGNORE_OUTPUT', 'IGNORE_WHITESPACE', 'FLOAT_CMP', 'NORMALIZE_REPR', 'IGNORE_WARNINGS', 'SHOW_WARNINGS', 'register_optionflag', 'runtime_state_to_optionflags', 'optionflags_to_runtime_state', 'is_registered_optionflag', 'get_optionflag', 'get_registered_optionflags', ]