Skip to main content

A CLI tester and documentation generator

Project description

clitag

A CLI tester and documentation generator.

clitag runs CLI commands as subprocesses, collects their output, and renders the results as Markdown documentation. It is designed to serve two purposes at once:

  1. Testing — exercise your CLI tool's entry-points from pytest and assert on their stdout.
  2. Documentation generation — because the steps are run in a real test, the resulting Markdown is always up-to-date. Write it to a file from your test and commit it alongside your code.

Installation

pip install clitag

If you want subprocess coverage tracking (see below):

pip install clitag[coverage]

For development / running the test suite:

pip install clitag[testing]

Basic usage

from clitag import CliTester

ct = CliTester()
ct.title = "My CLI example"
ct.description = "The following steps show typical usage."

output = ct.run_step(
    cmd="echo",
    args=["hello, world"],
    description="Print a greeting",
)
assert "hello" in output

ct.run_step(
    cmd="my-tool-status",
    description="Check the status",
)

print(ct.format_md())

run_step returns the decoded stdout of the process, so you can assert on it directly alongside capturing the documentation.

Writing the Markdown to a file

A common pattern is to write the Markdown from within a pytest test so it is regenerated on every run:

def test_cli_example(tmp_path):
    ct = CliTester()
    ct.title = "Example usage"
    ct.run_step("my-tool-cmd", description="Run the command")

    with open("docs/example_usage.md", "w") as f:
        f.write(ct.format_md())

Coverage convention

Why subprocess coverage is tricky

pytest-cov / coverage.py only tracks the current Python process. When your CLI entry-points are invoked as subprocesses (e.g. my-tool-cmd), coverage is lost for those code paths.

How clitag solves it

When a CLI_PREFIX is configured, clitag rewrites matching commands to:

coverage run --data-file=... --rcfile=... -m <module> <args>

For example, with CLI_PREFIX = "my_tool_" and CLI_PREFIX_REPLACE = "my_tool.cli_", the command my-tool-cmd becomes:

coverage run --data-file=.coverage --rcfile=.coveragerc -m my_tool.cli_cmd

clitag also sets the COVERAGE_PROCESS_START environment variable so that child processes started by the CLI command also report coverage.

Requirements for coverage to work

  1. Install coverage: pip install clitag[coverage]

  2. A .coveragerc (or [tool.coverage] in pyproject.toml) in your project root with:

    [run]
    parallel = true
    branch = true
    
  3. Run coverage combine after the test suite to merge the parallel data files.

Usage patterns

Constructor — ad-hoc:

ct = CliTester(
    cli_prefix="my_tool_",
    cli_prefix_replace="my_tool.cli_",
)

Subclass — recommended for whole-project reuse:

class MyProjectTester(CliTester):
    CLI_PREFIX = "my_tool_"
    CLI_PREFIX_REPLACE = "my_tool.cli_"

Subclassing is preferred when you have many test files that all test the same project, because the prefix convention is defined once and inherited everywhere.

When CLI_PREFIX is None (the default), no coverage wrapping is applied and commands are executed as-is.


pytest fixture

Installing clitag registers a cli_tester pytest fixture automatically via the pytest11 entry-point — no import or conftest declaration is needed:

def test_something(cli_tester):
    output = cli_tester.run_step("echo", args=["hello"])
    assert "hello" in output

The fixture returns a plain CliTester() with no prefix configured. To use a project-specific subclass, override the fixture in your conftest.py:

# conftest.py
import pytest
from clitag import CliTester

class MyTester(CliTester):
    CLI_PREFIX = "my_tool_"
    CLI_PREFIX_REPLACE = "my_tool.cli_"

@pytest.fixture()
def cli_tester():
    return MyTester()

Cross-platform notes

clitag handles the platform difference internally:

  • Linux / macOS — uses pexpect.spawn, which supports a full PTY and interactive sendline input.
  • Windows — uses pexpect.popen_spawn.PopenSpawn, which does not require a PTY but otherwise behaves the same.

License

MIT — see LICENSE.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

clitag-1.0.0.tar.gz (16.1 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

clitag-1.0.0-py3-none-any.whl (7.1 kB view details)

Uploaded Python 3

File details

Details for the file clitag-1.0.0.tar.gz.

File metadata

  • Download URL: clitag-1.0.0.tar.gz
  • Upload date:
  • Size: 16.1 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for clitag-1.0.0.tar.gz
Algorithm Hash digest
SHA256 0ce42cb852476e297895a34467b24cb4ad6425ced8955f048fb557066a7a74f1
MD5 7696397c9a8ecbb791237a7dbca40c73
BLAKE2b-256 d846b2742a074081adc9ad53dd9ccd0ff208d6a55d77b1ae39e6a51979af05c5

See more details on using hashes here.

Provenance

The following attestation bundles were made for clitag-1.0.0.tar.gz:

Publisher: ci.yml on MrKevinWeiss/cli-tester

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file clitag-1.0.0-py3-none-any.whl.

File metadata

  • Download URL: clitag-1.0.0-py3-none-any.whl
  • Upload date:
  • Size: 7.1 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for clitag-1.0.0-py3-none-any.whl
Algorithm Hash digest
SHA256 54e430c9e7755bb6da7bb2466c0879190c9728585dca0a28259d74d71d1945ab
MD5 93830dbeca239abfea85a9cc241f3ba8
BLAKE2b-256 4ab8cc40a38abd2425e572e48b368736f79d112482f00a0cb305a398a25e6421

See more details on using hashes here.

Provenance

The following attestation bundles were made for clitag-1.0.0-py3-none-any.whl:

Publisher: ci.yml on MrKevinWeiss/cli-tester

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page