CodeRigor

LESSON

Split blocks without losing code or locations

Recognize blank-separated headings, paragraphs, flat lists and fenced code while preserving source positions.

Block structure decides whether a line is a heading, paragraph, list item or literal code. Parse it before looking for inline emphasis and links. Otherwise a # inside a code fence can accidentally become a heading.

Our subset uses blank-separated ordinary blocks. A heading occupies one line; a flat list has one - item per source line; a paragraph can wrap across ordinary lines. Inline constructs must open and close on one source line. These rules are smaller than general Markdown and are documented deliberately.

Lines become located blocks
start / 3
A blank-separated group becomes a heading, paragraph or list. Fenced mode preserves its entire literal body and locations.
Lines become located blocks
A blank-separated group becomes a heading, paragraph or list. Fenced mode preserves its entire literal body and locations.

A fence opener is exactly with optional text or python immediately after it. The closing line is exactly. Everything between them is literal, including blank lines, indentation and strings that look like markup. An absent closer reports the opener’s location.

For other groups, classify the first line and reject mixed structure. A paragraph followed immediately by a - item needs a blank separator under this language contract.

Python
lines = ["# Title", "", "First paragraph", "second line", "", "- one", "- two"]
groups, current = [], []
for number,text in enumerate(lines,1):
    if not text.strip():
        if current:groups.append(current);current=[]
    else:current.append((text,number))
if current:groups.append(current)
print(groups)

Store content with its original line and content-start column. For # headings the first text column is three; for ## it is four; for - items it is three. A later unclosed inline marker can then report the actual source position rather than a column in a silently joined paragraph.

The supplied Block dataclass contains kind, located lines, first line and optional code language. Its tuples are immutable. Parsing creates structure only; it does not emit HTML or open files.

Unsupported indentation, deeper headings, ordered lists, quotes, tables and horizontal rules fail with location/code pairs. A narrow parser should report those limits rather than guessing an interpretation that the renderer cannot honor.

Fence content stays literal
start / 3
The parser enters fence mode, stores unchanged lines and exits only at an exact closing marker. Inline parsing never touches those code lines.
Fence content stays literal
The parser enters fence mode, stores unchanged lines and exits only at an exact closing marker. Inline parsing never touches those code lines.
Python
source_line = "## A *small* heading"
prefix = 3
content = source_line[prefix:]
marker = content.index("*")
print("content",content)
print("asterisk source column", prefix+1+marker)

Implement the splitter and inspect records for the supplied heading/paragraph/list/code fixture. Test blank code lines and indentation as well as an unclosed fence, mixed blocks and an unsupported language.

Next the inline parser will turn each ordinary content line into text/code/emphasis/link tokens without generating HTML.

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

Markdown
# 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

JSON
[
  {
    "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 →

Watch & read

Free lectures, talks and readings that cover this skill from another angle. All resources →

Optional file-I/O companion for the local build command. The original parser and HTML-escaping rules are taught in the authored lessons.