A fast, lightweight Markdown-to-HTML converter written in Go.
Built as a learning project — no dependencies, standard library only.
If you have Go installed:
go install github.com/itsjasonpierrot/hypermarkdown@latestThe binary lands in ~/go/bin/. If that directory is on your $PATH, hypermarkdown works from anywhere immediately.
git clone https://github.com/itsjasonpierrot/hypermarkdown
cd hypermarkdown
go build -o hypermarkdown
sudo mv hypermarkdown /usr/local/bin/hypermarkdown input.md output.htmlThat's it. Pass in a Markdown file, get back an HTML file.
If either argument is missing, hypermarkdown prints:
Error: usage: hypermarkdown <input.md> <output.html>
# H1
## H2
### H3
#### H4
##### H5
###### H6
Setext H1
=========
Setext H2
---------**bold**
***bold italic***
*italic*
__underline__
~~strikethrough~~
`inline code`Prefix any ASCII punctuation character with \ to render it literally:
\*not italic\* → *not italic*
\**not bold\** → **not bold**
\[not a link\](url) → [not a link](url)
\\ → \Only ASCII punctuation is escapable. \n and other non-punctuation sequences are left as-is.
Inline syntax:
[link text](https://example.com)
[link text](https://example.com "optional title")

Reference-style syntax:
[link text][ref-id]
![alt text][img-id]
[ref-id]: https://example.com
[img-id]: image.jpgReference IDs are case-insensitive and can be defined anywhere in the document. Link and image labels may wrap across lines inside a paragraph.
- unordered item
* also unordered
+ plus marker
1. ordered item
2. another item
1) ordered with parenthesisNested lists are supported — indent with spaces or tabs:
- fruit
- apples
- bananas
- vegetables
1. carrots
2. peasMixed ul/ol types are allowed at different nesting levels.
- [x] completed task
- [X] also completed
- [ ] incomplete taskRenders as disabled checkboxes inside list items.
> This is a blockquote.
> It can span multiple lines.Nested blockquotes are supported:
> Outer quote.
> > Inner quote.
> > > Triple nested.Fenced with ~~~ or triple backticks:
~~~
code here
~~~
~~~python
import os
~~~
```go
fmt.Println("hello")
```Or indented with 4 spaces:
# this is code, not a heading
for i in range(10): ...| Name | Age | City |
| :------ | :-: | --------: |
| Jason | 25 | London |
| Bob | 30 | New York |Column alignment is controlled by colon placement on the separator row:
:---— left:---:— center---:— right---— no alignment attribute
Alignment is applied as an inline style="text-align:..." on each <th> and <td>. Inline formatting works inside cells. Use \| for a literal pipe inside a table cell.
&, <, >, ", and ' in plain text are automatically escaped:
1 < 2 and 3 > 2 → 1 < 2 and 3 > 2
Fish & chips → Fish & chips
"quoted" → "quoted"Raw inline HTML is escaped by design rather than passed through.
Here is a sentence with a footnote.[^1]
Another sentence with a named footnote.[^note]
[^1]: This is the footnote text.
[^note]: This is a named footnote.
Continuation lines (indented 4 spaces or a tab) are appended to the definition.Inline references render as superscript links: [1]
Definitions are collected in a pre-scan and rendered at the bottom of the document inside <section class="footnotes">.
Output files include a complete HTML shell:
<!DOCTYPE html><meta charset="utf-8">- responsive viewport meta tag
<title>hypermarkdown output</title>- small default CSS for readable documents, code blocks, tables, and blockquotes
--- horizontal rule (also *** and ___)
| Line one line block
| Line twohypermarkdown/
├── main.go — reads args, calls parser, writes output
├── go.mod
└── internal/
├── parser/
│ ├── parser.go — block-based parser pipeline
│ ├── block_helpers.go — shared parser helpers
│ ├── blockquotes.go — recursive blockquote rendering
│ ├── footnotes.go — footnote collection and parsing
│ ├── links.go — reference-style link/image resolution
│ ├── lists.go — nested list rendering
│ ├── tables.go — pipe table rendering with alignment
│ └── parser_test.go — parser and block-level tests
└── converters/
├── headers.go — ATX and setext heading extraction
├── formatting.go — inline formatting (bold, italic, etc.)
├── headers_test.go
└── formatting_test.go
The parser uses a block-based approach rather than line-by-line processing.
Consecutive lines of the same type are accumulated into a block, then flushed as a unit when a different block type is detected. This allows correct wrapping of <ul>, <ol>, <blockquote>, and <p> tags across multiple lines.
Block detection order (order matters for correctness):
- Code fences (
~~~or triple backticks) — highest priority - Lines inside a fence — collected raw, no formatting applied
- Blank lines — flush all pending blocks
- Footnote definitions — collected in a pre-scan and skipped in body output
- Footnote continuation lines (indented, following a definition) — skipped
- Link definitions — collected in a pre-scan and skipped in body output
- Horizontal rules — before list/italic to avoid
---and*conflicts - Indented code (4 spaces or tab) — before ATX headers so
# foois code - ATX headers (
#) - Setext headers (look-ahead at next line)
- Blockquotes (
>) — rendered recursively to support nesting - Unordered lists (
-/*/+) - Ordered lists (
1./1)) - Pipe tables
- Line blocks (
|) - Paragraph text — default fallback
Inline formatting runs after block detection, on the text content only. The pipeline is:
- Escaped backticks are protected first so ``` suppresses code spans
- Code spans are protected, so their contents stay literal
- Other backslash escapes are protected
- Images, bold+italic, bold, italic, underline, strikethrough, links, and autolinks are applied in order
- Backslash escapes, code spans, and escaped backticks are restored
This ensures that markdown syntax inside backticks is never processed, and that \* suppresses italic rather than producing <em>.
go test ./...136 tests covering CLI behavior, block types, inline formatting, edge cases, and ordering rules.
- Math (
$...$) — not standard markdown, not supported - Dashless pipe tables — only standard pipe tables with a separator row are supported
- Raw HTML pass-through — raw HTML is escaped for safety rather than rendered as HTML
- Go standard library only
strings,fmt,regexp,os- No third-party dependencies