kmdo#

Maintaining documentation can be prone to error and cumbersome. Especially for demos, tutorials, and usage guides for command-line tools. To aid that, kmdo runs through a directory and executes command-files, storing stdout and stderr in corresponding output-files.

This documentation is an example of how this can be used in concert with Sphinx and Read the Docs.

Installation#

Install kmdo system-wide via the pip using pipx:

pipx install kmdo

Note

Ensure that command-line tools installed using pipx are added to PATH by running pipx ensurepath.

Usage#

usage: kmdo [-h] [-r] [-s SHELL] [-x EXCLUDE] [-l LABEL] [-L LABEL]
            [-f {yaml,jsonl}] [-n] [-t TIMEOUT]
            path

Run commands from .cmd files, storing output in .out files

positional arguments:
  path                  Path to DIR containing .cmd files

options:
  -h, --help            show this help message and exit
  -r, --recursive       go deepah!
  -s, --shell SHELL     Absolute path to the Shell to use
  -x, --exclude EXCLUDE
                        Exclude command-files matching this
  -l, --label LABEL     Only run command-files carrying LABEL; repeatable,
                        matches any
  -L, --exclude-label LABEL
                        Skip command-files carrying LABEL; repeatable
  -f, --output-format {yaml,jsonl}
                        Output format (default: yaml)
  -n, --dry-run         List commands without executing them
  -t, --timeout TIMEOUT
                        Timeout in seconds for each command

Error-handling#

kmdo has exit code 0 upon success, that is when all commands succeed, ignoring command errors from command-files with .uone in the file name. On error, kmdo has a non zero exit code.

Additionally, kmdo outputs a YAML representation of what it has executed to stdout. For example, when using kmdo to generate command output for the documentation you are reading now.

kmdo src/examples

Outputs the following YAML:

args:
  path: '/home/odus/git/kmdo/docs/src/examples'
  recursive: false
results:
- out_fp: '/home/odus/git/kmdo/docs/src/examples/kmdo.out'
  cmd_fp: '/home/odus/git/kmdo/docs/src/examples/kmdo.cmd'
  cmd: 'kmdo --help'
  rcode: 0
  uone: false
  err: false
- out_fp: '/home/odus/git/kmdo/docs/src/examples/kmdo.uone.out'
  cmd_fp: '/home/odus/git/kmdo/docs/src/examples/kmdo.uone.cmd'
  cmd: 'kmdo'
  rcode: 2
  uone: true
  err: false
nerrs: 0

Empty command-file and update-on-error#

When the command-file is empty, then the fname part of the command-file file name is treated as the command to execute.

For example, the empty file named kmdo.uone.cmd, will execute the command kmdo, and because of .uone in the file name then it create the output file kmdo.uone.out:

usage: kmdo [-h] [-r] [-s SHELL] [-x EXCLUDE] [-l LABEL] [-L LABEL]
            [-f {yaml,jsonl}] [-n] [-t TIMEOUT]
            path
kmdo: error: the following arguments are required: path

Labels#

A documentation tree tends to accumulate command-files that should not all run every time. Some are slow, some need hardware that is not always present, some only make sense on one platform. Labels let you partition them.

A label is a dot-separated segment sitting between the fname and the .cmd extension. The file xnvme_io.slow.cmd carries the label slow, and xnvme_io.slow.linux.cmd carries both slow and linux. Labels are yours to invent; kmdo gives meaning to uone only.

Without --label every command-file runs, as before. Passing --label runs only the command-files carrying it:

kmdo docs/                    # runs everything
kmdo --label slow docs/       # runs only the 'slow' command-files
kmdo -l slow -l linux docs/   # runs those carrying 'slow' or 'linux'

Repeating --label matches any of them rather than all of them. To go the other way, --exclude-label drops command-files carrying a label, and is applied after --label:

kmdo --exclude-label slow docs/           # everything except the slow ones
kmdo -l linux -L slow docs/               # 'linux' ones, minus the slow ones

Since uone is a label like any other, -l uone selects the update-on-error command-files and -L uone skips them.

Note

Labels and dotted fname parts occupy the same place in the file name, so kmdo cannot tell them apart. This only matters for empty command-files, where the file name supplies the command: an empty foo.sh.cmd runs foo with label sh, not the command foo.sh. kmdo writes a warning to stderr when it hits that case. Non-empty command-files are unaffected, since a label that nobody selects on does nothing.