Skip to main content

Automatically execute code blocks within a Markdown file and update the output in-place

Project description

Markdown Code Runner

PyPI Python License Downloads CI Docs

Markdown Code Runner Logo

markdown-code-runner is a Python package that automatically executes code blocks within a Markdown file, including hidden code blocks, and updates the output in-place. This package is particularly useful for maintaining Markdown files with embedded code snippets, ensuring that the output displayed is up-to-date and accurate. It also enables the generation of content such as tables, plots, and other visualizations directly from the code.

Documentation: https://markdown-code-runner.nijho.lt

Features

  • Automatically execute code blocks, including hidden code blocks, within a Markdown file
  • Allows hidden code blocks (i.e., code blocks that are not displayed in the Markdown file) to generate content like tables, plots, etc.
  • Works with Python and Bash code blocks
  • Keeps the output of the code blocks up-to-date
  • Easily integrates with GitHub Actions
  • No external dependencies and works with Python 3.7+
  • Execute all languages by using the file code blocks and executing it with bash (see Rust example)
  • Built-in include_section() function to extract and reuse content from other Markdown files

Problem Statement

When creating Markdown files with code examples, it's essential to keep the output of these code snippets accurate and up-to-date. Manually updating the output can be time-consuming and error-prone, especially when working with large files or multiple collaborators. In addition, there might be cases where hidden code blocks are needed to generate content such as tables, plots, and other visualizations without displaying the code itself in the Markdown file.

markdown-code-runner solves this problem by automatically executing the code blocks, including hidden ones, within a Markdown file and updating the output in-place. This ensures that the displayed output is always in sync with the code, and content generated by hidden code blocks is seamlessly integrated.

Table of Contents

Installation

Install markdown-code-runner via pip:

pip install markdown-code-runner

Quick Start

To get started with markdown-code-runner, follow these steps:

  1. Add code blocks to your Markdown file using either of the following methods:

    Method 1 (show your code): Use a triple backtick code block with the language specifier python markdown-code-runner.

    Example:

    ```python markdown-code-runner
    print('Hello, world!')
    ```
    (Optionally, you can place some text between the code block and the output markers)
    <!-- OUTPUT:START -->
    This content will be replaced by the output of the code block above.
    <!-- OUTPUT:END -->
    

    or for Bash:

    ```bash markdown-code-runner
    echo 'Hello, world!'
    ```
    (Optionally, you can place some text between the code block and the output markers)
    <!-- OUTPUT:START -->
    This content will be replaced by the output of the code block above.
    <!-- OUTPUT:END -->
    

    Method 2 (hide your code): Place the code between <!-- CODE:START --> and <!-- CODE:END --> markers. Add the output markers <!-- OUTPUT:START --> and <!-- OUTPUT:END --> where you want the output to be displayed.

    Example:

    This is an example code block:
    
    <!-- CODE:START -->
    <!-- print('Hello, world!') -->
    <!-- CODE:END -->
    <!-- OUTPUT:START -->
    This content will be replaced by the output of the code block above.
    <!-- OUTPUT:END -->
    

    or for Bash:

    This is an example code block:
    
    <!-- CODE:BASH:START -->
    <!-- MY_VAR="Hello, World!" -->
    <!-- echo $MY_VAR -->
    <!-- CODE:END -->
    <!-- OUTPUT:START -->
    This content will be replaced by the output of the code block above.
    <!-- OUTPUT:END -->
    
  2. Run markdown-code-runner on your Markdown file(s):

    markdown-code-runner /path/to/your/markdown_file.md
    

    You can also process multiple files at once:

    markdown-code-runner docs/*.md README.md
    
  3. The output of the code block will be automatically executed and inserted between the output markers.

Python API

To use markdown-code-runner, simply import the update_markdown_file function from the package and call it with the path to your Markdown file:

from markdown_code_runner import update_markdown_file

update_markdown_file("path/to/your/markdown_file.md")

Examples

Here are a few examples demonstrating the usage of markdown-code-runner:

Example 1: Simple code block

This is an example of a simple hidden code block:

<!-- CODE:START -->
<!-- print('Hello, world!') -->
<!-- CODE:END -->
<!-- OUTPUT:START -->
This content will be replaced by the output of the code block above.
<!-- OUTPUT:END -->

After running markdown-code-runner:

This is an example of a simple code block:

<!-- CODE:START -->
<!-- print('Hello, world!') -->
<!-- CODE:END -->
<!-- OUTPUT:START -->
<!-- ⚠️ This content is auto-generated by `markdown-code-runner`. -->
Hello, world!

<!-- OUTPUT:END -->

Example 2: Multiple code blocks

Here are two code blocks:

First code block:

<!-- CODE:START -->
<!-- print('Hello, world!') -->
<!-- CODE:END -->
<!-- OUTPUT:START -->
This content will be replaced by the output of the first code block.
<!-- OUTPUT:END -->
Second code block:

<!-- CODE:START -->
<!-- print('Hello again!') -->
<!-- CODE:END -->
<!-- OUTPUT:START -->
This content will be replaced by the output of the second code block.
<!-- OUTPUT:END -->

After running markdown-code-runner:

Here are two code blocks:

First code block:

<!-- CODE:START -->
<!-- print('Hello, world!') -->
<!-- CODE:END -->
<!-- OUTPUT:START -->
<!-- ⚠️ This content is auto-generated by `markdown-code-runner`. -->
Hello, world!

<!-- OUTPUT:END -->

Second code block:

<!-- CODE:START -->
<!-- print('Hello again!') -->
<!-- CODE:END -->
<!-- OUTPUT:START -->
<!-- ⚠️ This content is auto-generated by `markdown-code-runner`. -->
Hello again!

<!-- OUTPUT:END -->

Built-in Functions

Code blocks executed by markdown-code-runner have access to built-in helper functions.

include_section(file, name, strip_heading=False)

Extract content from a markdown file that is marked with SECTION markers. This is useful for reusing content across multiple files without duplication.

Parameters:

  • file: Path to the markdown file (relative to the current file, or absolute)
  • name: The section name to extract
  • strip_heading: If True, removes the first heading from the extracted content

Example:

In your source file (README.md):

<!-- SECTION:intro:START -->
## Introduction

This is reusable content.
<!-- SECTION:intro:END -->

In another file that wants to include this section:

<!-- CODE:START -->
<!-- print(include_section("README.md", "intro")) -->
<!-- CODE:END -->
<!-- OUTPUT:START -->
<!-- OUTPUT:END -->

After running markdown-code-runner, the output section will contain:

## Introduction

This is reusable content.

Use strip_heading=True to remove the heading:

<!-- CODE:START -->
<!-- print(include_section("README.md", "intro", strip_heading=True)) -->
<!-- CODE:END -->

Usage Ideas

Markdown Code Runner can be used for various purposes, such as creating Markdown tables, generating visualizations, and showcasing code examples with live outputs. Here are some usage ideas to get you started:

Idea 1: Continuous Integration with GitHub Actions

You can use markdown-code-runner to automatically update your Markdown files in a CI environment. The following example demonstrates how to configure a GitHub Actions workflow that updates your README.md whenever changes are pushed to the main branch.

  1. Create a new workflow file in your repository at .github/workflows/markdown-code-runner.yml.

  2. Add the following content to the workflow file:

name: Update README.md

on:
  push:
    branches:
      - main
  pull_request:

jobs:
  update_readme:
    runs-on: ubuntu-latest
    steps:
      - name: Check out repository
        uses: actions/checkout@v6
        with:
          persist-credentials: false
          fetch-depth: 0

      - name: Set up Python
        uses: actions/setup-python@v6
        with:
          python-version: "3.14.2"

      - name: Install uv
        uses: astral-sh/setup-uv@v7

      - name: Install markdown-code-runner
        run: |
          uv venv
          uv pip install markdown-code-runner

      # Install dependencies you're using in your README.md
      - name: Install other Python dependencies
        run: |
          uv pip install pandas tabulate pytest matplotlib requests

      # Rust is only needed for an example in our README.md
      - uses: actions-rust-lang/setup-rust-toolchain@v1

      - name: Run update-readme.py
        run: uv run markdown-code-runner --verbose README.md

      - name: Commit updated README.md
        id: commit
        run: |
          git add README.md
          git config --local user.email "github-actions[bot]@users.noreply.github.com"
          git config --local user.name "github-actions[bot]"
          if git diff --quiet && git diff --staged --quiet; then
            echo "No changes in README.md, skipping commit."
            echo "commit_status=skipped" >> $GITHUB_ENV
          else
            git commit -m "Update README.md"
            echo "commit_status=committed" >> $GITHUB_ENV
          fi

      - name: Push changes
        if: env.commit_status == 'committed'
        uses: ad-m/github-push-action@master
        with:
          github_token: ${{ secrets.GITHUB_TOKEN }}
          branch: ${{ github.head_ref }}
  1. Commit and push the workflow file to your repository. The workflow will now automatically run whenever you push changes to the main branch, updating your README.md with the latest outputs from your code blocks.

For more information on configuring GitHub Actions, check out the official documentation.

Idea 2: Show command-line output

Use markdown-code-runner to display the output of a command-line program. For example, the following Markdown file shows the helper options of this package.

Using a backtick bash code block:

export PATH=~/micromamba/bin:$PATH
echo '```bash'
markdown-code-runner --help
echo '```'

Which is rendered as:

usage: markdown-code-runner [-h] [-o OUTPUT] [-d] [-v]
                            [--no-backtick-standardize] [-s] [-n]
                            input [input ...]

Automatically update Markdown files with code block output.

positional arguments:
  input                 Path(s) to the input Markdown file(s).

options:
  -h, --help            show this help message and exit
  -o, --output OUTPUT   Path to the output Markdown file. (default: overwrite
                        input file)
  -d, --verbose         Enable debugging mode (default: False)
  -v, --version         show program's version number and exit
  --no-backtick-standardize
                        Disable backtick standardization (default: enabled for
                        separate output files, disabled for in-place)
  -s, --standardize     Post-process to standardize ALL code fences, removing
                        'markdown-code-runner' modifiers
  -n, --no-execute      Skip code execution entirely (useful with
                        --standardize for compatibility processing only)

Idea 3: Generating Markdown Tables

Use the pandas library to create a Markdown table from a DataFrame. The following example demonstrates how to create a table with random data:

import pandas as pd
import numpy as np

# Generate random data
np.random.seed(42)
data = np.random.randint(1, 101, size=(5, 3))

# Create a DataFrame and column names
df = pd.DataFrame(data, columns=["Column A", "Column B", "Column C"])

# Convert the DataFrame to a Markdown table
print(df.to_markdown(index=False))

Which is rendered as:

Column A Column B Column C
52 93 15
72 61 21
83 87 75
75 88 100
24 3 22

Idea 4: Generating Visualizations

Create a visualization using the matplotlib library and save it as an image. Then, reference the image in your Markdown file. The following example demonstrates how to create a bar chart.

Using a triple-backtick code block:

import matplotlib.pyplot as plt
import io
import base64
from urllib.parse import quote

# Example data for the plot
x = [1, 2, 3, 4, 5]
y = [2, 4, 6, 8, 10]

# Create a simple line plot
plt.plot(x, y)
plt.xlabel("X-axis")
plt.ylabel("Y-axis")
plt.title("Sample Line Plot")

# Save the plot to a BytesIO buffer
buf = io.BytesIO()
plt.savefig(buf, format='png')
plt.close()

# Encode the buffer as a base64 string
data = base64.b64encode(buf.getvalue()).decode('utf-8')

# Create an inline HTML img tag with the base64 string
from urllib.parse import quote
img_html = f'<img src="data:image/png;base64,{quote(data)}" alt="Sample Line Plot"/>'

print(img_html)

NOTE: This output is disabled here because GitHub Markdown doesn't support inline image HTML. This will work on other Markdown renderers.

Idea 5: Generating a table from CSV data

Suppose you have a CSV file containing data that you want to display as a table in your Markdown file. You can use pandas to read the CSV file, convert it to a DataFrame, and then output it as a Markdown table.

Using a triple-backtick code block:

import pandas as pd
csv_data = "Name,Age,Score\nAlice,30,90\nBob,25,85\nCharlie,22,95"
with open("sample_data.csv", "w") as f:
    f.write(csv_data)
df = pd.read_csv("sample_data.csv")
print(df.to_markdown(index=False))

Which is rendered as:

Name Age Score
Alice 30 90
Bob 25 85
Charlie 22 95

Idea 6: Displaying API data as a list

You can use markdown-code-runner to make API calls and display the data as a list in your Markdown file. In this example, we'll use the requests library to fetch data from an API and display the results as a list.

Using a hidden code block:

<!-- CODE:START -->
<!-- import requests -->
<!-- response = requests.get("https://jsonplaceholder.typicode.com/todos?_limit=5") -->
<!-- todos = response.json() -->
<!-- for todo in todos: -->
<!--     print(f"- {todo['title']} (User ID: {todo['userId']}, Completed: {todo['completed']})") -->
<!-- CODE:END -->
<!-- OUTPUT:START -->
<!-- OUTPUT:END -->

Which is rendered as:

  • delectus aut autem (User ID: 1, Completed: False)
  • quis ut nam facilis et officia qui (User ID: 1, Completed: False)
  • fugiat veniam minus (User ID: 1, Completed: False)
  • et porro tempora (User ID: 1, Completed: True)
  • laboriosam mollitia et enim quasi adipisci quia provident illum (User ID: 1, Completed: False)

Idea 7: Run a Rust program

We can use markdown-code-runner to write Rust code to a file and then a hidden bash code block to run the code and display the output.

The code below is actually executed, check out the README.md in plain text to see how this works.

fn main() {
    println!("Hello, world!");
}

Which when executed produces:

Hello, world!

These are just a few examples of how you can use Markdown Code Runner to enhance your Markdown documents with dynamic content. The possibilities are endless!

License

markdown-code-runner is released under the MIT License. Please include the LICENSE file when using this package in your project, and cite the original source.

Contributing

Contributions are welcome! To contribute, please follow these steps:

  1. Fork the repository on GitHub: https://github.com/basnijholt/markdown-code-runner
  2. Create a new branch for your changes.
  3. Make your changes, ensuring that they adhere to the code style and guidelines.
  4. Submit a pull request with a description of your changes.

Please report any issues or bugs on the GitHub issue tracker: https://github.com/basnijholt/markdown-code-runner/issues

Thank you for your interest in markdown-code-runner!

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

markdown_code_runner-2.7.0.tar.gz (99.8 kB view details)

Uploaded Source

Built Distribution

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

markdown_code_runner-2.7.0-py3-none-any.whl (14.7 kB view details)

Uploaded Python 3

File details

Details for the file markdown_code_runner-2.7.0.tar.gz.

File metadata

  • Download URL: markdown_code_runner-2.7.0.tar.gz
  • Upload date:
  • Size: 99.8 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.14.2

File hashes

Hashes for markdown_code_runner-2.7.0.tar.gz
Algorithm Hash digest
SHA256 b36f9314839c0db3ee5e5d644e1b4471454af8f624eb15bd5a5d68dbc6430afe
MD5 7e778b8d10600ec2e1492ca32a22d29f
BLAKE2b-256 28d12a7753e05dcc711552721048a34341b63a6a2b09e70ac738c7a4c81ca414

See more details on using hashes here.

File details

Details for the file markdown_code_runner-2.7.0-py3-none-any.whl.

File metadata

File hashes

Hashes for markdown_code_runner-2.7.0-py3-none-any.whl
Algorithm Hash digest
SHA256 fad5d5fbad9c49687141ab9c39ed7e054f857b97aa79d11f031de157afd21c20
MD5 bd406917f6b32abc3ff4844e8f8a0af5
BLAKE2b-256 cf929d7fa0567cf3d38b551bb1a30c177285e0e42ae2b10c16946af7e8a38e73

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