Skip to main content

A lightweight Markdown demo runner.

Project description

md-demo

md-demo is a lightweight Markdown demo runner. It executes explicitly marked code blocks, captures stdout, stderr, and Python last expressions, and writes generated output back into the Markdown file.

It is meant for readable demo documents that stay useful as plain Markdown. It is not a notebook system, a sandbox, or a runner for untrusted code.

Repository: Jwink3101/md-demo

Warning: md-demo executes code from the document. Run only trusted files.

Install

pip install md-demo

For development from a source checkout, install in editable mode with test dependencies and verify the checkout:

python -m pip install -e ".[test]"
python -m compileall -q src
pytest

Quick start

Create a Markdown file with an executable Python block. No config header is needed for the Python defaults.

```python exe
print("hello")
```

Run:

md-demo demo.md

md-demo updates the file in place by default and inserts a generated result block:

```python exe
print("hello")
```

<!-- md-demo: result start. Do not edit; this block is overwritten. -->
```
hello
```
<!-- md-demo: result end -->

Do not edit generated result blocks. They are cleared and recreated on normal runs.

Document config

Runnable documents use Python defaults when no config is present. Add config only when you need to change a default, use shell, add a result label, or run hidden setup code before the first executable block. There are two supported forms.

Use YAML front matter by default:

---
md-demo:
  runtime: python
---

If your Markdown renderer shows front matter as visible page content, use hidden HTML comment config instead. The content between <!-- md-demo and --> is YAML:

<!-- md-demo
runtime: python
-->

Both forms are parsed only at the top of the document. YAML front matter config must be namespaced under md-demo: so it does not collide with other front matter keys. Hidden HTML comment config is also YAML, but uses the same option names without the outer md-demo key. md-demo preserves whichever form the document already uses by default.

To convert config style while running or clearing a document, use --config-style:

md-demo demo.md --config-style preserve
md-demo demo.md --config-style front-matter
md-demo demo.md --config-style hidden

preserve is the default and does not rewrite the config style. front-matter rewrites the document's config as namespaced YAML front matter. hidden rewrites the document's config as an HTML comment. Only the md-demo config is converted; unrelated front matter is preserved when practical.

Main config options:

Option Default Purpose
runtime python Selects python, python3, bash, or shell.
display last-expression Set to none to disable Python final-expression output.
preface-text empty Adds visible text before each generated output block.
result-language "" Sets the info string for generated result fences, such as text or console.
setup empty Runs hidden setup code before the first executable block.

In YAML front matter, these options must live under the md-demo: key. In hidden HTML comment config, the comment body is YAML and uses the option names directly.

Supported runtime values:

  • python
  • python3
  • bash
  • shell

python3 is an alias for the Python runner. shell is an alias for the bash runner, not /bin/sh.

Output labels

You can optionally add visible text before every generated output block with preface-text.

YAML front matter:

---
md-demo:
  runtime: python
  preface-text: "Output:"
---

Hidden HTML comment config with a YAML body:

<!-- md-demo
runtime: python
preface-text: "Output:"
-->

If preface-text is missing, empty, or null, no label is inserted. The label is generated inside the result region, so changing preface-text updates existing results the next time md-demo runs.

Generated result fences have no info string by default. Set result-language to add one, such as text or console.

Python last-expression display is enabled by default. To capture only stdout and stderr, set display: none:

---
md-demo:
  display: none
---

Use setup for imports or initialization that should run before the first executable block without being shown in generated output:

---
md-demo:
  setup: |
    import os
    from pathlib import Path
---

Setup code runs before visible executable blocks. If a library reads environment variables when it is imported, put those environment variables in setup before importing the library there. Environment variables assigned in later visible blocks cannot affect import-time configuration that has already happened in setup.

Executable blocks

Only matching-language fenced code blocks marked with exe run. The opening fence must begin in column zero; indented code blocks are ordinary Markdown and do not run.

```python exe
print("runs")
```

Ordinary code blocks are examples only:

```python
print("shown, not run")
```

Executable blocks run top-to-bottom in one persistent runtime. Python variables, imports, functions, shell variables, and shell directory changes can carry forward to later executable blocks. Python blocks can import modules from the Markdown file's directory. If setup is configured, it runs before the first executable block in that same persistent runtime.

md-demo captures stdout and stderr. Python logging handlers created in one block with logging.StreamHandler(), logging.StreamHandler(sys.stderr), or logging.StreamHandler(sys.stdout) are captured in the block where each log is emitted. For Python blocks, the final expression is also displayed by default when it is not assigned, does not evaluate to None, and is not followed by a trailing semicolon.

Finding undocumented demos

Repository-specific demo paths in AGENTS.md are the preferred source of truth. When that list is absent or incomplete, find candidate documents from the repository root without executing anything:

rg -l --glob '*.md' --pcre2 '^`{3,}[[:space:]]*(?:python|python3|bash|shell)\b[[:space:]].*\bexe\b'

This searches for column-zero backtick fences with an md-demo runtime language and an exe marker. Treat every match as a clue, not a runnable-document list: the pattern can also find fenced examples embedded in documentation, and it does not validate the document's configuration or code. Inspect the relevant blocks and repository guidance before running one trusted document at a time. Once the actual demos are known, add their repository-relative paths to AGENTS.md when you are authorized to update it.

CLI

Update a document in place:

md-demo demo.md

Write the updated Markdown elsewhere:

md-demo demo.md --output rendered.md

Write the updated Markdown to stdout:

md-demo demo.md --output -

Clear generated result blocks without executing code:

md-demo demo.md --clear

Rewrite config style without executing code:

md-demo demo.md --clear --config-style hidden

Print concise help:

md-demo --help

Print the installed version:

md-demo --version

Print the detailed manual:

md-demo --manual

Proposed AGENTS.md guidance

Repositories containing md-demo demos can include the following minimal guidance in AGENTS.md:

## md-demo demos

- The `md-demo` demos are [list the repository's demo files or directories here]. Do not leave this unspecified: agents need repository-specific paths to distinguish demos from ordinary Markdown.
- If that list is unavailable, run `rg -l --glob '*.md' --pcre2 '^`{3,}[[:space:]]*(?:python|python3|bash|shell)\b[[:space:]].*\bexe\b'` to identify candidates, then inspect them before running anything. The search result is not an authorization to run every matching file.
- Executable fenced code blocks are marked with `exe`, run top-to-bottom in one persistent runtime, and have their captured output written into generated result blocks.
- Edit the demo prose and executable source blocks, not generated `md-demo` result blocks. Inspect the executable blocks before running them, regenerate output with `md-demo path/to/demo.md`, require a successful exit, and review the resulting diff.
- Run only trusted demos because their code executes locally.
- If the `md-demo` command is unavailable, install it with `pip install md-demo` or use the repository's documented development installation.
- Use `md-demo --manual` when authoring or troubleshooting a demo requires more detail. Do not call it by default for unrelated repository work.

Failure behavior

A normal run behaves like clear and execute:

  1. Old generated results are cleared.
  2. Executable blocks run top-to-bottom.
  3. Fresh result blocks are inserted for blocks that produced output.

If a block fails, md-demo writes output through the failed block, stops before later executable blocks, and exits nonzero. Later executable blocks are left without result blocks because they did not run.

Intentional failures should be handled inside the demo code:

try:
    validate("")
except ValueError as exc:
    print(type(exc).__name__, exc)

Converting existing documents

AI Disclosure

This tool was primarily generated with assistance from ChatGPT Codex, guided and directed by a human developer. Human involvement included requirements definition, some implementation direction, and cursory code review. The code has not undergone a comprehensive human audit or formal security review.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

md_demo-0.4.0.tar.gz (21.4 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

md_demo-0.4.0-py3-none-any.whl (18.4 kB view details)

Uploaded Python 3

File details

Details for the file md_demo-0.4.0.tar.gz.

File metadata

  • Download URL: md_demo-0.4.0.tar.gz
  • Upload date:
  • Size: 21.4 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.13.13

File hashes

Hashes for md_demo-0.4.0.tar.gz
Algorithm Hash digest
SHA256 f98c0a5f87603357ef96fc65c32115400cc75fa954008ecd183531e142388e4b
MD5 603a1782516c07e4d0b22342224d2ccb
BLAKE2b-256 ddce255772a2e5c3d9af8d41b7ebb5ed6ce5a0986945fe704195af2cd925e94a

See more details on using hashes here.

File details

Details for the file md_demo-0.4.0-py3-none-any.whl.

File metadata

  • Download URL: md_demo-0.4.0-py3-none-any.whl
  • Upload date:
  • Size: 18.4 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.13.13

File hashes

Hashes for md_demo-0.4.0-py3-none-any.whl
Algorithm Hash digest
SHA256 3264b1763c666260127dedf15ffa6a16bebdfd6215c45af832691ade51f70130
MD5 23fafea9aa13263060313d5d6719bdae
BLAKE2b-256 9f670a879386f6d1255f889692b7b996323c45d1de89bdc8c9f9760cd58d55ee

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page