sphinxext-bsb 0.2.1__tar.gz → 0.2.3__tar.gz

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.

@@ -0,0 +1,13 @@
1
+ Metadata-Version: 2.4
2
+ Name: sphinxext-bsb
3
+ Version: 0.2.3
4
+ Summary: BSB Sphinx documentation extension
5
+ Requires-Python: >=3.9,<3.12
6
+ Description-Content-Type: text/markdown
7
+ Requires-Dist: bsb-core
8
+ Requires-Dist: sphinx-design~=0.5
9
+
10
+ # sphinxext-bsb
11
+
12
+ Project description here.
13
+
@@ -0,0 +1,3 @@
1
+ # sphinxext-bsb
2
+
3
+ Project description here.
@@ -0,0 +1,97 @@
1
+ {
2
+ "name": "sphinxext-bsb",
3
+ "$schema": "../node_modules/nx/schemas/project-schema.json",
4
+ "projectType": "library",
5
+ "sourceRoot": "sphinxext-bsb/sphinxext_bsb",
6
+ "targets": {
7
+ "lock": {
8
+ "executor": "@nxlv/python:lock",
9
+ "options": {
10
+ "update": false
11
+ }
12
+ },
13
+ "sync": {
14
+ "executor": "@nxlv/python:sync",
15
+ "options": {}
16
+ },
17
+ "add": {
18
+ "executor": "@nxlv/python:add",
19
+ "options": {}
20
+ },
21
+ "update": {
22
+ "executor": "@nxlv/python:update",
23
+ "options": {}
24
+ },
25
+ "remove": {
26
+ "executor": "@nxlv/python:remove",
27
+ "options": {}
28
+ },
29
+ "build": {
30
+ "executor": "@nxlv/python:build",
31
+ "outputs": [
32
+ "{projectRoot}/dist"
33
+ ],
34
+ "options": {
35
+ "outputPath": "{projectRoot}/dist",
36
+ "publish": true,
37
+ "lockedVersions": false,
38
+ "bundleLocalDependencies": false
39
+ },
40
+ "cache": true
41
+ },
42
+ "lint": {
43
+ "executor": "@nxlv/python:ruff-check",
44
+ "outputs": [],
45
+ "options": {
46
+ "lintFilePatterns": [
47
+ "sphinxext_bsb",
48
+ "tests"
49
+ ]
50
+ },
51
+ "cache": true
52
+ },
53
+ "format": {
54
+ "executor": "@nxlv/python:ruff-format",
55
+ "outputs": [],
56
+ "options": {
57
+ "filePatterns": [
58
+ "sphinxext_bsb",
59
+ "tests"
60
+ ]
61
+ },
62
+ "cache": true
63
+ },
64
+ "test": {
65
+ "executor": "@nxlv/python:run-commands",
66
+ "outputs": [
67
+ "{workspaceRoot}/reports/{projectRoot}/unittests",
68
+ "{workspaceRoot}/coverage/{projectRoot}"
69
+ ],
70
+ "options": {
71
+ "command": "uv run pytest tests/",
72
+ "cwd": "{projectRoot}"
73
+ },
74
+ "cache": true
75
+ },
76
+ "install": {
77
+ "executor": "@nxlv/python:install",
78
+ "options": {
79
+ "silent": false,
80
+ "args": "",
81
+ "verbose": false,
82
+ "debug": false
83
+ }
84
+ },
85
+ "nx-release-publish": {
86
+ "executor": "@nxlv/python:publish",
87
+ "options": {},
88
+ "outputs": []
89
+ }
90
+ },
91
+ "tags": [],
92
+ "release": {
93
+ "version": {
94
+ "generator": "@nxlv/python:release-version"
95
+ }
96
+ }
97
+ }
@@ -0,0 +1,79 @@
1
+ [tool.coverage.run]
2
+ branch = true
3
+ source = [ "sphinxext_bsb" ]
4
+
5
+ [tool.coverage.report]
6
+ exclude_lines = ['if TYPE_CHECKING:']
7
+ show_missing = true
8
+
9
+ [tool.pytest.ini_options]
10
+ addopts = """
11
+ --cov
12
+ --cov-report html:'../coverage/sphinxext-bsb/html'
13
+ --cov-report xml:'../coverage/sphinxext-bsb/coverage.xml'
14
+ --html='../reports/sphinxext-bsb/unittests/html/index.html'
15
+ --junitxml='../reports/sphinxext-bsb/unittests/junit.xml'
16
+ """
17
+
18
+ [project]
19
+ name = "sphinxext-bsb"
20
+ version = "0.2.3"
21
+ description = "BSB Sphinx documentation extension"
22
+ requires-python = ">=3.9,<3.12"
23
+ readme = 'README.md'
24
+ dependencies = [
25
+ "bsb-core",
26
+ "sphinx-design~=0.5",
27
+ ]
28
+
29
+ [tool.flit.module]
30
+ name = "sphinxext.bsb"
31
+
32
+ [dependency-groups]
33
+ dev = [
34
+ "autopep8>=2.3.1",
35
+ "ruff>=0.8.2",
36
+ "pytest>=8.3.4",
37
+ "pytest-sugar>=1.0.0",
38
+ "pytest-cov>=6.0.0",
39
+ "pytest-html>=4.1.1",
40
+ ]
41
+
42
+ [build-system]
43
+ requires = ["flit>=3.5"]
44
+ build-backend = "flit.buildapi"
45
+
46
+ [tool.ruff]
47
+ exclude = [
48
+ ".ruff_cache",
49
+ ".svn",
50
+ ".tox",
51
+ ".venv",
52
+ "dist",
53
+ ]
54
+
55
+ line-length = 88
56
+ indent-width = 4
57
+
58
+ [tool.ruff.lint]
59
+ select = [
60
+ # pycodestyle
61
+ "E",
62
+ # Pyflakes
63
+ "F",
64
+ # pyupgrade
65
+ "UP",
66
+ # flake8-bugbear
67
+ "B",
68
+ # flake8-simplify
69
+ "SIM",
70
+ # isort
71
+ "I",
72
+ ]
73
+ ignore = []
74
+
75
+ fixable = ["ALL"]
76
+ unfixable = []
77
+
78
+ [tool.uv.sources]
79
+ bsb-core = { path = "../../packages/bsb-core", editable = true }
@@ -1,376 +1,375 @@
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_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_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
-
177
- def guess_example_value(self, attr):
178
- type_ = self.get_attr_type(attr)
179
- # The attribute may have a hinted example from the declaration `hint` kwarg,
180
- # or from the `__hint__` method of its type handler
181
- hint = attr.get_hint()
182
- if hint is not MISSING:
183
- example = hint
184
- else:
185
- # No hint, so check if the default is sensible
186
- default = attr.get_default()
187
- example = None
188
- if default is not None:
189
- example = default
190
- elif isclass(type_):
191
- # No default value, and likely a primitive was passed as type handler
192
- for parent_type, value in _example_values.items():
193
- # Loop over some basic possible basic primitives
194
- if issubclass(type_, parent_type):
195
- example = value
196
- break
197
- if example is None:
198
- # Try to have the type handler cast some primitive examples,
199
- # if no error is raised we assume it's a good example
200
- example = self.try_types(type_, *_example_values.values())
201
- # `str` is a kind of greedy type handler, so correct the casts
202
- if example == "[]":
203
- example = []
204
- elif example == "{}":
205
- example = {}
206
- elif example == "true":
207
- example = True
208
- # Some values need to be cast back to a tree-form, so we create a shim
209
- # for the attribute descriptor to use as an instance.
210
- shim = type("AttrShim", (), {})()
211
- setattr(shim, f"_{attr.attr_name}", example)
212
- try:
213
- example = attr.tree(shim)
214
- except Exception:
215
- pass
216
- # Hope we have a default value.
217
- return example
218
-
219
- def get_attr_type(self, attr):
220
- type_ = attr.get_type()
221
- type_ = str if type_ is None else type_
222
- return type_
223
-
224
- def try_types(self, type_, arg, *args):
225
- try:
226
- return type_(arg)
227
- except Exception:
228
- if args:
229
- return self.try_types(type_, *args)
230
-
231
- def get_python_lines(self, cls, tree):
232
- return [
233
- f"{cls.__name__}(",
234
- *(
235
- f" {line}"
236
- for line in itertools.chain.from_iterable(
237
- self.get_argument_lines(key, *value) for key, value in tree.items()
238
- )
239
- ),
240
- ")",
241
- ]
242
-
243
- def get_argument_lines(self, key, untree, value):
244
- return untree(key, value)
245
-
246
- def get_parser_lines(self, name, parser, tree):
247
- raw = self.raw_untree((None, tree))
248
- language = getattr(parser, "data_syntax", False) or name
249
- return [
250
- f" .. code-block:: {language}",
251
- "",
252
- *(
253
- f" {line}"
254
- for line in parser.generate(raw, pretty=True).split("\n")
255
- ),
256
- "",
257
- ]
258
-
259
- def raw_untree(self, tree):
260
- try:
261
- node_data = tree[1]
262
- except TypeError:
263
- # If the element wasn't packed as a tuple it's just a list/dict attr
264
- return tree
265
- list_repack = getattr(tree[0], "list_repack", False)
266
- dict_repack = getattr(tree[0], "dict_repack", False)
267
- if node_data is ...:
268
- if list_repack:
269
- return []
270
- else:
271
- return {}
272
- if dict_repack:
273
- node_data = {"name_of_the_thing": (None, node_data)}
274
- if list_repack:
275
- node_data = [(None, node_data)]
276
- if isinstance(node_data, dict):
277
- return {k: self.raw_untree(v) for k, v in node_data.items()}
278
- elif isinstance(node_data, list):
279
- return [self.raw_untree(v) for v in node_data]
280
- else:
281
- return node_data
282
-
283
- def public_untree(self, cls):
284
- def untree(key, value):
285
- if value is ...:
286
- lines = self.get_python_lines(cls, {})
287
- else:
288
- lines = self.get_python_lines(cls, value)
289
- lines[0] = f"{key}={lines[0]}"
290
- lines[-1] += ","
291
- return lines
292
-
293
- return untree
294
-
295
- def private_untree(self, cls):
296
- def untree(key, value):
297
- if value is ...:
298
- lines = self.get_python_lines(cls, {})
299
- else:
300
- lines = self.get_python_lines(cls, value)
301
- lines[0] = key + "={"
302
- for i in range(1, len(lines) - 1):
303
- line = lines[i]
304
- if "=" in line:
305
- lp = line.split("=")
306
- indent = len(lp[0]) - len(lp[0].lstrip())
307
- lines[i] = f"{' ' * indent}'{lp[0].lstrip()}': " + "=".join(lp[1:])
308
- lines[-1] = "},"
309
- return lines
310
-
311
- return untree
312
-
313
- def dict_untree(self, inner_untree):
314
- def untree(key, value):
315
- lines = inner_untree(key, value)
316
- return lines
317
-
318
- untree.dict_repack = True
319
- return untree
320
-
321
- def list_untree(self, inner_untree):
322
- def untree(key, value):
323
- if value is ...:
324
- return [f"{key}=[", "],"]
325
- lines = inner_untree(key, value)
326
- lines[0] = lines[0].split("=")[0] + "=["
327
- lines.insert(1, " {")
328
- lines[2:] = [" " + line for line in lines[2:]]
329
- lines.append("],")
330
- return lines
331
-
332
- untree.list_repack = True
333
- return untree
334
-
335
- def argument_untree(self, key, value):
336
- return [f"{key}={repr(value)},"]
337
-
338
- def get_imports(self, cls, deeper):
339
- imports = {}
340
- if not any(m.startswith("_") for m in cls.__module__.split(".")):
341
- imports.setdefault(cls.__module__, set()).add(cls.__name__)
342
- if deeper > 0:
343
- for attr in get_config_attributes(cls).values():
344
- if attr.is_node_type():
345
- for k, v in self.get_imports(attr.get_type(), deeper - 1).items():
346
- imports.setdefault(k, set()).update(v)
347
-
348
- return imports
349
-
350
- def get_import_lines(self, cls, depth):
351
- return [
352
- f"from {key} import {', '.join(value)}"
353
- for key, value in self.get_imports(cls, depth).items()
354
- ]
355
-
356
-
357
- def setup(app):
358
- if "sphinx_design" not in app.extensions:
359
- from sphinx_design import setup as sphinx_design_setup
360
-
361
- sphinx_design_setup(app)
362
-
363
- app.add_node(
364
- AutoconfigNode,
365
- html=(visit_autoconfig_node, depart_autoconfig_node),
366
- latex=(visit_autoconfig_node, depart_autoconfig_node),
367
- text=(visit_autoconfig_node, depart_autoconfig_node),
368
- )
369
- app.add_directive("bsb_component_intro", ComponentIntro)
370
- app.add_directive("autoconfig", AutoconfigDirective)
371
-
372
- return {
373
- "version": __version__,
374
- "parallel_read_safe": True,
375
- "parallel_write_safe": True,
376
- }
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,21 +0,0 @@
1
- MIT License
2
-
3
- Copyright (c) 2022 Robin De Schepper
4
-
5
- Permission is hereby granted, free of charge, to any person obtaining a copy
6
- of this software and associated documentation files (the "Software"), to deal
7
- in the Software without restriction, including without limitation the rights
8
- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
- copies of the Software, and to permit persons to whom the Software is
10
- furnished to do so, subject to the following conditions:
11
-
12
- The above copyright notice and this permission notice shall be included in all
13
- copies or substantial portions of the Software.
14
-
15
- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
- SOFTWARE.
@@ -1,5 +0,0 @@
1
- Metadata-Version: 2.1
2
- Name: sphinxext-bsb
3
- Version: 0.2.1
4
- Summary: BSB Sphinx documentation extension
5
- License-File: LICENSE
@@ -1,2 +0,0 @@
1
- # sphinxext-bsb
2
- Sphinx extension for the BSB documentation
@@ -1,11 +0,0 @@
1
- [build-system]
2
- requires = ["setuptools"]
3
- build-backend = "setuptools.build_meta"
4
-
5
- [project]
6
- name = "sphinxext-bsb"
7
- description = "BSB Sphinx documentation extension"
8
- version = "0.2.1"
9
- dependencies = [
10
- "sphinx-design"
11
- ]
@@ -1,4 +0,0 @@
1
- [egg_info]
2
- tag_build =
3
- tag_date = 0
4
-
@@ -1,5 +0,0 @@
1
- Metadata-Version: 2.1
2
- Name: sphinxext-bsb
3
- Version: 0.2.1
4
- Summary: BSB Sphinx documentation extension
5
- License-File: LICENSE
@@ -1,9 +0,0 @@
1
- LICENSE
2
- README.md
3
- bsbdocs.py
4
- pyproject.toml
5
- sphinxext_bsb.egg-info/PKG-INFO
6
- sphinxext_bsb.egg-info/SOURCES.txt
7
- sphinxext_bsb.egg-info/dependency_links.txt
8
- sphinxext_bsb.egg-info/requires.txt
9
- sphinxext_bsb.egg-info/top_level.txt
@@ -1 +0,0 @@
1
- sphinx-design
@@ -1 +0,0 @@
1
- bsbdocs