diff options
-rw-r--r-- | src/main/kotlin/moe/nea/blog/md/BlockList.kt | 12 | ||||
-rw-r--r-- | src/main/kotlin/moe/nea/blog/md/LinePreProcessor.kt | 5 | ||||
-rw-r--r-- | src/main/kotlin/moe/nea/blog/md/ListParser.kt | 60 | ||||
-rw-r--r-- | src/main/kotlin/moe/nea/blog/md/MDList.kt | 24 | ||||
-rw-r--r-- | src/main/kotlin/moe/nea/blog/md/MarkdownParser.kt | 55 | ||||
-rw-r--r-- | src/main/kotlin/moe/nea/blog/md/ParagraphParser.kt | 2 | ||||
-rw-r--r-- | src/test/kotlin/moe/nea/blog/md/test/ListTest.kt | 104 | ||||
-rw-r--r-- | src/test/kotlin/moe/nea/blog/md/test/TestItalics.kt | 1 |
8 files changed, 255 insertions, 8 deletions
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>) : 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<MarkdownBlock>() + + while (true) { + bypassLineIndex = parser.getLineIndex() + val elements = mutableListOf<MarkdownBlock>() + 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> +) : MarkdownBlock { + override fun debugFormat(indent: Int, printStream: PrintStream) { + printStream.indent(indent) + printStream.println("<list>") + + elements.forEach { + printStream.indent(indent + 2) + printStream.println("<element>") + it.debugFormat(indent + 4, printStream) + printStream.indent(indent + 2) + printStream.println("</element>") + } + + printStream.indent(indent) + printStream.println("</list>") + } +}
\ 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<BlockParser>() private val inlineParsers = mutableListOf<InlineParser>() + private val preprocessors = Stack<LinePreProcessor>() + 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>): 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: + <list> + <element> + First + </element> + <element> + Second + </element> + </list> + """.trimIndent(), + """ + Some Text: + - First + - Second + """.trimIndent() + ) + } + @Test + fun testBlockContinuation() { + assertDocumentFormat( + """ + Some Text: + <list> + <element> + First More First + </element> + <element> + Second + More Second + </element> + </list> + """.trimIndent(), + """ + Some Text: + - First + More First + - Second + + More Second + """.trimIndent() + ) + } + @Test + fun testNestedLists() { + assertDocumentFormat( + """ + Some Text: + <list> + <element> + First + <list> + <element> + Another List + </element> + </list> + </element> + <element> + Second + </element> + </list> + """.trimIndent(), + """ + Some Text: + - First + - Another List + - Second + """.trimIndent() + ) + } + @Test + fun testCodeBlockInList() { + assertDocumentFormat( + """ + <list> + <element> + <code language=java> + public class Test { + + public static void main(String...args) {} + } + </code> + </element> + </list> + """.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("<i><b>both</b> just italics</i>", "***both** just italics*") } - @Test fun otherMarkdownInItalics() { assertInlineFormat( |