tesh [tɛʃ] - TEstable SHell sessions in Markdown
Showing shell interactions how to run a tool is useful for teaching and explaining.
Making sure that example still works over the years is painfully hard.
Not anymore.
$ tesh demo/
📄 Checking demo/happy.md
✨ Running foo ✅ Passed
✨ Running bar ✅ Passed
📄 Checking demo/sad.md
✨ Running foo ❌ Failed
Command: echo "foo"
Expected:
sad panda
Got:
foo
Taking you into the shell ...
Enter `!!` to rerun the last command.
$
To mark a code block as testable, append tesh-session="NAME"
to the header line.
You can use any syntax highlighting directive, such as bash
, shell
, shell-session
, console
or others.
```console tesh-session="hello"
$ echo "Hello World!"
Hello World!
```
Besides marking a code block as testable, tesh-session
is a unique identifier that allows for multiple code blocks to share the same session.
```console tesh-session="multiple_blocks"
$ export NAME=Earth
```
```console tesh-session="multiple_blocks"
$ echo "Hello $NAME!"
Hello Earth!
```
Parts of the inline output can be ignored with ...
:
```console tesh-session="ignore"
$ echo "Hello from Space!"
Hello ... Space!
```
The same can be done for multiple lines of output. Note that trailing whitespace in every line is trimmed.
```console tesh-session="ignore"
$ printf "Hello \nthere \nfrom \nSpace!"
Hello
...
Space!
```
Commands can continue across multiple lines by prefixing lines with >
.
```console tesh-session="ignore"
$ echo "Hello from" \
> "another" \
> "line!"
Hello from another line!
```
You can set a few other optional directives in the header line:
tesh-exitcodes
: a list of exit codes in the order of commands executed inside the code block,tesh-setup
: a filename of a script to run before running the commands in the code block,tesh-ps1
: allow an additional PS1 prompt besides the default$
,tesh-platform
: specify on which platforms this session block should be tested (linux
,darwin
,windows
),tesh-fixture
: a filename to save the current snippet,tesh-timeout
: number of seconds before a command timeouts (defaults to 30s),tesh-long-running
: set totrue
to showcase long-running commands such asdocker compose up
.
Let's look at all of these through examples!
tesh-exitcodes
accepts a list of integers, which represent the exit code for every command in the block.
```console tesh-session="exitcodes" tesh-exitcodes="1 0"
$ false
$ true
```
Sometimes you need to do some test setup before running the examples in your code blocks. Put those in a file and point to it with the tesh-setup
directive.
```console tesh-session="setup" tesh-setup="readme.sh"
$ echo "Hello $NAME!"
Hello Gaea!
```
Every so often you need to drop into a virtualenv or similar shell that changes the prompt. tesh
supports this via test-ps1
directive.
```console tesh-session="prompt" tesh-ps1="(foo) $"
$ PS1="(foo) $ "
(foo) $ echo "hello"
hello
```
Some examples should only run on certain platforms, use tesh-platform
to declare them as such.
```console tesh-session="platform" tesh-platform="linux"
$ uname
...Linux...
```
```console tesh-session="platform" tesh-platform="darwin"
$ uname
...Darwin...
```
Occasionally your examples consist of first showing contents of a file, then executing a command that uses said file. This is supported, use the tesh-fixture
directive.
```bash tesh-session="fixture" tesh-fixture="foo.sh"
echo "foo"
```
```console tesh-session="fixture"
$ chmod +x foo.sh
$ ./foo.sh
foo
```
By default, tesh
will fail if an example command does not finish in 30 seconds. This number can be modified using the tesh-timeout
directive.
```console tesh-session="timeout" tesh-timeout="3"
$ sleep 1
```
Some processes that you want to show examples for are long-running processes, like docker compose up
. They are supported in tesh
blocks using the tesh-long-running
directive. Note that they need to be the last command in the block.
```console tesh-session="long-running" tesh-timeout="1" tesh-long-running="true"
$ nmap 1.1.1.1
Starting Nmap ...
```
The best way to install tesh
is with your favorite Python package manager.
$ pip install tesh
- Supports Linux / macOS.
- Not tied to a specific markdown flavor or tooling.
- Renders reasonably well on GitHub.
tesh | mdsh | pandoc filters | |
---|---|---|---|
Execute shell session | ✔️ | ✔️ | ✔️ |
Modify markdown file with the new output | 🚧[1] | ✔️ | ✔️ |
Shared session between code blocks | ✔️ | ✖️ | ✖️ |
Custom PS1 prompts | ✔️ | ✖️ | ✖️ |
Assert non-zero exit codes | ✔️ | ✖️ | ✖️ |
Setup the shell environment | ✔️ | ✖️ | ✖️ |
Reference fixtures from other snippets | ✔️ | ✖️ | ✖️ |
Wildcard matching of the command output | ✔️ | ✖️ | ✖️ |
Starts the shell in debugging mode | ✔️ | ✖️ | ✖️ |
Specify timeout | ✔️ | ✖️ | ✖️ |
Support long-running commands | ✔️ | ✖️ | ✖️ |
- ✔️: Supported
- C: Possible but you have to write some code yourself
- 🚧: Under development
- ✖️: Not supported
- ?: I don't know.
You need to have poetry and Python 3.9 through 3.11 installed on your machine.
Alternatively, if you use nix, run nix-shell
to drop into a shell that has everything prepared for development.
Then you can run make tests
to run all tests & checks. Additional make
commands are available:
# run tesh on all Markdown files
$ make tesh
# run flake8 linters on changed files only
$ make lint
# run flake8 linters on all files
$ make lint all=true
# run mypy type checker
$ make types
# run unit tests
$ make unit
# run a subset of unit tests (regex find)
$ make unit filter=foo
# re-lock Python dependencies (for example after adding or removing one from pyproject.toml)
$ make lock