summaryrefslogtreecommitdiff
path: root/src/main/kotlin/moe/nea/blog/gen/MD2HtmlGenerator.kt
blob: 0077469427127d02cf3150a52eb758d0838ff043 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
package moe.nea.blog.gen

import moe.nea.blog.md.Begin
import moe.nea.blog.md.Bold
import moe.nea.blog.md.CodeBlock
import moe.nea.blog.md.Document
import moe.nea.blog.md.FormatSequence
import moe.nea.blog.md.Header
import moe.nea.blog.md.Italics
import moe.nea.blog.md.Link
import moe.nea.blog.md.MarkdownElement
import moe.nea.blog.md.Paragraph
import moe.nea.blog.md.Whitespace
import moe.nea.blog.md.Word
import kotlin.reflect.KClass


class MD2HtmlGenerator {
	private val generators = mutableMapOf<KClass<out MarkdownElement>, HtmlFragmentGenerator<MarkdownElement>>()

	fun <T : MarkdownElement> getGeneratorFor(kClass: KClass<out T>) = generators[kClass] as HtmlFragmentGenerator<T>?

	inline fun <reified T : MarkdownElement> registerFragmentGenerator(noinline outputter: HtmlDsl.(generator: MD2HtmlGenerator, node: T) -> Unit) {
		registerGeneratorFor(T::class) { gen, node ->
			HtmlDsl()
				.apply { outputter.invoke(this, gen, node) }
				.intoFragment()
		}
	}

	inline fun <reified T : MarkdownElement> registerGeneratorFor(outputter: HtmlFragmentGenerator<T>) {
		registerGeneratorFor(T::class, outputter)
	}

	fun <T : MarkdownElement> registerGeneratorFor(kClass: KClass<T>, outputter: HtmlFragmentGenerator<T>) {
		generators[kClass] = outputter as HtmlFragmentGenerator<MarkdownElement>
	}

	fun registerDefaultGenerators() {
		registerGeneratorFor<Begin> { _, _ -> HtmlFragment.empty() }
		registerFragmentGenerator<Header> { gen, header ->
			element("h${header.level}", mapOf()) {
				+header.text
			}
		}
		registerFragmentGenerator<Bold> { gen, node ->
			element("b", mapOf(), gen.generateHtml(node.inner))
		}
		registerFragmentGenerator<Italics> { generator, node ->
			element("em", mapOf(), generator.generateHtml(node.inner))
		}
		registerFragmentGenerator<Link> { generator, node ->
			element("a", mapOf("href" to node.target), generator.generateHtml(node.label ?: Begin()))
		}
		registerFragmentGenerator<Paragraph> { generator, node ->
			element("p", mapOf(), generator.generateHtml(node.format))
		}
		registerFragmentGenerator<CodeBlock> { generator, node ->
			element("pre", mapOf()) {
				element("code", mapOf("class" to "language-${node.language}")) {
					append(node.lines.joinToString("\n"))
				}
			}
		}
		registerFragmentGenerator<FormatSequence> { generator, node ->
			for (markdownFormat in node.list) {
				append(generator.generateHtml(markdownFormat))
			}
		}
		registerFragmentGenerator<Word> { generator, node ->
			append(node.text)
		}
		registerFragmentGenerator<Whitespace> { generator, node ->
			append(" ")
		}
		registerFragmentGenerator<Document> { generator, node ->
			for (markdownBlock in node.list) {
				append(generator.generateHtml(markdownBlock))
			}
		}
	}

	fun <T : MarkdownElement> generateHtml(node: T): HtmlFragment {
		val gen = getGeneratorFor(node::class) ?: error("Missing html generator for $node")
		return gen.generateHtml(this, node)
	}
}