diff --git a/dargs/notebook.py b/dargs/notebook.py new file mode 100644 index 0000000..30b9708 --- /dev/null +++ b/dargs/notebook.py @@ -0,0 +1,307 @@ +r'''IPython/Jupyter Notebook display for dargs. + +It is expected to be used in Jupyter Notebook, where +the IPython module is available. + +Examples +-------- +>>> from dargs.sphinx import _test_argument +>>> from dargs.notebook import JSON +>>> jstr = """ +... { +... "test_argument": "test1", +... "test_variant": "test_variant_argument", +... "_comment": "This is an example data" +... } +... """ +>>> JSON(jstr, _test_argument()) +''' + +import json +import re +from typing import List, Union + +from IPython.display import HTML, display + +from dargs import Argument, Variant + +__all__ = ["JSON"] + +# https://www.w3schools.com/css/css_tooltip.asp +css = """ +""" + + +def JSON(data: Union[dict, str], arg: Union[Argument, List[Argument]]): + """Display JSON data with Argument in the Jupyter Notebook. + + Parameters + ---------- + data : dict or str + The JSON data to be displayed, either JSON string or a dict. + arg : dargs.Argument or list[dargs.Argument] + The Argument that describes the JSON data. + """ + display(HTML(print_html(data, arg))) + + +def print_html(data: Union[dict, str], arg: Union[Argument, List[Argument]]) -> str: + """Print HTML string with Argument in the Jupyter Notebook. + + Parameters + ---------- + data : dict or str + The JSON data to be displayed, either JSON string or a dict. + arg : dargs.Argument or list[dargs.Argument] + The Argument that describes the JSON data. + + Returns + ------- + str + The HTML string. + """ + if isinstance(data, str): + data = json.loads(data) + elif isinstance(data, dict): + pass + else: + raise ValueError(f"Unknown type: {type(data)}") + + if isinstance(arg, list): + arg = Argument("data", dtype=dict, sub_fields=arg) + elif isinstance(arg, Argument): + pass + else: + raise ValueError(f"Unknown type: {type(arg)}") + argdata = ArgumentData(data, arg) + buff = [css, r"""
"""
+ + " " * (_level * 2)
+ + "
"
+ )
+ buff = []
+ buff.append(indent)
+ if _level > 0 and not (
+ isinstance(self.data, dict)
+ and isinstance(self.arg, Argument)
+ and self.arg.repeat
+ ):
+ if isinstance(self.arg, (Argument, Variant)):
+ buff.append(r"""""")
+ else:
+ buff.append(r"""""")
+ buff.append(r"""""")
+ buff.append('"')
+ if isinstance(self.arg, Argument):
+ buff.append(self.arg.name)
+ elif isinstance(self.arg, Variant):
+ buff.append(self.arg.flag_name)
+ elif isinstance(self.arg, str):
+ buff.append(self.arg)
+ else:
+ raise ValueError(f"Unknown type: {type(self.arg)}")
+ buff.append('"')
+ buff.append("
")
+ if isinstance(self.arg, (Argument, Variant)):
+ buff.append(r"""""")
+ if isinstance(self.arg, Argument):
+ doc_head = (
+ self.arg.gen_doc_head()
+ .replace("| type:", "type:")
+ .replace("\n", linebreak)
+ )
+ # use re to replace ``xx`` to xx
+ doc_head = re.sub(
+ r"``(.*?)``",
+ r'\1',
+ doc_head,
+ )
+ doc_head = re.sub(r"\*(.+)\*", r"\1", doc_head)
+ buff.append(doc_head)
+ elif isinstance(self.arg, Variant):
+ buff.append(f"{self.arg.flag_name}:""")
+ buff.append(": ")
+ buff.append("
")
+ if self.subdata and isinstance(self.data, dict):
+ buff.append(r"""""")
+ buff.append("{")
+ buff.append("
")
+ buff.append(linebreak)
+ for ii, sub in enumerate(self.subdata):
+ buff.append(
+ sub.print_html(_level + 1, _last_one=(ii == len(self.subdata) - 1))
+ )
+ buff.append(indent)
+ buff.append(r"""""")
+ buff.append("}")
+ if not _last_one:
+ buff.append(",")
+ buff.append("
")
+ buff.append(linebreak)
+ elif self.subdata and isinstance(self.data, list):
+ buff.append(r"""""")
+ buff.append("[")
+ buff.append("
")
+ buff.append(linebreak)
+ for ii, sub in enumerate(self.subdata):
+ buff.append(
+ sub.print_html(_level + 1, _last_one=(ii == len(self.subdata) - 1))
+ )
+ buff.append(indent)
+ buff.append(r"""""")
+ buff.append("]")
+ if not _last_one:
+ buff.append(",")
+ buff.append("
")
+ buff.append(linebreak)
+ else:
+ buff.append(r"""""")
+ buff.append(
+ json.dumps(self.data, indent=2).replace("\n", f"{linebreak}{indent}")
+ )
+ if not _last_one:
+ buff.append(",")
+ buff.append("
")
+ buff.append(linebreak)
+ return "".join(buff)
diff --git a/dargs/sphinx.py b/dargs/sphinx.py
index 2c7aa82..986f743 100644
--- a/dargs/sphinx.py
+++ b/dargs/sphinx.py
@@ -176,7 +176,25 @@ def _test_argument() -> Argument:
"test_variant",
doc=doc_test,
choices=[
- Argument("test_variant_argument", dtype=str, doc=doc_test),
+ Argument(
+ "test_variant_argument",
+ dtype=dict,
+ optional=True,
+ doc=doc_test,
+ sub_fields=[
+ Argument(
+ "test_repeat",
+ dtype=list,
+ repeat=True,
+ doc=doc_test,
+ sub_fields=[
+ Argument(
+ "test_repeat_item", dtype=bool, doc=doc_test
+ ),
+ ],
+ )
+ ],
+ ),
],
),
],
diff --git a/docs/conf.py b/docs/conf.py
index 4c106fc..88c1a40 100644
--- a/docs/conf.py
+++ b/docs/conf.py
@@ -45,7 +45,7 @@
"sphinx.ext.viewcode",
"sphinx.ext.intersphinx",
"numpydoc",
- "myst_parser",
+ "myst_nb",
"dargs.sphinx",
]
diff --git a/docs/index.rst b/docs/index.rst
index 482500d..5d6f0f2 100644
--- a/docs/index.rst
+++ b/docs/index.rst
@@ -13,6 +13,7 @@ Welcome to dargs's documentation!
intro
sphinx
dpgui
+ nb
api/api
credits
diff --git a/docs/nb.ipynb b/docs/nb.ipynb
new file mode 100644
index 0000000..1793515
--- /dev/null
+++ b/docs/nb.ipynb
@@ -0,0 +1,50 @@
+{
+ "cells": [
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "# Use with Jupyter Notebook\n",
+ "\n",
+ "In a [Jupyter Notebook](https://jupyter.org/), with {meth}`dargs.notebook.JSON`, one can render an JSON string with an Argument."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "from dargs.sphinx import _test_argument\n",
+ "from dargs.notebook import JSON\n",
+ "\n",
+ "jstr = \"\"\"\n",
+ "{\n",
+ " \"test_argument\": \"test1\",\n",
+ " \"test_variant\": \"test_variant_argument\",\n",
+ " \"test_repeat\": [\n",
+ " {\"test_repeat_item\": false},\n",
+ " {\"test_repeat_item\": true}\n",
+ " ],\n",
+ " \"_comment\": \"This is an example data\"\n",
+ "}\n",
+ "\"\"\"\n",
+ "JSON(jstr, _test_argument())"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "When the monse stays on an argument key, the documentation of this argument will pop up."
+ ]
+ }
+ ],
+ "metadata": {
+ "language_info": {
+ "name": "python"
+ }
+ },
+ "nbformat": 4,
+ "nbformat_minor": 2
+}
diff --git a/docs/requirements.txt b/docs/requirements.txt
index 54fdba7..d2c7bc5 100644
--- a/docs/requirements.txt
+++ b/docs/requirements.txt
@@ -1,5 +1,5 @@
.
numpydoc
deepmodeling_sphinx>=0.1.1
-myst_parser
+myst-nb
sphinx_rtd_theme
diff --git a/pyproject.toml b/pyproject.toml
index ab89bf1..0a6b0fe 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -27,6 +27,11 @@ Homepage = "https://github.com/deepmodeling/dargs"
documentation = "https://docs.deepmodeling.com/projects/dargs"
repository = "https://github.com/deepmodeling/dargs"
+[project.optional-dependencies]
+test = [
+ "ipython",
+]
+
[tool.setuptools.packages.find]
include = ["dargs*"]
diff --git a/tests/test_notebook.py b/tests/test_notebook.py
new file mode 100644
index 0000000..01e46a7
--- /dev/null
+++ b/tests/test_notebook.py
@@ -0,0 +1,70 @@
+import unittest
+from xml.etree import ElementTree as ET
+
+from dargs import Argument, Variant
+
+try:
+ import IPython # noqa: F401
+except ImportError:
+ ipython_installed = False
+else:
+ ipython_installed = True
+
+
+@unittest.skipUnless(ipython_installed, "IPython not installed")
+class TestNotebook(unittest.TestCase):
+ def test_html_validation(self):
+ from dargs.notebook import print_html
+
+ doc_test = "Test doc."
+ test_arg = Argument(
+ name="test",
+ dtype=str,
+ doc=doc_test,
+ sub_fields=[
+ Argument("test_argument", dtype=str, doc=doc_test, default="test"),
+ ],
+ sub_variants=[
+ Variant(
+ "test_variant",
+ doc=doc_test,
+ choices=[
+ Argument(
+ "test_variant_argument",
+ dtype=dict,
+ optional=True,
+ doc=doc_test,
+ sub_fields=[
+ Argument(
+ "test_repeat",
+ dtype=list,
+ repeat=True,
+ doc=doc_test,
+ sub_fields=[
+ Argument(
+ "test_repeat_item", dtype=bool, doc=doc_test
+ ),
+ ],
+ )
+ ],
+ ),
+ ],
+ ),
+ ],
+ )
+ jdata = {
+ "test_argument": "test1",
+ "test_variant": "test_variant_argument",
+ "test_repeat": [{"test_repeat_item": False}, {"test_repeat_item": True}],
+ "_comment": "This is an example data",
+ }
+ html = print_html(
+ jdata,
+ test_arg,
+ )
+ # https://stackoverflow.com/a/29533744/9567349
+ # https://stackoverflow.com/a/35591479/9567349
+ magic = """
+ ]>"""
+ ET.fromstring(magic + f"{html}")