|
| 1 | +r'''IPython/Jupyter Notebook display for dargs. |
| 2 | +
|
| 3 | +It is expected to be used in Jupyter Notebook, where |
| 4 | +the IPython module is available. |
| 5 | +
|
| 6 | +Examples |
| 7 | +-------- |
| 8 | +>>> from dargs.sphinx import _test_argument |
| 9 | +>>> from dargs.notebook import JSON |
| 10 | +>>> jstr = """ |
| 11 | +... { |
| 12 | +... "test_argument": "test1", |
| 13 | +... "test_variant": "test_variant_argument", |
| 14 | +... "_comment": "This is an example data" |
| 15 | +... } |
| 16 | +... """ |
| 17 | +>>> JSON(jstr, _test_argument()) |
| 18 | +''' |
| 19 | + |
| 20 | +import json |
| 21 | +import re |
| 22 | +from typing import List, Union |
| 23 | + |
| 24 | +from IPython.display import HTML, display |
| 25 | + |
| 26 | +from dargs import Argument, Variant |
| 27 | + |
| 28 | +__all__ = ["JSON"] |
| 29 | + |
| 30 | +# https://www.w3schools.com/css/css_tooltip.asp |
| 31 | +css = """<style> |
| 32 | +.dargs-codeblock { |
| 33 | + width: 100%; |
| 34 | + background-color: #f9f9f9; |
| 35 | + border-color: #f9f9f9; |
| 36 | + color: #000000; |
| 37 | +} |
| 38 | +.dargs-codeblock::before { |
| 39 | + counter-reset: listing; |
| 40 | +} |
| 41 | +.dargs-codeblock code.dargs-linebegin { |
| 42 | + counter-increment: listing; |
| 43 | +} |
| 44 | +.dargs-codeblock code.dargs-linebegin::before { |
| 45 | + content: counter(listing) " "; |
| 46 | + display: inline-block; |
| 47 | + width: 2em; |
| 48 | + padding-left: auto; |
| 49 | + margin-left: auto; |
| 50 | + text-align: right; |
| 51 | + color: #6e7781; |
| 52 | +} |
| 53 | +.dargs-codeblock code.dargs-code { |
| 54 | + padding-left: 0; |
| 55 | + padding-right: 0; |
| 56 | + margin-left: 0; |
| 57 | + margin-right: 0; |
| 58 | + background-color: #f9f9f9; |
| 59 | + border-color: #f9f9f9; |
| 60 | + color: #000000; |
| 61 | +} |
| 62 | +.dargs-codeblock .dargs-key { |
| 63 | + position: relative; |
| 64 | + display: inline-block; |
| 65 | + border-bottom: 1px dotted black; |
| 66 | +} |
| 67 | +.dargs-codeblock .dargs-key code.dargs-code { |
| 68 | + color: #0550ae; |
| 69 | +} |
| 70 | +.dargs-codeblock .dargs-key .dargs-doc { |
| 71 | + visibility: hidden; |
| 72 | + width: 600px; |
| 73 | + background-color: black; |
| 74 | + color: #fff; |
| 75 | + padding: 1em 1em; |
| 76 | + border-radius: 6px; |
| 77 | + position: absolute; |
| 78 | + z-index: 1; |
| 79 | +} |
| 80 | +.dargs-codeblock .dargs-key:hover .dargs-doc { |
| 81 | + visibility: visible; |
| 82 | +} |
| 83 | +.dargs-codeblock .dargs-key .dargs-doc .dargs-doc-code { |
| 84 | + color: #bbbbff; |
| 85 | +} |
| 86 | +</style> |
| 87 | +""" |
| 88 | + |
| 89 | + |
| 90 | +def JSON(data: Union[dict, str], arg: Union[Argument, List[Argument]]): |
| 91 | + """Display JSON data with Argument in the Jupyter Notebook. |
| 92 | +
|
| 93 | + Parameters |
| 94 | + ---------- |
| 95 | + data : dict or str |
| 96 | + The JSON data to be displayed, either JSON string or a dict. |
| 97 | + arg : dargs.Argument or list[dargs.Argument] |
| 98 | + The Argument that describes the JSON data. |
| 99 | + """ |
| 100 | + display(HTML(print_html(data, arg))) |
| 101 | + |
| 102 | + |
| 103 | +def print_html(data: Union[dict, str], arg: Union[Argument, List[Argument]]) -> str: |
| 104 | + """Print HTML string with Argument in the Jupyter Notebook. |
| 105 | +
|
| 106 | + Parameters |
| 107 | + ---------- |
| 108 | + data : dict or str |
| 109 | + The JSON data to be displayed, either JSON string or a dict. |
| 110 | + arg : dargs.Argument or list[dargs.Argument] |
| 111 | + The Argument that describes the JSON data. |
| 112 | +
|
| 113 | + Returns |
| 114 | + ------- |
| 115 | + str |
| 116 | + The HTML string. |
| 117 | + """ |
| 118 | + if isinstance(data, str): |
| 119 | + data = json.loads(data) |
| 120 | + elif isinstance(data, dict): |
| 121 | + pass |
| 122 | + else: |
| 123 | + raise ValueError(f"Unknown type: {type(data)}") |
| 124 | + |
| 125 | + if isinstance(arg, list): |
| 126 | + arg = Argument("data", dtype=dict, sub_fields=arg) |
| 127 | + elif isinstance(arg, Argument): |
| 128 | + pass |
| 129 | + else: |
| 130 | + raise ValueError(f"Unknown type: {type(arg)}") |
| 131 | + argdata = ArgumentData(data, arg) |
| 132 | + buff = [css, r"""<div class="dargs-codeblock">""", argdata.print_html(), r"</div>"] |
| 133 | + return "".join(buff) |
| 134 | + |
| 135 | + |
| 136 | +class ArgumentData: |
| 137 | + """ArgumentData is a class to hold the data and Argument. |
| 138 | +
|
| 139 | + It is used to print the data with Argument in the Jupyter Notebook. |
| 140 | +
|
| 141 | + Parameters |
| 142 | + ---------- |
| 143 | + data : dict |
| 144 | + The data to be displayed. |
| 145 | + arg : dargs.Argument |
| 146 | + The Argument that describes the data. |
| 147 | + """ |
| 148 | + |
| 149 | + def __init__(self, data: dict, arg: Argument): |
| 150 | + self.data = data |
| 151 | + self.arg = arg |
| 152 | + self.subdata = [] |
| 153 | + self._init_subdata() |
| 154 | + |
| 155 | + def _init_subdata(self): |
| 156 | + """Initialize sub ArgumentData.""" |
| 157 | + if isinstance(self.data, dict) and isinstance(self.arg, Argument): |
| 158 | + sub_fields = self.arg.sub_fields.copy() |
| 159 | + # extend subfiles with sub_variants |
| 160 | + for vv in self.arg.sub_variants.values(): |
| 161 | + choice = self.data.get(vv.flag_name, vv.default_tag) |
| 162 | + if choice and choice in vv.choice_dict: |
| 163 | + sub_fields.update(vv.choice_dict[choice].sub_fields) |
| 164 | + |
| 165 | + for kk in self.data: |
| 166 | + if kk in sub_fields: |
| 167 | + self.subdata.append(ArgumentData(self.data[kk], sub_fields[kk])) |
| 168 | + elif kk in self.arg.sub_variants: |
| 169 | + self.subdata.append( |
| 170 | + ArgumentData(self.data[kk], self.arg.sub_variants[kk]) |
| 171 | + ) |
| 172 | + else: |
| 173 | + self.subdata.append(ArgumentData(self.data[kk], kk)) |
| 174 | + elif ( |
| 175 | + isinstance(self.data, list) |
| 176 | + and isinstance(self.arg, Argument) |
| 177 | + and self.arg.repeat |
| 178 | + ): |
| 179 | + for dd in self.data: |
| 180 | + self.subdata.append(ArgumentData(dd, self.arg)) |
| 181 | + |
| 182 | + def print_html(self, _level=0, _last_one=True): |
| 183 | + """Print the data with Argument in HTML format. |
| 184 | +
|
| 185 | + Parameters |
| 186 | + ---------- |
| 187 | + _level : int, optional |
| 188 | + The level of indentation, by default 0 |
| 189 | + _last_one : bool, optional |
| 190 | + Whether it is the last one, by default True |
| 191 | + """ |
| 192 | + linebreak = "<br/>" |
| 193 | + indent = ( |
| 194 | + r"""<code class="dargs-code dargs-linebegin">""" |
| 195 | + + " " * (_level * 2) |
| 196 | + + "</code>" |
| 197 | + ) |
| 198 | + buff = [] |
| 199 | + buff.append(indent) |
| 200 | + if _level > 0 and not ( |
| 201 | + isinstance(self.data, dict) |
| 202 | + and isinstance(self.arg, Argument) |
| 203 | + and self.arg.repeat |
| 204 | + ): |
| 205 | + if isinstance(self.arg, (Argument, Variant)): |
| 206 | + buff.append(r"""<span class="dargs-key">""") |
| 207 | + else: |
| 208 | + buff.append(r"""<span>""") |
| 209 | + buff.append(r"""<code class="dargs-code">""") |
| 210 | + buff.append('"') |
| 211 | + if isinstance(self.arg, Argument): |
| 212 | + buff.append(self.arg.name) |
| 213 | + elif isinstance(self.arg, Variant): |
| 214 | + buff.append(self.arg.flag_name) |
| 215 | + elif isinstance(self.arg, str): |
| 216 | + buff.append(self.arg) |
| 217 | + else: |
| 218 | + raise ValueError(f"Unknown type: {type(self.arg)}") |
| 219 | + buff.append('"') |
| 220 | + buff.append("</code>") |
| 221 | + if isinstance(self.arg, (Argument, Variant)): |
| 222 | + buff.append(r"""<span class="dargs-doc">""") |
| 223 | + if isinstance(self.arg, Argument): |
| 224 | + doc_head = ( |
| 225 | + self.arg.gen_doc_head() |
| 226 | + .replace("| type:", "type:") |
| 227 | + .replace("\n", linebreak) |
| 228 | + ) |
| 229 | + # use re to replace ``xx`` to <code>xx</code> |
| 230 | + doc_head = re.sub( |
| 231 | + r"``(.*?)``", |
| 232 | + r'<span class="dargs-doc-code">\1</span>', |
| 233 | + doc_head, |
| 234 | + ) |
| 235 | + doc_head = re.sub(r"\*(.+)\*", r"<i>\1</i>", doc_head) |
| 236 | + buff.append(doc_head) |
| 237 | + elif isinstance(self.arg, Variant): |
| 238 | + buff.append(f"{self.arg.flag_name}:<br/>type: ") |
| 239 | + buff.append(r"""<span class="dargs-doc-code">""") |
| 240 | + buff.append("str") |
| 241 | + buff.append(r"""</span>""") |
| 242 | + if self.arg.default_tag: |
| 243 | + buff.append(", default: ") |
| 244 | + buff.append(r"""<span class="dargs-doc-code">""") |
| 245 | + buff.append(self.arg.default_tag) |
| 246 | + buff.append(r"""</span>""") |
| 247 | + else: |
| 248 | + raise ValueError(f"Unknown type: {type(self.arg)}") |
| 249 | + |
| 250 | + doc_body = self.arg.doc.strip() |
| 251 | + if doc_body: |
| 252 | + buff.append("<hr/>") |
| 253 | + doc_body = re.sub(r"""\n+""", "\n", doc_body) |
| 254 | + doc_body = doc_body.replace("\n", linebreak) |
| 255 | + doc_body = re.sub( |
| 256 | + r"`+(.*?)`+", r'<span class="dargs-doc-code">\1</span>', doc_body |
| 257 | + ) |
| 258 | + doc_body = re.sub(r"\*(.+)\*", r"<i>\1</i>", doc_body) |
| 259 | + buff.append(doc_body) |
| 260 | + |
| 261 | + buff.append(r"""</span>""") |
| 262 | + buff.append(r"""</span>""") |
| 263 | + buff.append(r"""<code class="dargs-code">""") |
| 264 | + buff.append(": ") |
| 265 | + buff.append("</code>") |
| 266 | + if self.subdata and isinstance(self.data, dict): |
| 267 | + buff.append(r"""<code class="dargs-code">""") |
| 268 | + buff.append("{") |
| 269 | + buff.append("</code>") |
| 270 | + buff.append(linebreak) |
| 271 | + for ii, sub in enumerate(self.subdata): |
| 272 | + buff.append( |
| 273 | + sub.print_html(_level + 1, _last_one=(ii == len(self.subdata) - 1)) |
| 274 | + ) |
| 275 | + buff.append(indent) |
| 276 | + buff.append(r"""<code class="dargs-code">""") |
| 277 | + buff.append("}") |
| 278 | + if not _last_one: |
| 279 | + buff.append(",") |
| 280 | + buff.append("</code>") |
| 281 | + buff.append(linebreak) |
| 282 | + elif self.subdata and isinstance(self.data, list): |
| 283 | + buff.append(r"""<code class="dargs-code">""") |
| 284 | + buff.append("[") |
| 285 | + buff.append("</code>") |
| 286 | + buff.append(linebreak) |
| 287 | + for ii, sub in enumerate(self.subdata): |
| 288 | + buff.append( |
| 289 | + sub.print_html(_level + 1, _last_one=(ii == len(self.subdata) - 1)) |
| 290 | + ) |
| 291 | + buff.append(indent) |
| 292 | + buff.append(r"""<code class="dargs-code">""") |
| 293 | + buff.append("]") |
| 294 | + if not _last_one: |
| 295 | + buff.append(",") |
| 296 | + buff.append("</code>") |
| 297 | + buff.append(linebreak) |
| 298 | + else: |
| 299 | + buff.append(r"""<code class="dargs-code">""") |
| 300 | + buff.append( |
| 301 | + json.dumps(self.data, indent=2).replace("\n", f"{linebreak}{indent}") |
| 302 | + ) |
| 303 | + if not _last_one: |
| 304 | + buff.append(",") |
| 305 | + buff.append("</code>") |
| 306 | + buff.append(linebreak) |
| 307 | + return "".join(buff) |
0 commit comments