Complete DFG-8 adapter proposal support
This commit is contained in:
parent
f1e31487c0
commit
591bf0ae7d
14 changed files with 525 additions and 75 deletions
|
|
@ -7,6 +7,7 @@ import json
|
|||
import subprocess
|
||||
import tomllib
|
||||
from collections import Counter
|
||||
from collections.abc import Mapping
|
||||
from dataclasses import replace
|
||||
from pathlib import Path
|
||||
from typing import Any
|
||||
|
|
@ -500,6 +501,46 @@ def validate_graph(nodes: tuple[Node, ...], edges: tuple[Edge, ...]) -> None:
|
|||
visit(node_id, ())
|
||||
|
||||
|
||||
def validate_source_layout(nodes: tuple[Node, ...]) -> None:
|
||||
"""Validate the generic Markdown and TOML source-layout contract."""
|
||||
|
||||
markdown_sources: dict[str, list[str]] = {}
|
||||
anchors: dict[tuple[str, str], list[str]] = {}
|
||||
for node in nodes:
|
||||
if Path(node.source_path).suffix == ".md":
|
||||
markdown_sources.setdefault(node.source_path, []).append(node.node_id)
|
||||
continue
|
||||
if not node.source_anchor:
|
||||
raise DocForgeError(
|
||||
"source_anchor_required",
|
||||
"TOML nodes require a stable source anchor",
|
||||
node_id=node.node_id,
|
||||
)
|
||||
anchors.setdefault((node.source_path, node.source_anchor), []).append(node.node_id)
|
||||
markdown_conflicts = {
|
||||
source: sorted(node_ids)
|
||||
for source, node_ids in markdown_sources.items()
|
||||
if len(node_ids) > 1
|
||||
}
|
||||
if markdown_conflicts:
|
||||
raise DocForgeError(
|
||||
"source_conflict",
|
||||
"Markdown sources may contain only one node",
|
||||
sources=markdown_conflicts,
|
||||
)
|
||||
anchor_conflicts = [
|
||||
{"source": source, "source_anchor": anchor, "nodes": sorted(node_ids)}
|
||||
for (source, anchor), node_ids in sorted(anchors.items())
|
||||
if len(node_ids) > 1
|
||||
]
|
||||
if anchor_conflicts:
|
||||
raise DocForgeError(
|
||||
"source_anchor_conflict",
|
||||
"TOML source anchors must be unique within their source",
|
||||
conflicts=anchor_conflicts,
|
||||
)
|
||||
|
||||
|
||||
def _revision(root: Path) -> str:
|
||||
try:
|
||||
result = subprocess.run(
|
||||
|
|
@ -588,7 +629,7 @@ class Project:
|
|||
digest.update(relative.encode())
|
||||
digest.update(b"\0")
|
||||
digest.update(hashlib.sha256(captured[path]).digest())
|
||||
digest.update(b"docforge-core:0.5.0:index:1")
|
||||
digest.update(b"docforge-core:0.6.0:index:1")
|
||||
return ProjectSnapshot(
|
||||
descriptor=self.descriptor,
|
||||
nodes=ordered_nodes,
|
||||
|
|
@ -617,3 +658,14 @@ class Project:
|
|||
if not ordered_sources:
|
||||
raise DocForgeError("empty_project", "No canonical Markdown or TOML sources were found")
|
||||
return tuple(ordered_sources)
|
||||
|
||||
def validate_proposal(
|
||||
self,
|
||||
base: ProjectSnapshot,
|
||||
projected: ProjectSnapshot,
|
||||
operations: tuple[Mapping[str, object], ...],
|
||||
) -> None:
|
||||
"""Generic sources require no policy beyond the core proposal validation."""
|
||||
|
||||
del base, operations
|
||||
validate_source_layout(projected.nodes)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue