diff options
author | Linnea Gräf <nea@nea.moe> | 2024-05-02 16:05:38 +0200 |
---|---|---|
committer | Linnea Gräf <nea@nea.moe> | 2024-05-02 16:05:38 +0200 |
commit | 5316d761132a28df639eb70099c8af367f792733 (patch) | |
tree | 2a16543bb74216b7a853332bf7382dbfbaabcc5d | |
parent | 5f0552a7ea251a52fb89f7886f6a8dd48ce566d5 (diff) | |
download | blog-infra-5316d761132a28df639eb70099c8af367f792733.tar.gz blog-infra-5316d761132a28df639eb70099c8af367f792733.tar.bz2 blog-infra-5316d761132a28df639eb70099c8af367f792733.zip |
Split up html generation
-rw-r--r-- | .github/workflows/test.yml | 20 | ||||
-rw-r--r-- | gradle/wrapper/gradle-wrapper.properties | 2 | ||||
-rw-r--r-- | src/main/kotlin/moe/nea/blog/gen/HtmlDsl.kt | 50 | ||||
-rw-r--r-- | src/main/kotlin/moe/nea/blog/gen/HtmlDslMarker.kt | 3 | ||||
-rw-r--r-- | src/main/kotlin/moe/nea/blog/gen/HtmlFragment.kt | 18 | ||||
-rw-r--r-- | src/main/kotlin/moe/nea/blog/gen/HtmlFragmentGenerator.kt | 7 | ||||
-rw-r--r-- | src/main/kotlin/moe/nea/blog/gen/HtmlGenerator.kt | 149 | ||||
-rw-r--r-- | src/main/kotlin/moe/nea/blog/gen/MD2HtmlGenerator.kt | 87 | ||||
-rw-r--r-- | src/test/kotlin/moe/nea/blog/gen/HtmlTest.kt | 101 |
9 files changed, 240 insertions, 197 deletions
diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml new file mode 100644 index 0000000..a9ea5f6 --- /dev/null +++ b/.github/workflows/test.yml @@ -0,0 +1,20 @@ +name: Test + +on: + push: + +jobs: + test: + runs-on: ubuntu-latest + steps: + - name: Checkout sources + uses: actions/checkout@v4 + - name: Setup Java + uses: actions/setup-java@v4 + with: + distribution: 'temurin' + java-version: 21 + - name: Setup Gradle + uses: gradle/actions/setup-gradle@v3 + - name: Test with Gradle + run: ./gradlew test diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 3fa8f86..b82aa23 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-8.4-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.7-bin.zip networkTimeout=10000 validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME diff --git a/src/main/kotlin/moe/nea/blog/gen/HtmlDsl.kt b/src/main/kotlin/moe/nea/blog/gen/HtmlDsl.kt new file mode 100644 index 0000000..4574b50 --- /dev/null +++ b/src/main/kotlin/moe/nea/blog/gen/HtmlDsl.kt @@ -0,0 +1,50 @@ +package moe.nea.blog.gen + +@HtmlDslMarker +class HtmlDsl { + val parts = mutableListOf<HtmlFragment>() + + fun append(string: String) { + parts.add(HtmlFragment.ofUnescaped(string)) + } + + fun append(fragment: HtmlFragment) { + parts.add(fragment) + } + + fun appendPreEscaped(markup: String) { + append(HtmlFragment.ofPreEscaped(markup)) + } + + operator fun String.unaryPlus() = append(this) + operator fun HtmlFragment.unaryPlus() = append(this) + + fun element(name: String, attributes: Map<String, String>, block: HtmlDsl.() -> Unit) { + element(name, attributes, HtmlDsl().also(block).intoFragment()) + } + + fun element(name: String, attributes: Map<String, String>, fragment: HtmlFragment) { + appendPreEscaped(("<")) + append(name) + + for ((key, value) in attributes) { + appendPreEscaped((" ")) + append(key) + appendPreEscaped(("=\"")) + append(value) + appendPreEscaped(("\"")) + } + + appendPreEscaped((">")) + + append(fragment) + + appendPreEscaped(("</")) + append(name) + appendPreEscaped((">")) + } + + fun intoFragment(): HtmlFragment { + return HtmlFragment.ofPreEscaped(parts.joinToString("") { it.text }) + } +}
\ No newline at end of file diff --git a/src/main/kotlin/moe/nea/blog/gen/HtmlDslMarker.kt b/src/main/kotlin/moe/nea/blog/gen/HtmlDslMarker.kt new file mode 100644 index 0000000..e30a516 --- /dev/null +++ b/src/main/kotlin/moe/nea/blog/gen/HtmlDslMarker.kt @@ -0,0 +1,3 @@ +package moe.nea.blog.gen + +annotation class HtmlDslMarker
\ No newline at end of file diff --git a/src/main/kotlin/moe/nea/blog/gen/HtmlFragment.kt b/src/main/kotlin/moe/nea/blog/gen/HtmlFragment.kt new file mode 100644 index 0000000..0b75182 --- /dev/null +++ b/src/main/kotlin/moe/nea/blog/gen/HtmlFragment.kt @@ -0,0 +1,18 @@ +package moe.nea.blog.gen + +class HtmlFragment private constructor(val text: String) { + companion object { + fun ofUnescaped(text: String) = HtmlFragment( + text.replace("&", "&") + .replace("<", "<") + .replace(">", ">") + .replace("\"", """) + .replace("'", "'") + ) + + fun ofPreEscaped(text: String) = HtmlFragment(text) + fun empty(): HtmlFragment { + return HtmlFragment("") + } + } +}
\ No newline at end of file diff --git a/src/main/kotlin/moe/nea/blog/gen/HtmlFragmentGenerator.kt b/src/main/kotlin/moe/nea/blog/gen/HtmlFragmentGenerator.kt new file mode 100644 index 0000000..3ba4f79 --- /dev/null +++ b/src/main/kotlin/moe/nea/blog/gen/HtmlFragmentGenerator.kt @@ -0,0 +1,7 @@ +package moe.nea.blog.gen + +import moe.nea.blog.md.MarkdownElement + +fun interface HtmlFragmentGenerator<T : MarkdownElement> { + fun generateHtml(htmlGenerator: MD2HtmlGenerator, node: T): HtmlFragment +}
\ No newline at end of file diff --git a/src/main/kotlin/moe/nea/blog/gen/HtmlGenerator.kt b/src/main/kotlin/moe/nea/blog/gen/HtmlGenerator.kt deleted file mode 100644 index ef0858a..0000000 --- a/src/main/kotlin/moe/nea/blog/gen/HtmlGenerator.kt +++ /dev/null @@ -1,149 +0,0 @@ -package moe.nea.blog.gen - -import moe.nea.blog.md.* -import kotlin.reflect.KClass - -fun interface HtmlFragmentGenerator<T : MarkdownElement> { - fun generateHtml(htmlGenerator: HtmlGenerator, node: T): HtmlFragment -} - - -class HtmlFragment private constructor(val text: String) { - companion object { - fun ofUnescaped(text: String) = HtmlFragment( - text.replace("&", "&") - .replace("<", "<") - .replace(">", ">") - .replace("\"", """) - .replace("'", "'") - ) - - fun ofPreEscaped(text: String) = HtmlFragment(text) - fun empty(): HtmlFragment { - return HtmlFragment("") - } - } -} - -annotation class HtmlDslMarker - -@HtmlDslMarker -class HtmlDsl { - val parts = mutableListOf<HtmlFragment>() - - fun append(string: String) { - parts.add(HtmlFragment.ofUnescaped(string)) - } - - fun append(fragment: HtmlFragment) { - parts.add(fragment) - } - - fun appendPreEscaped(markup: String) { - append(HtmlFragment.ofPreEscaped(markup)) - } - - operator fun String.unaryPlus() = append(this) - operator fun HtmlFragment.unaryPlus() = append(this) - - fun element(name: String, attributes: Map<String, String>, block: HtmlDsl.() -> Unit) { - element(name, attributes, HtmlDsl().also(block).intoFragment()) - } - - fun element(name: String, attributes: Map<String, String>, fragment: HtmlFragment) { - appendPreEscaped(("<")) - append(name) - - for ((key, value) in attributes) { - appendPreEscaped((" ")) - append(key) - appendPreEscaped(("=\"")) - append(value) - appendPreEscaped(("\"")) - } - - appendPreEscaped((">")) - - append(fragment) - - appendPreEscaped(("</")) - append(name) - appendPreEscaped((">")) - } - - fun intoFragment(): HtmlFragment { - return HtmlFragment.ofPreEscaped(parts.joinToString("") { it.text }) - } -} - - -class HtmlGenerator { - 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: HtmlGenerator, 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) - } -}
\ No newline at end of file diff --git a/src/main/kotlin/moe/nea/blog/gen/MD2HtmlGenerator.kt b/src/main/kotlin/moe/nea/blog/gen/MD2HtmlGenerator.kt new file mode 100644 index 0000000..0077469 --- /dev/null +++ b/src/main/kotlin/moe/nea/blog/gen/MD2HtmlGenerator.kt @@ -0,0 +1,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) + } +}
\ No newline at end of file diff --git a/src/test/kotlin/moe/nea/blog/gen/HtmlTest.kt b/src/test/kotlin/moe/nea/blog/gen/HtmlTest.kt index 07c5a24..4b76a5f 100644 --- a/src/test/kotlin/moe/nea/blog/gen/HtmlTest.kt +++ b/src/test/kotlin/moe/nea/blog/gen/HtmlTest.kt @@ -1,56 +1,63 @@ package moe.nea.blog.gen -import moe.nea.blog.md.* +import moe.nea.blog.md.Bold +import moe.nea.blog.md.FormatSequence +import moe.nea.blog.md.MarkdownElement +import moe.nea.blog.md.MarkdownParser +import moe.nea.blog.md.Paragraph +import moe.nea.blog.md.Whitespace +import moe.nea.blog.md.Word import kotlin.test.Test import kotlin.test.assertEquals class HtmlTest { - fun assertGenerator(generatedHtml: String, markdown: MarkdownElement) { - val generator = HtmlGenerator() - generator.registerDefaultGenerators() - assertEquals(generatedHtml, generator.generateHtml(markdown).text) - } - - fun assertGeneratorMD(generatedHtml: String, text: String) { - val generator = HtmlGenerator() - generator.registerDefaultGenerators() - assertEquals(generatedHtml, generator.generateHtml(MarkdownParser(text).also { it.addDefaultParsers() }.readDocument()).text) - } - - @Test - fun testBiggerFile() { - assertGeneratorMD( - """ - <h1>Hello World</h1><p></p><p><b><em>lol</em> hehe</b></p><pre><code class="language-java">public class ObjectControllerFactoryManagerProvider<T extends ObjectControllerFactoryManager<T>> { - - }</code></pre> - """.trimIndent(), - """ - # Hello World - - ***lol* hehe** - - ```java - public class ObjectControllerFactoryManagerProvider<T extends ObjectControllerFactoryManager<T>> { - - } - ``` - """.trimIndent() - ) - } - - @Test - fun testBold() { - assertGenerator("<b>Hii</b>", Bold(Word("Hii"))) - } - - @Test - fun testParagraphs() { - assertGenerator( - "<p>Test <b>Whatever</b></p>", - Paragraph(FormatSequence(Word("Test"), Whitespace(), Bold(Word("Whatever")))) - ) - } + fun assertGenerator(generatedHtml: String, markdown: MarkdownElement) { + val generator = MD2HtmlGenerator() + generator.registerDefaultGenerators() + assertEquals(generatedHtml, generator.generateHtml(markdown).text) + } + + fun assertGeneratorMD(generatedHtml: String, text: String) { + val generator = MD2HtmlGenerator() + generator.registerDefaultGenerators() + assertEquals(generatedHtml, + generator.generateHtml(MarkdownParser(text).also { it.addDefaultParsers() }.readDocument()).text) + } + + @Test + fun testBiggerFile() { + assertGeneratorMD( + """ + |<h1>Hello World</h1><p></p><p><b><em>lol</em> hehe</b></p><pre><code class="language-java">public class ObjectControllerFactoryManagerProvider<T extends ObjectControllerFactoryManager<T>> { + | public ObjectControllerFactoryManagerProvider() {} + |}</code></pre> + """.trimMargin(), + """ + |# Hello World + | + |***lol* hehe** + | + |```java + |public class ObjectControllerFactoryManagerProvider<T extends ObjectControllerFactoryManager<T>> { + | public ObjectControllerFactoryManagerProvider() {} + |} + |``` + """.trimMargin() + ) + } + + @Test + fun testBold() { + assertGenerator("<b>Hii</b>", Bold(Word("Hii"))) + } + + @Test + fun testParagraphs() { + assertGenerator( + "<p>Test <b>Whatever</b></p>", + Paragraph(FormatSequence(Word("Test"), Whitespace(), Bold(Word("Whatever")))) + ) + } }
\ No newline at end of file |