diff --git a/docs/source/_extensions/kpm_plugin.py b/docs/source/_extensions/kpm_plugin.py
new file mode 100644
index 00000000..dacb019d
--- /dev/null
+++ b/docs/source/_extensions/kpm_plugin.py
@@ -0,0 +1,116 @@
+from __future__ import annotations
+
+from typing import Optional
+from urllib.parse import urlencode
+
+from docutils import nodes
+from docutils.parsers.rst.directives import path
+from sphinx.addnodes import download_reference
+from sphinx.application import Sphinx
+from sphinx.util.docutils import SphinxDirective
+from sphinx.util.typing import ExtensionMetadata
+from sphinx.writers.html5 import HTML5Translator
+from sphinx.writers.latex import LaTeXTranslator
+
+KPM_PATH = "_static/kpm/index.html"
+
+
+class KPMNode(nodes.container):
+ def __init__(
+ self,
+ depth: int,
+ preview: bool,
+ spec_ref: Optional[str],
+ graph_ref: Optional[str],
+ height: Optional[str],
+ ) -> None:
+ # we're leveraging the builtin download_reference node
+ # to automatically move necessary files from sources
+ # into the build directory and have a path to them
+ spec_node = graph_node = None
+ if spec_ref:
+ spec_node = download_reference("", "", reftarget=spec_ref, disabled=True)
+ if graph_ref:
+ graph_node = download_reference("", "", reftarget=graph_ref, disabled=True)
+
+ super().__init__("", *(node for node in (spec_node, graph_node) if node))
+ self.spec_node = spec_node
+ self.graph_node = graph_node
+ self.rel_pfx = "../" * depth
+ self.preview = preview
+ self.height = height
+
+ def _node_to_target(self, node: download_reference) -> str:
+ if "filename" in node:
+ return "relative://../../_downloads/" + node["filename"]
+ elif "refuri" in node:
+ return node["refuri"]
+
+ raise ValueError("The KPM file path is neither a valid file nor a URL")
+
+ @staticmethod
+ def visit_html(trans: HTML5Translator, node: KPMNode):
+ params = {}
+ if node.spec_node:
+ params["spec"] = node._node_to_target(node.spec_node)
+ if node.graph_node:
+ params["graph"] = node._node_to_target(node.graph_node)
+ if node.preview:
+ params["preview"] = str(node.preview).lower()
+
+ trans.body.append(
+ f"""
+"""
+ )
+
+ @staticmethod
+ def visit_latex(trans: LaTeXTranslator, _: KPMNode):
+ trans.body.append(
+ r"""
+\begin{sphinxadmonition}{warning}{Note:}
+\sphinxAtStartPar
+An interactive KPM frame, where you can explore the block design for this section,
+is available here in the HTML version of this documentation.
+\end{sphinxadmonition}"""
+ )
+
+ @staticmethod
+ def depart_node(*_):
+ pass
+
+
+class KPMDirective(SphinxDirective):
+ option_spec = {"spec": path, "dataflow": path, "preview": bool, "height": str}
+
+ def run(self) -> list[nodes.Node]:
+ return [
+ KPMNode(
+ self.env.docname.count("/"),
+ self.options.get("preview", False),
+ self.options.get("spec"),
+ self.options.get("dataflow"),
+ self.options.get("height"),
+ )
+ ]
+
+
+def setup(app: Sphinx) -> ExtensionMetadata:
+ app.add_node(
+ KPMNode,
+ html=(KPMNode.visit_html, KPMNode.depart_node),
+ latex=(KPMNode.visit_latex, KPMNode.depart_node),
+ )
+ app.add_directive("kpm_iframe", KPMDirective)
+
+ return {
+ "version": "0.1",
+ "parallel_read_safe": True,
+ "parallel_write_safe": True,
+ }
diff --git a/docs/source/conf.py b/docs/source/conf.py
index 6cb04c68..054b61d8 100644
--- a/docs/source/conf.py
+++ b/docs/source/conf.py
@@ -15,6 +15,8 @@
from datetime import datetime
from os import environ
+import os
+import sys
from antmicro_sphinx_utils.defaults import antmicro_html, antmicro_latex
from antmicro_sphinx_utils.defaults import extensions as default_extensions
@@ -29,7 +31,7 @@
# If extensions (or modules to document with autodoc) are in another directory,
# add these directories to sys.path here. If the directory is relative to the
# documentation root, use os.path.abspath to make it absolute, like shown here.
-#sys.path.insert(0, os.path.abspath('.'))
+sys.path.insert(0, os.path.abspath('./_extensions'))
# -- General configuration -----------------------------------------------------
@@ -51,7 +53,7 @@
# If you need to add extensions just add to those lists
extensions = list(set(default_extensions + [
- 'sphinx.ext.autodoc',
+ 'sphinx.ext.autodoc', 'kpm_plugin'
]))
myst_enable_extensions = default_myst_enable_extensions
myst_fence_as_directive = default_myst_fence_as_directive
diff --git a/docs/source/developers_guide/inline_kpm_howto.md b/docs/source/developers_guide/inline_kpm_howto.md
new file mode 100644
index 00000000..f088600a
--- /dev/null
+++ b/docs/source/developers_guide/inline_kpm_howto.md
@@ -0,0 +1,43 @@
+# Using KPM iframes inside docs
+
+It is possible to use the `kpm_iframe` Sphinx directive to embed KPM directly inside a doc.
+
+## Usage
+
+````
+```{kpm_iframe}
+:spec:
+:dataflow:
+:preview:
+:height:
+```
+````
+
+`URI` can represent either a local file from sources that gets copied into the build directory, or a remote resource.
+
+All parameters of this directive are optional.
+
+
+## Tests
+
+### Use remote specification
+
+```{kpm_iframe}
+:spec: https://raw.githubusercontent.com/antmicro/topwrap/main/tests/data/data_kpm/examples/hdmi/specification_hdmi.json
+```
+
+### Use local files
+
+```{kpm_iframe}
+:spec: ../../../tests/data/data_kpm/examples/hierarchy/specification_hierarchy.json
+:dataflow: ../../../tests/data/data_kpm/examples/hierarchy/dataflow_hierarchy.json
+:height: 80vh
+```
+
+### Open in preview mode
+
+```{kpm_iframe}
+:spec: ../../../tests/data/data_kpm/examples/hierarchy/specification_hierarchy.json
+:dataflow: ../../../tests/data/data_kpm/examples/hierarchy/dataflow_hierarchy.json
+:preview: true
+```
diff --git a/docs/source/index.md b/docs/source/index.md
index 34144f88..cc608cd2 100644
--- a/docs/source/index.md
+++ b/docs/source/index.md
@@ -31,4 +31,5 @@ developers_guide/config
developers_guide/parsing
developers_guide/examples
developers_guide/future_enhancements
+developers_guide/inline_kpm_howto
```
diff --git a/noxfile.py b/noxfile.py
index 077746aa..0a9d9e62 100644
--- a/noxfile.py
+++ b/noxfile.py
@@ -137,6 +137,15 @@ def doc_gen(session: nox.Session) -> None:
session.install(".[docs]")
session.run("make", "-C", "docs", "html", external=True)
session.run("make", "-C", "docs", "latexpdf", external=True)
+ session.run(
+ "pipeline_manager",
+ "build",
+ "static-html",
+ "--output-directory",
+ "docs/build/html/_static/kpm",
+ "--workspace-directory",
+ "docs/build/kpm",
+ )
session.run("cp", "docs/build/latex/topwrap.pdf", "docs/build/html", external=True)