Static documentation generator for Python projects
Project description
slop-doc
Static documentation generator for Python projects. Parses Python source code via AST, renders Markdown pages with embedded data tags and presentation functions into a styled 3-column HTML site with navigation tree, cross-linking, and search.
Installation
pip install slop-doc
Requires Python >= 3.10.
Quick Start
# 1. Create a docs folder with starter root.md
slop-doc init --name docs
# 2. Edit docs/root.md, add .md pages
# 3. Build
slop-doc build -d docs
# 4. Open in browser
slop-doc open -d docs
CLI Commands
| Command | Description |
|---|---|
slop-doc init [--name <folder>] |
Create a new docs folder with a starter root.md. Default name: docs |
slop-doc build [-d <dir>] |
Build documentation. Looks for root.md in -d dir or current directory |
slop-doc open [-d <dir>] [-p <port>] |
Serve built docs via local HTTP server and open in browser. SPA navigation works here (default port 8000) |
How It Works
The folder structure is the documentation tree. Every .md file becomes a page; every subfolder with a root.md becomes a folder node in the navigation.
my-docs/ ← docs root (contains root.md)
├── root.md ← project config + landing page
├── getting-started.md ← top-level page
├── 1-installation.md ← sorted by numeric prefix (prefix stripped from title)
├── 2-usage.md
└── api/ ← subfolder = folder node in nav
├── root.md ← folder config + folder landing page
├── overview.md ← page inside the folder
└── advanced/
└── root.md
Build output: a self-contained HTML site with assets/style.css, assets/app.js, and one .html per page. Use slop-doc open to serve locally — pages load via SPA navigation (no full reload, smooth fade transitions). Also works with file:// protocol (falls back to standard page loads).
Front-matter
Each .md file can start with a JSON config block. The block is relaxed JSON — supports // comments, # comments, trailing commas, and unquoted keys.
{
"title": "My Page",
"default_source_folder": "../src/mypackage"
}
# My Page
Page content here...
Page-level keys
| Key | Type | Description |
|---|---|---|
title |
string |
Display name in the nav tree. Falls back to the first # heading, then filename |
default_source_folder |
string |
Path to Python source folder for this page (and its children). Resolved relative to the parent directory of the docs root |
children |
object |
Auto-generated child pages from source code. See Children |
order |
int |
Explicit sort position in the nav tree (lower = shown first). Optional — unordered pages keep their filename-based position after ordered ones |
Project-level keys (only in the root root.md)
| Key | Type | Default | Description |
|---|---|---|---|
project_name |
string |
"Documentation" |
Displayed in the site header |
version |
string |
"" |
Shown in page titles |
output_dir |
string |
"build" |
Output folder (relative to docs root) |
assets_dir |
string |
— | Custom assets folder (relative to docs root). Files here override defaults |
Example root.md
{
"title": "MyProject Docs",
"project_name": "MyProject",
"version": "2.1.0",
"output_dir": "build",
"default_source_folder": "../src/myproject"
}
# Welcome to MyProject
This is the landing page.
Source Folder
The default_source_folder key tells slop-doc where to find your Python source code. It is inherited by child pages — set it once on a folder's root.md and all pages inside that folder use it automatically.
A deeper root.md or individual page can override it with its own default_source_folder.
Path resolution: always relative to the parent of the docs root, not relative to the .md file. For example, if your docs are in project/docs/ and your source is in project/src/mypackage/, use:
{
"default_source_folder": "../src/mypackage"
}
This works regardless of how deeply nested the .md file is.
Data Tags
Data tags are placeholders in Markdown that expand to lists of items from your Python source code. Write them as {{tag_name}} in the page body.
Available tags
| Tag | What it lists |
|---|---|
{{classes}} |
All classes (from files directly in the source folder) |
{{functions}} |
All module-level functions |
{{constants}} |
All constants (ALL_CAPS names) |
{{enums}} |
Enum classes |
{{dataclasses}} |
Dataclass classes |
{{interfaces}} |
Abstract base classes (ABC / have abstract methods) |
{{protocols}} |
Protocol classes |
{{exceptions}} |
Exception classes |
{{plain_classes}} |
Regular classes that don't fit any category above |
Each tag has a recursive variant with _rec suffix that includes files from all subfolders:
| Flat (direct files only) | Recursive (all subfolders) |
|---|---|
{{classes}} |
{{classes_rec}} |
{{functions}} |
{{functions_rec}} |
{{enums}} |
{{enums_rec}} |
| ... | ... |
Inline rendering
In the page body, class-type tags render as cross-linked lists:
## Available Classes
{{classes}}
Becomes something like: DataSource, FetchSpec, ColumnMap — each name is a clickable cross-link to its class page.
If no items are found, renders as: None found.
Children (Auto-Generated Pages)
The children key in front-matter generates child pages from source code. Each child gets a full dedicated page with class/function documentation.
{
"title": "API Reference",
"default_source_folder": "../src/mypackage",
"children": {
"classes": "{{classes}}"
}
}
This creates a child page for every class found in the source folder. Each child page appears in the nav tree under this folder.
Syntax
The children value is an object mapping a type to a list of names:
{
"children": {
"classes": "{{classes}}",
"functions": "{{functions}}"
}
}
You can also mix tag expansion with explicit names:
{
"children": {
"classes": ["{{interfaces}}", "MySpecialClass"]
}
}
Supported child types
| Type | Generates |
|---|---|
classes |
Class pages with full docs (description, info, properties, methods) |
enums |
Same as classes, filtered to enums |
dataclasses |
Same, filtered to dataclasses |
interfaces |
Same, filtered to interfaces/ABCs |
protocols |
Same, filtered to protocols |
exceptions |
Same, filtered to exceptions |
plain_classes |
Same, filtered to plain classes |
functions |
Function pages (name + stub) |
Auto-generated class page content
Each auto-generated class page contains (empty sections are automatically hidden):
# ClassName
(class description)
## Info
(module, file:line, base classes)
## Properties
(table of @property methods — hidden if none)
## Public Methods
(summary table with links — hidden if none)
## Private Methods
(summary table — hidden if none)
## Method Details
(full signature, parameters, returns, raises for each method)
Presentation Functions
Presentation functions render structured data tables and detail blocks from your source code. Write them as %function_name(args)% in the page body.
Table functions
| Function | Output |
|---|---|
%classes_table({{classes}})% |
Table of classes with descriptions. Class names are cross-links |
%functions_table({{functions}})% |
Table of functions with signatures and descriptions |
%constants_table({{constants}})% |
Table of constants with values and types |
The argument can be a {{tag}} (expands to all matching items), a comma-separated list of names, or empty (uses all).
Class detail functions
| Function | Output |
|---|---|
%class_description(ClassName)% |
Short + full description |
%class_info(ClassName)% |
Table: module, file:line, base classes |
%properties(ClassName)% |
Table of @property methods: name, type, description |
%base_classes(ClassName)% |
Comma-separated list of base classes |
%decorators(ClassName)% |
Comma-separated list of decorators |
%source_link(ClassName)% |
Source file path and line number |
Methods functions
| Function | Output |
|---|---|
%methods_table(ClassName)% |
Public methods summary table |
%methods_table(ClassName, private)% |
Private methods (_name) table |
%methods_table(ClassName, static)% |
Static methods table |
%methods_table(ClassName, classmethod)% |
Class methods table |
%methods_table(ClassName, dunder)% |
Dunder methods (__name__) table |
%methods_table(ClassName, all)% |
All methods (public + private, including dunder) |
%methods_details(ClassName)% |
Full detail blocks for all methods: signature, params, returns, raises |
Example page
{
"title": "API Reference",
"default_source_folder": "../src/mypackage"
}
# API Reference
## Classes
%classes_table({{classes}})%
## Functions
%functions_table({{functions}})%
## Constants
%constants_table({{constants}})%
Cross-Links
Link to any class or method page from anywhere in your documentation using [[double bracket]] syntax.
| Syntax | Links to |
|---|---|
[[folder/ClassName]] |
Class page |
[[folder/ClassName.method_name]] |
Method anchor on the class page |
[[folder/ClassName|Display Text]] |
Class page with custom display text |
The folder is the basename of the source folder. For example, if default_source_folder is ../src/mypackage, the folder slug is mypackage.
See the [[mypackage/DataSource]] class for details.
The [[mypackage/DataSource.fetch]] method handles data retrieval.
Check [[mypackage/DataSource|the data source]] documentation.
Hidden class pages
Every class in an indexed source folder automatically gets a dedicated page, even if not explicitly listed in children. These "hidden" pages:
- Are rendered with full class documentation
- Are indexed for cross-link resolution
- Appear in search results
- Do not appear in the navigation tree
This means [[folder/AnyClass]] always resolves, as long as the class exists in a parsed source folder.
Docstring Format
slop-doc parses Google-style docstrings:
class MyClass(BaseClass):
"""Short description of the class.
Longer description that provides more detail
about the class and its purpose.
"""
def my_method(self, name: str, count: int = 5) -> list[str]:
"""Short description of the method.
More detailed description here.
Args:
name: The name to process.
count: How many times to repeat. Defaults to 5.
Returns:
A list of processed strings.
Raises:
ValueError: If name is empty.
TypeError: If count is not an integer.
"""
What gets parsed
- Classes: all public classes (private
_ClassNameare skipped) - Methods: all methods including private and dunder, with full signatures
- Functions: top-level functions only (private and dunder are skipped)
- Constants:
ALL_CAPSnames assigned at module level - Properties: methods decorated with
@property - Type annotations: preserved from source, displayed in method signatures and tables
Class classification
Classes are automatically classified based on their base classes and decorators:
| Category | Detection rule |
|---|---|
| Enum | Inherits from Enum, IntEnum, StrEnum, Flag, IntFlag |
| Dataclass | Has @dataclass decorator |
| Interface/ABC | Inherits from ABC/ABCMeta or has any @abstractmethod |
| Protocol | Inherits from Protocol |
| Exception | Inherits from Exception/BaseException or name ends with Error/Exception |
| Plain class | None of the above |
File Sorting
Files are sorted in the nav tree by:
orderfront-matter field first: pages with"order": Nappear before unordered pages, sorted ascending by N- Numeric prefix second:
1-intro.md,2-setup.md,3-api.md— sorted by number - Alphabetical third: files without numeric prefix or order sort alphabetically
The numeric prefix is stripped from the display title: 1-introduction.md shows as "Introduction".
Example using order:
{
"title": "Getting Started",
"order": 1
}
Assets and Styling
slop-doc ships with a default dark theme (style.css) and client-side app (app.js).
The app provides:
- SPA navigation — internal links are fetched and swapped without full page reload (falls back to normal navigation on
file://or fetch failure) - Smooth transitions — content fade-in, nav tree expand/collapse animation, sidebar width transitions
- Client-side search — search index is embedded inline in each page (no server required)
- Scroll spy — right sidebar highlights the current section on scroll
- Anchor highlight — clicking an anchor link smoothly scrolls and flashes the target element
- Nav tree persistence — expand/collapse state is saved in localStorage across page loads
To customize styling:
-
Set
assets_dirin your rootroot.md:{ "assets_dir": "assets" }
-
Place your custom
style.cssin that folder. It will replace the default.
The app.js is always copied from defaults (the search index is embedded inline in each page).
Complete Example
Project structure
my-project/
├── src/
│ └── mylib/
│ ├── __init__.py
│ ├── client.py # Client, Config classes
│ ├── models.py # User, Product dataclasses
│ └── exceptions.py # ApiError, NotFoundError
└── docs/
├── root.md
├── 1-getting-started.md
└── api/
├── root.md
└── overview.md
docs/root.md
{
"title": "MyLib",
"project_name": "MyLib",
"version": "1.0.0",
"output_dir": "build"
}
# MyLib Documentation
Welcome to the MyLib documentation.
docs/1-getting-started.md
# Getting Started
## Installation
```bash
pip install mylib
```
## Quick Start
```python
from mylib import Client
client = Client(api_key="...")
result = client.fetch("data")
```
docs/api/root.md
{
"title": "API Reference",
"default_source_folder": "../../src/mylib",
"children": {
"classes": "{{classes}}"
}
}
# API Reference
## All Classes
%classes_table({{classes}})%
## Functions
%functions_table({{functions}})%
## Constants
%constants_table({{constants}})%
This generates:
- A nav tree: Getting Started, API Reference > Client, Config, User, Product, ApiError, NotFoundError
- Each class gets a full page with methods, properties, signatures
- Cross-links like
[[mylib/Client]]work from any page
Build and view
cd docs
slop-doc build
slop-doc open
Output Structure
docs/build/
├── index.html ← root.md landing page
├── getting-started.html
├── api/
│ ├── index.html ← api/root.md
│ ├── client.html ← auto-generated from children
│ ├── config.html
│ ├── user.html
│ └── ...
└── assets/
├── style.css
└── app.js
Summary of Syntax
| Syntax | Where | Purpose |
|---|---|---|
{ "key": "value" } |
Top of .md file |
Front-matter config |
{{tag}} |
Page body or children value |
Expand to list of items from source |
{{tag_rec}} |
Page body or children value |
Same but recursive (includes subfolders) |
%function(args)% |
Page body | Render tables/details from source data |
[[folder/Class]] |
Page body | Cross-link to class page |
[[folder/Class.method]] |
Page body | Cross-link to method anchor |
[[folder/Class|text]] |
Page body | Cross-link with custom display text |
Architecture
Modules
| Module | Purpose |
|---|---|
builder.py |
Build orchestrator — drives the full pipeline, CLI commands (init, build, open) |
tree_builder.py |
Walks the docs folder, builds the navigation tree of Node objects |
frontmatter.py |
Parses relaxed JSON front-matter from .md files |
parser.py |
Python AST source parser — extracts classes, functions, constants, docstrings |
tag_renderer.py |
Expands {{data tags}} and %presentation functions% into HTML |
cross_links.py |
Builds cross-link index and resolves [[Target]] patterns |
markdown_renderer.py |
Converts Markdown to HTML, adds heading anchors |
layout.py |
Assembles 3-column HTML pages: nav tree, content, contents sidebar |
Build Pipeline
When slop-doc build runs, the following steps execute in order:
1. Read project config
└─ Parse root.md front-matter → project_name, version, output_dir, assets_dir
2. Build navigation tree
└─ Recursively walk docs folder (tree_builder.py)
├─ Parse each .md front-matter + body
├─ Resolve default_source_folder (inherited down the tree)
├─ Expand children: generators ({{classes}}, {{functions}}, etc.)
├─ Parse Python source folders on demand (cached)
└─ Sort nodes: order field → numeric prefix → alphabetical
3. Build cross-link index
└─ Walk tree, index every class page URL + method anchors
4. Generate search index
└─ Walk tree, collect all pages/classes/methods/functions/constants → JSON
5. Render each page
│ For each node in the tree:
│
├─ Auto-class pages → generate Markdown body from presentation functions
├─ Regular pages → use .md file content
├─ Empty folders with children → simple "# Title" placeholder
│
└─ Page rendering pipeline:
a. Expand %presentation_functions(args)% → HTML tables/details
b. Expand remaining {{data_tags}} → cross-links or inline text
c. Strip empty sections → remove headings with no content
d. Markdown → HTML → via python-markdown
e. Resolve [[cross-links]] → relative <a href> tags
f. Assemble full HTML page → 3-column layout with nav, breadcrumb, search index
6. Copy assets
└─ User assets (override) → default style.css (fallback) → app.js (always from defaults)
Source Parsing
parser.py uses Python's ast module to extract structured data from .py files:
- Classes: name, base classes, decorators, docstring, properties, methods, classification (enum / dataclass / interface / protocol / exception / plain)
- Functions: name, args (with types + defaults), return type, decorators, docstring
- Constants:
ALL_CAPSmodule-level assignments with values and types - Docstrings: Google-style parsing —
Args:,Returns:,Raises:,Examples:sections
Two scopes per folder:
- Flat — only direct
.pyfiles (used by{{classes}},{{functions}}, etc.) - Recursive — includes all subfolders (used by
{{classes_rec}},{{functions_rec}}, etc.)
Tree Structure
Each node in the tree is a Node dataclass:
Node
├── title display title
├── content markdown body (from .md file)
├── source path to Python source folder
├── output_path relative output path (e.g. api/client.html)
├── children list of child Nodes
├── order explicit sort order (from front-matter)
├── is_auto true for auto-generated pages
├── auto_class class name (for auto class pages)
├── auto_function function name (for function nav nodes)
└── meta PageMeta from front-matter
Folder structure maps directly to the nav tree:
root.mdin a folder → folder node config + landing page- Other
.mdfiles → child pages - Subfolders with
root.md→ nested folder nodes childrenfront-matter → auto-generated class/function pages
HTML Output
Each page is a self-contained HTML file with embedded search index:
<header> project name | breadcrumb | search input </header>
<nav class="sidebar-left"> ← navigation tree (persistent expand/collapse state)
<main class="content"> ← rendered page content
<aside class="sidebar-right"> ← table of contents (h2/h3 headings, scroll spy)
<script src="assets/app.js"> ← SPA navigation, search, scroll spy, transitions
<script> ← embedded search index + prefix (inline, no CORS issues)
Client-Side App (app.js)
The app runs entirely client-side with no build step or dependencies:
- SPA navigation: intercepts internal link clicks, fetches pages via
fetch(), swaps.content/ sidebar / breadcrumb / nav without full reload. Falls back to normal navigation onfile://or fetch failure - Search: filters embedded
__SEARCH_INDEX__by title substring, renders dropdown - Scroll spy: highlights current section in the right sidebar on scroll
- Nav tree: expand/collapse with localStorage persistence, animated via
max-heightCSS transitions - Anchor navigation: smooth scroll + highlight flash animation on target element
- Sidebar sync:
ResizeObserverkeeps content margin aligned with dynamic sidebar width - History:
pushState/popstatefor browser back/forward support within SPA
License
MIT
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file slop_doc-1.0.tar.gz.
File metadata
- Download URL: slop_doc-1.0.tar.gz
- Upload date:
- Size: 66.3 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.14.3
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
198d184e4a3e4519bade2eff4a0e3cdab47f34d9b380fe757ae8a2bc941cf4d1
|
|
| MD5 |
2d2ab460c55a77559b0606d6b91715a2
|
|
| BLAKE2b-256 |
4e30cac645bdf6d01eff2e1edd60d7dbe99a09a4405a8b264b0eb091b14801fa
|
File details
Details for the file slop_doc-1.0-py3-none-any.whl.
File metadata
- Download URL: slop_doc-1.0-py3-none-any.whl
- Upload date:
- Size: 65.1 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.14.3
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
6bf8ce3193bb7a5c7f7bd7f461384d52bb3c54fd9f7b173c161786710a789e4a
|
|
| MD5 |
f838f89ae7df229dcefdfe083e3f4760
|
|
| BLAKE2b-256 |
fa1724592a7d009e482f4d23ce2e45e9d8ff62f1a32b7d0e562c3ccfe3be11d9
|