DEV Community

John Wick
John Wick

Posted on

Turn Your README Into a Live Website with StayPresent's Markdown Renderer

How to serve a markdown to html python page — README, changelog, or docs — with zero dependencies using StayPresent's built-in renderer.

Turn Your README Into a Live Website with StayPresent's Markdown Renderer

Most bots already have a README.md or CHANGELOG.md sitting in the repo, fully written and maintained, but invisible to anyone who isn't looking at the source code. StayPresent's web.markdown() turns any markdown to html python file into a styled, GitHub-like web page — served directly from your bot's own HTTP server, with zero extra dependencies.

Table of Contents

  1. Why Serve Markdown Directly
  2. Basic Usage
  3. What the Renderer Supports
  4. Light, Dark, and Auto Theming
  5. Favicon, Title, and Description
  6. Security: Escaping and URL Filtering
  7. What It Doesn't Support
  8. Full Example
  9. Best Practices
  10. Common Mistakes
  11. FAQs
  12. Conclusion

Why Serve Markdown Directly

A CHANGELOG.md is genuinely useful to end users and other developers — but only if they can actually read it without cloning the repo. Since StayPresent's HTTP server already exists for keep-alive purposes, serving that same Markdown file as a proper web page costs nothing extra and requires no separate documentation site or static site generator.

Basic Usage

import staypresent

staypresent.web.markdown("CHANGELOG.md")
staypresent.run("bot.py")
Enter fullscreen mode Exit fullscreen mode

Like html(), the file is read and re-rendered fresh on every request — editing CHANGELOG.md on disk is reflected the next time someone loads the page, with no restart required.

Signature:

staypresent.web.markdown(
    file_path: str,
    path: str = "/",
    mode: str = "auto",
    favicon: str = None,
    title: str = None,
    description: str = None,
) -> None
Enter fullscreen mode Exit fullscreen mode

file_path must exist at call time — a typo raises FileNotFoundError immediately rather than only 404ing once a browser requests the page.

What the Renderer Supports

Everything is handled by StayPresent's own built-in renderer — no markdown package or any other dependency is required. It covers the constructs people actually use in real READMEs and docs:

  • ATX and Setext headings (levels 1–6), each with a stable, GitHub-compatible anchor id.
  • Bold, italic, bold-italic, and strikethrough.
  • Inline code spans, including multi-backtick spans.
  • Links, images, and bare autolinks — including URLs with a single level of balanced parentheses.
  • Unordered and ordered lists, including nesting, multi-paragraph items, tight/loose rendering, custom start numbers, and GitHub-style task-list checkboxes (- [ ] / - [x]).
  • Nested blockquotes.
  • Fenced and indented code blocks, with language-tag syntax highlighting hooks.
  • GitHub-flavored pipe tables, including per-column alignment.
  • Horizontal rules and hard line breaks.
  • Raw HTML passthrough for recognized block-level tags — which is what makes a centered logo/badge header at the top of a README render correctly.
  • Backslash escapes for literal punctuation.

Nested inline content — a code span or bold text inside a link, or an image inside a link (the common "clickable badge" pattern) — is fully supported:

[![badge](badge.svg)](https://example.com)
Enter fullscreen mode Exit fullscreen mode

renders as a proper clickable image link, not broken markup.

Light, Dark, and Auto Theming

mode controls how the rendered page handles color scheme:

staypresent.web.markdown("guide.md", mode="dark")
Enter fullscreen mode Exit fullscreen mode
  • mode="auto" (default): the page includes <meta name="color-scheme" content="light dark"> with no forced theme — the visitor's own OS/browser preference decides.
  • mode="light" / mode="dark": forces that theme for every visitor via a data-theme attribute, regardless of their own system setting.

mode is case-insensitive and whitespace-tolerant (" Dark " works fine); anything outside "light"/"dark"/"auto"/None raises ValueError.

Favicon, Title, and Description

staypresent.web.markdown(
    "docs/guide.md",
    path="/docs",
    favicon="favicon.png",
    title="Project Docs",
    description="Everything you need to get started.",
)
Enter fullscreen mode Exit fullscreen mode
  • favicon accepts either a direct URL (used as-is) or a relative path next to file_path — resolved the same way neighboring static assets already are, and checked for existence immediately, just like file_path itself.
  • title sets both <title> and an og:title Open Graph tag, falling back to the Markdown file's own filename if omitted.
  • description adds a <meta name="description"> tag plus og:description — useful for link-preview cards when the page gets shared in a chat app or on social media.

Security: Escaping and URL Filtering

Plain text and every recognized Markdown construct is HTML-escaped before rendering, so a .md file containing <script> tags or stray </>/& characters can't inject markup into the page — only a fixed list of recognized block-level raw-HTML tags is ever passed through unescaped.

Link and image destinations are additionally checked against a scheme blocklist, independent of the escaping above: javascript: and vbscript: are rejected outright for both links and images, file: is rejected for both, and data: is rejected for links specifically (while still permitted for images, since inline data: images are a common, inert pattern). A rejected URL simply falls back to plain, escaped text rather than becoming a working link.

Neighboring files are exposed the same way html() exposes them. Everything in file_path's directory becomes servable as a static asset — not only the files actually referenced from the page. Keep the Markdown file's directory limited to files you're comfortable being publicly reachable; StayPresent logs a one-time WARNING the first time a directory gets exposed this way.

What It Doesn't Support

This is a lightweight renderer, not a full CommonMark implementation. It intentionally does not support: footnotes, definition lists as distinct syntax, custom containers/admonitions, LaTeX/math, reference-style links ([text][ref] + a separate [ref]: url line), or blockquote lazy continuation. For any of these, pre-render your Markdown with a different tool and serve the resulting HTML with web.html() instead.

Full Example

import staypresent

staypresent.web.markdown(
    "README.md",
    path="/",
    mode="auto",
    title="My Bot",
    description="A Telegram bot for X, Y, and Z.",
)
staypresent.web.markdown(
    "CHANGELOG.md",
    path="/changelog",
    mode="dark",
)

staypresent.run("bot.py")
Enter fullscreen mode Exit fullscreen mode

Best Practices

  • Keep .md files intended for public serving in a directory that contains only files you're fine exposing — the whole directory becomes reachable, not just the file you called markdown() on.
  • Use mode="auto" unless you have a specific brand reason to force a theme — it respects the visitor's own preference with zero extra effort.
  • Set title/description for any page you expect to get linked or shared, so previews render sensibly instead of falling back to a bare filename.

Common Mistakes

  • Expecting reference-style links ([text][ref]) to work. They don't — only the inline [text](url) form is supported; rewrite reference-style links before serving.
  • Placing sensitive files in the same directory as a publicly served .md file. They become reachable via the same static-asset mechanism, regardless of whether anything links to them.
  • Assuming a relative favicon resolves against the current working directory. It resolves relative to file_path's own directory, not the process's cwd.

Conclusion

Turning markdown to html in Python doesn't require pulling in a templating engine or a separate documentation build step. staypresent.web.markdown() renders your existing README.md or CHANGELOG.md into a clean, GitHub-styled page — complete with theming, favicon, and link-preview metadata — using nothing beyond what StayPresent already ships with.

pip install staypresent[prod]
Enter fullscreen mode Exit fullscreen mode

Top comments (0)