Context: Writing raw HTML for documentation is tedious. I wanted a CLI tool that lets me write Markdown and get clean HTML pages out — and I wanted to build it in Go to learn the language's standard library.
Challenges: Parsing Markdown correctly across headings, code blocks, inline code, links, and nested lists without pulling in an external parsing library. The main challenge was handling edge cases in the line-by-line parsing logic without a formal grammar.
Result: A working CLI tool that converts Markdown files into styled HTML — handles all standard elements including headings, code blocks, lists, and links. Serves as the foundation for generating styled article pages from plain Markdown source.
How the Parser Works
Markdown Parser — Line-by-Line State Machine (Go) ═════════════════════════════════════════════════════ file.md → bufio.Scanner → one line at a time │ ┌─────────────▼─────────────┐ │ PARSER STATE │ │ │ │ ┌─────────────────────┐ │ │ │ NORMAL │ │ │ └──────────┬──────────┘ │ │ │ │ │ line starts with: │ │ │ │ "#" → emit <h1>–<h6> │ │ │ │ "```" → CODE_BLOCK ────┤ │ collect lines │ │ until next "```"│◄── blank line inside? │ emit <pre><code>│ stay in state (fix) │ │ │ "- "/"* " → LIST │ │ emit <li> │ │ track <ul> open │ │ │ │ text → emit <p> │ └────────────────────────────┘ │ ▼ emit HTML tokens │ ▼ ┌─────────────────────────┐ │ output file.html │ │ + injected CSS │ └─────────────────────────┘ The state machine was the key fix — a stateless processor closed code blocks on blank lines, producing malformed HTML.
Inline Transforms — Applied Within Each Line ═══════════════════════════════════════════════ input: "See **bold** and `code` and [link](url)" │ ▼ strings.Replace passes (in order): "**text**" → <strong>text</strong> "*text*" → <em>text</em> "`code`" → <code>code</code> "[t](url)" → <a href="url">t</a> output: "See <strong>bold</strong> and <code>code</code> and <a href="url">link</a>" Applied after block detection decides the wrapper tag. Order of transforms matters — bold before italic prevents ** from being partially consumed.
In Action
CLI proof points
Markdown input turns into styled HTML without writing page markup by hand.
Go standard-library code keeps the converter small and dependency-light.
Stateful parsing handles multi-line blocks instead of treating every line alone.
CLI converting a Markdown file
Generated HTML proof points
Block detection chooses the wrapper tag before inline transforms run.
Inline transforms cover bold, italic, code, and link syntax in a predictable order.
The output can be dropped into static documentation pages.
Generated HTML output
Dev Notes
Problems Solved
Writing raw HTML for documentation by hand was tedious and error-prone. I needed a CLI that accepted Markdown as input and output styled HTML pages — and building it in Go was a chance to learn the standard library on a real task.
Errors & Fixes
The line-by-line parser broke on nested lists and code blocks that contained blank lines — it would close the block early and emit malformed HTML. Fixed by adding a state machine that tracked open block contexts across lines instead of processing each line independently.
What I Learned
Go's bufio.Scanner, string manipulation with the strings package, and CLI flag design with os.Args. More broadly, how stateful line parsing works and why a formal grammar is worth the extra effort on complex inputs.