Source code for conda_tasks.parsers

"""Task file parser registry and auto-detection."""

from __future__ import annotations

from functools import lru_cache
from pathlib import Path
from typing import TYPE_CHECKING

from ..exceptions import NoTaskFileError, TaskParseError
from .base import TaskFileParser
from .condarc import CondaRCParser
from .pixi_toml import PixiTomlParser
from .pyproject_toml import PyprojectTomlParser
from .toml import CondaTomlParser

if TYPE_CHECKING:
    from ..models import Task

__all__ = ["TaskFileParser", "detect_and_parse", "detect_task_file", "get_parser"]

_SEARCH_ORDER: tuple[str, ...] = (
    "pixi.toml",
    "conda.toml",
    "pyproject.toml",
    ".condarc",
)


def _parser_registry() -> list[TaskFileParser]:
    """Return all registered parser instances in detection priority order."""
    return [
        PixiTomlParser(),
        CondaTomlParser(),
        PyprojectTomlParser(),
        CondaRCParser(),
    ]


[docs] def get_parser(path: Path) -> TaskFileParser | None: """Return the first parser that can handle *path*, or ``None``.""" for parser in _parser_registry(): if parser.can_handle(path): return parser return None
[docs] def detect_task_file(start_dir: Path | None = None) -> Path | None: """Walk up from *start_dir* looking for a known task file. Returns the first match according to ``_SEARCH_ORDER``, or ``None``. """ if start_dir is None: start_dir = Path.cwd() start_dir = start_dir.resolve() current = start_dir while True: for name in _SEARCH_ORDER: candidate = current / name if candidate.is_file(): parser = get_parser(candidate) if parser is not None: return candidate parent = current.parent if parent == current: break current = parent return None
@lru_cache(maxsize=4) def _cached_parse(path: str) -> dict[str, Task]: """Parse a task file (cached by path string for memoisation).""" p = Path(path) parser = get_parser(p) if parser is None: raise TaskParseError(str(p), "no parser found for this file format") return parser.parse(p)
[docs] def detect_and_parse( file_path: Path | None = None, start_dir: Path | None = None, ) -> tuple[Path, dict[str, Task]]: """Detect (or use *file_path*) a task file and parse it. Returns ``(resolved_path, {task_name: Task})``. Raises ``NoTaskFileError`` when no file is found. """ if file_path is not None: path = file_path.resolve() else: path = detect_task_file(start_dir) if path is None: raise NoTaskFileError(str(start_dir or Path.cwd())) tasks = _cached_parse(str(path)) return path, tasks