Source code for conda_workspaces.parsers.base
"""Abstract base class for manifest parsers (workspaces and tasks)."""
from __future__ import annotations
from abc import ABC, abstractmethod
from typing import TYPE_CHECKING
import tomlkit
if TYPE_CHECKING:
from pathlib import Path
from typing import ClassVar
from tomlkit.container import Container
from tomlkit.items import InlineTable
from ..models import Task, WorkspaceConfig
[docs]
class ManifestParser(ABC):
"""Interface that every manifest parser must implement.
Each parser handles one file format (``conda.toml``, ``pixi.toml``,
or ``pyproject.toml``). Subclasses declare which files they can
handle via *filenames* and *extensions*. The registry in
``parsers/__init__.py`` uses these to auto-detect the right parser.
A single parser instance handles both workspace configuration and
task definitions from the same file.
"""
filenames: ClassVar[tuple[str, ...]] = ()
[docs]
@abstractmethod
def can_handle(self, path: Path) -> bool:
"""Return True if this parser can read *path*."""
[docs]
@abstractmethod
def has_workspace(self, path: Path) -> bool:
"""Return True if *path* contains workspace configuration."""
[docs]
@abstractmethod
def parse(self, path: Path) -> WorkspaceConfig:
"""Parse *path* and return a ``WorkspaceConfig``."""
[docs]
def has_tasks(self, path: Path) -> bool:
"""Return True if *path* contains task definitions."""
return False
[docs]
def parse_tasks(self, path: Path) -> dict[str, Task]:
"""Parse *path* and return a mapping of task-name to Task."""
return {}
[docs]
def add_task(self, path: Path, name: str, task: Task) -> None:
"""Persist a new task definition into *path*."""
raise NotImplementedError(
f"{type(self).__name__} does not support writing tasks to {path.name}."
)
[docs]
def remove_task(self, path: Path, name: str) -> None:
"""Remove the task named *name* from *path*."""
raise NotImplementedError(
f"{type(self).__name__} does not support writing tasks to {path.name}."
)
[docs]
def task_to_toml_inline(self, task: Task) -> str | InlineTable:
"""Convert a *task* to a TOML-serializable value (string or inline table)."""
table = tomlkit.inline_table()
if task.cmd is not None:
table.append("cmd", task.cmd)
if task.depends_on:
table.append("depends-on", [d.to_toml() for d in task.depends_on])
if task.description:
table.append("description", task.description)
if task.env:
table.append("env", dict(task.env))
if task.cwd:
table.append("cwd", task.cwd)
if task.clean_env:
table.append("clean-env", True)
if task.default_environment:
table.append("default-environment", task.default_environment)
if task.args:
table.append("args", [a.to_toml() for a in task.args])
if task.inputs:
table.append("inputs", list(task.inputs))
if task.outputs:
table.append("outputs", list(task.outputs))
if len(table) == 1 and "cmd" in table:
return str(table["cmd"])
return table
[docs]
def remove_target_overrides(self, container: Container, name: str) -> None:
"""Remove *name* from every ``[target.<platform>.tasks]`` under *container*."""
target = container.get("target")
if not target:
return
for _platform, tdata in target.items():
if tdata is None:
continue
tt = tdata.get("tasks")
if tt is not None and name in tt:
del tt[name]