From 9bacb15dae9dcf7eea34fee650c8025ca946fe47 Mon Sep 17 00:00:00 2001 From: Linnea Gräf Date: Sun, 24 Mar 2024 20:45:58 +0100 Subject: Add list parser --- src/main/kotlin/moe/nea/blog/md/BlockList.kt | 12 +++ .../kotlin/moe/nea/blog/md/LinePreProcessor.kt | 5 + src/main/kotlin/moe/nea/blog/md/ListParser.kt | 60 ++++++++++++ src/main/kotlin/moe/nea/blog/md/MDList.kt | 24 +++++ src/main/kotlin/moe/nea/blog/md/MarkdownParser.kt | 55 +++++++++-- src/main/kotlin/moe/nea/blog/md/ParagraphParser.kt | 2 +- src/test/kotlin/moe/nea/blog/md/test/ListTest.kt | 104 +++++++++++++++++++++ .../kotlin/moe/nea/blog/md/test/TestItalics.kt | 1 - 8 files changed, 255 insertions(+), 8 deletions(-) create mode 100644 src/main/kotlin/moe/nea/blog/md/BlockList.kt create mode 100644 src/main/kotlin/moe/nea/blog/md/LinePreProcessor.kt create mode 100644 src/main/kotlin/moe/nea/blog/md/ListParser.kt create mode 100644 src/main/kotlin/moe/nea/blog/md/MDList.kt create mode 100644 src/test/kotlin/moe/nea/blog/md/test/ListTest.kt diff --git a/src/main/kotlin/moe/nea/blog/md/BlockList.kt b/src/main/kotlin/moe/nea/blog/md/BlockList.kt new file mode 100644 index 0000000..ce51a58 --- /dev/null +++ b/src/main/kotlin/moe/nea/blog/md/BlockList.kt @@ -0,0 +1,12 @@ +package moe.nea.blog.md + +import java.io.PrintStream + +data class BlockList(val elements: List) : MarkdownBlock { + override fun debugFormat(indent: Int, printStream: PrintStream) { + elements.forEach { + it.debugFormat(indent, printStream) + } + } + +} diff --git a/src/main/kotlin/moe/nea/blog/md/LinePreProcessor.kt b/src/main/kotlin/moe/nea/blog/md/LinePreProcessor.kt new file mode 100644 index 0000000..cb9d81f --- /dev/null +++ b/src/main/kotlin/moe/nea/blog/md/LinePreProcessor.kt @@ -0,0 +1,5 @@ +package moe.nea.blog.md + +interface LinePreProcessor { + fun preprocess(lineIndex: Int, line: String): String? +} diff --git a/src/main/kotlin/moe/nea/blog/md/ListParser.kt b/src/main/kotlin/moe/nea/blog/md/ListParser.kt new file mode 100644 index 0000000..7d1e63b --- /dev/null +++ b/src/main/kotlin/moe/nea/blog/md/ListParser.kt @@ -0,0 +1,60 @@ +package moe.nea.blog.md + +import moe.nea.blog.util.indentSize + +object ListParser : BlockParser { + override fun detect(line: String): Boolean { + return line.matches(" *[*+-] .*".toRegex()) + } + + override fun parse(parser: MarkdownParser): MarkdownBlock { + val line = parser.peekLine()!! + val indentSize = line.indentSize()!! + val prefix = line.substring(0, indentSize + 2) + var bypassLineIndex = parser.getLineIndex() + val indentDepth = parser.getIndent() + val processor = object : LinePreProcessor { + override fun preprocess(lineIndex: Int, line: String): String? { + if (line.substring(indentDepth).startsWith(prefix)) { + if (bypassLineIndex == lineIndex) { + return line.substring(0, indentDepth) + + " ".repeat(indentSize + 2) + + line.substring(indentDepth + indentSize + 2) + } else { + return null + } + } + return line + } + } + parser.pushPreProcessor(processor) + parser.pushIndent(indentSize + 2) + + val listItems = mutableListOf() + + while (true) { + bypassLineIndex = parser.getLineIndex() + val elements = mutableListOf() + while (true) { + val child = parser.readChildBlock() ?: break + elements.add(child) + } + parser.popPreProcessor() + parser.popIndent() + val realLine = parser.peekLine() + parser.pushPreProcessor(processor) + parser.pushIndent(indentSize + 2) + listItems.add(parser.mergeBlocks(elements)) + if (realLine == null || !realLine.startsWith(prefix)) { + break + } + } + + parser.popIndent() + parser.popPreProcessor() + return MDList(listItems) + } + + override val prio: Int + get() = 10 +} \ No newline at end of file diff --git a/src/main/kotlin/moe/nea/blog/md/MDList.kt b/src/main/kotlin/moe/nea/blog/md/MDList.kt new file mode 100644 index 0000000..6fd0f74 --- /dev/null +++ b/src/main/kotlin/moe/nea/blog/md/MDList.kt @@ -0,0 +1,24 @@ +package moe.nea.blog.md + +import moe.nea.blog.util.indent +import java.io.PrintStream + +data class MDList( + val elements: List +) : MarkdownBlock { + override fun debugFormat(indent: Int, printStream: PrintStream) { + printStream.indent(indent) + printStream.println("") + + elements.forEach { + printStream.indent(indent + 2) + printStream.println("") + it.debugFormat(indent + 4, printStream) + printStream.indent(indent + 2) + printStream.println("") + } + + printStream.indent(indent) + printStream.println("") + } +} \ No newline at end of file diff --git a/src/main/kotlin/moe/nea/blog/md/MarkdownParser.kt b/src/main/kotlin/moe/nea/blog/md/MarkdownParser.kt index ad818dd..aaac6a1 100644 --- a/src/main/kotlin/moe/nea/blog/md/MarkdownParser.kt +++ b/src/main/kotlin/moe/nea/blog/md/MarkdownParser.kt @@ -12,6 +12,8 @@ class MarkdownParser(source: String) { private val blockParsers = mutableListOf() private val inlineParsers = mutableListOf() + private val preprocessors = Stack() + private var peekedLine: String? = null fun findParserFor(line: String): BlockParser? { return blockParsers.filter { it.detect(line) } @@ -24,30 +26,58 @@ class MarkdownParser(source: String) { return blockParser.parse(this) } + fun pushPreProcessor(preProcessor: LinePreProcessor) { + peekedLine = null + preprocessors.push(preProcessor) + } + + fun popPreProcessor() { + peekedLine = null + preprocessors.pop() + } + + fun preProcessLine(string: String): String? { + var acc = string + for (processor in preprocessors) { + acc = processor.preprocess(lineIndex, acc) ?: return null + } + return acc + } + fun pushIndent(newIndent: Int) { - require(newIndent > blockIndents) indentStack.push(blockIndents) - blockIndents = newIndent + blockIndents += newIndent + peekedLine = null } fun popIndent() { blockIndents = indentStack.pop() + peekedLine = null + } + + fun unpeekLine() { + peekedLine = null } fun consumeLine(): String? { val line = peekLine() - if (line != null) + if (line != null) { + peekedLine = null lineIndex++ + } return line } fun peekLine(): String? { if (lineIndex !in lines.indices) return null - val line = lines[lineIndex] + val line = peekedLine ?: preProcessLine(lines[lineIndex]) ?: return null + peekedLine = line val indent = line.indentSize() - if (indent != null && indent < blockIndents) + if (indent != null && indent < blockIndents) { + peekedLine = null return null - return line.substring(blockIndents) + } + return line.drop(blockIndents) } fun parseInlineTextUntil( @@ -139,8 +169,21 @@ class MarkdownParser(source: String) { fun addDefaultParsers() { blockParsers.add(CodeBlockParser) blockParsers.add(HeaderParser) + blockParsers.add(ListParser) inlineParsers.add(ItalicsParser) inlineParsers.add(LinkParser) } + + fun getLineIndex(): Int { + return lineIndex + } + + fun mergeBlocks(elements: List): MarkdownBlock { + return elements.singleOrNull() ?: BlockList(elements) + } + + fun getIndent(): Int { + return blockIndents + } } diff --git a/src/main/kotlin/moe/nea/blog/md/ParagraphParser.kt b/src/main/kotlin/moe/nea/blog/md/ParagraphParser.kt index 328c49b..5e51786 100644 --- a/src/main/kotlin/moe/nea/blog/md/ParagraphParser.kt +++ b/src/main/kotlin/moe/nea/blog/md/ParagraphParser.kt @@ -17,7 +17,7 @@ object ParagraphParser : BlockParser { } else { emptyLineCount = 0 } - if (emptyLineCount == 2) { + if (emptyLineCount == 1) { break } text += " $trimmedLine" diff --git a/src/test/kotlin/moe/nea/blog/md/test/ListTest.kt b/src/test/kotlin/moe/nea/blog/md/test/ListTest.kt new file mode 100644 index 0000000..0ac8f1e --- /dev/null +++ b/src/test/kotlin/moe/nea/blog/md/test/ListTest.kt @@ -0,0 +1,104 @@ +package moe.nea.blog.md.test + +import kotlin.test.Test + +class ListTest : MarkdownTest() { + @Test + fun testBasicList() { + assertDocumentFormat( + """ + Some Text: + + + First + + + Second + + + """.trimIndent(), + """ + Some Text: + - First + - Second + """.trimIndent() + ) + } + @Test + fun testBlockContinuation() { + assertDocumentFormat( + """ + Some Text: + + + First More First + + + Second + More Second + + + """.trimIndent(), + """ + Some Text: + - First + More First + - Second + + More Second + """.trimIndent() + ) + } + @Test + fun testNestedLists() { + assertDocumentFormat( + """ + Some Text: + + + First + + + Another List + + + + + Second + + + """.trimIndent(), + """ + Some Text: + - First + - Another List + - Second + """.trimIndent() + ) + } + @Test + fun testCodeBlockInList() { + assertDocumentFormat( + """ + + + + public class Test { + + public static void main(String...args) {} + } + + + + """.trimIndent(), + """ + - ```java + public class Test { + + public static void main(String...args) {} + } + ``` + """.trimIndent() + ) + } +} \ No newline at end of file diff --git a/src/test/kotlin/moe/nea/blog/md/test/TestItalics.kt b/src/test/kotlin/moe/nea/blog/md/test/TestItalics.kt index f4c1669..dee92bd 100644 --- a/src/test/kotlin/moe/nea/blog/md/test/TestItalics.kt +++ b/src/test/kotlin/moe/nea/blog/md/test/TestItalics.kt @@ -14,7 +14,6 @@ class TestItalics : MarkdownTest() { assertInlineFormat("both just italics", "***both** just italics*") } - @Test fun otherMarkdownInItalics() { assertInlineFormat( -- cgit