Skip to content

Commit

Permalink
Merge pull request #152 from timkpaine/tkp/lint
Browse files Browse the repository at this point in the history
Add linting
  • Loading branch information
timkpaine authored Sep 12, 2024
2 parents 2431f9a + c12a83d commit cf36547
Show file tree
Hide file tree
Showing 5 changed files with 78 additions and 100 deletions.
6 changes: 5 additions & 1 deletion .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -26,9 +26,13 @@ jobs:

- name: Install dependencies
run: |
python -m pip install -U pytest
python -m pip install -e .[test]
- name: Lint
run: |
ruff format --check sphinxcontrib
ruff check sphinxcontrib
- name: Test
run: |
pytest
20 changes: 20 additions & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
[tool.ruff]
line-length = 150

[tool.ruff.lint]
extend-select = ["I"]

[tool.ruff.lint.isort]
combine-as-imports = true
default-section = "third-party"
known-first-party = ["sphinxcontrib.mermaid"]
section-order = [
"future",
"standard-library",
"third-party",
"first-party",
"local-folder",
]

[tool.ruff.lint.per-file-ignores]
"__init__.py" = ["F401", "F403"]
6 changes: 3 additions & 3 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -58,9 +58,9 @@ def remove_block(text, token, margin=0):
"Topic :: Utilities",
],
platforms="any",
packages=find_namespace_packages(where='./', include=['sphinxcontrib.mermaid']),
package_dir={'': './'},
packages=find_namespace_packages(where="./", include=["sphinxcontrib.mermaid"]),
package_dir={"": "./"},
include_package_data=True,
install_requires=["sphinx", "pyyaml"],
extras_require={'test': ['myst-parser', 'defusedxml', 'sphinx', 'pytest']},
extras_require={"test": ["defusedxml", "myst-parser", "pytest", "ruff", "sphinx"]},
)
137 changes: 48 additions & 89 deletions sphinxcontrib/mermaid/__init__.py
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
"""
sphinx-mermaid
~~~~~~~~~~~~~~~
sphinx-mermaid
~~~~~~~~~~~~~~~
Allow mermaid diagrams to be included in Sphinx-generated
documents inline.
Allow mermaid diagrams to be included in Sphinx-generated
documents inline.
:copyright: Copyright 2016-2023 by Martín Gaitán and others
:license: BSD, see LICENSE for details.
:copyright: Copyright 2016-2023 by Martín Gaitán and others
:license: BSD, see LICENSE for details.
"""

from __future__ import annotations

import codecs
Expand Down Expand Up @@ -80,6 +81,7 @@
window.addEventListener("load", load);
"""


class mermaid(nodes.General, nodes.Inline, nodes.Element):
pass

Expand All @@ -90,9 +92,7 @@ def figure_wrapper(directive, node, caption):
figure_node["align"] = node.attributes.pop("align")

parsed = nodes.Element()
directive.state.nested_parse(
ViewList([caption], source=""), directive.content_offset, parsed
)
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
Expand Down Expand Up @@ -132,8 +132,7 @@ def get_mm_code(self):
if self.content:
return [
document.reporter.warning(
"Mermaid directive cannot have both content and "
"a filename argument",
"Mermaid directive cannot have both content and " "a filename argument",
line=self.lineno,
)
]
Expand All @@ -147,8 +146,7 @@ def get_mm_code(self):
except OSError:
return [
document.reporter.warning(
"External Mermaid file %r not found or reading "
"it failed" % filename,
"External Mermaid file %r not found or reading " "it failed" % filename,
line=self.lineno,
)
]
Expand Down Expand Up @@ -192,7 +190,7 @@ def run(self, **kwargs):
mm_config = "---"
if "config" in self.options:
mm_config += "\n"
mm_config += dump({"config": loads(self.options['config'])})
mm_config += dump({"config": loads(self.options["config"])})
if "title" in self.options:
mm_config += "\n"
mm_config += f"title: {self.options['title']}"
Expand All @@ -209,7 +207,6 @@ def run(self, **kwargs):


class MermaidClassDiagram(Mermaid):

has_content = False
required_arguments = 1
optional_arguments = 100
Expand Down Expand Up @@ -239,17 +236,15 @@ def render_mm(self, code, options, _fmt, prefix="mermaid"):

mermaid_cmd = self.builder.config.mermaid_cmd
mermaid_cmd_shell = self.builder.config.mermaid_cmd_shell in {True, "True", "true"}
hashkey = (
code + str(options) + str(self.builder.config.mermaid_sequence_config)
).encode("utf-8")
hashkey = (code + str(options) + str(self.builder.config.mermaid_sequence_config)).encode("utf-8")

basename = f"{prefix}-{sha1(hashkey).hexdigest()}"
fname = f"{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)
with TemporaryDirectory() as tempDir:
tmpfn = os.path.join(tempDir, basename)
tmpfn = os.path.join(tempDir, basename)

if os.path.isfile(outfn):
return relfn, outfn
Expand All @@ -265,82 +260,57 @@ def render_mm(self, code, options, _fmt, prefix="mermaid"):
mm_args = list(mermaid_cmd)

mm_args.extend(self.builder.config.mermaid_params)
mm_args += ['-i', tmpfn, '-o', outfn]
mm_args += ["-i", tmpfn, "-o", outfn]
if self.builder.config.mermaid_sequence_config:
mm_args.extend(["--configFile", self.builder.config.mermaid_sequence_config])

try:
p = Popen(
mm_args, shell=mermaid_cmd_shell, stdout=PIPE, stdin=PIPE, stderr=PIPE
)
p = Popen(mm_args, shell=mermaid_cmd_shell, stdout=PIPE, stdin=PIPE, stderr=PIPE)
except FileNotFoundError:
logger.warning(
"command %r cannot be run (needed for mermaid "
"output), check the mermaid_cmd setting" % mermaid_cmd
)
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(str.encode(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)
)
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)
)
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
):
def _render_mm_html_raw(self, node, code, options, prefix="mermaid", imgcls=None, alt=None):
classes = ["mermaid"]
attrs = {}

if "align" in node:
classes.append(f"align-{node['align']}")
attrs["align"] = node["align"]

if "zoom_id" in node:
attrs["data-zoom-id"] = node["zoom_id"]

if "ids" in node and len(node["ids"]) == 1:
attrs["id"] = node["ids"][0]

tag_template = """<pre {attr_defs} class="{classes}">
{code}
</pre>"""
attr_defs = ["{}=\"{}\"".format(k, v) for k, v in attrs.items()]
self.body.append(
tag_template.format(
attr_defs=" ".join(attr_defs),
classes=" ".join(classes),
code=self.encode(code)
)
)
attr_defs = ['{}="{}"'.format(k, v) for k, v in attrs.items()]
self.body.append(tag_template.format(attr_defs=" ".join(attr_defs), classes=" ".join(classes), 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
)
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
)
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:
Expand All @@ -360,9 +330,7 @@ def render_mm_html(self, node, code, options, prefix="mermaid", imgcls=None, alt
self.body.append(svgtag)
else:
if "align" in node:
self.body.append(
'<pre align="%s" class="align-%s">' % (node["align"], node["align"])
)
self.body.append('<pre align="%s" class="align-%s">' % (node["align"], node["align"]))

self.body.append(f'<img src="{fname}" alt="{alt}" {imgcss}/>\n')
if "align" in node:
Expand All @@ -387,31 +355,21 @@ def render_mm_latex(self, node, code, options, prefix="mermaid"):
try:
p = Popen(mm_args, stdout=PIPE, stdin=PIPE, stderr=PIPE)
except OSError as err:
if err.errno != errno.ENOENT: # No such file or directory
if err.errno != errno.ENOENT: # No such file or directory
raise
logger.warning(
f"command {self.builder.config.mermaid_pdfcrop!r} cannot be run (needed to crop pdf), check the mermaid_cmd setting"
)
logger.warning(f"command {self.builder.config.mermaid_pdfcrop!r} cannot be run (needed to crop pdf), check the mermaid_cmd setting")
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)
)
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)
)
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)
)
fname = "{filename[0]}-crop{filename[1]}".format(filename=os.path.splitext(fname))

is_inline = self.is_inline(node)
if is_inline:
Expand All @@ -428,9 +386,7 @@ def render_mm_latex(self, node, code, options, prefix="mermaid"):
elif node["align"] == "right":
self.body.append("{\\hspace*{\\fill}")
post = "}"
self.body.append(
"%s\\sphinxincludegraphics{%s}%s" % (para_separator, fname, para_separator)
)
self.body.append("%s\\sphinxincludegraphics{%s}%s" % (para_separator, fname, para_separator))
if post:
self.body.append(post)

Expand Down Expand Up @@ -492,15 +448,17 @@ def install_js(
_mermaid_js_url = f"https://cdn.jsdelivr.net/npm/mermaid@{app.config.mermaid_version}/dist/mermaid.esm.min.mjs"
elif app.config.mermaid_version:
raise MermaidError("Requires mermaid js version 10.3.0 or later")

app.add_js_file(_mermaid_js_url, priority=app.config.mermaid_js_priority, type="module")

if app.config.mermaid_elk_use_local:
_mermaid_elk_js_url = app.config.mermaid_elk_use_local
elif app.config.mermaid_include_elk == "latest":
_mermaid_elk_js_url = "https://cdn.jsdelivr.net/npm/@mermaid-js/layout-elk/dist/mermaid-layout-elk.esm.min.mjs"
elif app.config.mermaid_include_elk:
_mermaid_elk_js_url = f"https://cdn.jsdelivr.net/npm/@mermaid-js/layout-elk@{app.config.mermaid_include_elk}/dist/mermaid-layout-elk.esm.min.mjs"
_mermaid_elk_js_url = (
f"https://cdn.jsdelivr.net/npm/@mermaid-js/layout-elk@{app.config.mermaid_include_elk}/dist/mermaid-layout-elk.esm.min.mjs"
)
else:
_mermaid_elk_js_url = None
if _mermaid_elk_js_url:
Expand All @@ -510,17 +468,15 @@ def install_js(
# Update if esm is used and no custom init-js is provided
if _mermaid_elk_js_url:
# Add registration of ELK layouts
app.config.mermaid_init_js = f'import mermaid from "{_mermaid_js_url}";import elkLayouts from "{_mermaid_elk_js_url}";mermaid.registerLayoutLoaders(elkLayouts);{app.config.mermaid_init_js}';
app.config.mermaid_init_js = f'import mermaid from "{_mermaid_js_url}";import elkLayouts from "{_mermaid_elk_js_url}";mermaid.registerLayoutLoaders(elkLayouts);{app.config.mermaid_init_js}'
else:
app.config.mermaid_init_js = f'import mermaid from "{_mermaid_js_url}";{app.config.mermaid_init_js}';
app.config.mermaid_init_js = f'import mermaid from "{_mermaid_js_url}";{app.config.mermaid_init_js}'

if app.config.mermaid_init_js:
# If mermaid is local the init-call must be placed after `html_js_files` which has a priority of 800.
priority = (
app.config.mermaid_init_js_priority if _mermaid_js_url is not None else 801
)
priority = app.config.mermaid_init_js_priority if _mermaid_js_url is not None else 801
app.add_js_file(None, body=app.config.mermaid_init_js, priority=priority, type="module")

_wrote_mermaid_run = False
if app.config.mermaid_output_format == "raw":
if app.config.d3_use_local:
Expand Down Expand Up @@ -553,7 +509,10 @@ def install_js(
_wrote_mermaid_run = True

if not _wrote_mermaid_run and _mermaid_js_url:
app.add_js_file(None, body=_MERMAID_RUN_NO_D3_ZOOM.format(mermaid_js_url=_mermaid_js_url), priority=app.config.mermaid_js_priority, type="module")
app.add_js_file(
None, body=_MERMAID_RUN_NO_D3_ZOOM.format(mermaid_js_url=_mermaid_js_url), priority=app.config.mermaid_js_priority, type="module"
)


def setup(app):
app.add_node(
Expand All @@ -574,7 +533,7 @@ def setup(app):
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_use_local", "", "html")
app.add_config_value("mermaid_version", "11.2.0", "html")
app.add_config_value("mermaid_elk_use_local", "", "html")
Expand All @@ -587,4 +546,4 @@ def setup(app):
app.add_config_value("mermaid_d3_zoom", False, "html")
app.connect("html-page-context", install_js)

return {"version": sphinx.__display_version__, "parallel_read_safe": True}
return {"version": sphinx.__display_version__, "parallel_read_safe": True}
Loading

0 comments on commit cf36547

Please sign in to comment.