Package: tesh
tesh[tɛʃ]- TEstable SHell sessions in MarkdownShowing 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.$teshdemo/📄 Checking demo/happy.md✨ Running foo  ✅ Passed✨ Running bar  ✅ Passed📄 Checking demo/sad.md✨ Running foo  ❌ FailedCommand: echo "foo"Expected:sad pandaGot:fooTaking you into the shell ...Enter `!!` to rerun the last command.$SyntaxTo mark a code block as testable, appendtesh-session="NAME"to the header line.You can use any syntax highlighting directive, such asbash,shell,shell-session,consoleor others.```console tesh-session="hello"
$ echo "Hello World!"
Hello World!
```Linking multiple code blocks into a single shell sessionBesides marking a code block as testable,tesh-sessionis 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!
```Ignoring parts of the outputParts of the inline output can be ignored with...:```console tesh-session="ignore"
$ echo "Hello from Space!"
Hello ... Space!
```Multiline supportThe 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="multiline"
$ echo "Hello from" \
>   "another" \
>   "line!"
Hello from another line!
```Advanced directivesYou 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 totrueto showcase long-running commands such asdocker compose up.Let's look at all of these through examples!Testing exit codestesh-exitcodesaccepts 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

```Test setupSometimes you need to do some test setup before running the examples in your code blocks. Put thosein a fileand point to it with thetesh-setupdirective.```console tesh-session="setup" tesh-setup="readme.sh"
$ echo "Hello $NAME!"
Hello Gaea!
```Custom promptsEvery so often you need to drop into a virtualenv or similar shell that changes the prompt.teshsupports this viatest-ps1directive.```console tesh-session="prompt" tesh-ps1="(foo) $"
$ PS1="(foo) $ "


(foo) $ echo "hello"
hello
```Only run on certain platformsSome examples should only run on certain platforms, usetesh-platformto declare them as such.```console tesh-session="platform" tesh-platform="linux"
$ uname
...Linux...
``````console tesh-session="platform" tesh-platform="darwin"
$ uname
...Darwin...
```Dump file to diskOccasionally your examples consist of first showing contents of a file, then executing a command that uses said file. This is supported, use thetesh-fixturedirective.```bash tesh-session="fixture" tesh-fixture="foo.sh"
echo "foo"
``````console tesh-session="fixture"
$ chmod +x foo.sh

$ ./foo.sh
foo
```Custom timeoutBy default,teshwill fail if an example command does not finish in 30 seconds. This number can be modified using thetesh-timeoutdirective.```console tesh-session="timeout" tesh-timeout="3"
$ sleep 1

```Long running processesSome processes that you want to show examples for are long-running processes, likedocker compose up. They are supported inteshblocks using thetesh-long-runningdirective. 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 ...
```InstallationThe best way to installteshis with your favorite Python package manager.$pipinstallteshDesign decisionsSupports Linux / macOS.Not tied to a specific markdown flavor or tooling.Renders reasonably well on GitHub.Comparison with other toolsteshmdshpandoc filtersExecute 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✔️✖️✖️✔️: SupportedC: Possible but you have to write some code yourself🚧: Under development✖️: Not supported?: I don't know.DevelopingteshWe provide two development environments for people working on this project, one based onNixand one based onDocker.For Nix, runnix developto enter the development environment, where everything is ready for use.For Docker, rundocker build -t tesh . && docker run --rm -v .:/tesh -it teshto enter the development environment, where everything is ready for use.Then you can runmake teststo run all tests & checks.Additionalmakecommands are available to run just a subset of tests or checks.# 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=fooMultiple Python versionsBy default, the development environment uses the latest supported Python version. This is how you drop into an environment with an older PythonOn Linux:$ nix develop .#devShells.x86_64-linux.default-python39On macOS:$ nix develop .#devShells.aarch64-darwin.default-python39On CI, all supported versions of Python are tested.