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:
- Testing — exercise your CLI tool's entry-points from pytest and assert on their stdout.
- 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
-
Install
coverage:pip install clitag[coverage] -
A
.coveragerc(or[tool.coverage]inpyproject.toml) in your project root with:[run] parallel = true branch = true
-
Run
coverage combineafter 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 interactivesendlineinput. - Windows — uses
pexpect.popen_spawn.PopenSpawn, which does not require a PTY but otherwise behaves the same.
License
MIT — see LICENSE.
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
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
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
0ce42cb852476e297895a34467b24cb4ad6425ced8955f048fb557066a7a74f1
|
|
| MD5 |
7696397c9a8ecbb791237a7dbca40c73
|
|
| BLAKE2b-256 |
d846b2742a074081adc9ad53dd9ccd0ff208d6a55d77b1ae39e6a51979af05c5
|
Provenance
The following attestation bundles were made for clitag-1.0.0.tar.gz:
Publisher:
ci.yml on MrKevinWeiss/cli-tester
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
clitag-1.0.0.tar.gz -
Subject digest:
0ce42cb852476e297895a34467b24cb4ad6425ced8955f048fb557066a7a74f1 - Sigstore transparency entry: 1731263892
- Sigstore integration time:
-
Permalink:
MrKevinWeiss/cli-tester@72e5ea849172bef2102a706f2bf2c3fe0eaec7fe -
Branch / Tag:
refs/tags/v1.0.0 - Owner: https://github.com/MrKevinWeiss
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
ci.yml@72e5ea849172bef2102a706f2bf2c3fe0eaec7fe -
Trigger Event:
push
-
Statement type:
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
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
54e430c9e7755bb6da7bb2466c0879190c9728585dca0a28259d74d71d1945ab
|
|
| MD5 |
93830dbeca239abfea85a9cc241f3ba8
|
|
| BLAKE2b-256 |
4ab8cc40a38abd2425e572e48b368736f79d112482f00a0cb305a398a25e6421
|
Provenance
The following attestation bundles were made for clitag-1.0.0-py3-none-any.whl:
Publisher:
ci.yml on MrKevinWeiss/cli-tester
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
clitag-1.0.0-py3-none-any.whl -
Subject digest:
54e430c9e7755bb6da7bb2466c0879190c9728585dca0a28259d74d71d1945ab - Sigstore transparency entry: 1731263932
- Sigstore integration time:
-
Permalink:
MrKevinWeiss/cli-tester@72e5ea849172bef2102a706f2bf2c3fe0eaec7fe -
Branch / Tag:
refs/tags/v1.0.0 - Owner: https://github.com/MrKevinWeiss
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
ci.yml@72e5ea849172bef2102a706f2bf2c3fe0eaec7fe -
Trigger Event:
push
-
Statement type: