mirror of https://github.com/mamba-org/mamba.git
Add repo related concepts to documentation (#1004)
* add repo, channel, repodata, subdir, tarball concepts refactor detailed ops to use mermaid js remove install svg * fix code quality add flake8 exception for mermaid, incompatible check vs isort
This commit is contained in:
parent
72a3d4efeb
commit
98e8b611a4
2
.flake8
2
.flake8
|
@ -1,4 +1,4 @@
|
|||
[flake8]
|
||||
max-line-length=88
|
||||
extend-ignore=E203,D104,D100,I004
|
||||
exclude=test/*
|
||||
exclude=test/*,docs/source/tools/*
|
||||
|
|
|
@ -20,11 +20,29 @@ The ``install`` operation is using a ``package specification`` to add/install ad
|
|||
|
||||
The workflow for that operation is:
|
||||
|
||||
- computing :ref:`configuration<configuration>`
|
||||
- fetching/downloading packages index from repositor(y)(ies)
|
||||
- solving
|
||||
- fetching/downloading packages tarballs
|
||||
- extracting packages
|
||||
- :ref:`linking<linking>` packages
|
||||
.. mermaid::
|
||||
:align: center
|
||||
|
||||
.. image:: install.svg
|
||||
%%{init: {'themeVariables':{'edgeLabelBackground':'white'}}}%%
|
||||
graph TD
|
||||
style start fill:#00000000,stroke:#00000000,color:#00000000;
|
||||
config[Compute configuration fa:fa-link]
|
||||
fetch_index[Fetch repositories index fa:fa-unlink]
|
||||
solve[Solving fa:fa-unlink]
|
||||
fetch_pkgs[Fetch packages fa:fa-unlink]
|
||||
extract[Extract tarballs fa:fa-unlink]
|
||||
link[Link fa:fa-link]
|
||||
stop([Stop])
|
||||
|
||||
start-->|User spec|config;
|
||||
config-->fetch_index;
|
||||
fetch_index-->solve;
|
||||
solve-->fetch_pkgs;
|
||||
fetch_pkgs-->extract;
|
||||
extract-->link;
|
||||
link-->stop;
|
||||
|
||||
click config href "../user_guide/configuration.html"
|
||||
click link href "./more_concepts.html#linking"
|
||||
|
||||
See also: :ref:`package tarball<tarball>`
|
||||
|
|
File diff suppressed because one or more lines are too long
Before Width: | Height: | Size: 8.7 KiB |
|
@ -11,6 +11,72 @@ Overview
|
|||
While not necessary to understand the basic usage, those ``advanced concepts`` are fundamental to understand Mamba in details.
|
||||
|
||||
|
||||
.. _repo:
|
||||
|
||||
Packages repository
|
||||
-------------------
|
||||
|
||||
| A packages repository, also called ``repo``, is a generic way to describe a storage location for software packages.
|
||||
|
||||
In the Mamba's context, it may points to a packages server, a :ref:`channel<channel>` or a :ref:`subdir<subdir>`.
|
||||
|
||||
|
||||
.. _channel:
|
||||
|
||||
Channel
|
||||
-------
|
||||
|
||||
| A ``channel`` is an independant and isolated :ref:`repo<repo>` structure that is used to classify and administrate more easily a packages server.
|
||||
|
||||
.. note::
|
||||
A packages server may host one or multiple ``channels``.
|
||||
|
||||
|
||||
.. _subdir:
|
||||
|
||||
Subdir
|
||||
------
|
||||
|
||||
| A ``subdir`` is a :ref:`channel<channel>` subdirectory specific to a given operating system/platform pair.
|
||||
|
||||
Mamba is a general purpose, langage agnostic package manager. The ``subdir`` structure is a convenient way to provide and access packages depending on the targeted os and platform.
|
||||
|
||||
Typically ``linux-64``, ``osx-arm64`` or ``win-64`` but not limited to those ones.
|
||||
|
||||
A ``subdir`` provides the packages tarballs alongside a :ref:`packages index<repodata>` including additional metadata.
|
||||
|
||||
.. note::
|
||||
In most cases, both ``noarch`` and ``<os>-<platform>`` subdirs are used for an operation requiring data from the :ref:`repo<repo>`
|
||||
|
||||
|
||||
.. _repodata:
|
||||
|
||||
Packages index
|
||||
--------------
|
||||
|
||||
| A repository package index is a file containing metadata about all the packages tarballs served by a :ref:`repo<repo>`.
|
||||
|
||||
.. note::
|
||||
Those metadata include license, file size, checksums, etc.
|
||||
|
||||
In Mamba, it is more often called a ``repodata`` in reference to the index filename ``repodata.json``.
|
||||
|
||||
A ``repodata`` is specific to a :ref:`channel subdirectory<subdir>`.
|
||||
|
||||
|
||||
.. _tarball:
|
||||
|
||||
Package tarball
|
||||
---------------
|
||||
|
||||
| A tarball is a single archive file, compressed or not, that expands to multiples files/directories. It is typically a ``zip`` file or so.
|
||||
|
||||
In the case of Mamba, 2 ``conda`` formats are used as package tarball:
|
||||
|
||||
- ``tar.bz2`` is the historical formats: a ``tar`` file/ball that has been compressed using ``bzip2`` algorithm
|
||||
- ``conda`` more recent format that allows faster access to packages metadata
|
||||
|
||||
|
||||
.. _linking:
|
||||
|
||||
Linking
|
||||
|
|
|
@ -11,7 +11,9 @@
|
|||
# documentation root, use os.path.abspath to make it absolute, like shown here.
|
||||
#
|
||||
# import os
|
||||
# import sys
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
# sys.path.insert(0, os.path.abspath('.'))
|
||||
|
||||
|
||||
|
@ -27,10 +29,13 @@ release = "0.5.2"
|
|||
|
||||
# -- General configuration ---------------------------------------------------
|
||||
|
||||
# Load local extensions (e.g. mermaid)
|
||||
sys.path.insert(0, str(Path.cwd().resolve() / "tools"))
|
||||
|
||||
# Add any Sphinx extension module names here, as strings. They can be
|
||||
# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom
|
||||
# ones.
|
||||
extensions = ["myst_parser"]
|
||||
extensions = ["mermaid", "mermaid_inheritance", "myst_parser"]
|
||||
|
||||
# Add any paths that contain templates here, relative to this directory.
|
||||
templates_path = ["_templates"]
|
||||
|
|
|
@ -0,0 +1,3 @@
|
|||
.mermaid svg {
|
||||
max-width: 800px;
|
||||
}
|
|
@ -0,0 +1,412 @@
|
|||
"""
|
||||
sphinx-mermaid
|
||||
~~~~~~~~~~~~~~~
|
||||
|
||||
https://github.com/mgaitan/sphinxcontrib-mermaid
|
||||
Modified for the purpose of CoSApp by the CoSApp team
|
||||
https://gitlab.com/cosapp/cosapp
|
||||
|
||||
Allow mermaid diagramas to be included in Sphinx-generated
|
||||
documents inline.
|
||||
|
||||
:copyright: Copyright 2016 by Martín Gaitán and others, see AUTHORS.
|
||||
:license: BSD, see LICENSE for details.
|
||||
"""
|
||||
|
||||
import codecs
|
||||
import os
|
||||
import posixpath
|
||||
from hashlib import sha1
|
||||
from subprocess import PIPE, Popen
|
||||
from tempfile import _get_default_tempdir
|
||||
|
||||
import sphinx
|
||||
from docutils import nodes
|
||||
from docutils.parsers.rst import Directive, directives
|
||||
from docutils.statemachine import ViewList
|
||||
from six import text_type
|
||||
from sphinx.application import Sphinx
|
||||
from sphinx.errors import SphinxError
|
||||
from sphinx.locale import _
|
||||
from sphinx.util import logging
|
||||
from sphinx.util.fileutil import copy_asset
|
||||
from sphinx.util.i18n import search_image_for_language
|
||||
from sphinx.util.osutil import ENOENT, ensuredir
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class MermaidError(SphinxError):
|
||||
category = "Mermaid error"
|
||||
|
||||
|
||||
class mermaid(nodes.General, nodes.Inline, nodes.Element):
|
||||
pass
|
||||
|
||||
|
||||
def figure_wrapper(directive, node, caption):
|
||||
figure_node = nodes.figure("", node)
|
||||
if "align" in node:
|
||||
figure_node["align"] = node.attributes.pop("align")
|
||||
|
||||
parsed = nodes.Element()
|
||||
directive.state.nested_parse(
|
||||
ViewList([caption], source=""), directive.content_offset, parsed
|
||||
)
|
||||
caption_node = nodes.caption(parsed[0].rawsource, "", *parsed[0].children)
|
||||
caption_node.source = parsed[0].source
|
||||
caption_node.line = parsed[0].line
|
||||
figure_node += caption_node
|
||||
return figure_node
|
||||
|
||||
|
||||
def align_spec(argument):
|
||||
return directives.choice(argument, ("left", "center", "right"))
|
||||
|
||||
|
||||
class Mermaid(Directive):
|
||||
"""
|
||||
Directive to insert arbitrary Mermaid markup.
|
||||
"""
|
||||
|
||||
has_content = True
|
||||
required_arguments = 0
|
||||
optional_arguments = 1
|
||||
final_argument_whitespace = False
|
||||
option_spec = {
|
||||
"alt": directives.unchanged,
|
||||
"align": align_spec,
|
||||
"caption": directives.unchanged,
|
||||
}
|
||||
|
||||
def get_mm_code(self):
|
||||
if self.arguments:
|
||||
# try to load mermaid code from an external file
|
||||
document = self.state.document
|
||||
if self.content:
|
||||
return [
|
||||
document.reporter.warning(
|
||||
"Mermaid directive cannot have both content and "
|
||||
"a filename argument",
|
||||
line=self.lineno,
|
||||
)
|
||||
]
|
||||
env = self.state.document.settings.env
|
||||
argument = search_image_for_language(self.arguments[0], env)
|
||||
rel_filename, filename = env.relfn2path(argument)
|
||||
env.note_dependency(rel_filename)
|
||||
try:
|
||||
with codecs.open(filename, "r", "utf-8") as fp:
|
||||
mmcode = fp.read()
|
||||
except (IOError, OSError): # noqa
|
||||
return [
|
||||
document.reporter.warning(
|
||||
"External Mermaid file %r not found or reading "
|
||||
"it failed" % filename,
|
||||
line=self.lineno,
|
||||
)
|
||||
]
|
||||
else:
|
||||
# inline mermaid code
|
||||
mmcode = "\n".join(self.content)
|
||||
if not mmcode.strip():
|
||||
return [
|
||||
self.state_machine.reporter.warning(
|
||||
'Ignoring "mermaid" directive without content.',
|
||||
line=self.lineno,
|
||||
)
|
||||
]
|
||||
return mmcode
|
||||
|
||||
def run(self):
|
||||
|
||||
node = mermaid()
|
||||
node["code"] = self.get_mm_code()
|
||||
node["options"] = {}
|
||||
if "alt" in self.options:
|
||||
node["alt"] = self.options["alt"]
|
||||
if "align" in self.options:
|
||||
node["align"] = self.options["align"]
|
||||
if "inline" in self.options:
|
||||
node["inline"] = True
|
||||
|
||||
caption = self.options.get("caption")
|
||||
if caption:
|
||||
node = figure_wrapper(self, node, caption)
|
||||
|
||||
return [node]
|
||||
|
||||
|
||||
def render_mm(self, code, options, fmt, prefix="mermaid"):
|
||||
"""Render mermaid code into a PNG or PDF output file."""
|
||||
|
||||
if fmt == "raw":
|
||||
fmt = "png"
|
||||
|
||||
mermaid_cmd = self.builder.config.mermaid_cmd
|
||||
hashkey = (
|
||||
code + str(options) + str(self.builder.config.mermaid_sequence_config)
|
||||
).encode("utf-8")
|
||||
|
||||
basename = "%s-%s" % (prefix, sha1(hashkey).hexdigest())
|
||||
fname = "%s.%s" % (basename, fmt)
|
||||
relfn = posixpath.join(self.builder.imgpath, fname)
|
||||
outdir = os.path.join(self.builder.outdir, self.builder.imagedir)
|
||||
outfn = os.path.join(outdir, fname)
|
||||
tmpfn = os.path.join(_get_default_tempdir(), basename)
|
||||
|
||||
if os.path.isfile(outfn):
|
||||
return relfn, outfn
|
||||
|
||||
ensuredir(os.path.dirname(outfn))
|
||||
|
||||
# mermaid expects UTF-8 by default
|
||||
if isinstance(code, text_type):
|
||||
code = code.encode("utf-8")
|
||||
|
||||
with open(tmpfn, "wb") as t:
|
||||
t.write(code)
|
||||
|
||||
mm_args = [mermaid_cmd, "-i", tmpfn, "-o", outfn]
|
||||
mm_args.extend(self.builder.config.mermaid_params)
|
||||
if self.builder.config.mermaid_sequence_config:
|
||||
mm_args.extend("--configFile", self.builder.config.mermaid_sequence_config)
|
||||
|
||||
try:
|
||||
p = Popen(mm_args, stdout=PIPE, stdin=PIPE, stderr=PIPE)
|
||||
except OSError as err:
|
||||
if err.errno != ENOENT: # No such file or directory
|
||||
raise
|
||||
logger.warning(
|
||||
"command %r cannot be run (needed for mermaid "
|
||||
"output), check the mermaid_cmd setting" % mermaid_cmd
|
||||
)
|
||||
return None, None
|
||||
|
||||
stdout, stderr = p.communicate(code)
|
||||
if self.builder.config.mermaid_verbose:
|
||||
logger.info(stdout)
|
||||
|
||||
if p.returncode != 0:
|
||||
raise MermaidError(
|
||||
"Mermaid exited with error:\n[stderr]\n%s\n"
|
||||
"[stdout]\n%s" % (stderr, stdout)
|
||||
)
|
||||
if not os.path.isfile(outfn):
|
||||
raise MermaidError(
|
||||
"Mermaid did not produce an output file:\n[stderr]\n%s\n"
|
||||
"[stdout]\n%s" % (stderr, stdout)
|
||||
)
|
||||
return relfn, outfn
|
||||
|
||||
|
||||
def _render_mm_html_raw(
|
||||
self, node, code, options, prefix="mermaid", imgcls=None, alt=None
|
||||
):
|
||||
if "align" in node:
|
||||
tag_template = """<div align="{align}" class="mermaid align-{align}">
|
||||
{code}
|
||||
</div>
|
||||
"""
|
||||
else:
|
||||
tag_template = """<div class="mermaid">
|
||||
{code}
|
||||
</div>"""
|
||||
|
||||
self.body.append(
|
||||
tag_template.format(align=node.get("align"), code=self.encode(code))
|
||||
)
|
||||
raise nodes.SkipNode
|
||||
|
||||
|
||||
def render_mm_html(self, node, code, options, prefix="mermaid", imgcls=None, alt=None):
|
||||
fmt = self.builder.config.mermaid_output_format
|
||||
if fmt == "raw":
|
||||
return _render_mm_html_raw(
|
||||
self, node, code, options, prefix="mermaid", imgcls=None, alt=None
|
||||
)
|
||||
|
||||
try:
|
||||
if fmt not in ("png", "svg"):
|
||||
raise MermaidError(
|
||||
"mermaid_output_format must be one of 'raw', 'png', "
|
||||
"'svg', but is %r" % fmt
|
||||
)
|
||||
|
||||
fname, outfn = render_mm(self, code, options, fmt, prefix)
|
||||
except MermaidError as exc:
|
||||
logger.warning("mermaid code %r: " % code + str(exc))
|
||||
raise nodes.SkipNode
|
||||
|
||||
if fname is None:
|
||||
self.body.append(self.encode(code))
|
||||
else:
|
||||
if alt is None:
|
||||
alt = node.get("alt", self.encode(code).strip())
|
||||
imgcss = imgcls and 'class="%s"' % imgcls or ""
|
||||
if fmt == "svg":
|
||||
svgtag = """<object data="%s" type="image/svg+xml">
|
||||
<p class="warning">%s</p></object>\n""" % (
|
||||
fname,
|
||||
alt,
|
||||
)
|
||||
self.body.append(svgtag)
|
||||
else:
|
||||
if "align" in node:
|
||||
self.body.append(
|
||||
'<div align="%s" class="align-%s">' % (node["align"], node["align"])
|
||||
)
|
||||
|
||||
self.body.append('<img src="%s" alt="%s" %s/>\n' % (fname, alt, imgcss))
|
||||
if "align" in node:
|
||||
self.body.append("</div>\n")
|
||||
|
||||
raise nodes.SkipNode
|
||||
|
||||
|
||||
def html_visit_mermaid(self, node):
|
||||
render_mm_html(self, node, node["code"], node["options"])
|
||||
|
||||
|
||||
def render_mm_latex(self, node, code, options, prefix="mermaid"):
|
||||
try:
|
||||
fname, outfn = render_mm(self, code, options, "pdf", prefix)
|
||||
except MermaidError as exc:
|
||||
logger.warning("mm code %r: " % code + str(exc))
|
||||
raise nodes.SkipNode
|
||||
|
||||
if self.builder.config.mermaid_pdfcrop != "":
|
||||
mm_args = [self.builder.config.mermaid_pdfcrop, outfn]
|
||||
try:
|
||||
p = Popen(mm_args, stdout=PIPE, stdin=PIPE, stderr=PIPE)
|
||||
except OSError as err:
|
||||
if err.errno != ENOENT: # No such file or directory
|
||||
raise
|
||||
logger.warning(
|
||||
"command %r cannot be run (needed to crop pdf), \
|
||||
check the mermaid_cmd setting"
|
||||
% self.builder.config.mermaid_pdfcrop
|
||||
)
|
||||
return None, None
|
||||
|
||||
stdout, stderr = p.communicate()
|
||||
if self.builder.config.mermaid_verbose:
|
||||
logger.info(stdout)
|
||||
|
||||
if p.returncode != 0:
|
||||
raise MermaidError(
|
||||
"PdfCrop exited with error:\n[stderr]\n%s\n"
|
||||
"[stdout]\n%s" % (stderr, stdout)
|
||||
)
|
||||
if not os.path.isfile(outfn):
|
||||
raise MermaidError(
|
||||
"PdfCrop did not produce an output file:\n[stderr]\n%s\n"
|
||||
"[stdout]\n%s" % (stderr, stdout)
|
||||
)
|
||||
|
||||
fname = "{filename[0]}-crop{filename[1]}".format(
|
||||
filename=os.path.splitext(fname)
|
||||
)
|
||||
|
||||
is_inline = self.is_inline(node)
|
||||
if is_inline:
|
||||
para_separator = ""
|
||||
else:
|
||||
para_separator = "\n"
|
||||
|
||||
if fname is not None:
|
||||
post = None
|
||||
if not is_inline and "align" in node:
|
||||
if node["align"] == "left":
|
||||
self.body.append("{")
|
||||
post = "\\hspace*{\\fill}}"
|
||||
elif node["align"] == "right":
|
||||
self.body.append("{\\hspace*{\\fill}")
|
||||
post = "}"
|
||||
self.body.append(
|
||||
"%s\\sphinxincludegraphics{%s}%s" % (para_separator, fname, para_separator)
|
||||
)
|
||||
if post:
|
||||
self.body.append(post)
|
||||
|
||||
raise nodes.SkipNode
|
||||
|
||||
|
||||
def latex_visit_mermaid(self, node):
|
||||
render_mm_latex(self, node, node["code"], node["options"])
|
||||
|
||||
|
||||
def render_mm_texinfo(self, node, code, options, prefix="mermaid"):
|
||||
try:
|
||||
fname, outfn = render_mm(self, code, options, "png", prefix)
|
||||
except MermaidError as exc:
|
||||
logger.warning("mm code %r: " % code + str(exc))
|
||||
raise nodes.SkipNode
|
||||
if fname is not None:
|
||||
self.body.append("@image{%s,,,[mermaid],png}\n" % fname[:-4])
|
||||
raise nodes.SkipNode
|
||||
|
||||
|
||||
def texinfo_visit_mermaid(self, node):
|
||||
render_mm_texinfo(self, node, node["code"], node["options"])
|
||||
|
||||
|
||||
def text_visit_mermaid(self, node):
|
||||
if "alt" in node.attributes:
|
||||
self.add_text(_("[graph: %s]") % node["alt"])
|
||||
else:
|
||||
self.add_text(_("[graph]"))
|
||||
raise nodes.SkipNode
|
||||
|
||||
|
||||
def man_visit_mermaid(self, node):
|
||||
if "alt" in node.attributes:
|
||||
self.body.append(_("[graph: %s]") % node["alt"])
|
||||
else:
|
||||
self.body.append(_("[graph]"))
|
||||
raise nodes.SkipNode
|
||||
|
||||
|
||||
def config_inited(app, config):
|
||||
version = config.mermaid_version
|
||||
mermaid_js_url = "https://unpkg.com/mermaid@{}/dist/mermaid.min.js".format(version)
|
||||
app.add_js_file(mermaid_js_url)
|
||||
app.add_js_file(
|
||||
None,
|
||||
body='mermaid.initialize({startOnLoad:true, theme:"neutral", securityLevel="loose", \
|
||||
sequenceConfig: {mirrorActors: false}});',
|
||||
)
|
||||
app.add_css_file("mermaid.css")
|
||||
|
||||
|
||||
def on_build_finished(app: Sphinx, exc: Exception) -> None:
|
||||
if exc is None:
|
||||
src = os.path.join(os.path.dirname(__file__), "mermaid.css")
|
||||
dst = os.path.join(app.outdir, "_static")
|
||||
copy_asset(src, dst)
|
||||
|
||||
|
||||
def setup(app):
|
||||
app.add_node(
|
||||
mermaid,
|
||||
html=(html_visit_mermaid, None),
|
||||
latex=(latex_visit_mermaid, None),
|
||||
texinfo=(texinfo_visit_mermaid, None),
|
||||
text=(text_visit_mermaid, None),
|
||||
man=(man_visit_mermaid, None),
|
||||
)
|
||||
app.add_directive("mermaid", Mermaid)
|
||||
|
||||
#
|
||||
app.add_config_value("mermaid_cmd", "mmdc", "html")
|
||||
app.add_config_value("mermaid_pdfcrop", "", "html")
|
||||
app.add_config_value("mermaid_output_format", "raw", "html")
|
||||
app.add_config_value("mermaid_params", list(), "html")
|
||||
app.add_config_value("mermaid_verbose", False, "html")
|
||||
app.add_config_value("mermaid_sequence_config", False, "html")
|
||||
app.add_config_value("mermaid_version", "8.10.2", "html")
|
||||
|
||||
app.connect("config-inited", config_inited)
|
||||
app.connect("build-finished", on_build_finished)
|
||||
|
||||
return {"version": sphinx.__display_version__, "parallel_read_safe": True}
|
|
@ -0,0 +1,304 @@
|
|||
r"""
|
||||
mermaid_inheritance
|
||||
~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
Modified by the CoSApp team from sphinx.ext.inheritance_diagram
|
||||
https://gitlab.com/cosapp/cosapp
|
||||
|
||||
Defines a docutils directive for inserting inheritance diagrams.
|
||||
Provide the directive with one or more classes or modules (separated
|
||||
by whitespace). For modules, all of the classes in that module will
|
||||
be used.
|
||||
Example::
|
||||
Given the following classes:
|
||||
class A: pass
|
||||
class B(A): pass
|
||||
class C(A): pass
|
||||
class D(B, C): pass
|
||||
class E(B): pass
|
||||
.. inheritance-diagram: D E
|
||||
Produces a graph like the following:
|
||||
A
|
||||
/ \
|
||||
B C
|
||||
/ \ /
|
||||
E D
|
||||
The graph is inserted as a PNG+image map into HTML and a PDF in
|
||||
LaTeX.
|
||||
:copyright: Copyright 2007-2020 by the Sphinx team, see AUTHORS.
|
||||
:license: BSD, see LICENSE for details.
|
||||
"""
|
||||
|
||||
from typing import Any, Dict, Iterable, List, cast
|
||||
|
||||
import sphinx
|
||||
from docutils import nodes
|
||||
from docutils.nodes import Node
|
||||
from docutils.parsers.rst import directives
|
||||
from mermaid import render_mm_html, render_mm_latex, render_mm_texinfo
|
||||
from sphinx import addnodes
|
||||
from sphinx.application import Sphinx
|
||||
from sphinx.environment import BuildEnvironment
|
||||
from sphinx.ext.inheritance_diagram import (
|
||||
InheritanceDiagram,
|
||||
InheritanceException,
|
||||
InheritanceGraph,
|
||||
figure_wrapper,
|
||||
get_graph_hash,
|
||||
inheritance_diagram,
|
||||
skip,
|
||||
)
|
||||
from sphinx.writers.html import HTMLTranslator
|
||||
from sphinx.writers.latex import LaTeXTranslator
|
||||
from sphinx.writers.texinfo import TexinfoTranslator
|
||||
|
||||
|
||||
class MermaidGraph(InheritanceGraph):
|
||||
"""
|
||||
Given a list of classes, determines the set of classes that they inherit
|
||||
from all the way to the root "object", and then is able to generate a
|
||||
mermaid graph from them.
|
||||
"""
|
||||
|
||||
# These are the default attrs
|
||||
default_graph_attrs = {}
|
||||
# 'rankdir': 'LR',
|
||||
# 'size': '"8.0, 12.0"',
|
||||
# 'bgcolor': 'transparent',
|
||||
# }
|
||||
default_node_attrs = {}
|
||||
# 'shape': 'box',
|
||||
# 'fontsize': 10,
|
||||
# 'height': 0.25,
|
||||
# 'fontname': '"Vera Sans, DejaVu Sans, Liberation Sans, '
|
||||
# 'Arial, Helvetica, sans"',
|
||||
# 'style': '"setlinewidth(0.5),filled"',
|
||||
# 'fillcolor': 'white',
|
||||
# }
|
||||
default_edge_attrs = {}
|
||||
# 'arrowsize': 0.5,
|
||||
# 'style': '"setlinewidth(0.5)"',
|
||||
# }
|
||||
|
||||
def _format_node_attrs(self, attrs: Dict) -> str:
|
||||
# return ','.join(['%s=%s' % x for x in sorted(attrs.items())])
|
||||
return ""
|
||||
|
||||
def _format_graph_attrs(self, attrs: Dict) -> str:
|
||||
# return ''.join(['%s=%s;\n' % x for x in sorted(attrs.items())])
|
||||
return ""
|
||||
|
||||
def generate_dot(
|
||||
self,
|
||||
name: str,
|
||||
urls: Dict = {}, # noqa
|
||||
env: BuildEnvironment = None,
|
||||
graph_attrs: Dict = {}, # noqa
|
||||
node_attrs: Dict = {}, # noqa
|
||||
edge_attrs: Dict = {}, # noqa
|
||||
) -> str:
|
||||
"""Generate a mermaid graph from the classes that were passed in
|
||||
to __init__.
|
||||
*name* is the name of the graph.
|
||||
*urls* is a dictionary mapping class names to HTTP URLs.
|
||||
*graph_attrs*, *node_attrs*, *edge_attrs* are dictionaries containing
|
||||
key/value pairs to pass on as graphviz properties.
|
||||
"""
|
||||
# g_attrs = self.default_graph_attrs.copy()
|
||||
# n_attrs = self.default_node_attrs.copy()
|
||||
# e_attrs = self.default_edge_attrs.copy()
|
||||
# g_attrs.update(graph_attrs)
|
||||
# n_attrs.update(node_attrs)
|
||||
# e_attrs.update(edge_attrs)
|
||||
# if env:
|
||||
# g_attrs.update(env.config.inheritance_graph_attrs)
|
||||
# n_attrs.update(env.config.inheritance_node_attrs)
|
||||
# e_attrs.update(env.config.inheritance_edge_attrs)
|
||||
|
||||
res = [] # type: List[str]
|
||||
|
||||
res.append("classDiagram\n")
|
||||
for name, fullname, bases, tooltip in sorted(self.class_info):
|
||||
# Write the node
|
||||
res.append(" class {!s}\n".format(name))
|
||||
if fullname in urls:
|
||||
res.append(
|
||||
' link {!s} "./{!s}" {!s}\n'.format(
|
||||
name, urls[fullname], tooltip or '"{}"'.format(name)
|
||||
)
|
||||
)
|
||||
|
||||
# Write the edges
|
||||
for base_name in bases:
|
||||
res.append(" {!s} <|-- {!s}\n".format(base_name, name))
|
||||
|
||||
return "".join(res)
|
||||
|
||||
|
||||
class mermaid_inheritance(inheritance_diagram):
|
||||
"""
|
||||
A docutils node to use as a placeholder for the inheritance diagram.
|
||||
"""
|
||||
|
||||
pass
|
||||
|
||||
|
||||
class MermaidDiagram(InheritanceDiagram):
|
||||
"""
|
||||
Run when the mermaid_inheritance directive is first encountered.
|
||||
"""
|
||||
|
||||
has_content = False
|
||||
required_arguments = 1
|
||||
optional_arguments = 0
|
||||
final_argument_whitespace = True
|
||||
option_spec = {
|
||||
"parts": int,
|
||||
"private-bases": directives.flag,
|
||||
"caption": directives.unchanged,
|
||||
"top-classes": directives.unchanged_required,
|
||||
}
|
||||
|
||||
def run(self) -> List[Node]:
|
||||
node = mermaid_inheritance()
|
||||
node.document = self.state.document
|
||||
class_names = self.arguments[0].split()
|
||||
class_role = self.env.get_domain("py").role("class")
|
||||
# Store the original content for use as a hash
|
||||
node["parts"] = self.options.get("parts", 0)
|
||||
node["content"] = ", ".join(class_names)
|
||||
node["top-classes"] = []
|
||||
for cls in self.options.get("top-classes", "").split(","):
|
||||
cls = cls.strip()
|
||||
if cls:
|
||||
node["top-classes"].append(cls)
|
||||
|
||||
# Create a graph starting with the list of classes
|
||||
try:
|
||||
graph = MermaidGraph(
|
||||
class_names,
|
||||
self.env.ref_context.get("py:module"),
|
||||
parts=node["parts"],
|
||||
private_bases="private-bases" in self.options,
|
||||
aliases=self.config.inheritance_alias,
|
||||
top_classes=node["top-classes"],
|
||||
)
|
||||
except InheritanceException as err:
|
||||
return [node.document.reporter.warning(err, line=self.lineno)]
|
||||
|
||||
# Create xref nodes for each target of the graph's image map and
|
||||
# add them to the doc tree so that Sphinx can resolve the
|
||||
# references to real URLs later. These nodes will eventually be
|
||||
# removed from the doctree after we're done with them.
|
||||
for name in graph.get_all_class_names():
|
||||
refnodes, x = class_role( # type: ignore
|
||||
"class", ":class:`%s`" % name, name, 0, self.state
|
||||
) # type: ignore
|
||||
node.extend(refnodes)
|
||||
# Store the graph object so we can use it to generate the
|
||||
# dot file later
|
||||
node["graph"] = graph
|
||||
|
||||
if "caption" not in self.options:
|
||||
self.add_name(node)
|
||||
return [node]
|
||||
else:
|
||||
figure = figure_wrapper(self, node, self.options["caption"])
|
||||
self.add_name(figure)
|
||||
return [figure]
|
||||
|
||||
|
||||
def html_visit_mermaid_inheritance(
|
||||
self: HTMLTranslator, node: inheritance_diagram
|
||||
) -> None:
|
||||
"""
|
||||
Output the graph for HTML. This will insert a PNG with clickable
|
||||
image map.
|
||||
"""
|
||||
graph = node["graph"]
|
||||
|
||||
graph_hash = get_graph_hash(node)
|
||||
name = "inheritance%s" % graph_hash
|
||||
|
||||
# Create a mapping from fully-qualified class names to URLs.
|
||||
mermaid_output_format = self.builder.env.config.mermaid_output_format.upper()
|
||||
current_filename = self.builder.current_docname + self.builder.out_suffix
|
||||
urls = {}
|
||||
pending_xrefs = cast(Iterable[addnodes.pending_xref], node)
|
||||
for child in pending_xrefs:
|
||||
if child.get("refuri") is not None:
|
||||
if mermaid_output_format == "SVG":
|
||||
urls[child["reftitle"]] = "../" + child.get("refuri")
|
||||
else:
|
||||
urls[child["reftitle"]] = child.get("refuri")
|
||||
elif child.get("refid") is not None:
|
||||
if mermaid_output_format == "SVG":
|
||||
urls[child["reftitle"]] = (
|
||||
"../" + current_filename + "#" + child.get("refid")
|
||||
)
|
||||
else:
|
||||
urls[child["reftitle"]] = "#" + child.get("refid")
|
||||
dotcode = graph.generate_dot(name, urls, env=self.builder.env)
|
||||
render_mm_html(
|
||||
self,
|
||||
node,
|
||||
dotcode,
|
||||
{},
|
||||
"inheritance",
|
||||
"inheritance",
|
||||
alt="Inheritance diagram of " + node["content"],
|
||||
)
|
||||
raise nodes.SkipNode
|
||||
|
||||
|
||||
def latex_visit_mermaid_inheritance(
|
||||
self: LaTeXTranslator, node: inheritance_diagram
|
||||
) -> None:
|
||||
"""
|
||||
Output the graph for LaTeX. This will insert a PDF.
|
||||
"""
|
||||
graph = node["graph"]
|
||||
|
||||
graph_hash = get_graph_hash(node)
|
||||
name = "inheritance%s" % graph_hash
|
||||
|
||||
dotcode = graph.generate_dot(name, env=self.builder.env,)
|
||||
# graph_attrs={'size': '"6.0,6.0"'})
|
||||
render_mm_latex(self, node, dotcode, {}, "inheritance")
|
||||
raise nodes.SkipNode
|
||||
|
||||
|
||||
def texinfo_visit_mermaid_inheritance(
|
||||
self: TexinfoTranslator, node: inheritance_diagram
|
||||
) -> None:
|
||||
"""
|
||||
Output the graph for Texinfo. This will insert a PNG.
|
||||
"""
|
||||
graph = node["graph"]
|
||||
|
||||
graph_hash = get_graph_hash(node)
|
||||
name = "inheritance%s" % graph_hash
|
||||
|
||||
dotcode = graph.generate_dot(name, env=self.builder.env,)
|
||||
# graph_attrs={'size': '"6.0,6.0"'})
|
||||
render_mm_texinfo(self, node, dotcode, {}, "inheritance")
|
||||
raise nodes.SkipNode
|
||||
|
||||
|
||||
def setup(app: Sphinx) -> Dict[str, Any]:
|
||||
app.setup_extension("mermaid")
|
||||
app.add_node(
|
||||
mermaid_inheritance,
|
||||
latex=(latex_visit_mermaid_inheritance, None),
|
||||
html=(html_visit_mermaid_inheritance, None),
|
||||
text=(skip, None),
|
||||
man=(skip, None),
|
||||
texinfo=(texinfo_visit_mermaid_inheritance, None),
|
||||
)
|
||||
app.add_directive("mermaid-inheritance", MermaidDiagram)
|
||||
# app.add_config_value('mermaid_inheritance_graph_attrs', {}, False)
|
||||
# app.add_config_value('mermaid_inheritance_node_attrs', {}, False)
|
||||
# app.add_config_value('mermaid_inheritance_edge_attrs', {}, False)
|
||||
app.add_config_value("inheritance_alias", {}, False)
|
||||
|
||||
return {"version": sphinx.__display_version__, "parallel_read_safe": True}
|
Loading…
Reference in New Issue