You now have a complete one-page publisher. The pure model compiles source, produces a styled HTML document and fingerprints its UTF-8 bytes. The local command handles bounded file input and ownership of an output directory.
Download the lab, run its tests and build the sample. The course preview lets you inspect the same source, parsed block records and rendered page. The parser has no raw-HTML pass-through branch and does not execute fenced or inline code.
Count bytes after UTF-8 encoding. len(html_string) counts Unicode characters, which is not the length of the file written to disk. Hash the exact same byte sequence that the writer publishes.
The digest is a content fingerprint for repeatability, not proof of author identity. Equivalent LF/CRLF source produces the same normalized structure and output; changed source changes the page and digest.
import hashlib
html = "<p>Café and π</p>\n"
data = html.encode("utf-8")
print("characters",len(html))
print("bytes",len(data))
print("sha256",hashlib.sha256(data).hexdigest())The downloadable CLI writes only into a directory it created or that carries its exact ownership marker. It refuses an unrelated nonempty directory and a symlink index. It compiles before replacing output, writes a temporary file within the owned directory, then atomically replaces index.html. A source failure leaves the previous successful page intact.
The archive includes the working model, unfinished capstone, CLI, original Markdown fixtures, styled sample, tests, subset reference, architecture SVG and review checklist. All page CSS is included. No external Markdown package, font or image download is needed.
Review the security examples as real data boundaries: HTML-like source becomes visible text; link destinations need their policy check; quoted attributes need quote escaping; template values are not recursively interpreted. The browser tests open the generated attack page with JavaScript enabled and check the resulting DOM.
from pathlib import PurePosixPath
owned_directory = PurePosixPath("site-preview")
print("output",owned_directory / "index.html")
print("marker",owned_directory / ".coderigor-page-lab")
print("compile before replacing the owned output")Complete the byte/digest step, then build and inspect the downloaded sample. Run the included parser/rendering tests and inspect the generated page in a browser. Rebuild after changing one paragraph and compare the digest; try an unsupported construct and confirm the previous page stays intact.
Extend the subset only after defining syntax, escaping, diagnostics and tests for the new construct. For a multi-page generator, add the required classes, recursion and functional-programming preparation before taking the related Boot.dev project.
Download the complete lab. The archive includes runnable code, tests, fixtures and a README.
Follow the sample through the compiler.
Inspect the authored source, its located block records and the actual styled output from the downloadable generator.
Markdown source
# A small page, built deliberately
This page started as *plain text*. Its structure was parsed before any HTML was generated.
## A useful boundary
- Keep source text as data.
- Give links an explicit policy.
- Publish only after the complete page succeeds.
Literal HTML-like input stays visible: <script>this is displayed, never run</script>.
```python
from pathlib import Path
message = "<source is data>"
print(message)
```
## Continue the project
Inspect [the HTML escaping reference](https://docs.python.org/3.14/library/html.html) and then change one paragraph and rebuild.
The larger [Boot.dev Static Site Generator project](https://www.boot.dev/courses/build-static-site-generator-python) continues into richer nodes and a complete website after classes, recursion and functional programming.
Located block records
[
{
"kind": "h1",
"lines": [
[
"A small page, built deliberately",
1,
3
]
],
"line": 1,
"language": ""
},
{
"kind": "paragraph",
"lines": [
[
"This page started as *plain text*. Its structure was parsed before any HTML was generated.",
3,
1
]
],
"line": 3,
"language": ""
},
{
"kind": "h2",
"lines": [
[
"A useful boundary",
5,
4
]
],
"line": 5,
"language": ""
},
{
"kind": "list",
"lines": [
[
"Keep source text as data.",
7,
3
],
[
"Give links an explicit policy.",
8,
3
],
[
"Publish only after the complete page succeeds.",
9,
3
]
],
"line": 7,
"language": ""
},
{
"kind": "paragraph",
"lines": [
[
"Literal HTML-like input stays visible: <script>this is displayed, never run</script>.",
11,
1
]
],
"line": 11,
"language": ""
},
{
"kind": "code",
"lines": [
[
"from pathlib import Path",
14,
1
],
[
"",
15,
1
],
[
"message = \"<source is data>\"",
16,
1
],
[
"print(message)",
17,
1
]
],
"line": 13,
"language": "python"
},
{
"kind": "h2",
"lines": [
[
"Continue the project",
20,
4
]
],
"line": 20,
"language": ""
},
{
"kind": "paragraph",
"lines": [
[
"Inspect [the HTML escaping reference](https://docs.python.org/3.14/library/html.html) and then change one paragraph and rebuild.",
22,
1
]
],
"line": 22,
"language": ""
},
{
"kind": "paragraph",
"lines": [
[
"The larger [Boot.dev Static Site Generator project](https://www.boot.dev/courses/build-static-site-generator-python) continues into richer nodes and a complete website after classes, recursion and functional programming.",
24,
1
]
],
"line": 24,
"language": ""
}
]The generated page
9 blocks · 2,977 UTF-8 bytes · Repeatable source and output included in the lab.
Download the generator and this sample →