We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
Performance issues with buffer conversions from python to javascript
Hello @hoodmane, thank you for the magnificent APIs (deep/shallowCopyToJavaScript) that would eliminate the heavy cost of big array conversion from python to Javascript.  I think the shallowCopy version is designed for memory address reference instead of bulks of content copy. But I find the perfomance not so good as I've imagined, which is displayed as the following:
![image](https://user-images.githubusercontent.com/34263430/107121264-74600900-68cc-11eb-8e92-da3b8b09b5f4.png)

It takes about 1~7 seconds for shallowCopyToJavascript() to complete the memory address reference and maybe some necessary meta data copy I guess. However, it's not  adequate for a realtime computation.  Any suggestions for better conversion performance?

_Originally posted by @daoxian in https://github.com/iodide-project/pyodide/issues/1167#issuecomment-774488338_

--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
--- BEGIN FILES ---
Path: `docs/sphinx_pyodide/sphinx_pyodide/jsdoc.py`
Content:
```
1 from docutils import nodes
2 from docutils.parsers.rst import Directive, Parser as RstParser
3 from docutils.statemachine import StringList
4 from docutils.utils import new_document
5 
6 from collections import OrderedDict
7 
8 from sphinx import addnodes
9 from sphinx.util import rst
10 from sphinx.util.docutils import switch_source_input
11 from sphinx.ext.autosummary import autosummary_table, extract_summary
12 
13 from sphinx_js.jsdoc import Analyzer as JsAnalyzer
14 from sphinx_js.ir import Function
15 from sphinx_js.parsers import path_and_formal_params, PathVisitor
16 from sphinx_js.renderers import AutoFunctionRenderer, AutoAttributeRenderer
17 
18 
19 class PyodideAnalyzer:
20     """JsDoc automatically instantiates the JsAnalyzer. Rather than subclassing
21     or monkey patching it, we use composition (see getattr impl).
22 
23     The main extra thing we do is reorganize the doclets based on our globals /
24     functions / attributes scheme. This we use to subdivide the sections in our
25     summary. We store these in the "js_docs" field which is the only field that
26     we access later.
27     """
28 
29     def __init__(self, analyzer: JsAnalyzer) -> None:
30         self.inner = analyzer
31         self.create_js_doclets()
32 
33     def __getattr__(self, key):
34         return getattr(self.inner, key)
35 
36     def longname_to_path(self, name):
37         """Convert the longname field produced by jsdoc to a path appropriate to use
38         with _sphinxjs_analyzer.get_object. Based on:
39         https://github.com/mozilla/sphinx-js/blob/3.1/sphinx_js/jsdoc.py#L181
40         """
41         return PathVisitor().visit(path_and_formal_params["path"].parse(name))
42 
43     def get_object_from_json(self, json):
44         """Look up the JsDoc IR object corresponding to this object. We use the
45         "kind" field to decide whether the object is a "function" or an
46         "attribute". We use longname_to_path to convert the path into a list of
47         path components which JsAnalyzer.get_object requires.
48         """
49         path = self.longname_to_path(json["longname"])
50         kind = "function" if json["kind"] == "function" else "attribute"
51         obj = self.inner.get_object(path, kind)
52         obj.kind = kind
53         return obj
54 
55     def create_js_doclets(self):
56         """Search through the doclets generated by JsDoc and categorize them by
57         summary section. Skip docs labeled as "@private".
58         """
59 
60         def get_val():
61             return OrderedDict([["attribute", []], ["function", []]])
62 
63         self.js_docs = {key: get_val() for key in ["globals", "pyodide", "PyProxy"]}
64         items = {"PyProxy": []}
65         for (key, group) in self._doclets_by_class.items():
66             key = [x for x in key if "/" not in x]
67             if key[-1] == "globalThis":
68                 items["globals"] = group
69             if key[0] == "pyodide." and key[-1] == "Module":
70                 items["pyodide"] = group
71             if key[0] == "pyproxy.":
72                 items["PyProxy"] += group
73 
74         for key, value in items.items():
75             for json in value:
76                 if json.get("access", None) == "private":
77                     continue
78                 obj = self.get_object_from_json(json)
79                 if obj.name[0] == '"' and obj.name[-1] == '"':
80                     obj.name = "[" + obj.name[1:-1] + "]"
81                 self.js_docs[key][obj.kind].append(obj)
82 
83 
84 def get_jsdoc_content_directive(app):
85     """These directives need to close over app """
86 
87     class JsDocContent(Directive):
88         """A directive that just dumps a summary table in place. There are no
89         options, it only prints the one thing, we control the behavior from
90         here
91         """
92 
93         required_arguments = 1
94 
95         def get_rst(self, obj):
96             """Grab the appropriate renderer and render us to rst.
97             JsDoc also has an AutoClassRenderer which may be useful in the future."""
98             if isinstance(obj, Function):
99                 renderer = AutoFunctionRenderer
100             else:
101                 renderer = AutoAttributeRenderer
102             return renderer(self, app, arguments=["dummy"]).rst(
103                 [obj.name], obj, use_short_name=False
104             )
105 
106         def get_rst_for_group(self, objects):
107             return [self.get_rst(obj) for obj in objects]
108 
109         def parse_rst(self, rst):
110             """We produce a bunch of rst but directives are supposed to output
111             docutils trees. This is a helper that converts the rst to docutils.
112             """
113             settings = self.state.document.settings
114             doc = new_document("", settings)
115             RstParser().parse(rst, doc)
116             return doc.children
117 
118         def run(self):
119             module = self.arguments[0]
120             values = app._sphinxjs_analyzer.js_docs[module]
121             rst = []
122             rst.append([f".. js:module:: {module}"])
123             for group in values.values():
124                 rst.append(self.get_rst_for_group(group))
125             joined_rst = "\n\n".join(["\n\n".join(r) for r in rst])
126             return self.parse_rst(joined_rst)
127 
128     return JsDocContent
129 
130 
131 def get_jsdoc_summary_directive(app):
132     class JsDocSummary(Directive):
133         """A directive that just dumps the Js API docs in place. There are no
134         options, it only prints the one thing, we control the behavior from
135         here
136         """
137 
138         required_arguments = 1
139 
140         def run(self):
141             result = []
142             module = self.arguments[0]
143             value = app._sphinxjs_analyzer.js_docs[module]
144             for group_name, group_objects in value.items():
145                 if not group_objects:
146                     continue
147                 result.append(self.format_heading(group_name.title() + "s:"))
148                 table_items = self.get_summary_table(module, group_objects)
149                 table_markup = self.format_table(table_items)
150                 result.extend(table_markup)
151             return result
152 
153         def format_heading(self, text):
154             """Make a section heading. This corresponds to the rst: "**Heading:**"
155             autodocsumm uses headings like that, so this will match that style.
156             """
157             heading = nodes.paragraph("")
158             strong = nodes.strong("")
159             strong.append(nodes.Text(text))
160             heading.append(strong)
161             return heading
162 
163         def extract_summary(self, descr):
164             """Wrapper around autosummary extract_summary that is easier to use.
165             It seems like colons need escaping for some reason.
166             """
167             colon_esc = "esccolon\\\xafhoa:"
168             return extract_summary(
169                 [descr.replace(":", colon_esc)], self.state.document
170             ).replace(colon_esc, ":")
171 
172         def get_sig(self, obj):
173             """If the object is a function, get its signature (as figured by JsDoc)"""
174             if isinstance(obj, Function):
175                 return AutoFunctionRenderer(
176                     self, app, arguments=["dummy"]
177                 )._formal_params(obj)
178             else:
179                 return ""
180 
181         def get_summary_row(self, pkgname, obj):
182             """Get the summary table row for obj.
183 
184             The output is designed to be input to format_table. The link name
185             needs to be set up so that :any:`link_name` makes a link to the
186             actual api docs for this object.
187             """
188             sig = self.get_sig(obj)
189             display_name = obj.name
190             summary = self.extract_summary(obj.description)
191             link_name = pkgname + "." + display_name
192             return (display_name, sig, summary, link_name)
193 
194         def get_summary_table(self, pkgname, group):
195             """Get the data for a summary table. Return value is set up to be an
196             argument of format_table.
197             """
198             return [self.get_summary_row(pkgname, obj) for obj in group]
199 
200         # This following method is copied almost verbatim from autosummary
201         # (where it is called get_table).
202         #
203         # We have to change the value of one string: qualifier = 'obj   ==>
204         # qualifier = 'any'
205         # https://github.com/sphinx-doc/sphinx/blob/3.x/sphinx/ext/autosummary/__init__.py#L392
206         def format_table(self, items):
207             """Generate a proper list of table nodes for autosummary:: directive.
208 
209             *items* is a list produced by :meth:`get_items`.
210             """
211             table_spec = addnodes.tabular_col_spec()
212             table_spec["spec"] = r"\X{1}{2}\X{1}{2}"
213 
214             table = autosummary_table("")
215             real_table = nodes.table("", classes=["longtable"])
216             table.append(real_table)
217             group = nodes.tgroup("", cols=2)
218             real_table.append(group)
219             group.append(nodes.colspec("", colwidth=10))
220             group.append(nodes.colspec("", colwidth=90))
221             body = nodes.tbody("")
222             group.append(body)
223 
224             def append_row(*column_texts: str) -> None:
225                 row = nodes.row("")
226                 source, line = self.state_machine.get_source_and_line()
227                 for text in column_texts:
228                     node = nodes.paragraph("")
229                     vl = StringList()
230                     vl.append(text, "%s:%d:<autosummary>" % (source, line))
231                     with switch_source_input(self.state, vl):
232                         self.state.nested_parse(vl, 0, node)
233                         try:
234                             if isinstance(node[0], nodes.paragraph):
235                                 node = node[0]
236                         except IndexError:
237                             pass
238                         row.append(nodes.entry("", node))
239                 body.append(row)
240 
241             for name, sig, summary, real_name in items:
242                 qualifier = "any"  # <== Only thing changed from autosummary version
243                 if "nosignatures" not in self.options:
244                     col1 = ":%s:`%s <%s>`\\ %s" % (
245                         qualifier,
246                         name,
247                         real_name,
248                         rst.escape(sig),
249                     )
250                 else:
251                     col1 = ":%s:`%s <%s>`" % (qualifier, name, real_name)
252                 col2 = summary
253                 append_row(col1, col2)
254 
255             return [table_spec, table]
256 
257     return JsDocSummary
258
```
--- END FILES ---

Please first localize the bug based on the issue statement, and then generate a patch according to the `git diff` format fenced by three backticks.

Here is an example:

```diff
diff --git a/examples/server_async.py b/examples/server_async.py
--- a/examples/server_async.py
+++ b/examples/server_async.py
@@ -313,4 +313,4 @@
 
 
 if __name__ == "__main__":
-    asyncio.run(run_async_server("."), debug=True)
+    asyncio.run(run_async_server(), debug=True)
diff --git a/examples/server_sync.py b/examples/server_sync.py
--- a/examples/server_sync.py
+++ b/examples/server_sync.py
@@ -313,5 +313,5 @@
 
 
 if __name__ == "__main__":
-    server = run_sync_server(".")
+    server = run_sync_server()
     server.shutdown()

```

diff --git a/docs/sphinx_pyodide/sphinx_pyodide/jsdoc.py b/docs/sphinx_pyodide/sphinx_pyodide/jsdoc.py
--- a/docs/sphinx_pyodide/sphinx_pyodide/jsdoc.py
+++ b/docs/sphinx_pyodide/sphinx_pyodide/jsdoc.py
@@ -4,6 +4,7 @@
 from docutils.utils import new_document
 
 from collections import OrderedDict
+import re
 
 from sphinx import addnodes
 from sphinx.util import rst
@@ -11,9 +12,13 @@
 from sphinx.ext.autosummary import autosummary_table, extract_summary
 
 from sphinx_js.jsdoc import Analyzer as JsAnalyzer
-from sphinx_js.ir import Function
+from sphinx_js.ir import Class, Function
 from sphinx_js.parsers import path_and_formal_params, PathVisitor
-from sphinx_js.renderers import AutoFunctionRenderer, AutoAttributeRenderer
+from sphinx_js.renderers import (
+    AutoFunctionRenderer,
+    AutoAttributeRenderer,
+    AutoClassRenderer,
+)
 
 
 class PyodideAnalyzer:
@@ -47,7 +52,12 @@
         path components which JsAnalyzer.get_object requires.
         """
         path = self.longname_to_path(json["longname"])
-        kind = "function" if json["kind"] == "function" else "attribute"
+        if json["kind"] == "function":
+            kind = "function"
+        elif json["kind"] == "class":
+            kind = "class"
+        else:
+            kind = "attribute"
         obj = self.inner.get_object(path, kind)
         obj.kind = kind
         return obj
@@ -58,12 +68,16 @@
         """
 
         def get_val():
-            return OrderedDict([["attribute", []], ["function", []]])
+            return OrderedDict([["attribute", []], ["function", []], ["class", []]])
 
         self.js_docs = {key: get_val() for key in ["globals", "pyodide", "PyProxy"]}
         items = {"PyProxy": []}
         for (key, group) in self._doclets_by_class.items():
             key = [x for x in key if "/" not in x]
+            if key[-1] == "PyBuffer":
+                # PyBuffer stuff is documented as a class. Would be nice to have
+                # a less ad hoc way to deal with this...
+                continue
             if key[-1] == "globalThis":
                 items["globals"] = group
             if key[0] == "pyodide." and key[-1] == "Module":
@@ -76,7 +90,13 @@
                 if json.get("access", None) == "private":
                     continue
                 obj = self.get_object_from_json(json)
+                if isinstance(obj, Class):
+                    # sphinx-jsdoc messes up array types. Fix them.
+                    for x in obj.members:
+                        if hasattr(x, "type"):
+                            x.type = re.sub("Array\.<([a-zA-Z_0-9]*)>", r"\1[]", x.type)
                 if obj.name[0] == '"' and obj.name[-1] == '"':
+                    # sphinx-jsdoc messes up Symbol attributes. Fix them.
                     obj.name = "[" + obj.name[1:-1] + "]"
                 self.js_docs[key][obj.kind].append(obj)
 
@@ -97,11 +117,13 @@
             JsDoc also has an AutoClassRenderer which may be useful in the future."""
             if isinstance(obj, Function):
                 renderer = AutoFunctionRenderer
+            elif isinstance(obj, Class):
+                renderer = AutoClassRenderer
             else:
                 renderer = AutoAttributeRenderer
-            return renderer(self, app, arguments=["dummy"]).rst(
-                [obj.name], obj, use_short_name=False
-            )
+            return renderer(
+                self, app, arguments=["dummy"], options={"members": ["*"]}
+            ).rst([obj.name], obj, use_short_name=False)
 
         def get_rst_for_group(self, objects):
             return [self.get_rst(obj) for obj in objects]
@@ -144,6 +166,9 @@
             for group_name, group_objects in value.items():
                 if not group_objects:
                     continue
+                if group_name == "class":
+                    # Plural of class is "classes" not "classs"
+                    group_name += "e"
                 result.append(self.format_heading(group_name.title() + "s:"))
                 table_items = self.get_summary_table(module, group_objects)
                 table_markup = self.format_table(table_items)

{"golden_diff": "diff --git a/docs/sphinx_pyodide/sphinx_pyodide/jsdoc.py b/docs/sphinx_pyodide/sphinx_pyodide/jsdoc.py\n--- a/docs/sphinx_pyodide/sphinx_pyodide/jsdoc.py\n+++ b/docs/sphinx_pyodide/sphinx_pyodide/jsdoc.py\n@@ -4,6 +4,7 @@\n from docutils.utils import new_document\n \n from collections import OrderedDict\n+import re\n \n from sphinx import addnodes\n from sphinx.util import rst\n@@ -11,9 +12,13 @@\n from sphinx.ext.autosummary import autosummary_table, extract_summary\n \n from sphinx_js.jsdoc import Analyzer as JsAnalyzer\n-from sphinx_js.ir import Function\n+from sphinx_js.ir import Class, Function\n from sphinx_js.parsers import path_and_formal_params, PathVisitor\n-from sphinx_js.renderers import AutoFunctionRenderer, AutoAttributeRenderer\n+from sphinx_js.renderers import (\n+    AutoFunctionRenderer,\n+    AutoAttributeRenderer,\n+    AutoClassRenderer,\n+)\n \n \n class PyodideAnalyzer:\n@@ -47,7 +52,12 @@\n         path components which JsAnalyzer.get_object requires.\n         \"\"\"\n         path = self.longname_to_path(json[\"longname\"])\n-        kind = \"function\" if json[\"kind\"] == \"function\" else \"attribute\"\n+        if json[\"kind\"] == \"function\":\n+            kind = \"function\"\n+        elif json[\"kind\"] == \"class\":\n+            kind = \"class\"\n+        else:\n+            kind = \"attribute\"\n         obj = self.inner.get_object(path, kind)\n         obj.kind = kind\n         return obj\n@@ -58,12 +68,16 @@\n         \"\"\"\n \n         def get_val():\n-            return OrderedDict([[\"attribute\", []], [\"function\", []]])\n+            return OrderedDict([[\"attribute\", []], [\"function\", []], [\"class\", []]])\n \n         self.js_docs = {key: get_val() for key in [\"globals\", \"pyodide\", \"PyProxy\"]}\n         items = {\"PyProxy\": []}\n         for (key, group) in self._doclets_by_class.items():\n             key = [x for x in key if \"/\" not in x]\n+            if key[-1] == \"PyBuffer\":\n+                # PyBuffer stuff is documented as a class. Would be nice to have\n+                # a less ad hoc way to deal with this...\n+                continue\n             if key[-1] == \"globalThis\":\n                 items[\"globals\"] = group\n             if key[0] == \"pyodide.\" and key[-1] == \"Module\":\n@@ -76,7 +90,13 @@\n                 if json.get(\"access\", None) == \"private\":\n                     continue\n                 obj = self.get_object_from_json(json)\n+                if isinstance(obj, Class):\n+                    # sphinx-jsdoc messes up array types. Fix them.\n+                    for x in obj.members:\n+                        if hasattr(x, \"type\"):\n+                            x.type = re.sub(\"Array\\.<([a-zA-Z_0-9]*)>\", r\"\\1[]\", x.type)\n                 if obj.name[0] == '\"' and obj.name[-1] == '\"':\n+                    # sphinx-jsdoc messes up Symbol attributes. Fix them.\n                     obj.name = \"[\" + obj.name[1:-1] + \"]\"\n                 self.js_docs[key][obj.kind].append(obj)\n \n@@ -97,11 +117,13 @@\n             JsDoc also has an AutoClassRenderer which may be useful in the future.\"\"\"\n             if isinstance(obj, Function):\n                 renderer = AutoFunctionRenderer\n+            elif isinstance(obj, Class):\n+                renderer = AutoClassRenderer\n             else:\n                 renderer = AutoAttributeRenderer\n-            return renderer(self, app, arguments=[\"dummy\"]).rst(\n-                [obj.name], obj, use_short_name=False\n-            )\n+            return renderer(\n+                self, app, arguments=[\"dummy\"], options={\"members\": [\"*\"]}\n+            ).rst([obj.name], obj, use_short_name=False)\n \n         def get_rst_for_group(self, objects):\n             return [self.get_rst(obj) for obj in objects]\n@@ -144,6 +166,9 @@\n             for group_name, group_objects in value.items():\n                 if not group_objects:\n                     continue\n+                if group_name == \"class\":\n+                    # Plural of class is \"classes\" not \"classs\"\n+                    group_name += \"e\"\n                 result.append(self.format_heading(group_name.title() + \"s:\"))\n                 table_items = self.get_summary_table(module, group_objects)\n                 table_markup = self.format_table(table_items)\n", "issue": "Performance issues with buffer conversions from python to javascript\nHello @hoodmane, thank you for the magnificent APIs (deep/shallowCopyToJavaScript) that would eliminate the heavy cost of big array conversion from python to Javascript.  I think the shallowCopy version is designed for memory address reference instead of bulks of content copy. But I find the perfomance not so good as I've imagined, which is displayed as the following:\r\n![image](https://user-images.githubusercontent.com/34263430/107121264-74600900-68cc-11eb-8e92-da3b8b09b5f4.png)\r\n\r\nIt takes about 1~7 seconds for shallowCopyToJavascript() to complete the memory address reference and maybe some necessary meta data copy I guess. However, it's not  adequate for a realtime computation.  Any suggestions for better conversion performance?\r\n\r\n_Originally posted by @daoxian in https://github.com/iodide-project/pyodide/issues/1167#issuecomment-774488338_\n", "before_files": [{"content": "from docutils import nodes\nfrom docutils.parsers.rst import Directive, Parser as RstParser\nfrom docutils.statemachine import StringList\nfrom docutils.utils import new_document\n\nfrom collections import OrderedDict\n\nfrom sphinx import addnodes\nfrom sphinx.util import rst\nfrom sphinx.util.docutils import switch_source_input\nfrom sphinx.ext.autosummary import autosummary_table, extract_summary\n\nfrom sphinx_js.jsdoc import Analyzer as JsAnalyzer\nfrom sphinx_js.ir import Function\nfrom sphinx_js.parsers import path_and_formal_params, PathVisitor\nfrom sphinx_js.renderers import AutoFunctionRenderer, AutoAttributeRenderer\n\n\nclass PyodideAnalyzer:\n    \"\"\"JsDoc automatically instantiates the JsAnalyzer. Rather than subclassing\n    or monkey patching it, we use composition (see getattr impl).\n\n    The main extra thing we do is reorganize the doclets based on our globals /\n    functions / attributes scheme. This we use to subdivide the sections in our\n    summary. We store these in the \"js_docs\" field which is the only field that\n    we access later.\n    \"\"\"\n\n    def __init__(self, analyzer: JsAnalyzer) -> None:\n        self.inner = analyzer\n        self.create_js_doclets()\n\n    def __getattr__(self, key):\n        return getattr(self.inner, key)\n\n    def longname_to_path(self, name):\n        \"\"\"Convert the longname field produced by jsdoc to a path appropriate to use\n        with _sphinxjs_analyzer.get_object. Based on:\n        https://github.com/mozilla/sphinx-js/blob/3.1/sphinx_js/jsdoc.py#L181\n        \"\"\"\n        return PathVisitor().visit(path_and_formal_params[\"path\"].parse(name))\n\n    def get_object_from_json(self, json):\n        \"\"\"Look up the JsDoc IR object corresponding to this object. We use the\n        \"kind\" field to decide whether the object is a \"function\" or an\n        \"attribute\". We use longname_to_path to convert the path into a list of\n        path components which JsAnalyzer.get_object requires.\n        \"\"\"\n        path = self.longname_to_path(json[\"longname\"])\n        kind = \"function\" if json[\"kind\"] == \"function\" else \"attribute\"\n        obj = self.inner.get_object(path, kind)\n        obj.kind = kind\n        return obj\n\n    def create_js_doclets(self):\n        \"\"\"Search through the doclets generated by JsDoc and categorize them by\n        summary section. Skip docs labeled as \"@private\".\n        \"\"\"\n\n        def get_val():\n            return OrderedDict([[\"attribute\", []], [\"function\", []]])\n\n        self.js_docs = {key: get_val() for key in [\"globals\", \"pyodide\", \"PyProxy\"]}\n        items = {\"PyProxy\": []}\n        for (key, group) in self._doclets_by_class.items():\n            key = [x for x in key if \"/\" not in x]\n            if key[-1] == \"globalThis\":\n                items[\"globals\"] = group\n            if key[0] == \"pyodide.\" and key[-1] == \"Module\":\n                items[\"pyodide\"] = group\n            if key[0] == \"pyproxy.\":\n                items[\"PyProxy\"] += group\n\n        for key, value in items.items():\n            for json in value:\n                if json.get(\"access\", None) == \"private\":\n                    continue\n                obj = self.get_object_from_json(json)\n                if obj.name[0] == '\"' and obj.name[-1] == '\"':\n                    obj.name = \"[\" + obj.name[1:-1] + \"]\"\n                self.js_docs[key][obj.kind].append(obj)\n\n\ndef get_jsdoc_content_directive(app):\n    \"\"\"These directives need to close over app \"\"\"\n\n    class JsDocContent(Directive):\n        \"\"\"A directive that just dumps a summary table in place. There are no\n        options, it only prints the one thing, we control the behavior from\n        here\n        \"\"\"\n\n        required_arguments = 1\n\n        def get_rst(self, obj):\n            \"\"\"Grab the appropriate renderer and render us to rst.\n            JsDoc also has an AutoClassRenderer which may be useful in the future.\"\"\"\n            if isinstance(obj, Function):\n                renderer = AutoFunctionRenderer\n            else:\n                renderer = AutoAttributeRenderer\n            return renderer(self, app, arguments=[\"dummy\"]).rst(\n                [obj.name], obj, use_short_name=False\n            )\n\n        def get_rst_for_group(self, objects):\n            return [self.get_rst(obj) for obj in objects]\n\n        def parse_rst(self, rst):\n            \"\"\"We produce a bunch of rst but directives are supposed to output\n            docutils trees. This is a helper that converts the rst to docutils.\n            \"\"\"\n            settings = self.state.document.settings\n            doc = new_document(\"\", settings)\n            RstParser().parse(rst, doc)\n            return doc.children\n\n        def run(self):\n            module = self.arguments[0]\n            values = app._sphinxjs_analyzer.js_docs[module]\n            rst = []\n            rst.append([f\".. js:module:: {module}\"])\n            for group in values.values():\n                rst.append(self.get_rst_for_group(group))\n            joined_rst = \"\\n\\n\".join([\"\\n\\n\".join(r) for r in rst])\n            return self.parse_rst(joined_rst)\n\n    return JsDocContent\n\n\ndef get_jsdoc_summary_directive(app):\n    class JsDocSummary(Directive):\n        \"\"\"A directive that just dumps the Js API docs in place. There are no\n        options, it only prints the one thing, we control the behavior from\n        here\n        \"\"\"\n\n        required_arguments = 1\n\n        def run(self):\n            result = []\n            module = self.arguments[0]\n            value = app._sphinxjs_analyzer.js_docs[module]\n            for group_name, group_objects in value.items():\n                if not group_objects:\n                    continue\n                result.append(self.format_heading(group_name.title() + \"s:\"))\n                table_items = self.get_summary_table(module, group_objects)\n                table_markup = self.format_table(table_items)\n                result.extend(table_markup)\n            return result\n\n        def format_heading(self, text):\n            \"\"\"Make a section heading. This corresponds to the rst: \"**Heading:**\"\n            autodocsumm uses headings like that, so this will match that style.\n            \"\"\"\n            heading = nodes.paragraph(\"\")\n            strong = nodes.strong(\"\")\n            strong.append(nodes.Text(text))\n            heading.append(strong)\n            return heading\n\n        def extract_summary(self, descr):\n            \"\"\"Wrapper around autosummary extract_summary that is easier to use.\n            It seems like colons need escaping for some reason.\n            \"\"\"\n            colon_esc = \"esccolon\\\\\\xafhoa:\"\n            return extract_summary(\n                [descr.replace(\":\", colon_esc)], self.state.document\n            ).replace(colon_esc, \":\")\n\n        def get_sig(self, obj):\n            \"\"\"If the object is a function, get its signature (as figured by JsDoc)\"\"\"\n            if isinstance(obj, Function):\n                return AutoFunctionRenderer(\n                    self, app, arguments=[\"dummy\"]\n                )._formal_params(obj)\n            else:\n                return \"\"\n\n        def get_summary_row(self, pkgname, obj):\n            \"\"\"Get the summary table row for obj.\n\n            The output is designed to be input to format_table. The link name\n            needs to be set up so that :any:`link_name` makes a link to the\n            actual api docs for this object.\n            \"\"\"\n            sig = self.get_sig(obj)\n            display_name = obj.name\n            summary = self.extract_summary(obj.description)\n            link_name = pkgname + \".\" + display_name\n            return (display_name, sig, summary, link_name)\n\n        def get_summary_table(self, pkgname, group):\n            \"\"\"Get the data for a summary table. Return value is set up to be an\n            argument of format_table.\n            \"\"\"\n            return [self.get_summary_row(pkgname, obj) for obj in group]\n\n        # This following method is copied almost verbatim from autosummary\n        # (where it is called get_table).\n        #\n        # We have to change the value of one string: qualifier = 'obj   ==>\n        # qualifier = 'any'\n        # https://github.com/sphinx-doc/sphinx/blob/3.x/sphinx/ext/autosummary/__init__.py#L392\n        def format_table(self, items):\n            \"\"\"Generate a proper list of table nodes for autosummary:: directive.\n\n            *items* is a list produced by :meth:`get_items`.\n            \"\"\"\n            table_spec = addnodes.tabular_col_spec()\n            table_spec[\"spec\"] = r\"\\X{1}{2}\\X{1}{2}\"\n\n            table = autosummary_table(\"\")\n            real_table = nodes.table(\"\", classes=[\"longtable\"])\n            table.append(real_table)\n            group = nodes.tgroup(\"\", cols=2)\n            real_table.append(group)\n            group.append(nodes.colspec(\"\", colwidth=10))\n            group.append(nodes.colspec(\"\", colwidth=90))\n            body = nodes.tbody(\"\")\n            group.append(body)\n\n            def append_row(*column_texts: str) -> None:\n                row = nodes.row(\"\")\n                source, line = self.state_machine.get_source_and_line()\n                for text in column_texts:\n                    node = nodes.paragraph(\"\")\n                    vl = StringList()\n                    vl.append(text, \"%s:%d:<autosummary>\" % (source, line))\n                    with switch_source_input(self.state, vl):\n                        self.state.nested_parse(vl, 0, node)\n                        try:\n                            if isinstance(node[0], nodes.paragraph):\n                                node = node[0]\n                        except IndexError:\n                            pass\n                        row.append(nodes.entry(\"\", node))\n                body.append(row)\n\n            for name, sig, summary, real_name in items:\n                qualifier = \"any\"  # <== Only thing changed from autosummary version\n                if \"nosignatures\" not in self.options:\n                    col1 = \":%s:`%s <%s>`\\\\ %s\" % (\n                        qualifier,\n                        name,\n                        real_name,\n                        rst.escape(sig),\n                    )\n                else:\n                    col1 = \":%s:`%s <%s>`\" % (qualifier, name, real_name)\n                col2 = summary\n                append_row(col1, col2)\n\n            return [table_spec, table]\n\n    return JsDocSummary\n", "path": "docs/sphinx_pyodide/sphinx_pyodide/jsdoc.py"}], "after_files": [{"content": "from docutils import nodes\nfrom docutils.parsers.rst import Directive, Parser as RstParser\nfrom docutils.statemachine import StringList\nfrom docutils.utils import new_document\n\nfrom collections import OrderedDict\nimport re\n\nfrom sphinx import addnodes\nfrom sphinx.util import rst\nfrom sphinx.util.docutils import switch_source_input\nfrom sphinx.ext.autosummary import autosummary_table, extract_summary\n\nfrom sphinx_js.jsdoc import Analyzer as JsAnalyzer\nfrom sphinx_js.ir import Class, Function\nfrom sphinx_js.parsers import path_and_formal_params, PathVisitor\nfrom sphinx_js.renderers import (\n    AutoFunctionRenderer,\n    AutoAttributeRenderer,\n    AutoClassRenderer,\n)\n\n\nclass PyodideAnalyzer:\n    \"\"\"JsDoc automatically instantiates the JsAnalyzer. Rather than subclassing\n    or monkey patching it, we use composition (see getattr impl).\n\n    The main extra thing we do is reorganize the doclets based on our globals /\n    functions / attributes scheme. This we use to subdivide the sections in our\n    summary. We store these in the \"js_docs\" field which is the only field that\n    we access later.\n    \"\"\"\n\n    def __init__(self, analyzer: JsAnalyzer) -> None:\n        self.inner = analyzer\n        self.create_js_doclets()\n\n    def __getattr__(self, key):\n        return getattr(self.inner, key)\n\n    def longname_to_path(self, name):\n        \"\"\"Convert the longname field produced by jsdoc to a path appropriate to use\n        with _sphinxjs_analyzer.get_object. Based on:\n        https://github.com/mozilla/sphinx-js/blob/3.1/sphinx_js/jsdoc.py#L181\n        \"\"\"\n        return PathVisitor().visit(path_and_formal_params[\"path\"].parse(name))\n\n    def get_object_from_json(self, json):\n        \"\"\"Look up the JsDoc IR object corresponding to this object. We use the\n        \"kind\" field to decide whether the object is a \"function\" or an\n        \"attribute\". We use longname_to_path to convert the path into a list of\n        path components which JsAnalyzer.get_object requires.\n        \"\"\"\n        path = self.longname_to_path(json[\"longname\"])\n        if json[\"kind\"] == \"function\":\n            kind = \"function\"\n        elif json[\"kind\"] == \"class\":\n            kind = \"class\"\n        else:\n            kind = \"attribute\"\n        obj = self.inner.get_object(path, kind)\n        obj.kind = kind\n        return obj\n\n    def create_js_doclets(self):\n        \"\"\"Search through the doclets generated by JsDoc and categorize them by\n        summary section. Skip docs labeled as \"@private\".\n        \"\"\"\n\n        def get_val():\n            return OrderedDict([[\"attribute\", []], [\"function\", []], [\"class\", []]])\n\n        self.js_docs = {key: get_val() for key in [\"globals\", \"pyodide\", \"PyProxy\"]}\n        items = {\"PyProxy\": []}\n        for (key, group) in self._doclets_by_class.items():\n            key = [x for x in key if \"/\" not in x]\n            if key[-1] == \"PyBuffer\":\n                # PyBuffer stuff is documented as a class. Would be nice to have\n                # a less ad hoc way to deal with this...\n                continue\n            if key[-1] == \"globalThis\":\n                items[\"globals\"] = group\n            if key[0] == \"pyodide.\" and key[-1] == \"Module\":\n                items[\"pyodide\"] = group\n            if key[0] == \"pyproxy.\":\n                items[\"PyProxy\"] += group\n\n        for key, value in items.items():\n            for json in value:\n                if json.get(\"access\", None) == \"private\":\n                    continue\n                obj = self.get_object_from_json(json)\n                if isinstance(obj, Class):\n                    # sphinx-jsdoc messes up array types. Fix them.\n                    for x in obj.members:\n                        if hasattr(x, \"type\"):\n                            x.type = re.sub(\"Array\\.<([a-zA-Z_0-9]*)>\", r\"\\1[]\", x.type)\n                if obj.name[0] == '\"' and obj.name[-1] == '\"':\n                    # sphinx-jsdoc messes up Symbol attributes. Fix them.\n                    obj.name = \"[\" + obj.name[1:-1] + \"]\"\n                self.js_docs[key][obj.kind].append(obj)\n\n\ndef get_jsdoc_content_directive(app):\n    \"\"\"These directives need to close over app \"\"\"\n\n    class JsDocContent(Directive):\n        \"\"\"A directive that just dumps a summary table in place. There are no\n        options, it only prints the one thing, we control the behavior from\n        here\n        \"\"\"\n\n        required_arguments = 1\n\n        def get_rst(self, obj):\n            \"\"\"Grab the appropriate renderer and render us to rst.\n            JsDoc also has an AutoClassRenderer which may be useful in the future.\"\"\"\n            if isinstance(obj, Function):\n                renderer = AutoFunctionRenderer\n            elif isinstance(obj, Class):\n                renderer = AutoClassRenderer\n            else:\n                renderer = AutoAttributeRenderer\n            return renderer(\n                self, app, arguments=[\"dummy\"], options={\"members\": [\"*\"]}\n            ).rst([obj.name], obj, use_short_name=False)\n\n        def get_rst_for_group(self, objects):\n            return [self.get_rst(obj) for obj in objects]\n\n        def parse_rst(self, rst):\n            \"\"\"We produce a bunch of rst but directives are supposed to output\n            docutils trees. This is a helper that converts the rst to docutils.\n            \"\"\"\n            settings = self.state.document.settings\n            doc = new_document(\"\", settings)\n            RstParser().parse(rst, doc)\n            return doc.children\n\n        def run(self):\n            module = self.arguments[0]\n            values = app._sphinxjs_analyzer.js_docs[module]\n            rst = []\n            rst.append([f\".. js:module:: {module}\"])\n            for group in values.values():\n                rst.append(self.get_rst_for_group(group))\n            joined_rst = \"\\n\\n\".join([\"\\n\\n\".join(r) for r in rst])\n            return self.parse_rst(joined_rst)\n\n    return JsDocContent\n\n\ndef get_jsdoc_summary_directive(app):\n    class JsDocSummary(Directive):\n        \"\"\"A directive that just dumps the Js API docs in place. There are no\n        options, it only prints the one thing, we control the behavior from\n        here\n        \"\"\"\n\n        required_arguments = 1\n\n        def run(self):\n            result = []\n            module = self.arguments[0]\n            value = app._sphinxjs_analyzer.js_docs[module]\n            for group_name, group_objects in value.items():\n                if not group_objects:\n                    continue\n                if group_name == \"class\":\n                    # Plural of class is \"classes\" not \"classs\"\n                    group_name += \"e\"\n                result.append(self.format_heading(group_name.title() + \"s:\"))\n                table_items = self.get_summary_table(module, group_objects)\n                table_markup = self.format_table(table_items)\n                result.extend(table_markup)\n            return result\n\n        def format_heading(self, text):\n            \"\"\"Make a section heading. This corresponds to the rst: \"**Heading:**\"\n            autodocsumm uses headings like that, so this will match that style.\n            \"\"\"\n            heading = nodes.paragraph(\"\")\n            strong = nodes.strong(\"\")\n            strong.append(nodes.Text(text))\n            heading.append(strong)\n            return heading\n\n        def extract_summary(self, descr):\n            \"\"\"Wrapper around autosummary extract_summary that is easier to use.\n            It seems like colons need escaping for some reason.\n            \"\"\"\n            colon_esc = \"esccolon\\\\\\xafhoa:\"\n            return extract_summary(\n                [descr.replace(\":\", colon_esc)], self.state.document\n            ).replace(colon_esc, \":\")\n\n        def get_sig(self, obj):\n            \"\"\"If the object is a function, get its signature (as figured by JsDoc)\"\"\"\n            if isinstance(obj, Function):\n                return AutoFunctionRenderer(\n                    self, app, arguments=[\"dummy\"]\n                )._formal_params(obj)\n            else:\n                return \"\"\n\n        def get_summary_row(self, pkgname, obj):\n            \"\"\"Get the summary table row for obj.\n\n            The output is designed to be input to format_table. The link name\n            needs to be set up so that :any:`link_name` makes a link to the\n            actual api docs for this object.\n            \"\"\"\n            sig = self.get_sig(obj)\n            display_name = obj.name\n            summary = self.extract_summary(obj.description)\n            link_name = pkgname + \".\" + display_name\n            return (display_name, sig, summary, link_name)\n\n        def get_summary_table(self, pkgname, group):\n            \"\"\"Get the data for a summary table. Return value is set up to be an\n            argument of format_table.\n            \"\"\"\n            return [self.get_summary_row(pkgname, obj) for obj in group]\n\n        # This following method is copied almost verbatim from autosummary\n        # (where it is called get_table).\n        #\n        # We have to change the value of one string: qualifier = 'obj   ==>\n        # qualifier = 'any'\n        # https://github.com/sphinx-doc/sphinx/blob/3.x/sphinx/ext/autosummary/__init__.py#L392\n        def format_table(self, items):\n            \"\"\"Generate a proper list of table nodes for autosummary:: directive.\n\n            *items* is a list produced by :meth:`get_items`.\n            \"\"\"\n            table_spec = addnodes.tabular_col_spec()\n            table_spec[\"spec\"] = r\"\\X{1}{2}\\X{1}{2}\"\n\n            table = autosummary_table(\"\")\n            real_table = nodes.table(\"\", classes=[\"longtable\"])\n            table.append(real_table)\n            group = nodes.tgroup(\"\", cols=2)\n            real_table.append(group)\n            group.append(nodes.colspec(\"\", colwidth=10))\n            group.append(nodes.colspec(\"\", colwidth=90))\n            body = nodes.tbody(\"\")\n            group.append(body)\n\n            def append_row(*column_texts: str) -> None:\n                row = nodes.row(\"\")\n                source, line = self.state_machine.get_source_and_line()\n                for text in column_texts:\n                    node = nodes.paragraph(\"\")\n                    vl = StringList()\n                    vl.append(text, \"%s:%d:<autosummary>\" % (source, line))\n                    with switch_source_input(self.state, vl):\n                        self.state.nested_parse(vl, 0, node)\n                        try:\n                            if isinstance(node[0], nodes.paragraph):\n                                node = node[0]\n                        except IndexError:\n                            pass\n                        row.append(nodes.entry(\"\", node))\n                body.append(row)\n\n            for name, sig, summary, real_name in items:\n                qualifier = \"any\"  # <== Only thing changed from autosummary version\n                if \"nosignatures\" not in self.options:\n                    col1 = \":%s:`%s <%s>`\\\\ %s\" % (\n                        qualifier,\n                        name,\n                        real_name,\n                        rst.escape(sig),\n                    )\n                else:\n                    col1 = \":%s:`%s <%s>`\" % (qualifier, name, real_name)\n                col2 = summary\n                append_row(col1, col2)\n\n            return [table_spec, table]\n\n    return JsDocSummary\n", "path": "docs/sphinx_pyodide/sphinx_pyodide/jsdoc.py"}]}