mirror of
https://github.com/kbenestad/mdcms.git
synced 2026-06-18 15:24:32 +00:00
Revise CLAUDE.md for project updates and clarity
Updated project documentation for MD-CMS, including changes to the CLI tool description, repository layout, and version markers. Adjusted folder names and clarified local preview instructions.
This commit is contained in:
parent
bd3001c74e
commit
8a253e420e
1 changed files with 91 additions and 34 deletions
125
CLAUDE.md
125
CLAUDE.md
|
|
@ -6,55 +6,92 @@ This file provides guidance to Claude Code (claude.ai/code) when working with co
|
||||||
|
|
||||||
MD-CMS is a markdown-based static site system with two distinct parts:
|
MD-CMS is a markdown-based static site system with two distinct parts:
|
||||||
|
|
||||||
1. **`mdcms.py`** — a zero-dependency Python 3 CLI tool (standard library only). It scans content, generates `nav.yml` and `search.json`, validates config, and packages a zip for deployment.
|
1. **`mdcms.py`** — a Python 3 CLI tool (`click` + `PyYAML`). Manages a registry of sites, scans content, generates `nav.yml` and `search.json`, and is designed for both local use and GitHub Actions pipelines.
|
||||||
2. **`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.
|
2. **`app/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.
|
The `app/` folder is the deployable artifact and the starter template downloaded when registering a new site. `mdcms.py` lives outside it.
|
||||||
|
|
||||||
## Running the CLI
|
## Repository layout
|
||||||
|
|
||||||
```bash
|
```
|
||||||
python3 mdcms.py
|
mdcms.py ← CLI tool
|
||||||
|
pyproject.toml ← packaging (entry point, dependencies)
|
||||||
|
app/
|
||||||
|
index.html ← renderer + v0.3 version marker
|
||||||
|
config.yml ← starter config + v0.3 version marker
|
||||||
|
nav.yml ← generated
|
||||||
|
search.json ← generated
|
||||||
|
pages/
|
||||||
|
posts/
|
||||||
|
assets/
|
||||||
|
docs/
|
||||||
|
banner/
|
||||||
|
documentation.md
|
||||||
|
knownlimitations.md
|
||||||
|
quickstart.md
|
||||||
|
.github/workflows/release.yml ← cross-platform release builds
|
||||||
|
samplesite/ ← reference implementation (not deployed)
|
||||||
```
|
```
|
||||||
|
|
||||||
This opens an interactive menu. Key options:
|
## CLI commands
|
||||||
|
|
||||||
| Option | Action |
|
Install: `pip install mdcms` / `pipx install mdcms` — or use the standalone binary from a GitHub release.
|
||||||
|--------|--------|
|
|
||||||
| 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.
|
During development, run directly: `python3 mdcms.py <command>`
|
||||||
|
|
||||||
|
| Command | Description |
|
||||||
|
|---|---|
|
||||||
|
| `mdcms register <name> [path]` | Register a site. Downloads starter template from GitHub if no mdcms site is found at the path. Defaults to current directory. |
|
||||||
|
| `mdcms delete <name>` | Remove a site from the registry. Does not delete files. Prompts for confirmation. |
|
||||||
|
| `mdcms view` | List all registered sites with version and status. |
|
||||||
|
| `mdcms view <name>` | Show details: path, version, sitename, pages/posts count, sections, categories. |
|
||||||
|
| `mdcms build <name>` | Build `nav.yml` and `search.json` for a registered site. |
|
||||||
|
| `mdcms build --path <path>` | Build using an explicit path — no registry needed. Intended for CI/CD. |
|
||||||
|
| `mdcms build` | Build using current working directory. Simplest form for GitHub Actions. |
|
||||||
|
|
||||||
|
**Local preview:** Run `python3 -m http.server 8800` in the site directory and open `http://localhost:8800`. Do not open `index.html` directly — browsers block local file access due to CORS.
|
||||||
|
|
||||||
## Architecture of `mdcms.py`
|
## Architecture of `mdcms.py`
|
||||||
|
|
||||||
The file is a single-module Python script with these logical layers, in order:
|
Single-module Python script. Logical layers in order:
|
||||||
|
|
||||||
1. **Path registry** — `~/.mdcms/paths.json` stores named project paths. `active_path()` returns the currently selected project.
|
1. **Version helpers** — `read_site_version()` reads the `mdcms v0.3` marker from the first line of `config.yml`. `version_status()` classifies sites as `ok`, `outdated`, `newer`, or `unsupported` against `MIN_SUPPORTED_VERSION`.
|
||||||
2. **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).
|
2. **Registry** — `~/.config/mdcms/sites.json` stores `{name: {path, version}}`. `load_registry()` / `save_registry()` / `resolve_site_path()`.
|
||||||
3. **Category system** — category codes are parsed from `config.yml` by `parse_config_categories()`. Variant files use `basename.<code>.md` naming (e.g. `about.nb.md`). `identify_variant()` only recognises a suffix as a category code if it appears in the declared code list.
|
3. **Config reading** — `read_config()` reads `config.yml` with `yaml.safe_load()`. `get_category_info()` extracts category settings from the parsed dict.
|
||||||
4. **Scanner** (`scan_and_categorize`) — walks `pages/` or `posts/`, skips drafts (`draft: true` in frontmatter), returns records including first 5000 chars of body for search indexing.
|
4. **Frontmatter parser** (`parse_frontmatter`) — reads `---` YAML blocks using `yaml.safe_load()`. Returns `(meta_dict, body_text)`.
|
||||||
5. **Nav/search generators** — `generate_nav_yml()` and `generate_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).
|
5. **Category system** — `identify_variant()` splits `.md` paths into `(base, category_code)`. A suffix is only treated as a category code if it appears in the declared code list.
|
||||||
6. **Validators** (`validate_config`, `validate_nav`) — return lists of `{field, issue, current}` dicts for display; used in options 1, 5, and 6.
|
6. **Scanner** (`scan_and_categorize`) — walks a directory, skips drafts, returns records with the first 5000 chars of body for search indexing. Paths are relative to `site_root`.
|
||||||
7. **Menu actions** (`do_*` functions) — one per menu option, called by `main_menu()`.
|
7. **Nav/search generators** — `generate_nav_yml()` emits a fixed-format YAML subset. `generate_search_json()` emits a JSON array. `merge_sections()` preserves existing section metadata on rebuild.
|
||||||
|
8. **Core build** (`run_build`) — orchestrates the full build: version check → config read → scan → merge → write nav.yml and search.json.
|
||||||
|
9. **Template download** (`download_template`) — fetches `app/` from GitHub via the Contents API using `urllib` (no extra dependencies). Recursively downloads files and directories.
|
||||||
|
10. **CLI commands** (`register`, `delete`, `view`, `build`) — implemented with `click`. Entry point: `main()` → `cli()`.
|
||||||
|
|
||||||
## Content structure inside `website/`
|
## Version markers
|
||||||
|
|
||||||
|
Every mdcms site has a version marker on the first line of two files:
|
||||||
|
|
||||||
|
- `config.yml` line 1: `# mdcms v0.3 | DO NOT REMOVE THIS COMMENT`
|
||||||
|
- `index.html` line 1: `<!-- mdcms v0.3 | DO NOT REMOVE THIS COMMENT -->`
|
||||||
|
|
||||||
|
`register` and `build` both read the marker from `config.yml` to detect and validate the site. Sites with no marker are not recognised as mdcms sites. Sites below `MIN_SUPPORTED_VERSION` are rejected.
|
||||||
|
|
||||||
|
There are two distinct version numbers:
|
||||||
|
- **CLI version** (`CLI_VERSION` in `mdcms.py`, `version` in `pyproject.toml`) — bumped with every release.
|
||||||
|
- **Site format version** (markers in `config.yml` and `index.html`) — only bumped when the site file format has a breaking change. Many CLI releases may share the same site format version.
|
||||||
|
|
||||||
|
## Site structure
|
||||||
|
|
||||||
|
The registered path points directly to the directory containing `index.html` (the site root). There is no `website/` subdirectory.
|
||||||
|
|
||||||
```
|
```
|
||||||
website/
|
<site-root>/
|
||||||
index.html ← renderer (edit to change site behaviour)
|
index.html ← renderer
|
||||||
config.yml ← required: sitename, navigation; rest optional
|
config.yml ← required: sitename, navigation; rest optional
|
||||||
nav.yml ← generated; manual edits to section metadata are preserved on rebuild
|
nav.yml ← generated; manual edits to section metadata are preserved
|
||||||
search.json ← generated
|
search.json ← generated
|
||||||
pages/
|
pages/
|
||||||
home.md ← default landing page
|
home.md ← default landing page
|
||||||
about.md ← default variant
|
about.md
|
||||||
about.nb.md ← Norwegian variant (category suffix = nb)
|
about.nb.md ← Norwegian variant (category suffix = nb)
|
||||||
posts/
|
posts/
|
||||||
2025-01-01-my-first-post.md
|
2025-01-01-my-first-post.md
|
||||||
|
|
@ -113,15 +150,35 @@ 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.
|
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.
|
||||||
|
|
||||||
|
## Release workflow
|
||||||
|
|
||||||
|
`.github/workflows/release.yml` triggers on version tags (`v*`). Uses a matrix of three runners:
|
||||||
|
|
||||||
|
| Runner | Output |
|
||||||
|
|---|---|
|
||||||
|
| `ubuntu-latest` | `mdcms-linux-amd64` binary + `mdcms_<version>_amd64.deb` (via PyInstaller + fpm) |
|
||||||
|
| `macos-latest` | `mdcms-macos-arm64` binary |
|
||||||
|
| `windows-latest` | `mdcms-windows-amd64.exe` |
|
||||||
|
|
||||||
|
All artifacts are attached to the GitHub release using `gh release create`.
|
||||||
|
|
||||||
|
**Release checklist** — before tagging:
|
||||||
|
1. Update `CLI_VERSION` in `mdcms.py`
|
||||||
|
2. Update `version` in `pyproject.toml`
|
||||||
|
3. Update site format markers in `app/config.yml` and `app/index.html` only if the site format changed
|
||||||
|
|
||||||
|
Then: `git tag v0.3.1 && git push origin v0.3.1`
|
||||||
|
|
||||||
## Known limitations
|
## Known limitations
|
||||||
|
|
||||||
- `mdcms.py` always expects the deployable site in a `website/` subdirectory of the registered project path.
|
|
||||||
- Most `posts-*` tag variants are broken. Only `posts-datetime-chronological-byyearmonth` and `posts-datetime-reversechronological` reliably work.
|
- Most `posts-*` tag variants are broken. Only `posts-datetime-chronological-byyearmonth` and `posts-datetime-reversechronological` reliably work.
|
||||||
- Section headings in the nav are non-clickable (sections-sitemap is not yet implemented).
|
- Section headings in the nav are non-clickable (sections-sitemap is not yet implemented).
|
||||||
|
|
||||||
## Key implementation details
|
## Key implementation details
|
||||||
|
|
||||||
- `parse_nav_yml()` and `parse_config_categories()` are **not** general YAML parsers — they only handle the exact indentation/format that `mdcms.py` itself produces. Do not assume they handle arbitrary YAML.
|
- `generate_nav_yml()` emits a fixed-format YAML subset. It is **not** a general YAML emitter — do not assume it handles arbitrary structures.
|
||||||
|
- `yaml.safe_load()` is used for all YAML reading (config.yml, nav.yml, frontmatter). The nav.yml parser depends on PyYAML, not a hand-rolled parser.
|
||||||
- Category code validation uses `CATEGORY_CODE_RE = re.compile(r"^[a-zA-Z0-9\-]+$")` — codes must match this.
|
- Category code validation uses `CATEGORY_CODE_RE = re.compile(r"^[a-zA-Z0-9\-]+$")` — codes must match this.
|
||||||
|
- `scan_and_categorize()` takes both `directory` and `site_root` — paths in records are always relative to `site_root`.
|
||||||
- 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.
|
- 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).
|
- Template download uses `urllib` (stdlib) only — no `requests` dependency.
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue