Guard your docs¶
By the end of this you will have a test that fails when your documentation names a command your tool cannot run. It takes about ten minutes, and the finished guard is roughly twenty lines.
You will need a Go CLI with subcommands, and some documentation containing fenced examples of it.
1. Add the module¶
2. Hand it your command tree¶
docscheck does not import a CLI framework, so it cannot walk your commands for
you. You give it a docscheck.Command tree instead. From Cobra, that is a
short recursive function:
func tree(c *cobra.Command) docscheck.Command {
n := docscheck.Command{Name: c.Name()}
for _, sub := range c.Commands() {
n.Sub = append(n.Sub, tree(sub))
}
return n
}
The root's name is your tool's name as a reader types it — krites, not
main. Taking the tree from the running program is the point: a list of
command names maintained by hand is the same drift problem one level down.
3. Write the test¶
func TestDocumentedCommandsExist(t *testing.T) {
root, _ := root.NewCmdRoot(version.Info{})
c, err := docscheck.New(tree(root.Command))
if err != nil {
t.Fatal(err)
}
rep, err := c.Walk(os.DirFS(".."), "docs", "README.md")
if err != nil {
t.Fatal(err)
}
if !rep.OK() {
t.Fatalf("documentation names commands that cannot run:\n%s", rep)
}
}
Walk takes fs.Glob patterns. A pattern naming a directory contributes every
.md file beneath it, so "docs" covers the tree.
4. Watch it fail¶
A guard you have never seen fail is a guard you have no reason to trust. Break
a line in your docs on purpose — take a working tool group sub <arg> example
and delete the sub:
You should get the file, the line, the command as resolved, and what it accepts:
docs/tutorials/cull-a-shoot.md:103: `krites reset IMG_2043.CR2` — "IMG_2043.CR2" is not a subcommand of `krites reset` (have: exports, frame, shoot)
57 file(s), 97 documented invocation(s), 1 problem(s)
Put the line back and it passes. Note the invocation count: it is there so a run that scans your files but resolves nothing — usually a wrong tool name — does not look like a clean bill of health.
What you did¶
You turned a class of documentation bug into a test failure. From here:
- Run it in CI, which has one trap worth knowing about.
- Why only fenced blocks, if you want to know why the rule is as narrow as it is.