repomatic.tooling.runner module¶

Unified tool runner with managed config resolution.

Provides repomatic run <tool>: a single entry point that installs an external tool at a pinned version, resolves its configuration through a strict 4-level precedence chain, translates [tool.X] sections from pyproject.toml into the tool’s native format, and invokes the tool with the resolved config. The tool catalog it drives (ToolSpec entries, pinned versions, checksums) lives in tooling/registry.py, and tooling/binaries.py installs the binary tools.

Important

Config resolution precedence (first match wins, no merging):

  1. Native config file: the tool’s own config file in the repo.

  2. ``[tool.X]`` in ``pyproject.toml``: translated to the native format.

  3. Bundled default: from repomatic/data/.

  4. Bare invocation: no config at all.

repomatic.tooling.runner.GIT_DIRECTORY = '.git'¶

Directory _digest_targets leaves out of the tree it walks.

No tool rewrites a file there, and another process can: an editor that refreshes the index during a run would read as a file the tool rewrote.

repomatic.tooling.runner.LOCK_POLL_SECONDS = 0.1¶

Delay between two attempts to take a lock Windows reported as held.

Windows has no blocking lock call that waits indefinitely: msvcrt.LK_LOCK gives up after ten one-second attempts, so _acquire_lock polls instead.

repomatic.tooling.runner.NPM_INSTALL_FLAGS = ('--ignore-scripts', '--no-progress', '--no-update-notifier', '--no-fund', '--no-audit')¶

Flags of every npm install repomatic runs.

--ignore-scripts skips the preinstall, install and postinstall scripts of the package and of its whole transitive tree: the install-time vector behind most npm supply-chain worms. It is safe here because the npm backend only installs a repomatic-pinned, cooldown-gated tool, where no install script is needed. --no-audit stops npm from sending the resolved dependency tree to the audit endpoint of the registry on every install.

The install of the pinned npm that installs the tool carries them too: see _install_npm().

repomatic.tooling.runner.TOOL_CRASH_EXIT_CODE = 70¶

Exit code reported when a tool contradicts its own rewrite status.

EX_SOFTWARE from sysexits.h: an internal error in the tool being run. Deliberately distinct from the code a formatter uses to say “I reformatted a file”, so a crash cannot pass for a rewrite. See rewrite_exit_code.

class repomatic.tooling.runner.ConfigLevel(*values)[source]¶

Bases: IntEnum

Precedence level of a tool’s configuration: the first one present wins.

NATIVE = 1¶

The tool’s own config file in the repository.

PYPROJECT = 2¶

The tool’s [tool.X] table in pyproject.toml.

BUNDLED = 3¶

The default repomatic bundles in repomatic/data/.

BARE = 4¶

No configuration: the tool runs on its own defaults.

class repomatic.tooling.runner.ConfigSource(spec, level, native_file=None, table=None)[source]¶

Bases: object

The configuration a tool resolves to, as the precedence walk finds it.

Caution

The levels do not merge. The walk stops at its first hit, so a native config file or a [tool.X] section replaces the bundled default in full rather than layering on top of it. A downstream repo overriding one rule must restate every bundled rule it wants to keep, and gains nothing when the bundled default later grows a rule. label names such a shadowing config, so repomatic run --list shows the loss.

spec: ToolSpec¶

The tool the configuration is for.

level: ConfigLevel¶

The level that wins.

native_file: Path | None = None¶

The config file of the repository, at ConfigLevel.NATIVE.

table: dict[str, Any] | None = None¶

The [tool.X] table, at ConfigLevel.PYPROJECT.

The live table of the parsed pyproject.toml, a tomlrt.Table. It keeps its comments for the formats that can carry them (see serialize()). The parse is shared, so the table is read-only.

classmethod find(spec)[source]¶

Walk the precedence chain of spec in the working directory.

Return type:

ConfigSource

property label: str¶

Describe the source, as repomatic run --list shows it.

repomatic.tooling.runner.resolve_config(spec)[source]¶

Resolve config for a tool using the 4-level precedence chain.

See ConfigSource for the walk, and why its levels do not merge.

Parameters:

spec (ToolSpec) – Tool specification.

Return type:

tuple[list[str], Path | None]

Returns:

Tuple of (extra CLI args for config, path to clean up). The path is None when no cleanup is needed (cache-based configs persist across runs). Non-None paths are CWD files written for tools that have no --config flag.

repomatic.tooling.runner.resolve_default_args(spec)[source]¶

Build the argument batches for a bare repomatic run <tool>.

Combines default_args with the file list named by default_paths, splitting into one batch per file when per_file is set.

Parameters:

spec (ToolSpec) – The tool to resolve defaults for.

Return type:

list[list[str]] | None

Returns:

One argument list per invocation; a single empty-argument batch when the tool declares no defaults, so the caller runs it bare as before. None when the tool wants targets and the repository holds none, which means skip the tool rather than invoke it pathless.

repomatic.tooling.runner.run_tool(name, extra_args=(), version=None, checksum=None, skip_checksum=False, no_cache=False)[source]¶

Run an external tool with managed config resolution.

With no extra_args, a tool declaring default_args or default_paths runs the invocation CI performs, resolved in-process by resolve_default_args(). Any explicit argument suppresses that entirely and is passed through as before.

Parameters:
  • name (str) – Tool name (must be in TOOL_REGISTRY).

  • extra_args (Sequence[str]) – Extra arguments passed through to the tool.

  • version (str | None) – Override the pinned version.

  • checksum (str | None) – Override the SHA-256 checksum for the current platform.

  • skip_checksum (bool) – Skip SHA-256 verification entirely.

  • no_cache (bool) – Bypass the binary cache when True.

Return type:

int

Returns:

The tool’s exit code; the first non-zero one when the defaults resolved to several invocations. A tool declaring rewrite_exit_code reports 0 for a rewrite it performed, and TOOL_CRASH_EXIT_CODE for one it did not.

Raises:
  • ValueError – If no tool goes by name.

  • ToolInstallError – If the tool or one of its companions cannot be installed.

repomatic.tooling.runner.verify_via_write_path(name, extra_args=(), **run_kwargs)[source]¶

Check a post_process tool’s formatting without touching the tree.

A tool pairing post_process with check_flags has no trustworthy check mode: the fixup only runs on the write path, so the check status can flag drift the write path would reconcile, or miss drift it would introduce (see check_flags). This runs the write path against throwaway copies instead, then compares, which is the only authoritative answer.

Important

The copies are made inside the working directory, not in the system temp area. Formatters discover their config by walking up from each file, so a copy parked outside the repository resolves a different config and silently reports drift that does not exist.

The working tree is never written to: only the copies are formatted, and they are removed before returning.

Parameters:
  • name (str) – Tool name, as in run_tool().

  • extra_args (Sequence[str]) – Arguments for the tool. Any existing path among them is copied and rewritten to its copy; check flags are dropped, since they would defeat the write path this relies on. Every other argument is passed through untouched. Empty resolves the tool’s registry defaults, the same set run_tool() would have run, flattened into one batch: the copies are per-path already, so a per_file split would only cost extra invocations.

  • run_kwargs (Any) – Forwarded verbatim to run_tool().

Return type:

tuple[int, list[str]]

Returns:

(exit_code, drifted), where exit_code is 0 when every target is already formatted and 1 otherwise, and drifted names the paths the write path would have changed. A tool that fails on the copies yields its own exit code and no drift, since it measured nothing. So does a call with no existing path to copy, which yields 2, and a path that cannot be copied, which is logged and yields 2 too.

Raises:

ValueError – If no tool goes by name.

repomatic.tooling.runner.find_unmodified_configs(root=None)[source]¶

Find native config files identical to their bundled defaults.

Iterates over every tool in TOOL_REGISTRY that has a default_config. For each, checks whether any of its native_config_files exists on disk and is content-identical to the bundled default after trailing-whitespace normalization.

The normalization (rstrip() + "\n") matches the convention used by _init_config_files when writing files during init.

Parameters:

root (Path | None) – Directory the relative config paths resolve against. Defaults to the working directory; run_init passes its output_dir so the scan and the deletion the CLI derives from it (--delete-unmodified) agree on one tree.

Return type:

list[tuple[str, str]]

Returns:

List of (tool_name, relative_path) tuples for each unmodified file found.