Source code for sphinx_uml.pyreverse.main

"""
Based on the :py:mod:`pylint.pyreverse.main` module.
Under Debian, see the
``/usr/lib/python3/dist-packages/pylint/pyreverse/main.py``

**Background:**

We must overlad the :py:class:`pylint.pyreverse.main.Run` class as we need
an extra option to set the directory where Sphinx stores its output HTML files.

However, adding this option is not straightforward.
The :py:meth:`pylint.pyreverse.main.Run.__init__` constructor is monolotic
and runs the following operations:

- parsing the arguments from the CLI;
- initializing the configuration needed by the :py:meth:`pyreverse.run` method;
- calling the :py:meth:`pylint.pyreverse.main.Run.run` method with the
  remaining arguments;
- exiting the program using the :py:func:`sys.exit` function!

This design prevents to call `pyreverse` multiple times, which is needed to
process by a Sphinx extension producing muliple UML diagrams.

To address this problem, this file scatters those steps as follows:

- The :py:class:`ParsePyreverseArgs` class parses the arguments;
- The :py:class:`Run.__init__` constructor takes in parameter the configuration
  obtained from the :py:class:`ParsePyreverseArgs` class;
- The :py:class:`Run.run` constructor takes in parameter the remaining
  CLI arguments, obtained from the :py:attr:`ParsePyreverseArgs.remaining_args`
  attribute;
"""

# Inherited imports
from pylint.pyreverse.main import (
    augmented_sys_path,
    check_graphviz_availability,
    check_if_graphviz_supports_format,
    DiadefsHandler,
    DIRECTLY_SUPPORTED_FORMATS,
    # discover_package_path,
    insert_default_options,
    Linker,
    project_from_files,
    writer,
)

from pylint.config.arguments_manager import _ArgumentsManager
from pylint.config.arguments_provider import _ArgumentsProvider

# Custom imports
import argparse
from importlib.metadata import version
from packaging.version import Version
from pylint.pyreverse.main import Run as _Run, OPTIONS as _OPTIONS
from .dot_printer import DotPrinter
from .sphinx_html_proxy import SphinxHtmlProxy


# << Added options
OPTIONS = [
    option
    for option in sorted(list(_OPTIONS) + [
        (
            "sphinx-html-dir",
            {
                "default": "",
                "type": "path",
                "short": "d",
                "action": "store",
                "metavar": "<input_directory>",
                "help": (
                    "set the root directory of the HTML "
                    "documentation generated by Sphinx."
                ),
            },
        )
    ])
]
# >>


[docs] class ParsePyreverseArgs(_Run): options = OPTIONS name = "pyreverse" # << Based on _Run.__init__ def __init__(self, args: list[str]): """ Constructor. Based on :py:meth:`pylint.pyreverse.main.Run.__init__`. Args: args (list[str]): The arguments passed to the script. *Example:* ``sys.argv[1:]``. """ _ArgumentsManager.__init__(self, prog="pyreverse", description=__doc__) _ArgumentsProvider.__init__(self, self) # Parse options insert_default_options() # << We need to save them to call Run.run(...) self.remaining_args = self._parse_command_line_configuration(args) # >> if self.config.output_format not in DIRECTLY_SUPPORTED_FORMATS: check_graphviz_availability() print( f"Format {self.config.output_format} isn't supported natively." " pyreverse2 will try to generate it using Graphviz..." ) check_if_graphviz_supports_format(self.config.output_format)
[docs] class Run: def __init__(self, config: argparse.Namespace): """ Constructor. Args: config (argparse.Namespace): The configuration parsed from the command line. See also the ;py:class;`ParsePyreverseArgs` class. Returns: The execution code (``0`` means everything is fine). """ # pylint.pyreverse.main.Run is meant to be called only once # so let's rewrite __init__: self.config = config
[docs] def diadefs(self, args: list[str]): # Squeeze extra_packages_paths, because it explores src/ # TODO: We should see what pyreverse does # (especially, what is the value of self.config.source_roots) # << # extra_packages_paths = list({ # discover_package_path( # arg, # example.module.submodule.c1 # self.config.source_roots # () # ) # for arg in args # }) # === extra_packages_paths = list() # >> with augmented_sys_path(extra_packages_paths): project = project_from_files( args, project_name=self.config.project, black_list=self.config.ignore_list, verbose=self.config.verbose, ) linker = Linker(project, tag=True) if Version(version("pylint")) < Version("4.0.0"): handler = DiadefsHandler(self.config) else: handler = DiadefsHandler(self.config, args=list()) diadefs = handler.get_diadefs(project, linker) return diadefs
[docs] def run(self, args: list[str]) -> int: """ Checks the arguments and pyreverses the input project. Args: args (list[str]): The remaining arguments, that are not yet handled by the constructor, typically, the class or module being pyreversed. *Example:* ``['example.module.submodule.c1']``. """ dwriter = writer.DiagramWriter(self.config) # << Writer hijacking dwriter.printer_class = DotPrinter dwriter.api_doc = SphinxHtmlProxy() dwriter.api_doc.set_sphinx_html_dir(self.config.sphinx_html_dir) # NB: dwriter.write calls DotPrinter.__init__(...) diadefs = self.diadefs(args) # >> dwriter.write(diadefs) return 0