You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
151 lines
4.3 KiB
Python
151 lines
4.3 KiB
Python
"""This module defines the scope, a unit of indented text."""
|
|
from __future__ import annotations
|
|
|
|
import dataclasses
|
|
import logging
|
|
from typing import Any
|
|
from typing import Literal
|
|
|
|
from .helper import TypeHintedVariable
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
INDENT_SPACES = 4
|
|
|
|
|
|
class NoParentError(Exception):
|
|
"""Raised, when the parent of a scope is not available."""
|
|
|
|
|
|
@dataclasses.dataclass
|
|
class Scope:
|
|
"""A scope within the output .pyi file.
|
|
|
|
Scopes contain text and are indented by a certain amount. They often have parents, within which they are located.
|
|
|
|
Args:
|
|
name (str): The name of the scope. Use an empty name for the root scope ("").
|
|
id (int): A numerical identifier of the scope.
|
|
parent (Scope | None): The direct parent scope of this scope, if there is any.
|
|
return scope (Scope | None): The scope to which to return, when closing this one.
|
|
lines (list[str]): The list of text lines in this scope.
|
|
"""
|
|
|
|
name: str
|
|
id: int
|
|
parent: Scope | None
|
|
return_scope: Scope | None
|
|
lines: list[str] = dataclasses.field(default_factory=list)
|
|
|
|
def __post_init__(self):
|
|
"""Assures that, if this is the root scope, its name is empty."""
|
|
assert (self.is_root) == (self.name == "")
|
|
|
|
@property
|
|
def parents(self) -> list[Scope]:
|
|
"""A list of all parent scopes of this scope, starting from the first parent.
|
|
|
|
If the returned list is empty, this scope has no parents. The first parent in the list has no further
|
|
parents, it is the root scope.
|
|
"""
|
|
parents: list[Scope] = []
|
|
scope: Scope | None = self.parent
|
|
|
|
while scope is not None:
|
|
parents.append(scope)
|
|
scope = scope.parent
|
|
|
|
parents.reverse()
|
|
|
|
return parents
|
|
|
|
@property
|
|
def trace(self) -> list[Scope]:
|
|
"""A list of all scopes that lead to this scope, starting from the first parent.
|
|
|
|
The first parent has no further parents.
|
|
"""
|
|
return self.parents + [self]
|
|
|
|
@property
|
|
def root(self) -> Scope:
|
|
"""Get the root scope that has no further parents."""
|
|
if not self.parents:
|
|
return self
|
|
|
|
else:
|
|
return self.parents[0]
|
|
|
|
@property
|
|
def is_root(self) -> bool:
|
|
"""Determine, whether this is the root scope."""
|
|
return self.root == self
|
|
|
|
@property
|
|
def indent_spaces(self) -> int:
|
|
"""The number of spaces by which this scope is indented."""
|
|
return len(self.parents) * INDENT_SPACES
|
|
|
|
def add(self, content: str | TypeHintedVariable = ""):
|
|
"""Add content to this scope, taking into account the current indent spaces.
|
|
|
|
Args:
|
|
content (str | HintedVariable): The line or variable to add. Optional, defaults to "".
|
|
"""
|
|
if isinstance(content, TypeHintedVariable):
|
|
content = str(content)
|
|
|
|
if not content:
|
|
self.lines.append("")
|
|
|
|
else:
|
|
self.lines.append(" " * self.indent_spaces + content)
|
|
|
|
def trace_as_str(self, delimiter: Literal[".", "_"] = ".") -> str:
|
|
"""A string representation of this scope's relative trace.
|
|
|
|
Follow the trace of the scope, and connect parent scopes with a delimiter.
|
|
The root scope is not included in this trace string.
|
|
|
|
Args:
|
|
delimiter (Literal[".", "_"]): The delimiter to join the scope names with.
|
|
"""
|
|
return delimiter.join(scope.name for scope in self.trace if (not scope.is_root) and (scope.name))
|
|
|
|
def __repr__(self) -> str:
|
|
"""A string representation of this scope.
|
|
|
|
Follow the path of scopes, and connect parent scopes with '.'.
|
|
"""
|
|
return self.trace_as_str(".")
|
|
|
|
|
|
@dataclasses.dataclass
|
|
class CapnpType:
|
|
"""Represents a type that is extracted from a .capnp schema.
|
|
|
|
Args:
|
|
schema (Any):
|
|
name (str):
|
|
scope (Scope):
|
|
generic_params (list[str]):
|
|
"""
|
|
|
|
schema: Any
|
|
name: str
|
|
scope: Scope
|
|
generic_params: list[str] = dataclasses.field(default_factory=list)
|
|
|
|
@property
|
|
def scoped_name(self) -> str:
|
|
"""Extract the name of a type, taking into account its containing scope.
|
|
|
|
Returns:
|
|
str: The scoped type name.
|
|
"""
|
|
if not self.scope.is_root:
|
|
return f"{self.scope}.{self.name}"
|
|
|
|
else:
|
|
return self.name
|