Module ds.tasks

Parse and run tasks.

Global variables

var Tasks

Mapping of task names to Task objects.

var ORIGINAL_CWD

Save a reference to the original working directory.

Functions

def check_cycles(tasks: Tasks) ‑> List[str]

Raise a CycleError if there is a cycle in the task graph.

def print_tasks(path: Path, tasks: Tasks) ‑> None

Pretty print task names.

Classes

class CycleError (*args, **kwargs)

Subclass of ValueError raised by TopologicalSorter.prepare if cycles exist in the working graph.

If multiple cycles exist, only one undefined choice among them will be reported and included in the exception. The detected cycle can be accessed via the second element in the args attribute of the exception instance and consists in a list of nodes, such that each node is, in the graph, an immediate predecessor of the next node in the list. In the reported list, the first and the last node will be the same, to make it clear that it is cyclic.

Expand source code
class CycleError(ValueError):
    """Subclass of ValueError raised by TopologicalSorter.prepare if cycles
    exist in the working graph.

    If multiple cycles exist, only one undefined choice among them will be reported
    and included in the exception. The detected cycle can be accessed via the second
    element in the *args* attribute of the exception instance and consists in a list
    of nodes, such that each node is, in the graph, an immediate predecessor of the
    next node in the list. In the reported list, the first and the last node will be
    the same, to make it clear that it is cyclic.
    """

    pass

Ancestors

  • builtins.ValueError
  • builtins.Exception
  • builtins.BaseException
class Task (origin: Optional[Path] = None, origin_key: str = '', name: str = '', help: str = '', verbatim: bool = False, depends: List[Task] = <factory>, cmd: str = '', code: int = 0, args: List[str] = <factory>, cwd: Optional[Path] = None, env: Dict[str, str] = <factory>, env_file: Optional[Path] = None, keep_going: bool = False)

Represents a thing to be done.

Expand source code
@dataclass
class Task:
    """Represents a thing to be done."""

    origin: Optional[Path] = None
    """File from which this configuration came."""

    origin_key: str = ""
    """Key from which this task came."""

    name: str = ""
    """Task name."""

    help: str = ""
    """Task description."""

    verbatim: bool = False
    """Whether to format the command at all."""

    depends: List[Task] = field(default_factory=list)
    """Tasks to execute before this one."""

    cmd: str = ""
    """Shell command to execute after `depends`."""

    code: int = 0
    """Return code from running this task."""

    # NOTE: args, cwd, env, keep_going are overridable
    # via the CLI or when calling a composite command.

    args: List[str] = field(default_factory=list)
    """Additional arguments to `cmd`."""

    cwd: Optional[Path] = None
    """Task working directory."""

    env: Dict[str, str] = field(default_factory=dict)
    """Task environment variables."""

    _env: Dict[str, str] = field(default_factory=dict)
    """Hidden environment variables."""

    env_file: Optional[Path] = None
    """Path to an environment file to load."""

    keep_going: bool = False
    """Ignore a non-zero return code."""

    def pprint(self, override: Optional[Task] = None, dry_run: bool = False) -> None:
        """Print a representation of this task."""
        is_run = override or dry_run
        display = self
        if override:
            display = replace(self, cmd=override.cmd, keep_going=override.keep_going)

        print()

        if dry_run:
            print("[DRY RUN]")
        if display.help:
            print("#", display.help)
        print(">", wrap_cmd(self.as_args(override)))

        if not is_run and display.depends:
            print(
                [
                    f"{TASK_KEEP_GOING if t.keep_going else ''}{t.cmd}"
                    for t in display.depends
                ]
            )

        if display.cmd:
            if display.verbatim:
                print("$", display.cmd.strip().replace("\n", "\n$ "))
            else:
                print(f"$ {wrap_cmd(display.cmd)}")

    def as_args(self, override: Optional[Task] = None) -> str:
        """Return a shell representation of running this task."""
        override = override or Task()

        args = ["ds"]
        if self.cwd:
            args.extend(["--cwd", str(self.cwd)])
        if self.env_file:
            args.extend(["--env-file", str(self.env_file)])
        for key, val in (self.env or {}).items():
            args.extend(["-e", f"{key}={val}"])

        prefix = ""
        if self.keep_going or override.keep_going:
            prefix = TASK_KEEP_GOING
        if self.name == TASK_COMPOSITE:
            args.append(f"{prefix}{self.cmd}")
        elif self.name:
            args.append(f"{prefix}{self.name}")
        return join(args)

Class variables

var depends : List[Task]

Tasks to execute before this one.

var args : List[str]

Additional arguments to cmd.

var env : Dict[str, str]

Task environment variables.

var origin : Optional[pathlib.Path]

File from which this configuration came.

var origin_key : str

Key from which this task came.

var name : str

Task name.

var help : str

Task description.

var verbatim : bool

Whether to format the command at all.

var cmd : str

Shell command to execute after depends.

var code : int

Return code from running this task.

var cwd : Optional[pathlib.Path]

Task working directory.

var env_file : Optional[pathlib.Path]

Path to an environment file to load.

var keep_going : bool

Ignore a non-zero return code.

Methods

def pprint(self, override: Optional[Task] = None, dry_run: bool = False) ‑> None

Print a representation of this task.

def as_args(self, override: Optional[Task] = None) ‑> str

Return a shell representation of running this task.