<!-- Parts of this documentation is autogenerated using kmdo -->

# 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](http://www.sphinx-doc.org/) and [GitHub
Pages](https://pages.github.com/).

## Installation

Install `kmdo` system-wide via the pip using `pipx`:

```bash
pipx install kmdo
```

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

## Usage

```{literalinclude} examples/kmdo.out
:language: bash
```

### 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.

```{literalinclude} kmdo-examples.cmd
:language: bash
```

Outputs the following **YAML**:

```{literalinclude} kmdo-examples.out
:language: bash
```

### 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`:

```{literalinclude} examples/kmdo.uone.out
:language: bash
```

### 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 `--only` every command-file runs. Passing `--only` restricts the run
to the command-files carrying that label:

```bash
kmdo docs/                    # runs everything
kmdo --only slow docs/        # runs only the 'slow' command-files
kmdo -o slow -o linux docs/   # runs those carrying 'slow' or 'linux'
```

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

```bash
kmdo --skip slow docs/           # everything except the slow ones
kmdo -o linux -k slow docs/      # 'linux' ones, minus the slow ones
```

Since `uone` is a label like any other, `-o uone` selects the update-on-error
command-files and `-k 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.
```

### Excluding by name

Labels are the way to carve a tree into subsets you have named in advance.
When you just need to drop something without labelling anything first,
`--exclude-name` skips command-files whose name contains the given text:

```bash
kmdo --exclude-name scratch docs/  # skips anything with 'scratch' in the name
kmdo -x scratch -x wip docs/       # repeatable, skips names matching either
```

It matches a substring of the whole file name, labels included, so `-x slow`
and `--skip slow` cover much the same ground on labelled files and differ on a
file that merely happens to be called `slowpath.cmd`. Prefer `--skip` where the
intent is a subset, and keep `--exclude-name` for the one-off case it suits.

```{note}
`--exclude` is still accepted as a spelling of `--exclude-name`, so existing
invocations keep working, but it is no longer listed in `--help`.
```
