| #!/usr/bin/env python3 |
| """Generate CMake presets documentation (reST) and JSON schema. |
| |
| The documentation and JSON schema for CMake presets are both generated from |
| Help/manual/presets/schema.yaml, which describes the schema in a way that |
| allows both documentation forms to be combined in a single file, and also makes |
| it much easier to manage version revisions (compared to editing the JSON schema |
| 'by hand'). |
| |
| All input and output files are expected to be in known locations relative to |
| this script. |
| |
| Usage: python3 regenerate-presets.py |
| """ |
| |
| import json |
| import re |
| from copy import deepcopy |
| from dataclasses import dataclass |
| from pathlib import Path |
| from typing import Any, Self |
| |
| import yaml |
| |
| LATEST = 1 |
| TYPES = {} |
| REFS = {} |
| |
| PRESETS_REL = 'Help/manual/presets' |
| SCHEMA_YAML_FILENAME = 'schema.yaml' |
| SCHEMA_JSON_FILENAME = 'schema.json' |
| |
| WORKSPACE = Path(__file__).parent.parent.parent.absolute() |
| PRESETS = WORKSPACE / PRESETS_REL |
| DIAGNOSTICS = WORKSPACE / 'Source/cmDiagnostics.h' |
| |
| RST_BANNER = f"""\ |
| .. This file was generated by {Path(__file__).relative_to(WORKSPACE)} |
| from {PRESETS_REL}/{SCHEMA_YAML_FILENAME}. Do not edit. |
| """ |
| |
| DIAGNOSTIC_TABLE_MACRO = 'CM_FOR_EACH_DIAGNOSTIC_TABLE' |
| CONFIGURE_PRESET_PROPERTIES_PATH = ( |
| 'properties', 'configurePresets', 'items', 'properties' |
| ) |
| WARNING_DESCRIPTION = ( |
| 'An optional boolean. ' |
| 'Equivalent to passing -W{c} or -Wno-{c} on the command line. ' |
| 'This may not be set to false if errors.{p} is set to true.' |
| ) |
| WARNING_SPHINX_DESCRIPTION = """ |
| An optional boolean. Equivalent to passing :option:`-W{c} <cmake -W>` or |
| :option:`-Wno-{c} <cmake -Wno->` on the command line. |
| This may not be set to ``false`` if ``errors.{p}`` is set to ``true``. |
| """.strip() |
| ERROR_DESCRIPTION = ( |
| 'An optional boolean. ' |
| 'Equivalent to passing -Werror={c} or -Wno-error={c} on the command line. ' |
| 'This may not be set to true if warnings.{p} is set to false.' |
| ) |
| ERROR_SPHINX_DESCRIPTION = """ |
| An optional boolean. Equivalent to passing :cmake-option:`-Werror={c}` or |
| :cmake-option:`-Wno-error={c}` on the command line. |
| This may not be set to ``true`` if ``warnings.{p}`` is set to ``false``. |
| """.strip() |
| |
| |
| # ============================================================================= |
| @dataclass |
| class Diagnostic: |
| presetName: str |
| cliName: str |
| since: int |
| |
| # ------------------------------------------------------------------------- |
| def format(self, template: str) -> str: |
| return template.format(c=self.cliName, p=self.presetName) |
| |
| |
| # ============================================================================= |
| class Value: |
| |
| # ------------------------------------------------------------------------- |
| def __init__(self, data: list[dict[str, Any]]): |
| self.data = data |
| self.spans = None |
| |
| # ------------------------------------------------------------------------- |
| @property |
| def objects(self) -> dict[str, 'Object']: |
| return {} |
| |
| # ------------------------------------------------------------------------- |
| def updateSpans(self, since: int, until: int) -> None: |
| self.spans = [(since, until)] |
| |
| # ------------------------------------------------------------------------- |
| def updateRefs(self, objects: dict[str, 'Object']) -> None: |
| pass |
| |
| # ------------------------------------------------------------------------- |
| def defs(self) -> dict[str, Any]: |
| return {} |
| |
| # ------------------------------------------------------------------------- |
| def schema(self, version: int) -> dict[str, Any]: |
| return deepcopy(self.data) |
| |
| |
| # ============================================================================= |
| class Object(Value): |
| |
| # ------------------------------------------------------------------------- |
| def __init__(self, data: dict[str, Any], name: str, |
| label: str | None = None): |
| # Prevent data sharing from modifying unrelated parts of the schema as |
| # we pick apart our section. |
| data = deepcopy(data) |
| |
| self.name = data.pop('id', name) |
| self.label = label |
| |
| self.properties = {} |
| for n, p in data.pop('properties').items(): |
| pl = '.'.join([label, n]) if label is not None else n |
| self.properties[n] = Property(p, n, pl) |
| if len(self.properties): |
| self.properties[CommentProperty.name] = CommentProperty() |
| |
| ap = data.pop('additionalProperties', None) |
| if ap is not None: |
| self.additionalProperties = buildValue(ap, self.name, self.label) |
| else: |
| self.additionalProperties = None |
| |
| super().__init__(data) |
| |
| # ------------------------------------------------------------------------- |
| @property |
| def objects(self) -> dict[str, Self]: |
| out = {self.name: self} |
| for p in self.properties.values(): |
| out.update(p.objects) |
| if self.additionalProperties is not None: |
| out.update(self.additionalProperties.objects) |
| return out |
| |
| # ------------------------------------------------------------------------- |
| def updateSpans(self, since: int, until: int) -> None: |
| spans = [[(since, until)]] |
| for p in self.properties.values(): |
| p.updateSpans(since, until) |
| spans.append(p.spans) |
| if self.additionalProperties is not None: |
| self.additionalProperties.updateSpans(since, until) |
| spans.append(self.additionalProperties.spans) |
| self.spans = combineSpans(*spans) |
| |
| # ------------------------------------------------------------------------- |
| def updateRefs(self, objects: dict[str, 'Object']) -> None: |
| for p in self.properties.values(): |
| p.updateRefs(objects) |
| if self.additionalProperties is not None: |
| self.additionalProperties.updateRefs(objects) |
| |
| # ------------------------------------------------------------------------- |
| def doc(self, since: int, until: int) -> str | None: |
| since = max(since, self.spans[0][0]) |
| until = min(until, self.spans[-1][1]) |
| |
| out = [] |
| for _, p in self.properties.items(): |
| content = p.doc(since, until) |
| if not content: |
| continue |
| out.append(content) |
| |
| return '\n\n'.join(out) if len(out) else None |
| |
| # ------------------------------------------------------------------------- |
| def defs(self, exclude: set[str] = {}) -> dict[str, Any]: |
| out = {} |
| for n, p in self.properties.items(): |
| if n in exclude: |
| continue |
| for s in p.spans: |
| r = f'{p.label}@{spanRef(*s)}' |
| out[r] = p.schema(s[0]) |
| out.update(p.defs()) |
| if self.additionalProperties is not None: |
| out.update(self.additionalProperties.defs()) |
| return out |
| |
| # ------------------------------------------------------------------------- |
| def schema(self, version: int, inline: set[str] = {}) -> dict[str, Any]: |
| out = deepcopy(self.data) |
| |
| props = {} |
| required = [] |
| for n, p in self.properties.items(): |
| if n in inline: |
| props[n] = p.schema(version) |
| else: |
| r = p.ref(version) |
| if r is not None: |
| props[n] = r |
| if p.required: |
| required.append(n) |
| |
| out['properties'] = props |
| if len(required): |
| out['required'] = required |
| |
| if self.additionalProperties is not None: |
| ap = self.additionalProperties.schema(version) |
| out['additionalProperties'] = ap |
| return out |
| |
| |
| # ============================================================================= |
| class Ref(Value): |
| |
| # ------------------------------------------------------------------------- |
| def __init__(self, data: list[dict[str, Any]]): |
| self.target = data.pop('target') |
| self.targetSpans = None |
| super().__init__(data) |
| |
| # ------------------------------------------------------------------------- |
| def updateSpans(self, since: int, until: int) -> None: |
| if self.spans is None: |
| super().updateSpans(since, until) |
| self.spans = trimSpans(self.spans, since, until) |
| |
| # ------------------------------------------------------------------------- |
| def updateRefs(self, objects: dict[str, 'Object']) -> None: |
| global REFS |
| |
| t = objects.get(self.target) |
| if t is not None: |
| since = self.spans[0][0] |
| until = self.spans[-1][1] |
| self.spans = trimSpans(t.spans, since, until) |
| self.targetSpans = t.spans |
| |
| rs = REFS.get(self.target, (since, until)) |
| REFS[self.target] = (min(since, rs[0]), max(until, rs[1])) |
| |
| # ------------------------------------------------------------------------- |
| def schema(self, version: int) -> dict[str, Any]: |
| ts = trimSpans(self.targetSpans, *REFS[self.target]) |
| s = findSpan(ts, version) |
| if s is not None: |
| return { |
| '$ref': f'#/definitions/{self.target}@{spanRef(*s)}', |
| } |
| return {} |
| |
| |
| # ============================================================================= |
| class Array(Value): |
| |
| # ------------------------------------------------------------------------- |
| def __init__(self, data: dict[str, Any], name: str, label: str): |
| item = data.pop('items') |
| self.value = buildValue(item, name, label) |
| |
| super().__init__(data) |
| |
| # ------------------------------------------------------------------------- |
| @property |
| def objects(self) -> dict[str, Object]: |
| return self.value.objects |
| |
| # ------------------------------------------------------------------------- |
| def updateSpans(self, since: int, until: int) -> None: |
| self.value.updateSpans(since, until) |
| self.spans = self.value.spans |
| |
| # ------------------------------------------------------------------------- |
| def updateRefs(self, objects: dict[str, 'Object']) -> None: |
| self.value.updateRefs(objects) |
| |
| # ------------------------------------------------------------------------- |
| def defs(self) -> dict[str, Any]: |
| return self.value.defs() |
| |
| # ------------------------------------------------------------------------- |
| def schema(self, version: int) -> dict[str, Any]: |
| out = deepcopy(self.data) |
| out['items'] = self.value.schema(version) |
| return out |
| |
| |
| # ============================================================================= |
| class Union(Value): |
| |
| # ------------------------------------------------------------------------- |
| def __init__(self, data: list[dict[str, Any]], name: str, label: str): |
| self.items = [] |
| for i, d in enumerate(data): |
| self.items.append(buildValue(d, name, self.itemLabel(label, i))) |
| |
| super().__init__({}) |
| |
| # ------------------------------------------------------------------------- |
| def itemLabel(self, label: str, index: int) -> str: |
| return label |
| |
| # ------------------------------------------------------------------------- |
| @property |
| def objects(self) -> dict[str, Object]: |
| out = {} |
| for v in self.items: |
| out.update(v.objects) |
| return out |
| |
| # ------------------------------------------------------------------------- |
| def updateSpans(self, since: int, until: int) -> None: |
| spans = [[(since, until)]] |
| for i in self.items: |
| i.updateSpans(since, until) |
| spans.append(i.spans) |
| self.spans = combineSpans(*spans) |
| |
| # ------------------------------------------------------------------------- |
| def updateRefs(self, objects: dict[str, 'Object']) -> None: |
| for i in self.items: |
| i.updateRefs(objects) |
| |
| # ------------------------------------------------------------------------- |
| def defs(self) -> dict[str, Any]: |
| out = {} |
| for v in self.items: |
| out.update(v.defs()) |
| return out |
| |
| # ------------------------------------------------------------------------- |
| def schema(self, version: int) -> dict[str, Any]: |
| out = [] |
| for i in self.items: |
| out.append(i.schema(version)) |
| return {'anyOf': out} |
| |
| |
| # ============================================================================= |
| class Variant(Union): |
| |
| # ------------------------------------------------------------------------- |
| def __init__(self, data: list[dict[str, Any]], name: str, label: str): |
| self.identifier = data.pop('id', None) |
| |
| if self.identifier is not None: |
| name = self.identifier |
| label = self.identifier |
| |
| super().__init__(data.pop('items'), name, label) |
| |
| # ------------------------------------------------------------------------- |
| def itemLabel(self, label: str, index: int) -> str: |
| return f'{label}[{index}]' |
| |
| # ------------------------------------------------------------------------- |
| @property |
| def objects(self) -> dict[str, Object]: |
| out = super().objects |
| if self.identifier is not None: |
| out[self.identifier] = self |
| return out |
| |
| # ------------------------------------------------------------------------- |
| def doc(self, since: int, until: int) -> None: |
| return None |
| |
| # ------------------------------------------------------------------------- |
| def defs(self) -> dict[str, Any]: |
| out = super().defs() |
| |
| if self.identifier is not None: |
| for s in self.spans: |
| n = f'{self.identifier}@{spanRef(*s)}' |
| out[n] = super().schema(s[0]) |
| |
| return out |
| |
| # ------------------------------------------------------------------------- |
| def schema(self, version: int) -> dict[str, Any]: |
| if self.identifier is not None: |
| s = findSpan(self.spans, version) |
| if s is not None: |
| return { |
| '$ref': f'#/definitions/{self.identifier}@{spanRef(*s)}', |
| } |
| return {} |
| |
| return super().schema(version) |
| |
| |
| # ============================================================================= |
| class Property: |
| |
| # ------------------------------------------------------------------------- |
| def __init__(self, data: dict[str, Any], name: str, label: str): |
| self.name = name |
| self.label = label |
| self.since = data.pop('since', 1) |
| self.until = data.pop('until', LATEST + 1) |
| self.required = data.pop('required', False) |
| self.content = data.pop('sphinxDescription', data.get('description')) |
| |
| note = data.pop('sphinxNote', None) |
| if note is not None: |
| self.content += f'\n\n.. note::\n\n{indent(note)}' |
| |
| self._revisions = [] |
| for rev in data.pop('revisions', []): |
| rd = deepcopy(data) |
| rd.update(rev) |
| self._revisions.append(Property(rd, self.name, self.label)) |
| |
| if len(self._revisions): |
| self.since = max(self.since, |
| min([r.since for r in self._revisions])) |
| self.until = min(self.until, |
| max([r.until for r in self._revisions])) |
| self.value = None |
| else: |
| self.value = buildValue(data, self.name, self.label) |
| |
| self.spans = None |
| |
| # ------------------------------------------------------------------------- |
| @property |
| def objects(self) -> dict[str, Object]: |
| if len(self._revisions): |
| out = {} |
| for r in self._revisions: |
| out.update(r.objects) |
| return out |
| |
| return self.value.objects |
| |
| # ------------------------------------------------------------------------- |
| def updateSpans(self, since: int, until: int) -> None: |
| since = max(since, self.since) |
| until = min(until, self.until) |
| |
| if len(self._revisions): |
| spans = [[(since, until)]] |
| for r in self._revisions: |
| r.updateSpans(since, until) |
| spans.append(r.spans) |
| self.spans = combineSpans(*spans) |
| else: |
| self.value.updateSpans(since, until) |
| self.spans = self.value.spans |
| |
| # ------------------------------------------------------------------------- |
| def updateRefs(self, objects: dict[str, 'Object']) -> None: |
| if len(self._revisions): |
| for r in self._revisions: |
| r.updateRefs(objects) |
| else: |
| self.value.updateRefs(objects) |
| |
| # ------------------------------------------------------------------------- |
| def ref(self, version: int) -> dict[str, str] | None: |
| s = findSpan(self.spans, version) |
| if s is not None: |
| return { |
| '$ref': f'#/definitions/{self.label}@{spanRef(*s)}', |
| } |
| return None |
| |
| # ------------------------------------------------------------------------- |
| def doc(self, since: int, until: int) -> str | None: |
| if self.content is None: |
| return None |
| |
| out = f'.. _`CMakePresets.{self.label}`:\n\n``{self.name}``\n' |
| if self.since > since: |
| out += f' .. presets-versionadded:: {self.since}\n\n' |
| if self.until < until: |
| out += f' .. presets-versionremoved:: {self.until}\n\n' |
| out += indent(self.content) |
| |
| return out |
| |
| # ------------------------------------------------------------------------- |
| def defs(self) -> dict[str, Any]: |
| if len(self._revisions): |
| out = {} |
| for r in self._revisions: |
| out.update(r.defs()) |
| return out |
| |
| return self.value.defs() |
| |
| # ------------------------------------------------------------------------- |
| def schema(self, version: int) -> dict[str, Any]: |
| for r in self._revisions: |
| if version >= r.since and version < r.until: |
| return r.value.schema(version) |
| return self.value.schema(version) |
| |
| |
| # ============================================================================= |
| class CommentProperty(Property): |
| name: str = '$comment' |
| since: int = 10 |
| |
| # ------------------------------------------------------------------------- |
| def __init__(self): |
| data = { |
| 'since': type(self).since, |
| 'id': 'comment', |
| 'type': 'variant', |
| 'items': [ |
| { |
| 'type': 'string', |
| 'description': 'A single-line comment.', |
| }, |
| { |
| 'type': 'array', |
| 'description': 'A multi-line comment.', |
| 'minItems': 1, |
| 'items': { |
| 'type': 'string', |
| 'description': 'One line of the multi-line comment.', |
| } |
| } |
| ], |
| } |
| super().__init__(data, type(self).name, type(self).name) |
| |
| # ------------------------------------------------------------------------- |
| def doc(self, since: int, until: int) -> None: |
| return None |
| |
| |
| # ----------------------------------------------------------------------------- |
| def buildValue( |
| data: Any, name: str, label: str | None = None |
| ) -> Variant | Object | Array | Ref | Value: |
| if type(data) is dict: |
| if 'anyOf' in data: |
| return Union(data.pop('anyOf'), name, label) |
| elif data['type'] == 'variant': |
| return Variant(data, name, label) |
| elif data['type'] == 'array': |
| return Array(data, name, label) |
| elif data['type'] == 'object': |
| return Object(data, name, label) |
| elif data['type'] == 'ref': |
| return Ref(data) |
| |
| return Value(data) |
| |
| |
| # ----------------------------------------------------------------------------- |
| def extractDivisions(spans: list[tuple[int, int]]) -> set[int]: |
| return {n for s in spans for n in s} |
| |
| |
| # ----------------------------------------------------------------------------- |
| def trimSpans(spans: list[tuple[int, int]], |
| since: int, until: int) -> list[tuple[int, int]]: |
| divisions = extractDivisions(spans) |
| |
| last = since |
| out = [] |
| |
| for d in sorted(divisions): |
| if d > since: |
| out.append((last, min(d, until))) |
| last = min(d, until) |
| if d >= until: |
| break |
| |
| return out |
| |
| |
| # ----------------------------------------------------------------------------- |
| def combineSpans(*spans: list[tuple[int, int]]) -> list[tuple[int, int]]: |
| divisions = set() |
| for s in spans: |
| divisions.update(extractDivisions(s)) |
| |
| divisions = sorted(divisions) |
| last = divisions.pop(0) |
| out = [] |
| |
| for d in divisions: |
| out.append((last, d)) |
| last = d |
| |
| return out |
| |
| |
| # ----------------------------------------------------------------------------- |
| def findSpan(spans: list[tuple[int, int]], |
| version: int) -> tuple[int, int] | None: |
| for s in spans: |
| if version >= s[0] and version < s[1]: |
| return s |
| |
| return None |
| |
| |
| # ----------------------------------------------------------------------------- |
| def spanRef(since: int, until: int) -> str: |
| if since + 1 < until: |
| if until > LATEST: |
| return f'v{since}..' |
| else: |
| return f'v{since}..v{until - 1}' |
| return f'v{since}' |
| |
| |
| # ----------------------------------------------------------------------------- |
| def indent(text: str): |
| return '\n'.join([(' ' + line).rstrip() for line in text.split('\n')]) |
| |
| |
| # ----------------------------------------------------------------------------- |
| def trimDescriptions(data: Any): |
| if type(data) is dict: |
| for k, v in data.items(): |
| if type(v) is str and k in {'description', 'sphinxDescription'}: |
| data[k] = v.strip() |
| elif type(v) is dict: |
| data[k] = trimDescriptions(v) |
| elif type(v) is list: |
| data[k] = [trimDescriptions(item) for item in v] |
| |
| return data |
| |
| |
| # ----------------------------------------------------------------------------- |
| def getPath(data: Any, path: tuple[str]) -> Any: |
| if len(path) == 0: |
| return data |
| return getPath(data[path[0]], path[1:]) |
| |
| |
| # ----------------------------------------------------------------------------- |
| def diagnosticPresetName(symbol: str) -> str: |
| sep = False |
| out = '' |
| for c in symbol[4:]: |
| if c == '_': |
| sep = True |
| elif sep: |
| out += c |
| sep = False |
| else: |
| out += c.lower() |
| return out |
| |
| |
| # ----------------------------------------------------------------------------- |
| def buildDiagnosticsSchema( |
| diagnostics: list[Diagnostic], |
| descriptionTemplate: str, |
| sphinxDescriptionTemplate: str, |
| ) -> dict[str, dict[str, Any]]: |
| out = {} |
| |
| for d in diagnostics: |
| out[d.presetName] = { |
| 'since': d.since, |
| 'type': 'boolean', |
| 'description': d.format(descriptionTemplate), |
| 'sphinxDescription': d.format(sphinxDescriptionTemplate), |
| } |
| |
| return out |
| |
| |
| # ----------------------------------------------------------------------------- |
| def mergeDiagnostics( |
| generated: dict[str, dict[str, Any]], |
| extra: dict[str, dict[str, Any]], |
| ) -> dict[str, dict[str, Any]]: |
| # Get unsorted names. |
| tail = {} |
| for k in list(extra.keys()): |
| s = extra[k].pop('sort', True) |
| if not s: |
| tail[k] = extra.pop(k) |
| |
| # Combine inputs and sort. |
| out = {} |
| combined = deepcopy(generated) |
| combined.update(extra) |
| for k in sorted(combined.keys()): |
| out[k] = combined[k] |
| |
| # Add unsorted items and return result. |
| out.update(tail) |
| return out |
| |
| |
| # ----------------------------------------------------------------------------- |
| def readDiagnostics(path: Path | str) -> list[Diagnostic]: |
| content = '' |
| |
| # Extract diagnostics table from header. |
| with open(path, 'r') as f: |
| extracting = False |
| for line in f: |
| if DIAGNOSTIC_TABLE_MACRO in line: |
| extracting = True |
| continue |
| |
| if extracting: |
| content += line.replace('\\\n', ' ') |
| if not line.strip().endswith('\\'): |
| break |
| |
| out = [] |
| while True: |
| m = re.match(r'\s*SELECT[(]([^)]+)[)]', content) |
| if m is None: |
| break |
| |
| args = [a.strip() for a in m.group(1).split(',')] |
| p = diagnosticPresetName(args[3]) |
| c = args[3][4:].lower().replace('_', '-') |
| v = int(args[4]) |
| if v > 1: |
| out.append(Diagnostic(p, c, v)) |
| |
| content = content[m.span()[1]:] |
| |
| return out |
| |
| |
| # ----------------------------------------------------------------------------- |
| def main(): |
| # Read the schema definition. |
| with open(PRESETS / SCHEMA_YAML_FILENAME, 'r') as f: |
| schema = yaml.safe_load(f) |
| |
| # Extract the current (latest) version. |
| global LATEST |
| |
| del schema['definitions'] |
| LATEST = schema.pop('version') |
| |
| # Remove whitespace around descriptions. |
| schema = trimDescriptions(schema) |
| |
| # Read diagnostics and update schema. |
| diagnostics = readDiagnostics(DIAGNOSTICS) |
| configureSchema = getPath(schema, CONFIGURE_PRESET_PROPERTIES_PATH) |
| configureSchema['warnings']['properties'] = mergeDiagnostics( |
| buildDiagnosticsSchema(diagnostics, WARNING_DESCRIPTION, |
| WARNING_SPHINX_DESCRIPTION), |
| configureSchema['warnings']['properties']) |
| configureSchema['errors']['properties'] = mergeDiagnostics( |
| buildDiagnosticsSchema(diagnostics, ERROR_DESCRIPTION, |
| ERROR_SPHINX_DESCRIPTION), |
| configureSchema['errors']['properties']) |
| |
| # Extract global type definitions. |
| global TYPES, REFS |
| |
| for t in schema.pop('types', []): |
| name = t['id'] |
| value = buildValue(t, name) |
| value.updateSpans(1, LATEST + 1) |
| TYPES[name] = value |
| |
| for t in TYPES.values(): |
| t.updateRefs(TYPES) |
| |
| # Reset the spans for references, as these currently contain only spans for |
| # circular references of global types, which are likely broader than the |
| # actual usage spans of those types. We'll recalculate the correct spans |
| # when we update refs from the root object. |
| REFS = {} |
| |
| # Parse the root object. This will also recursively parse other objects. |
| root = Object(schema, 'root') |
| root.updateSpans(1, LATEST + 1) |
| |
| # Resolve references. |
| objects = root.objects |
| |
| types = objects |
| types.update(TYPES) |
| |
| root.updateRefs(types) |
| |
| for name, value in TYPES.items(): |
| span = REFS.get(name) |
| if span is not None: |
| value.updateSpans(*span) |
| |
| root.updateSpans(1, LATEST + 1) |
| |
| # Extract Sphinx documentation for each object's properties. |
| for n, o in objects.items(): |
| doc = o.doc(1, LATEST + 1) |
| if doc is not None: |
| print(f'- Generating reST documentation for {n} properties') |
| with open(PRESETS / f'{o.name}-properties.rst', 'w') as f: |
| print(RST_BANNER, file=f) |
| print(doc, file=f) |
| |
| # Generate schema for each version. |
| versions = [] |
| for v in range(1, LATEST + 1): |
| vs = root.schema(v, inline={'version'}) |
| vs['properties']['version']['const'] = v |
| versions.append(vs) |
| |
| # Add definitions and write the schema. |
| types = root.defs(exclude={'version'}) |
| for t in TYPES.values(): |
| types.update(t.defs()) |
| |
| schema = { |
| '$schema': 'http://json-schema.org/draft/2020-12/schema#', |
| 'type': 'object', |
| 'oneOf': versions, |
| 'required': ['version'], |
| 'definitions': types |
| } |
| |
| with open(PRESETS / SCHEMA_JSON_FILENAME, 'w') as f: |
| print(json.dumps(schema, indent=2), file=f) |
| |
| |
| # %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% |
| |
| main() |