third_party.pylibs.pylint.src/pylint/pyreverse/printer.py
2021-08-20 21:26:08 -07:00

132 lines
3.7 KiB
Python

# Copyright (c) 2021 Ashley Whetter <ashley@awhetter.co.uk>
# Copyright (c) 2021 Pierre Sassoulas <pierre.sassoulas@gmail.com>
# Copyright (c) 2021 Marc Mueller <30130371+cdce8p@users.noreply.github.com>
# Copyright (c) 2021 Andreas Finkler <andi.finkler@gmail.com>
# Licensed under the GPL: https://www.gnu.org/licenses/old-licenses/gpl-2.0.html
# For details: https://github.com/PyCQA/pylint/blob/main/LICENSE
"""
Base class defining the interface for a printer.
"""
from abc import ABC, abstractmethod
from enum import Enum
from typing import List, NamedTuple, Optional
from astroid import nodes
from pylint.pyreverse.utils import get_annotation_label
class NodeType(Enum):
CLASS = "class"
INTERFACE = "interface"
PACKAGE = "package"
class EdgeType(Enum):
INHERITS = "inherits"
IMPLEMENTS = "implements"
ASSOCIATION = "association"
USES = "uses"
class Layout(Enum):
LEFT_TO_RIGHT = "LR"
RIGHT_TO_LEFT = "RL"
TOP_TO_BOTTOM = "TB"
BOTTOM_TO_TOP = "BT"
class NodeProperties(NamedTuple):
label: str
attrs: Optional[List[str]] = None
methods: Optional[List[nodes.FunctionDef]] = None
color: Optional[str] = None
fontcolor: Optional[str] = None
class Printer(ABC):
"""Base class defining the interface for a printer"""
def __init__(
self,
title: str,
layout: Optional[Layout] = None,
use_automatic_namespace: Optional[bool] = None,
):
self.title: str = title
self.layout = layout
self.use_automatic_namespace = use_automatic_namespace
self.lines: List[str] = []
self._indent = ""
self._open_graph()
def _inc_indent(self):
"""increment indentation"""
self._indent += " "
def _dec_indent(self):
"""decrement indentation"""
self._indent = self._indent[:-2]
@abstractmethod
def _open_graph(self) -> None:
"""Emit the header lines, i.e. all boilerplate code that defines things like layout etc."""
def emit(self, line: str, force_newline: Optional[bool] = True) -> None:
if force_newline and not line.endswith("\n"):
line += "\n"
self.lines.append(self._indent + line)
@abstractmethod
def emit_node(
self,
name: str,
type_: NodeType,
properties: Optional[NodeProperties] = None,
) -> None:
"""Create a new node. Nodes can be classes, packages, participants etc."""
@abstractmethod
def emit_edge(
self,
from_node: str,
to_node: str,
type_: EdgeType,
label: Optional[str] = None,
) -> None:
"""Create an edge from one node to another to display relationships."""
@staticmethod
def _get_method_arguments(method: nodes.FunctionDef) -> List[str]:
if method.args.args:
arguments: List[nodes.AssignName] = [
arg for arg in method.args.args if arg.name != "self"
]
else:
arguments = []
annotations = dict(zip(arguments, method.args.annotations[1:]))
for arg in arguments:
annotation_label = ""
ann = annotations.get(arg)
if ann:
annotation_label = get_annotation_label(ann)
annotations[arg] = annotation_label
return [
f"{arg.name}: {ann}" if ann else f"{arg.name}"
for arg, ann in annotations.items()
]
def generate(self, outputfile: str) -> None:
"""Generate and save the final outputfile."""
self._close_graph()
with open(outputfile, "w", encoding="utf-8") as outfile:
outfile.writelines(self.lines)
@abstractmethod
def _close_graph(self) -> None:
"""Emit the lines needed to properly close the graph."""