123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198 |
- """Together with :mod:`~configupdater.container` this module forms the basis of
- the class hierarchy in **ConfigUpdater**.
- The :class:`Block` is the parent class of everything that can be nested inside a
- configuration file, e.g. comments, sections, options and even sequences of white space.
- """
- import sys
- from copy import deepcopy
- from inspect import cleandoc
- from typing import TYPE_CHECKING, Optional, TypeVar, Union, cast
- if sys.version_info[:2] >= (3, 9): # pragma: no cover
- List = list
- else: # pragma: no cover
- from typing import List
- if TYPE_CHECKING:
- from .builder import BlockBuilder
- from .container import Container
- T = TypeVar("T")
- B = TypeVar("B", bound="Block")
- def _short_repr(block) -> str:
- if isinstance(block, str):
- return block
- name = getattr(block, "raw_key", None) or getattr(block, "name", None)
- name = f" {name!r}" if name else ""
- return f"<{block.__class__.__name__}{name}>"
- class NotAttachedError(Exception):
- """{block} is not attached to a container yet. Try to insert it first."""
- def __init__(self, block: Union[str, "Block"] = "The block"):
- doc = cleandoc(cast(str, self.__class__.__doc__))
- msg = doc.format(block=_short_repr(block))
- super().__init__(msg)
- class AlreadyAttachedError(Exception):
- """{block} has been already attached to a container.
- Try to remove it first using ``detach`` or create a copy using stdlib's
- ``copy.deepcopy``.
- """
- def __init__(self, block: Union[str, "Block"] = "The block"):
- doc = cleandoc(cast(str, self.__class__.__doc__))
- msg = doc.format(block=_short_repr(block))
- super().__init__(msg)
- class AssignMultilineValueError(Exception):
- """Trying to assign a multi-line value to {block}.
- Use the ``set_values`` or ``append`` method to accomplish that.
- """
- def __init__(self, block: Union[str, "Block"] = "The block"):
- doc = cleandoc(cast(str, self.__class__.__doc__))
- msg = doc.format(block=_short_repr(block))
- super().__init__(msg)
- class Block:
- """Abstract Block type holding lines
- Block objects hold original lines from the configuration file and hold
- a reference to a container wherein the object resides.
- The type variable ``T`` is a reference for the type of the sibling blocks
- inside the container.
- """
- def __init__(self, container: Optional["Container"] = None):
- self._container = container
- self._lines: List[str] = []
- self._updated = False
- def __str__(self) -> str:
- return "".join(self._lines)
- def __repr__(self) -> str:
- return f"<{self.__class__.__name__}: {str(self)!r}>"
- def __eq__(self, other) -> bool:
- if isinstance(other, self.__class__):
- return str(self) == str(other)
- else:
- return False
- def __deepcopy__(self: B, memo: dict) -> B:
- clone = self._instantiate_copy()
- clone._lines = deepcopy(self._lines, memo)
- clone._updated = self._updated
- memo[id(self)] = clone
- return clone
- def _instantiate_copy(self: B) -> B:
- """Auxiliary method that allows subclasses calling ``__deepcopy__``"""
- return self.__class__(container=None) # allow overwrite for different init args
- # ^ A fresh copy should always be made detached from any container
- def add_line(self: B, line: str) -> B:
- """PRIVATE: this function is not part of the public API of Block.
- It is only used internally by other classes of the package during parsing.
- Add a line to the current block
- Args:
- line (str): one line to add
- """
- self._lines.append(line)
- return self
- @property
- def lines(self) -> List[str]:
- return self._lines
- @property
- def container(self) -> "Container":
- """Container holding the block"""
- if self._container is None:
- raise NotAttachedError(self)
- return self._container
- @property
- def container_idx(self: B) -> int:
- """Index of the block within its container"""
- return self.container.structure.index(self)
- @property
- def updated(self) -> bool:
- """True if the option was changed/updated, otherwise False"""
- # if no lines were added, treat it as updated since we added it
- return self._updated or not self._lines
- def _builder(self, idx: int) -> "BlockBuilder":
- from .builder import BlockBuilder
- return BlockBuilder(self.container, idx)
- @property
- def add_before(self) -> "BlockBuilder":
- """Block builder inserting a new block before the current block"""
- return self._builder(self.container_idx)
- @property
- def add_after(self) -> "BlockBuilder":
- """Block builder inserting a new block after the current block"""
- return self._builder(self.container_idx + 1)
- @property
- def next_block(self) -> Optional["Block"]:
- """Next block in the current container"""
- idx = self.container_idx + 1
- if idx < len(self.container):
- return self.container.structure[idx]
- else:
- return None
- @property
- def previous_block(self) -> Optional["Block"]:
- """Previous block in the current container"""
- idx = self.container_idx - 1
- if idx >= 0:
- return self.container.structure[idx]
- else:
- return None
- def detach(self: B) -> B:
- """Remove and return this block from container"""
- self.container._remove_block(self.container_idx)
- self._container = None
- return self
- def has_container(self) -> bool:
- """Checks if this block has a container attached"""
- return not (self._container is None)
- def attach(self: B, container: "Container") -> B:
- """PRIVATE: Don't use this as a user!
- Rather use `add_*` or the bracket notation
- """
- if self._container is not None and self._container is not container:
- raise AlreadyAttachedError(self)
- self._container = container
- return self
- class Comment(Block):
- """Comment block"""
- class Space(Block):
- """Vertical space block of new lines"""
|