Skip to content

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

go get gitlab.com/phpboyscout/go/docscheck

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:

go test ./... -run TestDocumentedCommandsExist

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: