Files
devops-skills/engine/devops_agent/spec.py

61 lines
1.7 KiB
Python

from __future__ import annotations
from dataclasses import dataclass
from pathlib import Path
from typing import Any
import yaml
class WorkflowSpecError(ValueError):
"""Raised when a workflow spec cannot be parsed or is incomplete."""
@dataclass(slots=True)
class WorkflowSpec:
name: str
provider: str
frontmatter: dict[str, Any]
body: str
source_path: Path
def _split_frontmatter(raw_text: str) -> tuple[str, str]:
if not raw_text.startswith("---"):
raise WorkflowSpecError("workflow spec must start with frontmatter")
parts = raw_text.split("\n---", 1)
if len(parts) != 2:
raise WorkflowSpecError("workflow spec frontmatter is not terminated")
frontmatter_text = parts[0][4:]
body = parts[1].lstrip("\r\n")
return frontmatter_text, body
def load_workflow_spec(path: str | Path) -> WorkflowSpec:
source_path = Path(path)
raw_text = source_path.read_text(encoding="utf-8")
frontmatter_text, body = _split_frontmatter(raw_text)
payload = yaml.safe_load(frontmatter_text) or {}
if not isinstance(payload, dict):
raise WorkflowSpecError("workflow spec frontmatter must be a mapping")
if True in payload and "on" not in payload:
payload["on"] = payload.pop(True)
name = str(payload.get("name") or "").strip()
provider = str(payload.get("provider") or "").strip()
if not name:
raise WorkflowSpecError("workflow spec is missing required field: name")
if not provider:
raise WorkflowSpecError("workflow spec is missing required field: provider")
return WorkflowSpec(
name=name,
provider=provider,
frontmatter=payload,
body=body,
source_path=source_path,
)