From 8c1eeca83fe0ff9b7801dbdf26af5aef17f20cea Mon Sep 17 00:00:00 2001 From: Jinzhe Zeng Date: Mon, 5 Feb 2024 11:27:44 -0500 Subject: [PATCH] Support rendering JSON in a Jupyter Notebook Signed-off-by: Jinzhe Zeng --- dargs/notebook.py | 217 ++++++++++++++++++++++++++++++++++++++++++ docs/conf.py | 2 +- docs/index.rst | 1 + docs/nb.ipynb | 46 +++++++++ docs/requirements.txt | 2 +- 5 files changed, 266 insertions(+), 2 deletions(-) create mode 100644 dargs/notebook.py create mode 100644 docs/nb.ipynb diff --git a/dargs/notebook.py b/dargs/notebook.py new file mode 100644 index 0000000..d21e3a8 --- /dev/null +++ b/dargs/notebook.py @@ -0,0 +1,217 @@ +from typing import List, Union +from IPython.core.display import display, HTML + +from dargs import Argument, Variant +import json +import re + +__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. + """ + 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"""
""", argdata.print_html(), "
"] + + display(HTML("".join(buff))) + + +class ArgumentData: + def __init__(self, data: dict, arg: Argument): + self.data = data + self.arg = arg + self.subdata = [] + self._init_subdata() + + def _init_subdata(self): + """Initialize sub ArgumentData.""" + if isinstance(self.data, dict): + sub_fields = self.arg.sub_fields.copy() + # extend subfiles with sub_variants + for vv in self.arg.sub_variants.values(): + choice = self.data.get(vv.flag_name, vv.default_tag) + if choice and choice in vv.choice_dict: + sub_fields.update(vv.choice_dict[choice].sub_fields) + + for kk in self.data: + if kk in sub_fields: + self.subdata.append(ArgumentData(self.data[kk], sub_fields[kk])) + elif kk in self.arg.sub_variants: + self.subdata.append( + ArgumentData(self.data[kk], self.arg.sub_variants[kk]) + ) + else: + self.subdata.append(ArgumentData(self.data[kk], kk)) + + def print_html(self, _level=0, _last_one=True): + linebreak = "
" + indent = ( + r"""""" + + " " * (_level * 2) + + "" + ) + buff = [] + buff.append(indent) + if _level > 0: + 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}:
type: ") + buff.append(r"""""") + buff.append("str") + buff.append(r"""""") + if self.arg.default_tag: + buff.append(", default: ") + buff.append(r"""""") + buff.append(self.arg.default_tag) + buff.append(r"""""") + else: + raise ValueError(f"Unknown type: {type(self.arg)}") + + buff.append(linebreak) + buff.append(linebreak) + doc_body = self.arg.doc.strip() + doc_body = re.sub(r"""\n+""", "\n", doc_body) + doc_body = doc_body.replace("\n", linebreak) + doc_body = re.sub( + r"`+(.*?)`+", r'\1', doc_body + ) + doc_body = re.sub(r"\*(.+)\*", r"\1", doc_body) + buff.append(doc_body) + + buff.append(r"""
""") + buff.append(r"""""") + buff.append(": ") + buff.append("") + if self.subdata: + 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/docs/conf.py b/docs/conf.py index 4bebdaa..aa65b81 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -46,7 +46,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..4934a34 --- /dev/null +++ b/docs/nb.ipynb @@ -0,0 +1,46 @@ +{ + "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", + " \"_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