Skip to content

Guide 05 of 9

Catalogues and builders

Name the programs an application may run, and wrap argument construction in small, validated builders.

Plate 14 A catalogue sheet of standard fittings, each drawn once and ready to order, as a catalogue declares the programs a builder may assemble.

Define an application catalogue

A catalogue names the programs an application may build commands for. Give them a project, a ProjectSettings, and every command carries the project's name, documentation locations, and noise rules. ProgramCatalogue.from_project() builds a catalogue from one project; ProgramCatalogue(projects=(...)) holds several, each owning its programs. Cuprum stores noise rules for log processing downstream; it does not filter output itself.

application_catalogue.py Tested
import sys

from cuprum import Program, ProgramCatalogue, ProjectSettings, sh
from cuprum.catalogue import DuplicateProgramError

PYTHON = Program(sys.executable)
settings = ProjectSettings(
    name="report-builder",
    programs=(PYTHON,),
    documentation_locations=("docs/reports.md",),
    noise_rules=(r"^progress:",),
)
catalogue = ProgramCatalogue.from_project(settings)

command = sh.make(PYTHON, catalogue=catalogue)("-c", "print('ok')")
assert command.project.name == "report-builder"
assert command.project.noise_rules == (r"^progress:",)
assert catalogue.lookup(PYTHON).project.documentation_locations == ("docs/reports.md",)

try:
    ProgramCatalogue.from_programs(PYTHON, PYTHON)
except DuplicateProgramError as exc:
    assert exc.program == PYTHON
else:
    raise AssertionError("a program listed twice should be rejected")
Run 2026-10-19 against Cuprum 0.2.0b2 · Python 3.12 · Linux

A catalogue that lists the same program twice raises DuplicateProgramError, and two projects with one name raise DuplicateProjectError; both are ValueError subclasses from cuprum.catalogue. An absolute path is allowlisted exactly as written: /usr/bin/git does not also permit git found on PATH. catalogue.visible_settings maps each project name to its settings for services that pass this metadata on.

Wrap commands in project builders

Put argument construction in small functions that validate their input and return a SafeCmd. Callers then cannot build an unvalidated command line. cuprum.builders provides ready-made builders for Git, rsync, and tar, and the safe_path() and git_ref() validators they use. They use the default catalogue, so building their commands does not need the tools installed; running them does.

project_builders.py Tested
import sys
from pathlib import Path

from cuprum import Program, ProgramCatalogue, SafeCmd, sh
from cuprum.builders import git_rev_parse, safe_path

PYTHON = Program(sys.executable)
CATALOGUE = ProgramCatalogue.from_programs(PYTHON, name="line-counter")


def count_lines(path: Path) -> SafeCmd:
    """Build a command that counts the lines in one existing file."""
    checked = safe_path(path.resolve())
    script = "import sys; print(sum(1 for _ in open(sys.argv[1])))"
    return sh.make(PYTHON, catalogue=CATALOGUE)("-c", script, str(checked))


result = count_lines(Path("README.md")).run_sync()
assert result.ok and int(result.stdout) > 0

command = git_rev_parse("main")
assert command.argv_with_program == ("git", "rev-parse", "main")
try:
    git_rev_parse("main..evil")
except ValueError:
    pass
else:
    raise AssertionError("an unsafe ref should be rejected")
Run 2026-10-19 against Cuprum 0.2.0b2 · Python 3.12 · Linux

safe_path() rejects empty paths, NUL characters, and .. segments, and requires an absolute path unless allow_relative=True. git_ref() rejects whitespace, a leading -, .., @{, and other constructs Git would read as options or revision syntax.