plssg is a static site generator inspired by werc and
oscean). It runs on the
Python 3 standard library alone — no pip install,
no node_modules, no build tool to learn — and ships with its JavaScript
virginity intact: every page is plain HTML + CSS, served (or opened via
file://) as-is.
Write Markdown under src/, run python3 bin/main.py, get a navigable
multi-site tree under build/html/. Optionally get matching LaTeX books under
build/tex/ if xelatex is around.
plssg/
├── bin/ # the generator itself (one job per file)
│ ├── main.py # entry point + pipeline orchestration
│ ├── layout.py # CSS, template, top/side nav, asset copy
│ ├── render.py # walks src/, dispatches every .md/.html to html.py
│ ├── html.py # Markdown subset → HTML (pure function)
│ ├── tex.py # Markdown subset → LaTeX (one book per sub-site)
│ └── rss.py # build/html/rss.xml from rendered pages, sorted by mtime
│
├── etc/ # all configuration lives here
│ ├── info.conf # site title, tagline, favicon, logo, footer, RSS
│ ├── themes.conf # [global] + per-sub-site CSS variable overrides
│ ├── html.conf # html.py toggles (paragraph tag, lists, auto-id, …)
│ ├── template.html # HTML skeleton with {{placeholders}}
│ └── template.tex # LaTeX skeleton (xeCJK-ready)
│
├── src/ # your content
│ ├── index.md # the root pseudo-site (rendered at build root)
│ ├── assets/ # shared favicon / logo / etc.
│ └── 01_Example/ # one sub-site; NN_ prefix only controls nav order
│ ├── index.md # → /Example/index.html
│ └── assets/ # img/ file/ auto-created on first run
│
└── build/ # output, regenerated each run (safe to delete)
├── html/ # the static site
└── tex/ # one xelatex-able book per sub-site (if template.tex exists)
Conventions worth remembering:
- A directory name
NN_Nameis stripped toNamein URLs and display — the prefix exists purely to order top-nav entries. - Files / directories starting with
.or_are skipped by the generator. index.mdbecomesindex.htmlin place; everything elseNN_thing.md→thing.html.- A directory without an
index.mdgets a synthesised listing page, so navigation never 404s.
-
Clone & write. Drop Markdown into
src/. The shippedsrc/index.mdandsrc/01_Example/index.mdare minimal placeholders — overwrite them. -
Configure. Edit
etc/info.conffor title / logo / footer / RSS, andetc/themes.conffor colours, fonts, sidebar width, etc. Every key in athemes.confsection becomes a CSS variable (color_text→--color-text), overlaid on the defaults baked intobin/layout.py. -
Build.
python3 bin/main.pyThe pipeline, per sub-site:
layout.generate_css→build/html/<site>/style.csslayout.generate_temp_html→build/html/<site>/_temp.html(template with everything but{{article_content}}filled in)render.render_site→ one.htmlper source.md, with_temp.htmlas the wrapperlayout.copy_assets→ mirrorsassets/,images/,_img/,static/,media/,pub/verbatim
Then once globally:
rss.generate_rsswritesbuild/html/rss.xml(newest-50 by mtime), andrender.post_process_relative_urlsrewrites absolute hrefs to relative so each page opens cleanly underfile://. -
(Optional) PDF. If
etc/template.texis present,bin/tex.pyemits one book per sub-site intobuild/tex/<book>/. Compile withcd build/tex/<book>/ && xelatex <book>.tex. -
Serve. Anything that serves static files works. For local preview:
cd build/html && python3 -m http.serverOr just double-click any
.html— relative URLs makefile://viable too.
New sub-site? Make a folder under src/, run main.py again; an empty
[<site_id>] section is auto-appended to themes.conf for you to fill in
overrides, and assets/img/ + assets/file/ skeletons are created
underneath.