"""This module provides a container for global variables of a single parse. It uses the `threading.local` object to ensure that global variables are not changed by different threads. """ from collections import OrderedDict from collections.abc import MutableSet from copy import deepcopy from importlib import import_module import logging from threading import local from typing import Optional THREAD = local() LOGGER = logging.getLogger(__name__) class OrderedSet(MutableSet): """An ordered set, optimized for `a in set` tests""" def __init__(self, iterable=()): self._items = OrderedDict((t, None) for t in iterable) def __repr__(self): return list(self._items).__repr__() def __contains__(self, item): return item in self._items def __iter__(self): for item in self._items: yield item def __len__(self): return len(self._items) def add(self, item): if item not in self._items: self._items[item] = None def discard(self, item): self._items.pop(item, None) def insert(self, index, item): item_list = list(self._items.items()) item_list.insert(index, (item, None)) self._items = OrderedDict(item_list) def insert_after(self, item, after_item): assert after_item in self._items, after_item indx = list(self._items.keys()).index(after_item) + 1 token_list = list(self._items.items()) token_list.insert(indx, (item, None)) self._items = OrderedDict(token_list) def insert_before(self, item, before_item): assert before_item in self._items indx = list(self._items.keys()).index(before_item) token_list = list(self._items.items()) token_list.insert(indx, (item, None)) self._items = OrderedDict(token_list) [docs]class ParseContext: """A class to contain context for a single parse. :param find_blocks: a list of block tokens to use during the parse. If None, the standard blocks will be used from `BaseRenderer.default_block_token`. :param find_spans: a list of span tokens to use during the parse. If None, the standard blocks will be used from `BaseRenderer.default_span_tokens`. :param link_definitions: a dict of link definitons, obtained from `[def]: link` :param foot_definitions: a dict of footnote definitons, obtained from `[^def]: link` (if Footnote token active) :param nesting_matches: a dict of matches recorded from `find_nested_tokenizer` """ def __init__( self, find_blocks=None, find_spans=None, link_definitions=None, foot_definitions=None, logger: Optional[logging.Logger] = None, ): # tokens used for matching if find_blocks is not None: self.block_tokens = OrderedSet(tokens_from_classes(find_blocks)) else: from mistletoe.renderers.base import BaseRenderer self.block_tokens = OrderedSet(BaseRenderer.default_block_tokens) if find_spans is not None: self.span_tokens = OrderedSet(tokens_from_classes(find_spans)) else: from mistletoe.renderers.base import BaseRenderer self.span_tokens = OrderedSet(BaseRenderer.default_span_tokens) # definition references, collected during parsing if link_definitions is None: self._link_definitions = {} else: self._link_definitions = link_definitions if foot_definitions is None: self._foot_definitions = OrderedDict() else: self._foot_definitions = foot_definitions self.nesting_matches = {} self._foot_references = OrderedSet() if logger is None: logger = LOGGER self._logger = logger def __repr__(self): return "{0}(block_cls={1},span_cls={2},link_defs={3},footnotes={4})".format( self.__class__.__name__, len(self.block_tokens), len(self.span_tokens), len(self.link_definitions), len(self.foot_definitions), ) @property def link_definitions(self) -> dict: return self._link_definitions @property def foot_definitions(self) -> dict: return self._foot_definitions @property def foot_references(self) -> OrderedSet: return self._foot_references @property def logger(self) -> logging.Logger: return self._logger @logger.setter def logger(self, logger: logging.Logger): self._logger = logger [docs] def reset_definitions(self): self._link_definitions = {} self._foot_definitions = {} self._foot_references = OrderedSet() [docs] def copy(self): return deepcopy(self) [docs]def get_parse_context(reset=False) -> ParseContext: """Return the current ``ParseContext`` (one per thread).""" global THREAD if reset: THREAD.context = ParseContext() else: try: return THREAD.context except AttributeError: THREAD.context = ParseContext() return THREAD.context [docs]def set_parse_context(parse_context): """Set an existing ``ParseContext`` (one per thread).""" global THREAD THREAD.context = parse_context [docs]def tokens_from_module(module): """ Helper method; takes a module and returns a list of all token classes specified in module.__all__. Useful when custom tokens are defined in single module. """ return [getattr(module, name) for name in module.__all__] [docs]def tokens_from_classes(classes): """ Helper method; take a list of classes and/or class paths (e.g. `mistletoe.span_tokens.Math`) and return the loaded classes. """ return [ getattr(import_module(".".join(cls.split(".")[:-1])), cls.split(".")[-1]) if isinstance(cls, str) else cls for cls in classes ]