sphinxext-bsb 0.2.6__py3-none-any.whl → 6.0.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of sphinxext-bsb might be problematic. Click here for more details.

@@ -1,375 +1,396 @@
1
- import itertools
2
- from inspect import isclass
3
- from types import FunctionType
4
-
5
- import docutils.parsers.rst.directives
6
- from bsb.config import get_config_attributes
7
- from bsb.config._make import MISSING
8
- from bsb.config._attrs import (
9
- ConfigurationListAttribute,
10
- ConfigurationDictAttribute,
11
- )
12
- from bsb.config.parsers import get_configuration_parser_classes
13
- from bsb.config.types import class_
14
- from docutils import nodes
15
- from docutils.parsers.rst import Directive
16
- from docutils.statemachine import StringList
17
- from sphinx.util.docutils import SphinxDirective
18
-
19
- __version__ = "0.2.1"
20
-
21
-
22
- def example_function():
23
- pass
24
-
25
-
26
- _example_values = {
27
- bool: True,
28
- list: [],
29
- str: "example",
30
- int: 42,
31
- float: 3.14,
32
- FunctionType: "my_module.my_function",
33
- dict: {},
34
- }
35
-
36
-
37
- class ComponentIntro(Directive):
38
- has_content = False
39
-
40
- def run(self):
41
- # Use the state machine to generate a block quote for us and parse our text :)
42
- return self.state.block_quote(
43
- StringList(
44
- [
45
- ":octicon:`light-bulb;1em;sd-text-info` New to components?"
46
- + " Write your first one with :doc:`our guide </guides/components>`"
47
- ]
48
- ),
49
- self.content_offset,
50
- )
51
-
52
-
53
- class AutoconfigNode(nodes.General, nodes.Element):
54
- pass
55
-
56
-
57
- def visit_autoconfig_node(self, node):
58
- pass
59
-
60
-
61
- def depart_autoconfig_node(self, node):
62
- pass
63
-
64
-
65
- class AutoconfigDirective(SphinxDirective):
66
- required_arguments = 1
67
- has_content = False
68
- option_spec = {
69
- "no-imports": docutils.parsers.rst.directives.flag,
70
- "max-depth": docutils.parsers.rst.directives.positive_int,
71
- }
72
-
73
- def run(self):
74
- clsref = self.arguments[0]
75
- cls = class_()(clsref)
76
- max_depth = self.options.get("max-depth", 100)
77
- tree = self.guess_example(cls, max_depth)
78
- elem = AutoconfigNode()
79
- self.state.nested_parse(
80
- StringList(
81
- [
82
- ".. tab-set-code::",
83
- "",
84
- " .. code-block:: Python",
85
- "",
86
- *self.get_python_tab(
87
- cls, tree, "no-imports" not in self.options, max_depth
88
- ),
89
- "",
90
- *itertools.chain.from_iterable(
91
- self.get_parser_lines(key, parser(), tree)
92
- for key, parser in get_configuration_parser_classes().items()
93
- ),
94
- ]
95
- ),
96
- self.content_offset,
97
- elem,
98
- )
99
- return [elem]
100
-
101
- def get_python_tab(self, cls, tree, imports, max_depth):
102
- lines = [
103
- *(
104
- f" {imp}"
105
- for imp in self.get_import_lines(cls, max_depth if imports else 0)
106
- ),
107
- "",
108
- *(f" {line}" for line in self.get_python_lines(cls, tree)),
109
- ]
110
- return self.collapse_empties(lines)
111
-
112
- def collapse_empties(self, lines, chars=[("(", ")"), ("[", "]"), ("{", "}")]):
113
- outlines = []
114
- skip = False
115
- for i in range(len(lines)):
116
- if skip:
117
- skip = False
118
- continue
119
- line1 = lines[i]
120
- if i == len(lines) - 1:
121
- outlines.append(line1)
122
- break
123
- line2 = lines[i + 1]
124
- for schar, echar in chars:
125
- if line1.endswith(schar) and line2.strip().startswith(echar):
126
- outlines.append(line1 + f"{echar},")
127
- skip = True
128
- break
129
- else:
130
- outlines.append(line1)
131
- return outlines
132
-
133
- def guess_example(self, cls, deeper):
134
- if deeper == 0:
135
- return ...
136
- attrs = get_config_attributes(cls)
137
- tree = {
138
- attr.attr_name: self.guess_default(attr, deeper) for attr in attrs.values()
139
- }
140
- return tree
141
-
142
- def guess_default(self, attr, deeper):
143
- """
144
- Guess a default value/structure for the given attribute. Defaults are paired in
145
- tuples with functions that can unpack the tree value to their Python
146
- representation. The most basic "argument unpacker" exemplifies this, and turns
147
- the value ``x`` into ``f"{key}={repr(x)}"``.
148
-
149
- :param attr:
150
- :return:
151
- """
152
- type_ = self.get_attr_type(attr)
153
-
154
- # If the attribute is a node type, we have to guess recursively.
155
- if attr.is_node_type():
156
- # Node types that come from private modules shouldn't be promoted,
157
- # so instead we make use of the dictionary notation. Or, this autoconfig
158
- # has the "no-imports" option set, which disables the prepended import stmnt
159
- if "no-imports" in self.options or any(
160
- m.startswith("_") for m in type_.__module__.split(".")
161
- ):
162
- untree = self.private_untree(type_)
163
- else:
164
- untree = self.public_untree(type_)
165
- value = self.guess_example(type_, deeper - 1)
166
- else:
167
- untree = self.argument_untree
168
- value = self.guess_example_value(attr)
169
- # Configuration lists and dicts should be packed into a list/dict
170
- if isinstance(attr, ConfigurationListAttribute):
171
- untree = self.list_untree(untree)
172
- elif isinstance(attr, ConfigurationDictAttribute):
173
- untree = self.dict_untree(untree)
174
- return untree, value
175
-
176
- def guess_example_value(self, attr):
177
- type_ = self.get_attr_type(attr)
178
- # The attribute may have a hinted example from the declaration `hint` kwarg,
179
- # or from the `__hint__` method of its type handler
180
- hint = attr.get_hint()
181
- if hint is not MISSING:
182
- example = hint
183
- else:
184
- # No hint, so check if the default is sensible
185
- default = attr.get_default()
186
- example = None
187
- if default is not None:
188
- example = default
189
- elif isclass(type_):
190
- # No default value, and likely a primitive was passed as type handler
191
- for parent_type, value in _example_values.items():
192
- # Loop over some basic possible basic primitives
193
- if issubclass(type_, parent_type):
194
- example = value
195
- break
196
- if example is None:
197
- # Try to have the type handler cast some primitive examples,
198
- # if no error is raised we assume it's a good example
199
- example = self.try_types(type_, *_example_values.values())
200
- # `str` is a kind of greedy type handler, so correct the casts
201
- if example == "[]":
202
- example = []
203
- elif example == "{}":
204
- example = {}
205
- elif example == "true":
206
- example = True
207
- # Some values need to be cast back to a tree-form, so we create a shim
208
- # for the attribute descriptor to use as an instance.
209
- shim = type("AttrShim", (), {})()
210
- setattr(shim, f"_{attr.attr_name}", example)
211
- try:
212
- example = attr.tree(shim)
213
- except Exception:
214
- pass
215
- # Hope we have a default value.
216
- return example
217
-
218
- def get_attr_type(self, attr):
219
- type_ = attr.get_type()
220
- type_ = str if type_ is None else type_
221
- return type_
222
-
223
- def try_types(self, type_, arg, *args):
224
- try:
225
- return type_(arg)
226
- except Exception:
227
- if args:
228
- return self.try_types(type_, *args)
229
-
230
- def get_python_lines(self, cls, tree):
231
- return [
232
- f"{cls.__name__}(",
233
- *(
234
- f" {line}"
235
- for line in itertools.chain.from_iterable(
236
- self.get_argument_lines(key, *value) for key, value in tree.items()
237
- )
238
- ),
239
- ")",
240
- ]
241
-
242
- def get_argument_lines(self, key, untree, value):
243
- return untree(key, value)
244
-
245
- def get_parser_lines(self, name, parser, tree):
246
- raw = self.raw_untree((None, tree))
247
- language = getattr(parser, "data_syntax", False) or name
248
- return [
249
- f" .. code-block:: {language}",
250
- "",
251
- *(
252
- f" {line}"
253
- for line in parser.generate(raw, pretty=True).split("\n")
254
- ),
255
- "",
256
- ]
257
-
258
- def raw_untree(self, tree):
259
- try:
260
- node_data = tree[1]
261
- except TypeError:
262
- # If the element wasn't packed as a tuple it's just a list/dict attr
263
- return tree
264
- list_repack = getattr(tree[0], "list_repack", False)
265
- dict_repack = getattr(tree[0], "dict_repack", False)
266
- if node_data is ...:
267
- if list_repack:
268
- return []
269
- else:
270
- return {}
271
- if dict_repack:
272
- node_data = {"name_of_the_thing": (None, node_data)}
273
- if list_repack:
274
- node_data = [(None, node_data)]
275
- if isinstance(node_data, dict):
276
- return {k: self.raw_untree(v) for k, v in node_data.items()}
277
- elif isinstance(node_data, list):
278
- return [self.raw_untree(v) for v in node_data]
279
- else:
280
- return node_data
281
-
282
- def public_untree(self, cls):
283
- def untree(key, value):
284
- if value is ...:
285
- lines = self.get_python_lines(cls, {})
286
- else:
287
- lines = self.get_python_lines(cls, value)
288
- lines[0] = f"{key}={lines[0]}"
289
- lines[-1] += ","
290
- return lines
291
-
292
- return untree
293
-
294
- def private_untree(self, cls):
295
- def untree(key, value):
296
- if value is ...:
297
- lines = self.get_python_lines(cls, {})
298
- else:
299
- lines = self.get_python_lines(cls, value)
300
- lines[0] = key + "={"
301
- for i in range(1, len(lines) - 1):
302
- line = lines[i]
303
- if "=" in line:
304
- lp = line.split("=")
305
- indent = len(lp[0]) - len(lp[0].lstrip())
306
- lines[i] = f"{' ' * indent}'{lp[0].lstrip()}': " + "=".join(lp[1:])
307
- lines[-1] = "},"
308
- return lines
309
-
310
- return untree
311
-
312
- def dict_untree(self, inner_untree):
313
- def untree(key, value):
314
- lines = inner_untree(key, value)
315
- return lines
316
-
317
- untree.dict_repack = True
318
- return untree
319
-
320
- def list_untree(self, inner_untree):
321
- def untree(key, value):
322
- if value is ...:
323
- return [f"{key}=[", "],"]
324
- lines = inner_untree(key, value)
325
- lines[0] = lines[0].split("=")[0] + "=["
326
- lines.insert(1, " {")
327
- lines[2:] = [" " + line for line in lines[2:]]
328
- lines.append("],")
329
- return lines
330
-
331
- untree.list_repack = True
332
- return untree
333
-
334
- def argument_untree(self, key, value):
335
- return [f"{key}={repr(value)},"]
336
-
337
- def get_imports(self, cls, deeper):
338
- imports = {}
339
- if not any(m.startswith("_") for m in cls.__module__.split(".")):
340
- imports.setdefault(cls.__module__, set()).add(cls.__name__)
341
- if deeper > 0:
342
- for attr in get_config_attributes(cls).values():
343
- if attr.is_node_type():
344
- for k, v in self.get_imports(attr.get_type(), deeper - 1).items():
345
- imports.setdefault(k, set()).update(v)
346
-
347
- return imports
348
-
349
- def get_import_lines(self, cls, depth):
350
- return [
351
- f"from {key} import {', '.join(value)}"
352
- for key, value in self.get_imports(cls, depth).items()
353
- ]
354
-
355
-
356
- def setup(app):
357
- if "sphinx_design" not in app.extensions:
358
- from sphinx_design import setup as sphinx_design_setup
359
-
360
- sphinx_design_setup(app)
361
-
362
- app.add_node(
363
- AutoconfigNode,
364
- html=(visit_autoconfig_node, depart_autoconfig_node),
365
- latex=(visit_autoconfig_node, depart_autoconfig_node),
366
- text=(visit_autoconfig_node, depart_autoconfig_node),
367
- )
368
- app.add_directive("bsb_component_intro", ComponentIntro)
369
- app.add_directive("autoconfig", AutoconfigDirective)
370
-
371
- return {
372
- "version": __version__,
373
- "parallel_read_safe": True,
374
- "parallel_write_safe": True,
375
- }
1
+ import contextlib
2
+ import importlib.metadata
3
+ import itertools
4
+ from inspect import isclass
5
+ from types import FunctionType
6
+
7
+ import docutils.parsers.rst.directives
8
+ from docutils import nodes
9
+ from docutils.parsers.rst import Directive
10
+ from docutils.statemachine import StringList
11
+ from sphinx.util.docutils import SphinxDirective
12
+
13
+ from bsb.config import get_config_attributes
14
+ from bsb.config._attrs import (
15
+ ConfigurationDictAttribute,
16
+ ConfigurationListAttribute,
17
+ )
18
+ from bsb.config._make import MISSING
19
+ from bsb.config.parsers import get_configuration_parser_classes
20
+ from bsb.config.types import class_
21
+
22
+ from .project import Project
23
+
24
+
25
+ def example_function():
26
+ pass
27
+
28
+
29
+ _example_values = {
30
+ bool: True,
31
+ list: [],
32
+ str: "example",
33
+ int: 42,
34
+ float: 3.14,
35
+ FunctionType: "my_module.my_function",
36
+ dict: {},
37
+ }
38
+
39
+
40
+ class ComponentIntro(Directive):
41
+ has_content = False
42
+
43
+ def run(self):
44
+ # Use the state machine to generate a block quote for us and parse our text :)
45
+ return self.state.block_quote(
46
+ StringList(
47
+ [
48
+ ":octicon:`light-bulb;1em;sd-text-info` New to components?"
49
+ + " Write your first one with :doc:`our guide </guides/components>`"
50
+ ]
51
+ ),
52
+ self.content_offset,
53
+ )
54
+
55
+
56
+ class AutoconfigNode(nodes.General, nodes.Element):
57
+ pass
58
+
59
+
60
+ def visit_autoconfig_node(self, node):
61
+ pass
62
+
63
+
64
+ def depart_autoconfig_node(self, node):
65
+ pass
66
+
67
+
68
+ class AutoconfigDirective(SphinxDirective):
69
+ required_arguments = 1
70
+ has_content = False
71
+ option_spec = {
72
+ "no-imports": docutils.parsers.rst.directives.flag,
73
+ "max-depth": docutils.parsers.rst.directives.positive_int,
74
+ }
75
+
76
+ def run(self):
77
+ clsref = self.arguments[0]
78
+ cls = class_()(clsref)
79
+ max_depth = self.options.get("max-depth", 100)
80
+ tree = self.guess_example(cls, max_depth)
81
+ elem = AutoconfigNode()
82
+ self.state.nested_parse(
83
+ StringList(
84
+ [
85
+ ".. tab-set-code::",
86
+ "",
87
+ " .. code-block:: Python",
88
+ "",
89
+ *self.get_python_tab(
90
+ cls, tree, "no-imports" not in self.options, max_depth
91
+ ),
92
+ "",
93
+ *itertools.chain.from_iterable(
94
+ self.get_parser_lines(key, parser(), tree)
95
+ for key, parser in get_configuration_parser_classes().items()
96
+ ),
97
+ ]
98
+ ),
99
+ self.content_offset,
100
+ elem,
101
+ )
102
+ return [elem]
103
+
104
+ def get_python_tab(self, cls, tree, imports, max_depth):
105
+ lines = [
106
+ *(
107
+ f" {imp}"
108
+ for imp in self.get_import_lines(cls, max_depth if imports else 0)
109
+ ),
110
+ "",
111
+ *(f" {line}" for line in self.get_python_lines(cls, tree)),
112
+ ]
113
+ return self.collapse_empties(lines)
114
+
115
+ def collapse_empties(self, lines, chars=None):
116
+ if chars is None:
117
+ chars = [("(", ")"), ("[", "]"), ("{", "}")]
118
+ outlines = []
119
+ skip = False
120
+ for i in range(len(lines)):
121
+ if skip:
122
+ skip = False
123
+ continue
124
+ line1 = lines[i]
125
+ if i == len(lines) - 1:
126
+ outlines.append(line1)
127
+ break
128
+ line2 = lines[i + 1]
129
+ for schar, echar in chars:
130
+ if line1.endswith(schar) and line2.strip().startswith(echar):
131
+ outlines.append(line1 + f"{echar},")
132
+ skip = True
133
+ break
134
+ else:
135
+ outlines.append(line1)
136
+ return outlines
137
+
138
+ def guess_example(self, cls, deeper):
139
+ if deeper == 0:
140
+ return ...
141
+ attrs = get_config_attributes(cls)
142
+ tree = {
143
+ attr.attr_name: self.guess_default(attr, deeper) for attr in attrs.values()
144
+ }
145
+ return tree
146
+
147
+ def guess_default(self, attr, deeper):
148
+ """
149
+ Guess a default value/structure for the given attribute. Defaults are paired in
150
+ tuples with functions that can unpack the tree value to their Python
151
+ representation. The most basic "argument unpacker" exemplifies this, and turns
152
+ the value ``x`` into ``f"{key}={repr(x)}"``.
153
+
154
+ :param attr:
155
+ :return:
156
+ """
157
+ type_ = self.get_attr_type(attr)
158
+
159
+ # If the attribute is a node type, we have to guess recursively.
160
+ if attr.is_node_type():
161
+ # Node types that come from private modules shouldn't be promoted,
162
+ # so instead we make use of the dictionary notation. Or, this autoconfig
163
+ # has the "no-imports" option set, which disables the prepended import stmnt
164
+ if "no-imports" in self.options or any(
165
+ m.startswith("_") for m in type_.__module__.split(".")
166
+ ):
167
+ untree = self.private_untree(type_)
168
+ else:
169
+ untree = self.public_untree(type_)
170
+ value = self.guess_example(type_, deeper - 1)
171
+ else:
172
+ untree = self.argument_untree
173
+ value = self.guess_example_value(attr)
174
+ # Configuration lists and dicts should be packed into a list/dict
175
+ if isinstance(attr, ConfigurationListAttribute):
176
+ untree = self.list_untree(untree)
177
+ elif isinstance(attr, ConfigurationDictAttribute):
178
+ untree = self.dict_untree(untree)
179
+ return untree, value
180
+
181
+ def guess_example_value(self, attr):
182
+ type_ = self.get_attr_type(attr)
183
+ # The attribute may have a hinted example from the declaration `hint` kwarg,
184
+ # or from the `__hint__` method of its type handler
185
+ hint = attr.get_hint()
186
+ if hint is not MISSING:
187
+ example = hint
188
+ else:
189
+ # No hint, so check if the default is sensible
190
+ default = attr.get_default()
191
+ example = None
192
+ if default is not None:
193
+ example = default
194
+ elif isclass(type_):
195
+ # No default value, and likely a primitive was passed as type handler
196
+ for parent_type, value in _example_values.items():
197
+ # Loop over some basic possible basic primitives
198
+ if issubclass(type_, parent_type):
199
+ example = value
200
+ break
201
+ if example is None:
202
+ # Try to have the type handler cast some primitive examples,
203
+ # if no error is raised we assume it's a good example
204
+ example = self.try_types(type_, *_example_values.values())
205
+ # `str` is a kind of greedy type handler, so correct the casts
206
+ if example == "[]":
207
+ example = []
208
+ elif example == "{}":
209
+ example = {}
210
+ elif example == "true":
211
+ example = True
212
+ # Some values need to be cast back to a tree-form, so we create a shim
213
+ # for the attribute descriptor to use as an instance.
214
+ shim = type("AttrShim", (), {})()
215
+ setattr(shim, f"_{attr.attr_name}", example)
216
+ with contextlib.suppress(Exception):
217
+ example = attr.tree(shim)
218
+ # Hope we have a default value.
219
+ return example
220
+
221
+ def get_attr_type(self, attr):
222
+ type_ = attr.get_type()
223
+ type_ = str if type_ is None else type_
224
+ return type_
225
+
226
+ def try_types(self, type_, arg, *args):
227
+ try:
228
+ return type_(arg)
229
+ except Exception:
230
+ if args:
231
+ return self.try_types(type_, *args)
232
+
233
+ def get_python_lines(self, cls, tree):
234
+ return [
235
+ f"{cls.__name__}(",
236
+ *(
237
+ f" {line}"
238
+ for line in itertools.chain.from_iterable(
239
+ self.get_argument_lines(key, *value) for key, value in tree.items()
240
+ )
241
+ ),
242
+ ")",
243
+ ]
244
+
245
+ def get_argument_lines(self, key, untree, value):
246
+ return untree(key, value)
247
+
248
+ def get_parser_lines(self, name, parser, tree):
249
+ raw = self.raw_untree((None, tree))
250
+ language = getattr(parser, "data_syntax", False) or name
251
+ return [
252
+ f" .. code-block:: {language}",
253
+ "",
254
+ *(
255
+ f" {line}"
256
+ for line in parser.generate(raw, pretty=True).split("\n")
257
+ ),
258
+ "",
259
+ ]
260
+
261
+ def raw_untree(self, tree):
262
+ try:
263
+ node_data = tree[1]
264
+ except TypeError:
265
+ # If the element wasn't packed as a tuple it's just a list/dict attr
266
+ return tree
267
+ list_repack = getattr(tree[0], "list_repack", False)
268
+ dict_repack = getattr(tree[0], "dict_repack", False)
269
+ if node_data is ...:
270
+ if list_repack:
271
+ return []
272
+ else:
273
+ return {}
274
+ if dict_repack:
275
+ node_data = {"name_of_the_thing": (None, node_data)}
276
+ if list_repack:
277
+ node_data = [(None, node_data)]
278
+ if isinstance(node_data, dict):
279
+ return {k: self.raw_untree(v) for k, v in node_data.items()}
280
+ elif isinstance(node_data, list):
281
+ return [self.raw_untree(v) for v in node_data]
282
+ else:
283
+ return node_data
284
+
285
+ def public_untree(self, cls):
286
+ def untree(key, value):
287
+ if value is ...:
288
+ lines = self.get_python_lines(cls, {})
289
+ else:
290
+ lines = self.get_python_lines(cls, value)
291
+ lines[0] = f"{key}={lines[0]}"
292
+ lines[-1] += ","
293
+ return lines
294
+
295
+ return untree
296
+
297
+ def private_untree(self, cls):
298
+ def untree(key, value):
299
+ if value is ...:
300
+ lines = self.get_python_lines(cls, {})
301
+ else:
302
+ lines = self.get_python_lines(cls, value)
303
+ lines[0] = key + "={"
304
+ for i in range(1, len(lines) - 1):
305
+ line = lines[i]
306
+ if "=" in line:
307
+ lp = line.split("=")
308
+ indent = len(lp[0]) - len(lp[0].lstrip())
309
+ lines[i] = f"{' ' * indent}'{lp[0].lstrip()}': " + "=".join(lp[1:])
310
+ lines[-1] = "},"
311
+ return lines
312
+
313
+ return untree
314
+
315
+ def dict_untree(self, inner_untree):
316
+ def untree(key, value):
317
+ lines = inner_untree(key, value)
318
+ return lines
319
+
320
+ untree.dict_repack = True
321
+ return untree
322
+
323
+ def list_untree(self, inner_untree):
324
+ def untree(key, value):
325
+ if value is ...:
326
+ return [f"{key}=[", "],"]
327
+ lines = inner_untree(key, value)
328
+ lines[0] = lines[0].split("=")[0] + "=["
329
+ lines.insert(1, " {")
330
+ lines[2:] = [" " + line for line in lines[2:]]
331
+ lines.append("],")
332
+ return lines
333
+
334
+ untree.list_repack = True
335
+ return untree
336
+
337
+ def argument_untree(self, key, value):
338
+ return [f"{key}={repr(value)},"]
339
+
340
+ def get_imports(self, cls, deeper):
341
+ imports = {}
342
+ if not any(m.startswith("_") for m in cls.__module__.split(".")):
343
+ imports.setdefault(cls.__module__, set()).add(cls.__name__)
344
+ if deeper > 0:
345
+ for attr in get_config_attributes(cls).values():
346
+ if attr.is_node_type():
347
+ for k, v in self.get_imports(attr.get_type(), deeper - 1).items():
348
+ imports.setdefault(k, set()).update(v)
349
+
350
+ return imports
351
+
352
+ def get_import_lines(self, cls, depth):
353
+ return [
354
+ f"from {key} import {', '.join(value)}"
355
+ for key, value in self.get_imports(cls, depth).items()
356
+ ]
357
+
358
+
359
+ def resolve_type_aliases(app, env, node, contnode):
360
+ if node["refdomain"] == "py" and node["reftype"] == "class":
361
+ return app.env.get_domain("py").resolve_xref(
362
+ env, node["refdoc"], app.builder, "data", node["reftarget"], node, contnode
363
+ )
364
+
365
+
366
+ def setup(app):
367
+ if "sphinx_design" not in app.extensions:
368
+ from sphinx_design import setup as sphinx_design_setup
369
+
370
+ sphinx_design_setup(app)
371
+
372
+ app.add_node(
373
+ AutoconfigNode,
374
+ html=(visit_autoconfig_node, depart_autoconfig_node),
375
+ latex=(visit_autoconfig_node, depart_autoconfig_node),
376
+ text=(visit_autoconfig_node, depart_autoconfig_node),
377
+ )
378
+ app.add_directive("bsb_component_intro", ComponentIntro)
379
+ app.add_directive("autoconfig", AutoconfigDirective)
380
+
381
+ app.connect("missing-reference", resolve_type_aliases)
382
+
383
+ return {
384
+ "version": importlib.metadata.version("sphinxext-bsb"),
385
+ "parallel_read_safe": True,
386
+ "parallel_write_safe": True,
387
+ }
388
+
389
+
390
+ __all__ = [
391
+ "AutoconfigDirective",
392
+ "AutoconfigNode",
393
+ "ComponentIntro",
394
+ "Project",
395
+ "setup",
396
+ ]