Markdown to HTML Converter

A simple Markdown to HTML converter built in Go

Go CLI tool that converts Markdown files to HTML for static site generation.

Languages
GitHub

Started Last updated

Links

2026

Try it

Hello, Markdown

Type on the left — the preview updates as you go.

  • Lists, emphasis and inline code
  • Links
  • Mistakes fixed
Quotes work too.
code blocks stay as written

All projects · About Matt

Try it!



Visit the GitHub Repo

Description


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.


Back to top