Source code for pytableaux.proof.writers

# -*- coding: utf-8 -*-
# pytableaux, a multi-logic proof generator.
# Copyright (C) 2014-2023 Doug Owings.
# 
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
# 
# This program 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 Affero General Public License for more details.
# 
# You should have received a copy of the GNU Affero General Public License
# along with this program.  If not, see <http://www.gnu.org/licenses/>.
"""
pytableaux.proof.writers
========================

"""
from __future__ import annotations

from abc import abstractmethod
from typing import TYPE_CHECKING, Callable, MutableMapping, Self, TypeVar

from ...errors import Emsg, check
from ...lang import LexWriter, Notation, StringTable
from ...tools import EMPTY_MAP, MapCover, abcs
from ..tableaux import Tableau

if TYPE_CHECKING:
    from typing import overload

NOARG = object()
_T = TypeVar('_T')
_TWT = TypeVar('_TWT', bound='TabWriter')

__all__ = (
    'registries',
    'registry',
    'TabWriter',
    'TabWriterRegistry')

class TabWriterMeta(abcs.AbcMeta):

    def __call__(cls: type[_T]|Self, *args, **kw) -> _T:
        if cls is not TabWriter:
            return super().__call__(*args, **kw)
        try:
            reg = registries['default']
        except KeyError:
            reg = registry
        if args:
            fmt, *args = args
        elif 'format' in kw:
            fmt = kw.pop('format')
        else:
            return reg.default(*args, **kw)
        try:
            reg[fmt]
        except KeyError:
            for reg in registries.values():
                if fmt in reg:
                    break
            else:
                raise
        return reg[fmt](*args, **kw)

[docs] class TabWriter(metaclass=TabWriterMeta): """Tableau writer base class. Constructing a ``TabWriter``. Examples:: >>> # make an instance of the default writer class, with the default notation. >>> writer = TabWriter() >>> # make an HtmlTabWriter, with the default notation. >>> writer = TabWriter('html') >>> # make an HtmlTabWriter, with standard notation. >>> writer = TabWriter('html', 'standard') """ engine: str = 'unknown' "The writer engine (jinja, doctree, etc.)" format: str "The format registry identifier." file_extension: str "The file extension for output files." defaults = EMPTY_MAP "Default options." lw: LexWriter "The writer's `LexWriter` instance." opts: dict "The writer's options." __slots__ = ('lw', 'opts') def __init__(self, notation: Notation = None, strings: StringTable|None = None, *, lw: LexWriter = None, **opts): """__init__(self, format: str|None = None, notation: Notation = None, strings: StringTable|None = None, *, lw: LexWriter = None, **opts) Create a TabWriter Args: format (str): The format, e.g. 'html', 'latex' notation (str|Notation): The notation strings (StringTable): A specific string table to use Keyword Args: lw (LexWriter): A specific LexWriter to use **opts: Options to pass to the TabWriter """ if lw is None: if notation is None: notation = LexWriter.DEFAULT_NOTATION else: notation = Notation(notation) lw = LexWriter(notation=notation, format=self.format, strings=strings, **opts) elif lw.format != self.format or strings is not None and strings.format != self.format: raise Emsg.ValueConflict(lw.format, self.format) self.lw = lw self.opts = dict(self.defaults) | opts def attachments(self, /): return EMPTY_MAP @abstractmethod def __call__(self, tab: Tableau, **kw) -> str: raise NotImplementedError def __init_subclass__(cls) -> None: super().__init_subclass__() try: cls.file_extension except AttributeError: try: cls.file_extension = cls.format except AttributeError: pass
[docs] class TabWriterRegistry(MapCover[str, type[TabWriter]], MutableMapping[str, type[TabWriter]]): "A tableau writer class registry." __slots__ = ('default', 'name', 'register', '__delitem__') name: str|None default: type[TabWriter]|None def __init__(self, *, name: str|None = None): super().__init__(mapping := {}) self.name = name self.default = None def register(cls=NOARG, /, *, key=None, force=False, default=None): if cls is NOARG: return lambda cls: register(cls, key=key, force=force, default=default) if abcs.isabstract(cls := check.subcls(cls, TabWriter)): raise TypeError(f'Cannot register abstract class: {cls}') if key is None: key = cls.format if not force and key in self: raise KeyError(f"Format/key {key} already registered") mapping[check.inst(key, str)] = cls if default or default is None and not self.default: self.default = cls return cls self.register = register self.__delitem__ = mapping.__delitem__ def __setitem__(self, key, value): self.register(value, key=key, force=True) if TYPE_CHECKING: @overload def register(self, cls:type[_TWT], /, *, key:str=..., force:bool=..., default:bool|None=...) -> type[_TWT]: """Register a ``TabWriter`` class. Returns the argument, so it can be used as a decorator. Args: cls: The writer class. Keyword Args: key: An alternate key to store, default is the writer's format. force: Replace format/key if exists, default False. default: Set as default writer. Returns: The writer class. """ @overload def register(self, /, *, key:str=..., force:bool=..., default:bool|None=...) -> Callable[[type[_TWT]], type[_TWT]]: """Decorator factory for registering with options. Keyword Args: key: An alternate key to store, default is the writer's format. force: Replace format/key if exists. default: Set as default writer. Returns: Class decorator. """
registry = TabWriterRegistry(name='default') "The default tableau writer class registry." registries = {registry.name: registry} try: from . import jinja as jinja except ImportError: import traceback import warnings traceback.print_exc() warnings.warn('Failed to import jinja') else: registries[jinja.registry.name] = jinja.registry registry.update(jinja.registry) from . import doctree registries[doctree.registry.name] = doctree.registry registry.update(doctree.registry) registry.default = doctree.registry.default