Your tasks, documented¶
footman ships a first-party plugin that renders a project's task tree as
markdown — the same names, params, docstring help, defaults, and examples
that fm --help shows, as pages you can publish. Everything on this page is
dogfooded: the task reference in this site's nav is
generated output, and the sample further down is embedded live.
Write plain prose¶
footman surfaces a docstring — and a doc()
string — as plain text: verbatim in fm --help, so it reads in any terminal
and survives a pipe (fm --help build | less) byte for byte, and as plain
paragraphs in the exported markdown. It renders no rich markup in the terminal:
no bold, no headings, no reflowed tables. That is deliberate — footman is
zero-dependency, and plain text is the one format every
terminal, pager, and CI log agrees on.
So write your help as prose. A sentence or two that reads straight is worth more than markup footman won't paint, and it exports cleanly either way. The one styling footman does apply is colour — to its own chrome (names, receipts, the progress line) and, through the tools it spawns, to their output — never injected into your text. Your task's own stdout is yours; footman routes it untouched.
(A future opt-in could render markdown in the terminal too — see the roadmap — but it stays off by default and never becomes a dependency.)
Mount it¶
The plugin mounts like any other — two lines of config, no tasks-file edit:
That's also the two-line demo of the plugin system: a plugin's
name is its command path, so footman.docs mounts under footman, and after
it fm --list shows footman docs page and footman docs site. (Cherry-pick
or remount with include(plugin("footman.docs"), …) if you'd rather not take
the whole group. The maintainer-facing stub toolkit is a separate plugin,
footman.tools.)
One page: fm footman docs page¶
fm footman docs page > TASKS.md # the whole tree, one document
fm footman docs page --target docs # just one group…
fm footman docs page --target docs.build # …or one task
fm footman docs page --out TASKS.md # write the file directly
The page goes to stdout (stdout is the answer; footman's summary is stderr commentary), so it pipes:
--heading 2 (up to 6) makes the headings start deeper, so the output nests
under a host page's own title — which is exactly how the sample below is
embedded, via a pymdownx.snippets
include of a file the docs build regenerates:
### docs { #docs }
Documentation site (Zensical)
#### docs serve { #docs-serve }
Build and serve the docs with live reload.
```text
fm docs serve
docs coverage¶
Generate the coverage HTML report into docs/htmlcov (embedded in the site).
docs build¶
Build the docs site into ./site; regenerates llms.txt and docs/tasks/.
| Parameter | Type | Default | Description |
|---|---|---|---|
--check |
flag | false |
build strictly (what CI runs) |
`--flavor plain` (the default) is pure CommonMark and pipe tables — safe for
pandoc and any renderer. `--flavor material` opts into what a
zensical/mkdocs-material site already understands: heading anchors for
stable deep links and an `!!! example` admonition for the synthesized
invocation.
## A linked site: `fm footman docs site`
```console
$ fm footman docs site docs/tasks
wrote 19 pages under docs/tasks
One file per task, an index.md per group with relative links, directories
mirroring your group tree — drop it into your docs source and put the index
in your nav. This site's Task reference section is exactly that, wired
into zensical.toml's
nav. site defaults to --flavor material because a docs site is where it
lands; pass --flavor plain for anything else.
The runner itself: fm footman docs globals¶
Your tasks aren't the only thing worth documenting — the runner's global
options deserve a page too. globals renders them as a markdown table
straight from the CLI grammar: the same rows, in the same order, with the
same words --help prints. This site's CLI reference table
is exactly that, regenerated on every docs build — it cannot drift,
because it was never written by hand.
Terminal screenshots: fm footman docs shots¶
Prose about colours drifts the moment the palette changes; a screenshot
generated from the CLI cannot. shots runs a command on a real
pseudo-terminal — colours, receipts, taught errors, exactly as a terminal
shows them — collapses the live rewrites to their final frame, and renders
the capture as an SVG in a macOS-style window:
$ fm footman docs shots --out docs/_generated/shots/run.svg -- format lint
wrote docs/_generated/shots/run.svg
Everything after -- is the command line to capture; --width sets the
terminal columns, --title the window title, and --cmd swaps the
executable (default: the CLI that invoked it, so a branded CLI screenshots
itself). The command really executes — don't screenshot tasks whose side
effects you don't want. Every terminal image on this site is one of these,
regenerated by the docs build.
This task needs rich and a POSIX
pseudo-terminal. Neither is a footman dependency: the task is gated with
footman's own @requires_dep("rich"), so without rich it simply lists as
shots … (unavailable: requires rich) and refuses to run with that
message — add rich to your docs dependency group and it comes alive.
footman documenting itself with its own availability machinery is exactly
the use @requires_dep was built for.
Animated sessions: fm footman docs cast¶
A static frame can't show Tab completion. cast boots a real
interactive shell (zsh, bash, fish, pwsh, or nushell) from a scratch config
with footman's hook loaded, types a keystroke script, and replays the
captured bytes through a terminal emulator into an animated SVG — CSS
keyframes with the session's own timing, no JavaScript, plays anywhere an
image does. The session even answers its shell's terminal interrogations
(capability, cursor, and colour queries) the way a plain xterm would —
modern shells refuse to paint a prompt into silence:
$ fm footman docs cast --out docs/_generated/shots/zsh-cast.svg \
--shell zsh -- "fm " "<TAB>" "<WAIT>" "che" "<TAB>" "<ENTER>" "<WAIT:2500>"
wrote docs/_generated/shots/zsh-cast.svg (55 frames)
Everything after -- is the script: plain text is typed at a human-ish
cadence; <TAB>, <ENTER>, <SPACE>, <BACKSPACE>, <CTRL-C>,
<WAIT>, and <WAIT:ms> are keys. The shell really runs what you type —
the recording on the zsh completion page ends with a
real fm check. Needs rich and pyte
(the terminal emulator), gated the same way: without them the task lists
as unavailable and says which package to add.
Keep it fresh¶
Generated pages drift unless a build regenerates them. The tasks are plain functions, so footman's own docs task calls them directly — copy the shape:
@docs.task(name="build")
def docs_build(check: bool = False):
"Build the docs site; regenerates the task reference first."
from footman.tasks.docs import globals_, page, site
from footman.tools import zensical
site(Path("docs/tasks"))
page(target="docs", heading=3, out=Path("docs/_generated/tasks-page.md"))
globals_(out=Path("docs/_generated/globals.md"))
zensical.build(clean=True, strict=check)
Add the generated paths to .gitignore — they're build output, not source.
Under --json, both tasks return the list of files they wrote,
so returned carries it for CI to verify.
Two flags to know: usage lines and examples carry the CLI you invoked —
a branded CLI documents itself as acme with no flag at
all, and --prog overrides the name when you need to. --all includes the
mounted footman group itself (excluded by default — the documenter
doesn't document itself unless asked).
The live sample¶
Everything below this line is fm footman docs page --target docs
--heading 3 --flavor material, regenerated on every docs build:
docs¶
Documentation site (Zensical)
docs serve¶
Build and serve the docs with live reload.
docs coverage¶
Generate the coverage HTML report into docs/htmlcov (embedded in the site).
docs build¶
Build the docs site into ./site; regenerates llms.txt and docs/tasks/.
| Parameter | Type | Default | Description |
|---|---|---|---|
--check |
flag | false |
build strictly (what CI runs) |