diff --git a/README.md b/README.md index b395b3f..b3d4f7f 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,8 @@ Markdown renderer. ## Features - +- Headers +- Paragraphs ## Installation @@ -15,16 +16,25 @@ go get git.akyoto.dev/go/markdown ## Usage ```go +html := markdown.Render("# Header") +fmt.Println(html) ``` ## Tests ``` +PASS: TestEmpty +PASS: TestParagraphs +PASS: TestHeader +PASS: TestCombined +coverage: 100.0% of statements ``` ## Benchmarks - +``` +BenchmarkSmall-12 2187232 544.9 ns/op 296 B/op 9 allocs/op +``` ## License diff --git a/Render.go b/Render.go index 3197db0..55c3f83 100644 --- a/Render.go +++ b/Render.go @@ -1,6 +1,71 @@ package markdown +import ( + "html" + "strings" +) + +var ( + headerStart = []string{"

", "

", "

", "

", "

", "
"} + headerEnd = []string{"
", "", "", "", "", ""} +) + // Render creates HTML from the supplied markdown text. func Render(markdown string) string { - return "" + var ( + out = strings.Builder{} + paragraph = strings.Builder{} + i = 0 + lineStart = 0 + ) + + flush := func() { + if paragraph.Len() == 0 { + return + } + + out.WriteString("

") + out.WriteString(html.EscapeString(paragraph.String())) + out.WriteString("

") + paragraph.Reset() + } + + for { + if i > len(markdown) { + flush() + return out.String() + } + + if i != len(markdown) && markdown[i] != '\n' { + i++ + continue + } + + line := markdown[lineStart:i] + lineStart = i + 1 + i++ + + switch { + case strings.HasPrefix(line, "#"): + flush() + space := strings.IndexByte(line, ' ') + + if space > 0 && space <= 6 { + out.WriteString(headerStart[space-1]) + out.WriteString(html.EscapeString(line[space+1:])) + out.WriteString(headerEnd[space-1]) + } + + default: + if len(line) == 0 { + continue + } + + if paragraph.Len() > 0 { + paragraph.WriteByte(' ') + } + + paragraph.WriteString(line) + } + } } diff --git a/Render_test.go b/Render_test.go index c2ab965..d20a666 100644 --- a/Render_test.go +++ b/Render_test.go @@ -7,7 +7,28 @@ import ( "git.akyoto.dev/go/markdown" ) -func TestRender(t *testing.T) { - html := markdown.Render("# Hello") - assert.Equal(t, html, "

Hello

") +func TestEmpty(t *testing.T) { + assert.Equal(t, markdown.Render(""), "") +} + +func TestParagraphs(t *testing.T) { + assert.Equal(t, markdown.Render("Text"), "

Text

") + assert.Equal(t, markdown.Render("Text\n"), "

Text

") + assert.Equal(t, markdown.Render("Text\n\n"), "

Text

") + assert.Equal(t, markdown.Render("Text\n\n\n"), "

Text

") + assert.Equal(t, markdown.Render("Line 1\nLine 2"), "

Line 1 Line 2

") +} + +func TestHeader(t *testing.T) { + assert.Equal(t, markdown.Render("# Header"), "

Header

") + assert.Equal(t, markdown.Render("## Header"), "

Header

") + assert.Equal(t, markdown.Render("### Header"), "

Header

") + assert.Equal(t, markdown.Render("#### Header"), "

Header

") + assert.Equal(t, markdown.Render("##### Header"), "
Header
") + assert.Equal(t, markdown.Render("###### Header"), "
Header
") +} + +func TestCombined(t *testing.T) { + assert.Equal(t, markdown.Render("# Header\nLine 1.\nLine 2.\nLine 3."), "

Header

Line 1. Line 2. Line 3.

") + assert.Equal(t, markdown.Render("# Header 1\nLine 1.\n# Header 2\nLine 2."), "

Header 1

Line 1.

Header 2

Line 2.

") } diff --git a/benchmarks_test.go b/benchmarks_test.go new file mode 100644 index 0000000..ffdb413 --- /dev/null +++ b/benchmarks_test.go @@ -0,0 +1,13 @@ +package markdown_test + +import ( + "testing" + + "git.akyoto.dev/go/markdown" +) + +func BenchmarkSmall(b *testing.B) { + for range b.N { + markdown.Render("# Header\nText.\nText.\n# Header\nText.\nText.") + } +}