Skip to content

Commit 6dfdad7

Browse files
author
Arnaud Riess
committed
feat: implement needs_types serialization for ubproject.toml
1 parent 3808b58 commit 6dfdad7

3 files changed

Lines changed: 186 additions & 2 deletions

File tree

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ user.bazelrc
1212
/_build*
1313
docs/ubproject.toml
1414
docs/schemas.json
15+
docs/needs_types_generated.toml
1516

1617
# Vale - editorial style guide
1718
.vale.ini
Lines changed: 132 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,132 @@
1+
# score_sync_toml
2+
3+
A Sphinx extension that configures and drives
4+
[`needs-config-writer`](https://needs-config-writer.useblocks.com) to produce a
5+
`ubproject.toml` file at build time. This file is consumed **statically** by the
6+
[ubCode VS Code extension](https://ubcode.useblocks.com) to power the language server
7+
(need indexing, autocompletion, hover, schema validation, etc.).
8+
9+
**The problem it solves:** ubCode cannot run Sphinx. It needs a static
10+
`ubproject.toml` to know which RST directives are valid need types, what link types
11+
exist, what schemas apply, and so on. `score_sync_toml` bridges this gap by
12+
serialising the live Sphinx-Needs configuration into that file after every build.
13+
14+
---
15+
16+
## Files
17+
18+
| File | Purpose |
19+
|---|---|
20+
| `__init__.py` | Sphinx extension — configures `needs_config_writer` and generates helper TOML |
21+
| `shared.toml` | Static TOML fragment always merged into the output `ubproject.toml` |
22+
| `BUILD` | Bazel build definition |
23+
24+
---
25+
26+
## How it works
27+
28+
### 1. `setup(app)` configures `needs_config_writer`
29+
30+
| Setting | Value | Reason |
31+
|---|---|---|
32+
| `needscfg_outpath` | `ubproject.toml` | Where to write the final file (relative to `confdir`) |
33+
| `needscfg_overwrite` | `True` | Always regenerate on each build |
34+
| `needscfg_write_all` | `True` | Write full config, not just diffs, so the file is self-contained |
35+
| `needscfg_exclude_defaults` | `True` | Omit default values to keep the file readable |
36+
| `needscfg_warn_on_diff` | `False` | Don't fail CI when the file changes |
37+
38+
### 2. Excluded variables
39+
40+
Some Sphinx-Needs config values are excluded from serialisation because they either
41+
cannot be represented in TOML or are managed elsewhere:
42+
43+
| Variable | Reason for exclusion |
44+
|---|---|
45+
| `needs_from_toml` / `needs_from_toml_table` | Meta-config consumed by `needs_config_writer` itself |
46+
| `needs_schema_definitions_from_json` | Pointer handled via `shared.toml` |
47+
| `needs_schema_definitions` | Generated schema content — managed via `schemas.json` / `score_metamodel` |
48+
| `needs_render_context` | Contains Python callables (`draw_full_interface` etc.) — not serialisable |
49+
50+
### 3. Merging `shared.toml`
51+
52+
The static `shared.toml` is always merged into the output. It contributes:
53+
54+
- **`[parse.extend_directives]`** — teaches ubCode to parse third-party RST
55+
directives (`grid`, `grid-item-card`, `uml`) without treating them as errors.
56+
- **`schema_definitions_from_json = "schemas.json"`** — points ubCode to the
57+
JSON Schema validation file generated by `score_metamodel`.
58+
- **`index_on_save = true`** — tells ubCode to re-index whenever a file is saved.
59+
60+
### 4. `_write_needs_types_toml(app)` — generating `[[needs.types]]`
61+
62+
`needs_config_writer` cannot serialise the project's custom `ScoreNeedType` dicts
63+
(they contain Python-specific data) and silently skips them. Without
64+
`[[needs.types]]` entries in `ubproject.toml`, **ubCode does not recognise any
65+
directives as needs** and the index is empty.
66+
67+
`_write_needs_types_toml` works around this by:
68+
69+
1. Reading `app.config.needs_types` — already populated by `score_metamodel` from
70+
`metamodel.yaml` before `score_sync_toml` runs.
71+
2. Writing `needs_types_generated.toml` next to `conf.py` containing clean
72+
`[[needs.types]]` entries with only the fields ubCode requires:
73+
`directive`, `title`, `prefix`, and optionally `color` / `style`.
74+
3. Appending that file to `needscfg_merge_toml_files` so it is merged into the
75+
final `ubproject.toml`.
76+
77+
### 5. Relative path handling
78+
79+
Fields such as `needs_external_needs[*].json_path` contain **absolute paths**
80+
injected by Bazel. `needscfg_relative_path_fields` converts these to paths
81+
relative to `confdir` in the output TOML so the file remains portable.
82+
83+
### 6. Suppressed warnings
84+
85+
```
86+
needs_config_writer.path_conversion
87+
needs_config_writer.unsupported_type
88+
```
89+
90+
Suppressed so that Bazel builds running with `-W` (warnings-as-errors) do not fail
91+
due to known, handled edge cases in serialisation.
92+
93+
---
94+
95+
## Data flow
96+
97+
```
98+
metamodel.yaml
99+
100+
101+
score_metamodel.setup() ──► app.config.needs_types (Python objects)
102+
103+
104+
_write_needs_types_toml()
105+
106+
107+
needs_types_generated.toml ──┐
108+
109+
shared.toml ────────────────────────────────────────────┤ merge via
110+
│ needs_config_writer
111+
app.config (all other needs_* settings) ────────────────┤
112+
113+
ubproject.toml
114+
115+
116+
Sphinx-Needs
117+
ubCode language server
118+
```
119+
120+
---
121+
122+
## Generated files
123+
124+
Both files are written to `confdir` (the `docs/` directory) on every Sphinx build
125+
and are committed to the repository so that ubCode works without needing to run a
126+
build first.
127+
128+
| File | Generated by | Used by |
129+
|---|---|---|
130+
| `ubproject.toml` | `needs_config_writer` + merges above | ubCode language server |
131+
| `schemas.json` | `score_metamodel.sn_schemas.write_sn_schemas` | ubCode + sphinx-needs schema validation |
132+
| `needs_types_generated.toml` | `_write_needs_types_toml` | Merged into `ubproject.toml`, not committed |

src/extensions/score_sync_toml/__init__.py

Lines changed: 53 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,44 @@
1717
from src.helper_lib import config_setdefault
1818

1919

20+
def _write_needs_types_toml(app: Sphinx) -> Path:
21+
"""Serialize ``app.config.needs_types`` as ``[[needs.types]]`` TOML entries.
22+
23+
``needs_config_writer`` cannot serialize the complex ``ScoreNeedType``
24+
dicts (it emits an ``unsupported_type`` warning and skips them), so
25+
``ubproject.toml`` ends up with no type definitions. Without those,
26+
the ubCode language server does not recognise any RST directives as needs
27+
and indexes nothing.
28+
29+
This function writes only the fields that ubCode requires to identify
30+
need directives (``directive``, ``title``, ``prefix``, and optionally
31+
``color``/``style``) to a separate TOML file that is then merged into
32+
``ubproject.toml`` by ``needs_config_writer``.
33+
34+
Must be called *after* ``score_metamodel.setup()`` has run (i.e. after
35+
``app.config.needs_types`` has been extended with the metamodel types).
36+
"""
37+
lines: list[str] = [
38+
"# Auto-generated – do not edit manually.\n",
39+
"# Contains [[needs.types]] entries derived from metamodel.yaml.\n",
40+
"# Required for the ubCode language server to recognise need directives.\n\n",
41+
]
42+
for nt in app.config.needs_types:
43+
lines.append("[[needs.types]]\n")
44+
lines.append(f'directive = "{nt["directive"]}"\n')
45+
lines.append(f'title = "{nt["title"]}"\n')
46+
lines.append(f'prefix = "{nt["prefix"]}"\n')
47+
if color := nt.get("color"):
48+
lines.append(f'color = "{color}"\n')
49+
if style := nt.get("style"):
50+
lines.append(f'style = "{style}"\n')
51+
lines.append("\n")
52+
53+
output_path = Path(app.confdir) / "needs_types_generated.toml"
54+
output_path.write_text("".join(lines), encoding="utf-8")
55+
return output_path
56+
57+
2058
def setup(app: Sphinx) -> dict[str, str | bool]:
2159
"""
2260
Extension to configure needs-config-writer for syncing needs configuration to TOML.
@@ -50,6 +88,10 @@ def setup(app: Sphinx) -> dict[str, str | bool]:
5088
# These are managed via schemas.json / score_metamodel and must not be
5189
# duplicated in ubproject.toml (ubCode only needs schema_definitions_from_json).
5290
"needs_schema_definitions",
91+
# needs_render_context contains Python callable objects (draw_full_interface,
92+
# draw_full_module, etc.) that cannot be serialized to TOML.
93+
# needs_config_writer emits unsupported_type warnings for these and skips them.
94+
"needs_render_context",
5395
]
5496
"""Exclude resolved/generated config values that don't belong in ubproject.toml."""
5597

@@ -58,6 +100,14 @@ def setup(app: Sphinx) -> dict[str, str | bool]:
58100
)
59101
"""Merge the static TOML file into the generated configuration."""
60102

103+
# Write [[needs.types]] from the metamodel into a separate TOML fragment and
104+
# merge it. needs_config_writer cannot serialise the complex ScoreNeedType
105+
# dicts itself (unsupported_type), so ubproject.toml would otherwise contain
106+
# no type definitions and the ubCode language server would index nothing.
107+
needs_types_toml = _write_needs_types_toml(app)
108+
app.config.needscfg_merge_toml_files.append(str(needs_types_toml))
109+
"""Merge the generated [[needs.types]] TOML into the final ubproject.toml."""
110+
61111
app.config.needscfg_relative_path_fields.extend(
62112
[
63113
"needs_external_needs[*].json_path",
@@ -70,10 +120,11 @@ def setup(app: Sphinx) -> dict[str, str | bool]:
70120
"""Relative paths to confdir for Bazel provided absolute paths."""
71121

72122
app.config.suppress_warnings += [
73-
"needs_config_writer.unsupported_type",
74123
"needs_config_writer.path_conversion",
124+
# needs_render_context values are Python callables; they are excluded via
125+
# needscfg_exclude_vars above, but suppress the warning as a safety net.
126+
"needs_config_writer.unsupported_type",
75127
]
76-
# TODO remove the suppress_warnings once fixed
77128

78129
return {
79130
"version": "0.1",

0 commit comments

Comments
 (0)