Documents the two-part architecture (mdcms.py CLI + index.html renderer), menu option reference, content structure, frontmatter fields, category system, nav.yml behaviour, dynamic post tags, known limitations, and key implementation quirks (non-general YAML parsers, category code validation). https://claude.ai/code/session_018gathVoTZhBFDJVp1mVaTc
6.2 KiB
CLAUDE.md
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
What this project is
MD-CMS is a markdown-based static site system with two distinct parts:
mdcms.py— a zero-dependency Python 3 CLI tool (standard library only). It scans content, generatesnav.ymlandsearch.json, validates config, and packages a zip for deployment.website/index.html— a single-file browser renderer that reads markdown, config, and nav at runtime entirely client-side. There is no build pipeline, no compilation, no server.
The website/ folder is the deployable artifact. mdcms.py lives outside it.
Running the CLI
python3 mdcms.py
This opens an interactive menu. Key options:
| Option | Action |
|---|---|
| 1 | Validate config + nav, rebuild search.json, export website.zip |
| 2 | Interactive wizard to create config.yml and folder structure from scratch |
| 3 | Scan pages/ and posts/, write nav.yml and search.json |
| 4 | Rebuild search.json only (faster, for content-only updates) |
| 5 | Check and interactively fix config.yml issues |
| 6 | Check and interactively fix nav.yml issues |
| 7 | Register/switch website paths (stored in ~/.mdcms/paths.json) |
| 8 | Start python3 -m http.server 8800 and open browser |
Local preview: Always use option 8 (or run python3 -m http.server 8800 in website/) rather than opening index.html directly — browsers block local file access due to CORS.
Architecture of mdcms.py
The file is a single-module Python script with these logical layers, in order:
- Path registry —
~/.mdcms/paths.jsonstores named project paths.active_path()returns the currently selected project. - Frontmatter parser (
parse_frontmatter) — reads---YAML blocks with a hand-rolled parser (no PyYAML). Returns a flat{key: value}dict with type coercion (bool, int, quoted strings). - Category system — category codes are parsed from
config.ymlbyparse_config_categories(). Variant files usebasename.<code>.mdnaming (e.g.about.nb.md).identify_variant()only recognises a suffix as a category code if it appears in the declared code list. - Scanner (
scan_and_categorize) — walkspages/orposts/, skips drafts (draft: truein frontmatter), returns records including first 5000 chars of body for search indexing. - Nav/search generators —
generate_nav_yml()andgenerate_search_json()produce the output files.parse_nav_yml()is a hand-rolled parser for reading back nav.yml (not a general YAML parser — only handles the exact format it emits). - Validators (
validate_config,validate_nav) — return lists of{field, issue, current}dicts for display; used in options 1, 5, and 6. - Menu actions (
do_*functions) — one per menu option, called bymain_menu().
Content structure inside website/
website/
index.html ← renderer (edit to change site behaviour)
config.yml ← required: sitename, navigation; rest optional
nav.yml ← generated; manual edits to section metadata are preserved on rebuild
search.json ← generated
pages/
home.md ← default landing page
about.md ← default variant
about.nb.md ← Norwegian variant (category suffix = nb)
posts/
2025-01-01-my-first-post.md
assets/
fonts/
images/
Page frontmatter fields
All optional except title:
---
title: Page Title
sort: 100 # controls nav ordering (lower = higher)
section-id: blog # assigns page to a nav section
draft: true # exclude from nav and search
author: Name
date: 2025-01-01
datetime: 2025-01-01 13:00 # use this for posts (not `date` alone — see known limitations)
modified: 2025-01-15 09:00
keywords: foo, bar
description: Short description for search
language: en
---
nav.yml structure
Sections and pages are separate lists. mdcms.py preserves manual edits to section fields (defaultname, sort, parent, parent-sort, pagesvisibility, categorynames) on each rebuild. New sections are auto-created from section-id values found in frontmatter.
pagesvisibility can be visible, hidden, or draft (draft excludes pages from search.json).
For nested navigation, set parent: <parent-section-code> and parent-sort on a section.
Category system
categories-use: yesinconfig.ymlenables categoriesdefault-category.codeis required when categories are enabled- Variant files:
<base>.<code>.md— the suffix is only treated as a category if the code is declared in config categories-sectionnames: per-categoryrequires each section innav.ymlto have acategorynamesblock with an entry per category code- RTL is set per category via
direction: rtl
Dynamic post tags (mdcms code blocks)
Embed post lists in pages using fenced blocks:
```mdcms
posts-datetime-reversechronological
limit: 10
paginate: yes
```
Reliable tags (others are known-broken): posts-datetime-chronological-byyearmonth, posts-datetime-reversechronological. Use datetime frontmatter (format: YYYY-MM-DD HH:MM) for posts — date alone does not work reliably.
Known limitations
mdcms.pyalways expects the deployable site in awebsite/subdirectory of the registered project path.- Most
posts-*tag variants are broken. Onlyposts-datetime-chronological-byyearmonthandposts-datetime-reversechronologicalreliably work. - Section headings in the nav are non-clickable (sections-sitemap is not yet implemented).
Key implementation details
parse_nav_yml()andparse_config_categories()are not general YAML parsers — they only handle the exact indentation/format thatmdcms.pyitself produces. Do not assume they handle arbitrary YAML.- Category code validation uses
CATEGORY_CODE_RE = re.compile(r"^[a-zA-Z0-9\-]+$")— codes must match this. - The
samplesite/directory is a reference implementation with multi-language categories (English, Norwegian, Arabic including RTL). It is not deployed; it exists for reference and testing. website/in the repo root is a minimal starter template (single page, no categories).