Source code for bpc_utils.context
"""BPC conversion context."""
import abc
import parso
from .misc import Config, UUID4Generator
from .typing import Callable, Linesep, Tuple, TypeVar
BaseContextType = TypeVar('BaseContextType', bound='BaseContext')
[docs]class BaseContext(abc.ABC):
"""Abstract base class for general conversion context."""
def __init__(self, node: parso.tree.NodeOrLeaf, config: Config, *,
indent_level: int = 0, raw: bool = False) -> None:
"""Initialize BaseContext.
Args:
node: parso AST
config (:class:`~bpc_utils.Config`): conversion configurations
indent_level: current indentation level
raw: raw processing flag
"""
#: :class:`~bpc_utils.Config`: Internal configurations.
self.config = config
#: str: Indentation sequence.
self._indentation = config.indentation # type: str # type: ignore[attr-defined]
#: :data:`~bpc_utils.Linesep`: Line seperator.
self._linesep = config.linesep # type: Linesep # type: ignore[attr-defined]
#: bool: :pep:`8` compliant conversion flag.
self._pep8 = config.pep8 # type: bool # type: ignore[attr-defined]
#: parso.tree.NodeOrLeaf: Root node given by the ``node`` parameter.
self._root = node
#: int: Current indentation level.
self._indent_level = indent_level
#: UUID4Generator: UUID generator.
self._uuid_gen = UUID4Generator(dash=False)
#: str: Code before insertion point.
self._prefix = ''
#: str: Code after insertion point.
self._suffix = ''
#: str: Final converted result.
self._buffer = ''
#: bool: Flag if buffer is now :attr:`self._prefix <bpc_utils.BaseContext._prefix>`.
self._prefix_or_suffix = True
#: Optional[parso.tree.NodeOrLeaf]: Preceding node with the target expression, i.e. the *insertion point*.
self._node_before_expr = None
self._walk(node) # traverse children
if raw:
self._buffer = self._prefix + self._suffix
else:
self._concat() # generate final result
[docs] def __iadd__(self: BaseContextType, code: str) -> BaseContextType:
"""Support of the ``+=`` operator.
If :attr:`self._prefix_or_suffix <bpc_utils.BaseContext._prefix_or_suffix>` is :data:`True`,
then the ``code`` will be appended to :attr:`self._prefix <bpc_utils.BaseContext._prefix>`;
else it will be appended to :attr:`self._suffix <bpc_utils.BaseContext._suffix>`.
Args:
code: code string
Returns:
BaseContext: self
"""
if self._prefix_or_suffix:
self._prefix += code
else:
self._suffix += code
return self
[docs] def __str__(self) -> str:
"""Returns a *stripped* version of :attr:`self._buffer <bpc_utils.BaseContext._buffer>`."""
return self._buffer.strip()
@property
def string(self) -> str:
"""Returns conversion buffer (:attr:`self._buffer <bpc_utils.BaseContext._buffer>`)."""
return self._buffer
[docs] def _walk(self, node: parso.tree.NodeOrLeaf) -> None:
"""Start traversing the AST module.
The method traverses through all *children* of ``node``. It first checks
if such child has the target expression. If so, it will toggle
:attr:`self._prefix_or_suffix <bpc_utils.BaseContext._prefix_or_suffix>`
(set to :data:`False`) and save the last previous child as
:attr:`self._node_before_expr <bpc_utils.BaseContext._node_before_expr>`.
Then it processes the child with :meth:`self._process <bpc_utils.BaseContext._process>`.
Args:
node: parso AST
"""
# process node
if hasattr(node, 'children'):
last_node = None
for child in node.children: # type: ignore[attr-defined]
if self.has_expr(child):
self._prefix_or_suffix = False
self._node_before_expr = last_node
self._process(child)
last_node = child
return
# preserve leaf node as is by default
self += node.get_code()
[docs] def _process(self, node: parso.tree.NodeOrLeaf) -> None:
"""Recursively process parso AST.
All processing methods for a specific ``node`` type are defined as
``_process_{type}``. This method first checks if such processing
method exists. If so, it will call such method on the ``node``;
otherwise it will traverse through all *children* of ``node``, and perform
the same logic on each child.
Args:
node: parso AST
"""
func_name = '_process_%s' % node.type
if hasattr(self, func_name):
func = getattr(self, func_name) # type: Callable[[parso.tree.NodeOrLeaf], None]
func(node)
return
if hasattr(node, 'children'):
for child in node.children: # type: ignore[attr-defined]
self._process(child)
return
# preserve leaf node as is by default
self += node.get_code()
[docs] @abc.abstractmethod
def _concat(self) -> None:
"""Concatenate final string."""
raise NotImplementedError # pragma: no cover
[docs] @abc.abstractmethod
def has_expr(self, node: parso.tree.NodeOrLeaf) -> bool:
"""Check if node has the target expression.
Args:
node: parso AST
Returns:
whether ``node`` has the target expression
"""
raise NotImplementedError # pragma: no cover
[docs] @staticmethod
def missing_newlines(prefix: str, suffix: str, expected: int, linesep: Linesep) -> int:
"""Count missing blank lines for code insertion given surrounding code.
Args:
prefix: preceding source code
suffix: succeeding source code
expected: number of expected blank lines
linesep (:data:`~bpc_utils.Linesep`): line seperator
Returns:
number of blank lines to add
"""
current = 0
# count trailing newlines in `prefix`
if prefix:
for line in reversed(prefix.split(linesep)): # pragma: no branch
if line.strip():
break
current += 1
if current > 0: # keep a trailing newline in `prefix`
current -= 1
# count leading newlines in `suffix`
if suffix:
for line in suffix.split(linesep): # pragma: no branch
if line.strip():
break
current += 1
missing = expected - current
return max(missing, 0)
__all__ = ['BaseContext']