From 63c24f6355b4cd4bebb82e83040ae6810692e312 Mon Sep 17 00:00:00 2001 From: rnentjes Date: Tue, 2 Dec 2025 19:44:45 +0100 Subject: [PATCH] Refactor project to focus on Markdown parser development. Removed unused database and template-related code, added Markdown parsing functionality, and updated build configuration. --- .gitignore | 1 + .idea/.name | 1 - .../markdown_parser_js_0_1_0_SNAPSHOT.xml | 8 + .idea/artifacts/markdown_parser_js_1_0_0.xml | 8 + .../markdown_parser_jvm_0_1_0_SNAPSHOT.xml | 8 + .idea/artifacts/markdown_parser_jvm_1_0_0.xml | 8 + .idea/misc.xml | 2 +- build.gradle.kts | 100 +++++++--- gradle.properties | 1 - settings.gradle.kts | 22 ++- .../nl/astraeus/markdown/parser/Markdown.kt | 77 ++++++++ .../nl/astraeus/markdown/parser/Paragraph.kt | 174 ++++++++++++++++++ .../nl/astraeus/markdown/parser/Parser.kt | 166 +++++++++++++++++ .../nl/astraeus/markdown/parser/Table.kt | 48 +++++ .../nl/astraeus/markdown/parser/ParseTest.kt | 50 +++++ .../astraeus/markdown/parser/TestParagraph.kt | 23 +++ src/jvmMain/kotlin/nl/astraeus/tmpl/Main.kt | 39 ---- .../kotlin/nl/astraeus/tmpl/db/Database.kt | 73 -------- .../kotlin/nl/astraeus/tmpl/db/Migrations.kt | 105 ----------- 19 files changed, 661 insertions(+), 253 deletions(-) delete mode 100644 .idea/.name create mode 100644 .idea/artifacts/markdown_parser_js_0_1_0_SNAPSHOT.xml create mode 100644 .idea/artifacts/markdown_parser_js_1_0_0.xml create mode 100644 .idea/artifacts/markdown_parser_jvm_0_1_0_SNAPSHOT.xml create mode 100644 .idea/artifacts/markdown_parser_jvm_1_0_0.xml delete mode 100644 gradle.properties create mode 100644 src/commonMain/kotlin/nl/astraeus/markdown/parser/Markdown.kt create mode 100644 src/commonMain/kotlin/nl/astraeus/markdown/parser/Paragraph.kt create mode 100644 src/commonMain/kotlin/nl/astraeus/markdown/parser/Parser.kt create mode 100644 src/commonMain/kotlin/nl/astraeus/markdown/parser/Table.kt create mode 100644 src/commonTest/kotlin/nl/astraeus/markdown/parser/ParseTest.kt create mode 100644 src/commonTest/kotlin/nl/astraeus/markdown/parser/TestParagraph.kt delete mode 100644 src/jvmMain/kotlin/nl/astraeus/tmpl/Main.kt delete mode 100644 src/jvmMain/kotlin/nl/astraeus/tmpl/db/Database.kt delete mode 100644 src/jvmMain/kotlin/nl/astraeus/tmpl/db/Migrations.kt diff --git a/.gitignore b/.gitignore index c82ce0d..35b4a7f 100644 --- a/.gitignore +++ b/.gitignore @@ -44,3 +44,4 @@ bin/ ### .kotlin ### .kotlin kotlin-js-store +gradle.properties diff --git a/.idea/.name b/.idea/.name deleted file mode 100644 index 8f91b47..0000000 --- a/.idea/.name +++ /dev/null @@ -1 +0,0 @@ -template \ No newline at end of file diff --git a/.idea/artifacts/markdown_parser_js_0_1_0_SNAPSHOT.xml b/.idea/artifacts/markdown_parser_js_0_1_0_SNAPSHOT.xml new file mode 100644 index 0000000..3dfe715 --- /dev/null +++ b/.idea/artifacts/markdown_parser_js_0_1_0_SNAPSHOT.xml @@ -0,0 +1,8 @@ + + + $PROJECT_DIR$/build/libs + + + + + \ No newline at end of file diff --git a/.idea/artifacts/markdown_parser_js_1_0_0.xml b/.idea/artifacts/markdown_parser_js_1_0_0.xml new file mode 100644 index 0000000..10879b6 --- /dev/null +++ b/.idea/artifacts/markdown_parser_js_1_0_0.xml @@ -0,0 +1,8 @@ + + + $PROJECT_DIR$/build/libs + + + + + \ No newline at end of file diff --git a/.idea/artifacts/markdown_parser_jvm_0_1_0_SNAPSHOT.xml b/.idea/artifacts/markdown_parser_jvm_0_1_0_SNAPSHOT.xml new file mode 100644 index 0000000..6e74eef --- /dev/null +++ b/.idea/artifacts/markdown_parser_jvm_0_1_0_SNAPSHOT.xml @@ -0,0 +1,8 @@ + + + $PROJECT_DIR$/build/libs + + + + + \ No newline at end of file diff --git a/.idea/artifacts/markdown_parser_jvm_1_0_0.xml b/.idea/artifacts/markdown_parser_jvm_1_0_0.xml new file mode 100644 index 0000000..182343e --- /dev/null +++ b/.idea/artifacts/markdown_parser_jvm_1_0_0.xml @@ -0,0 +1,8 @@ + + + $PROJECT_DIR$/build/libs + + + + + \ No newline at end of file diff --git a/.idea/misc.xml b/.idea/misc.xml index 7b907e9..46040d5 100644 --- a/.idea/misc.xml +++ b/.idea/misc.xml @@ -3,7 +3,7 @@ - + diff --git a/build.gradle.kts b/build.gradle.kts index 98b7144..2890f7b 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -1,9 +1,14 @@ +import com.vanniktech.maven.publish.SonatypeHost + plugins { - kotlin("multiplatform") version "2.1.10" + kotlin("multiplatform") version "2.2.21" + signing + id("org.jetbrains.dokka") version "2.0.0" + id("com.vanniktech.maven.publish") version "0.31.0" } group = "nl.astraeus" -version = "0.1.0-SNAPSHOT" +version = "1.0.0" repositories { mavenCentral() @@ -16,45 +21,80 @@ repositories { } kotlin { - jvmToolchain(17) + jvmToolchain(11) jvm() js { - binaries.executable() - browser { - distribution { - outputDirectory.set(File("$projectDir/web/")) - } - } + binaries.library() + browser {} } sourceSets { val commonMain by getting { dependencies { - api("nl.astraeus:kotlin-simple-logging:1.1.1") - api("nl.astraeus:kotlin-css-generator:1.0.10") } } - val commonTest by getting - val jvmMain by getting { + val commonTest by getting { dependencies { - implementation("io.undertow:undertow-core:2.3.14.Final") - implementation("org.jetbrains.kotlinx:kotlinx-html-jvm:0.11.0") - - implementation("org.xerial:sqlite-jdbc:3.32.3.2") - implementation("com.zaxxer:HikariCP:4.0.3") - implementation("nl.astraeus:simple-jdbc-stats:1.6.1") { - exclude(group = "org.slf4j", module = "slf4j-api") - } - } - } - val jvmTest by getting { - dependencies { - } - } - val jsMain by getting { - dependencies { - implementation("nl.astraeus:kotlin-komponent:1.2.5") + implementation(kotlin("test")) } } + val jvmTest by getting val jsTest by getting } } + +publishing { + repositories { + maven { + name = "gitea" + setUrl("https://gitea.astraeus.nl/api/packages/rnentjes/maven") + + credentials { + val giteaUsername: String? by project + val giteaPassword: String? by project + + username = giteaUsername + password = giteaPassword + } + } + mavenLocal() + } +} + +tasks.withType { + dependsOn(tasks.withType()) +} + +signing { + sign(publishing.publications) +} + +mavenPublishing { + publishToMavenCentral(SonatypeHost.CENTRAL_PORTAL) + + signAllPublications() + + coordinates(group.toString(), name, version.toString()) + + pom { + name = "markdown-parser" + description = "Markdown parser" + inceptionYear = "2025" + url = "https://gitea.astraeus.nl/rnentjes/markdown-parser" + licenses { + license { + name = "MIT" + url = "https://gitea.astraeus.nl/rnentjes/markdown-parser" + } + } + developers { + developer { + id = "rnentjes" + name = "Rien Nentjes" + email = "info@nentjes.com" + } + } + scm { + url = "https://gitea.astraeus.nl/rnentjes/markdown-parser" + } + } +} diff --git a/gradle.properties b/gradle.properties deleted file mode 100644 index 7fc6f1f..0000000 --- a/gradle.properties +++ /dev/null @@ -1 +0,0 @@ -kotlin.code.style=official diff --git a/settings.gradle.kts b/settings.gradle.kts index 8f9f202..b30b63f 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -1,5 +1,21 @@ -plugins { - id("org.gradle.toolchains.foojay-resolver-convention") version "0.5.0" +pluginManagement { + repositories { + google() + mavenCentral() + gradlePluginPortal() + } } -val REPO_NAME = "dummy so the gitea template compiles, please remove" + +dependencyResolutionManagement { + repositories { + google() + mavenCentral() + } +} + +plugins { + id("org.gradle.toolchains.foojay-resolver-convention") version "0.8.0" +} + rootProject.name = "markdown-parser" + diff --git a/src/commonMain/kotlin/nl/astraeus/markdown/parser/Markdown.kt b/src/commonMain/kotlin/nl/astraeus/markdown/parser/Markdown.kt new file mode 100644 index 0000000..4a0cec1 --- /dev/null +++ b/src/commonMain/kotlin/nl/astraeus/markdown/parser/Markdown.kt @@ -0,0 +1,77 @@ +package nl.astraeus.wiki.parser + +sealed class MarkdownPart { + + data object NewLine : MarkdownPart() + + data object PageBreak : MarkdownPart() + + sealed class ParagraphPart() { + data class Text( + val text: String + ) : ParagraphPart() + + data object LineBreak : ParagraphPart() + + data class Link( + val url: String, + val label: String? = null, + val title: String? = null, + ) : ParagraphPart() + + data class Image( + val alt: String, + val src: String, + val url: String? = null, + ) : ParagraphPart() + + data class Bold( + val text: String + ) : ParagraphPart() + + data class Italic( + val text: String + ) : ParagraphPart() + + class BoldItalic( + val text: String + ) : ParagraphPart() + + class StrikeThrough( + val text: String + ) : ParagraphPart() + + class InlineCode( + val text: String + ) : ParagraphPart() + } + + data class Paragraph( + val parts: List + ) : MarkdownPart() + + data class Header( + val text: String, + val size: Int + ) : MarkdownPart() + + data class UnorderedList( + val lines: List, + ) : MarkdownPart() + + data class OrderedList( + val lines: List, + ) : MarkdownPart() + + data class CodeBlock( + val text: String, + val language: String + ) : MarkdownPart() + + data class Table( + val headers: List, + val rows: List>, + ) : MarkdownPart() + + class Ruler() : MarkdownPart() +} diff --git a/src/commonMain/kotlin/nl/astraeus/markdown/parser/Paragraph.kt b/src/commonMain/kotlin/nl/astraeus/markdown/parser/Paragraph.kt new file mode 100644 index 0000000..bafe741 --- /dev/null +++ b/src/commonMain/kotlin/nl/astraeus/markdown/parser/Paragraph.kt @@ -0,0 +1,174 @@ +package nl.astraeus.wiki.parser + +import nl.astraeus.wiki.parser.MarkdownPart.ParagraphPart.* + +private enum class ParType { + TEXT, + LINK_LABEL, + LINK_URL, + LINK_TITLE, + LINK_END, + BOLD, + ITALIC, + BOLD_ITALIC, + STRIKETHROUGH, + INLINE_CODE, + IMAGE_ALT, + IMAGE_SRC, + LINK_IMAGE_ALT, + LINK_IMAGE_SRC, + LINK_IMAGE_LINK, +} + +private typealias ParagraphData = MutableMap + +private data class ParState( + val fromType: ParType, + val text: String, + val toType: ParType, + val out: (ParagraphData) -> MarkdownPart.ParagraphPart? = { _ -> null } +) + +private val states = listOf( + // Image with link + ParState(ParType.TEXT, "[![", ParType.LINK_IMAGE_ALT) { data -> + Text(data[ParType.TEXT]!!) + }, + ParState(ParType.LINK_IMAGE_ALT, "](", ParType.LINK_IMAGE_SRC), + ParState(ParType.LINK_IMAGE_SRC, ")](", ParType.LINK_IMAGE_LINK), + ParState(ParType.LINK_IMAGE_LINK, ")", ParType.TEXT) { data -> + Image( + data[ParType.LINK_IMAGE_ALT]!!, + data[ParType.LINK_IMAGE_SRC]!!, + data[ParType.LINK_IMAGE_LINK], + ) + }, + + // Image without link + ParState(ParType.TEXT, "![", ParType.IMAGE_ALT) { data -> + Text(data[ParType.TEXT]!!) + }, + ParState(ParType.IMAGE_ALT, "](", ParType.IMAGE_SRC), + ParState(ParType.IMAGE_SRC, ")", ParType.TEXT) { data -> + Image( + data[ParType.IMAGE_ALT]!!, + data[ParType.IMAGE_SRC]!!, + ) + }, + + // Links + ParState(ParType.TEXT, "[", ParType.LINK_LABEL) { data -> + Text(data[ParType.TEXT]!!) + }, + ParState(ParType.LINK_LABEL, "](", ParType.LINK_URL), + ParState(ParType.LINK_LABEL, "]", ParType.LINK_URL) { data -> + Text(data[ParType.LINK_LABEL]!!) + }, + ParState(ParType.LINK_URL, ")", ParType.TEXT) { data -> + Link(data[ParType.LINK_URL]!!, data[ParType.LINK_LABEL]) + }, + + ParState(ParType.LINK_URL, "\"", ParType.LINK_TITLE), + ParState(ParType.LINK_TITLE, "\"", ParType.LINK_END), + ParState(ParType.LINK_END, ")", ParType.TEXT) { data -> + Link( + data[ParType.LINK_URL]!!, + data[ParType.LINK_LABEL], + data[ParType.LINK_TITLE], + ) + }, + + ParState(ParType.TEXT, "***", ParType.BOLD_ITALIC) { data -> + Text(data[ParType.TEXT]!!) + }, + ParState(ParType.BOLD_ITALIC, "***", ParType.TEXT) { data -> + BoldItalic(data[ParType.BOLD_ITALIC]!!) + }, + + ParState(ParType.TEXT, "~~", ParType.STRIKETHROUGH) { data -> + Text(data[ParType.TEXT]!!) + }, + ParState(ParType.STRIKETHROUGH, "~~", ParType.TEXT) { data -> + StrikeThrough(data[ParType.STRIKETHROUGH]!!) + }, + + ParState(ParType.TEXT, "**", ParType.BOLD) { data -> + Text(data[ParType.TEXT]!!) + }, + ParState(ParType.BOLD, "**", ParType.TEXT) { data -> + Bold(data[ParType.BOLD]!!) + }, + + ParState(ParType.TEXT, "*", ParType.ITALIC) { data -> + Text(data[ParType.TEXT]!!) + }, + ParState(ParType.ITALIC, "*", ParType.TEXT) { data -> + BoldItalic(data[ParType.ITALIC]!!) + }, + + ParState(ParType.TEXT, "`", ParType.INLINE_CODE) { data -> + Text(data[ParType.TEXT]!!) + }, + ParState(ParType.INLINE_CODE, "`", ParType.TEXT) { data -> + InlineCode(data[ParType.INLINE_CODE]!!) + }, +) + +private fun String.test(index: Int, value: String): Boolean { + return this.length > index + value.length && this.substring(index, index + value.length) == value +} + +fun parseParagraph(text: String): MarkdownPart.Paragraph { + val result = mutableListOf() + val buffer = StringBuilder() + var type = ParType.TEXT + val data: ParagraphData = mutableMapOf() + var index = 0 + var activeStates = states.filter { it.fromType == type } + + while (index < text.length) { + var found = false + for (state in activeStates) { + if (state.fromType == type && text.test(index, state.text)) { + data[state.fromType] = buffer.toString() + buffer.clear() + state.out(data)?.let { + if (it !is Text || it.text.isNotBlank()) { + result.add(it) + } + } + type = state.toType + index += state.text.length + found = true + activeStates = states.filter { it.fromType == type } + break + } + } + if (!found) { + val ch = text[index] + if (ch == '\n') { + // Markdown hard line break: two or more spaces at end of line + if (buffer.length >= 2 && buffer.endsWith(" ")) { + val textBefore = buffer.substring(0, buffer.length - 2) + if (textBefore.isNotEmpty()) { + result.add(Text(textBefore)) + } + result.add(LineBreak) + buffer.clear() + } else { + // Keep original behavior for soft breaks (collapse later in HTML) + buffer.append(ch) + } + } else { + buffer.append(ch) + } + index++ + } + } + + if (buffer.isNotEmpty()) { + result.add(Text(buffer.toString())) + } + + return MarkdownPart.Paragraph(result) +} diff --git a/src/commonMain/kotlin/nl/astraeus/markdown/parser/Parser.kt b/src/commonMain/kotlin/nl/astraeus/markdown/parser/Parser.kt new file mode 100644 index 0000000..8bb6c11 --- /dev/null +++ b/src/commonMain/kotlin/nl/astraeus/markdown/parser/Parser.kt @@ -0,0 +1,166 @@ +package nl.astraeus.wiki.parser + +enum class MarkdownType { + CODE, + PARAGRAPH, + ORDERED_LIST, + UNORDERED_LIST, + TABLE, +} + +fun markdown(text: String): List { + val lines = text.lines() + val parts = mutableListOf() + var language = "" + var type = MarkdownType.PARAGRAPH + var listIndex = 1 + + var index = 0 + val buffer = StringBuilder() + + fun parseBuffer() { + if (buffer.isNotBlank()) { + parts.addAll(handleBuffer(type, buffer.toString(), language)) + } + buffer.clear() + type = MarkdownType.PARAGRAPH + language = "" + } + + while (index < lines.size) { + val rawLine = lines[index] + val line = rawLine.trim() + //println("BUFFER [${buffer.length}] TYPE ${type} \t LINE - ${line}") + when { + type == MarkdownType.ORDERED_LIST -> { + if (!line.startsWith("${listIndex++}.")) { + parseBuffer() + continue + } else { + buffer.append(line.substring(2)) + buffer.append("\n") + } + } + + type == MarkdownType.UNORDERED_LIST -> { + if (!line.startsWith("- ") && + !line.startsWith("* ") + ) { + parseBuffer() + continue + } else { + buffer.append(line.substring(2)) + buffer.append("\n") + } + } + + type == MarkdownType.TABLE -> { + if (!line.startsWith("|")) { + parseBuffer() + continue + } else { + buffer.append(line) + buffer.append("\n") + } + } + + type == MarkdownType.PARAGRAPH && line.isBlank() -> { + buffer.append("\n") + parseBuffer() + } + + line.startsWith("```") -> { + if (type != MarkdownType.CODE) { + parseBuffer() + type = MarkdownType.CODE + language = line.substring(3).trim() + } else { + parseBuffer() + } + } + + type == MarkdownType.CODE -> { + buffer.append(rawLine) + buffer.append("\n") + index++ + continue + } + + line.startsWith("1.") -> { + parseBuffer() + type = MarkdownType.ORDERED_LIST + listIndex = 2 + buffer.append(line.substring(2)) + buffer.append("\n") + } + + line.startsWith("- ") || line.startsWith("* ") -> { + parseBuffer() + type = MarkdownType.UNORDERED_LIST + buffer.append(line.substring(2)) + buffer.append("\n") + } + + line.startsWith("|") -> { + parseBuffer() + type = MarkdownType.TABLE + buffer.append(line) + buffer.append("\n") + } + + line.startsWith("---") -> { + parseBuffer() + parts.add(MarkdownPart.Ruler()) + } + + line.startsWith("#") -> { + parseBuffer() + val headerLevel = line.takeWhile { it == '#' }.length + val headerText = line.substring(headerLevel).trim() + parts.add(MarkdownPart.Header(headerText, headerLevel)) + } + + line == "[break]" -> { + parseBuffer() + parts.add(MarkdownPart.PageBreak) + } + + else -> { + // Preserve trailing spaces for hard line breaks (two spaces at end of line) + buffer.append(rawLine) + buffer.append("\n") + } + } + index++ + } + + parseBuffer() + + return parts +} + +private fun handleBuffer( + type: MarkdownType, + text: String, + language: String = "" +): List = when (type) { + MarkdownType.CODE -> { + listOf(MarkdownPart.CodeBlock(text, language)) + } + + MarkdownType.PARAGRAPH -> { + listOf(parseParagraph(text)) + } + + MarkdownType.ORDERED_LIST -> { + listOf(MarkdownPart.OrderedList(text.lines())) + } + + MarkdownType.UNORDERED_LIST -> { + listOf(MarkdownPart.UnorderedList(text.lines())) + } + + MarkdownType.TABLE -> { + parseTable(text) + } +} diff --git a/src/commonMain/kotlin/nl/astraeus/markdown/parser/Table.kt b/src/commonMain/kotlin/nl/astraeus/markdown/parser/Table.kt new file mode 100644 index 0000000..d44741d --- /dev/null +++ b/src/commonMain/kotlin/nl/astraeus/markdown/parser/Table.kt @@ -0,0 +1,48 @@ +package nl.astraeus.wiki.parser + +fun parseTable(text: String): List { + val lines = text.lines().map { it.trim() }.filter { it.isNotEmpty() } + + fun parseCells(line: String): List { + val trimmed = line.trim().trim('|') + return if (trimmed.isEmpty()) emptyList() else trimmed.split("|").map { it.trim() } + } + + fun isSeparatorRow(cells: List): Boolean { + if (cells.isEmpty()) return false + return cells.all { cell -> + val dashCount = cell.count { it == '-' } + val cleaned = cell.replace("-", "").replace("|", "").trim() + dashCount >= 3 && cleaned.isEmpty() + } + } + + return if (lines.size < 2) { + // Not enough lines to be a table, fallback to code block + listOf(MarkdownPart.CodeBlock(text, "table")) + } else { + val headerCells = parseCells(lines.first()) + val sepCells = parseCells(lines[1]) + + if (headerCells.isEmpty() || !isSeparatorRow(sepCells)) { + // Invalid table format, fallback to code block + listOf(MarkdownPart.CodeBlock(text, "table")) + } else { + val colCount = headerCells.size + val rows = mutableListOf>() + for (i in 2 until lines.size) { + val rowCells = parseCells(lines[i]).toMutableList() + // Normalize column count to headers size + if (rowCells.size < colCount) { + while (rowCells.size < colCount) rowCells.add("") + } else if (rowCells.size > colCount) { + // Trim extras + while (rowCells.size > colCount) rowCells.removeAt(rowCells.lastIndex) + } + rows.add(rowCells) + } + + listOf(MarkdownPart.Table(headers = headerCells, rows = rows)) + } + } +} \ No newline at end of file diff --git a/src/commonTest/kotlin/nl/astraeus/markdown/parser/ParseTest.kt b/src/commonTest/kotlin/nl/astraeus/markdown/parser/ParseTest.kt new file mode 100644 index 0000000..bca4e15 --- /dev/null +++ b/src/commonTest/kotlin/nl/astraeus/markdown/parser/ParseTest.kt @@ -0,0 +1,50 @@ +package nl.astraeus.markdown.parser + +import nl.astraeus.wiki.parser.MarkdownPart +import nl.astraeus.wiki.parser.markdown +import kotlin.test.Test + +class ParseTest { + + @Test + fun testParagraph() { + val input = """ + Dit is een **test**, laat ***mij*** maar eens zien! + + link: [NOS](www.nos.nl "Nos title") of zo. + + + - link: [NU](www.nu.nl "Nu site") of zo. + + """.trimIndent() + + + val md = markdown(input) + + printMarkdownParts(md) + } + + @Test + fun testImage() { + val input = """ + [![test2](https://upload.wikimedia.org/wikipedia/commons.png)](https://upload.wikimedia.org/wikipedia/commons.png) + + """.trimIndent() + + val md = markdown(input) + + printMarkdownParts(md) + } + + private fun printMarkdownParts(md: List) { + for (part in md) { + if (part is MarkdownPart.Paragraph) { + for (para in part.parts) { + println("PARA: ${para::class.simpleName} - ${para.toString().take(75)}") + } + } else { + println("PART: ${part::class.simpleName} - ${part.toString().take(75)}") + } + } + } +} diff --git a/src/commonTest/kotlin/nl/astraeus/markdown/parser/TestParagraph.kt b/src/commonTest/kotlin/nl/astraeus/markdown/parser/TestParagraph.kt new file mode 100644 index 0000000..23ca8cc --- /dev/null +++ b/src/commonTest/kotlin/nl/astraeus/markdown/parser/TestParagraph.kt @@ -0,0 +1,23 @@ +package nl.astraeus.markdown.parser + +import nl.astraeus.wiki.parser.MarkdownPart +import nl.astraeus.wiki.parser.parseParagraph +import kotlin.test.Test +import kotlin.test.assertEquals +import kotlin.test.assertTrue + +class TestParagraph { + + @Test + fun testBold() { + val input = "Text **bold** Text" + + val result = parseParagraph(input) + + assertEquals(3, result.parts.size) + + assertTrue(result.parts[0] is MarkdownPart.ParagraphPart.Text) + assertTrue(result.parts[1] is MarkdownPart.ParagraphPart.Bold) + assertTrue(result.parts[2] is MarkdownPart.ParagraphPart.Text) + } +} diff --git a/src/jvmMain/kotlin/nl/astraeus/tmpl/Main.kt b/src/jvmMain/kotlin/nl/astraeus/tmpl/Main.kt deleted file mode 100644 index e58cc71..0000000 --- a/src/jvmMain/kotlin/nl/astraeus/tmpl/Main.kt +++ /dev/null @@ -1,39 +0,0 @@ -package nl.astraeus.tmpl - -import com.zaxxer.hikari.HikariConfig -import nl.astraeus.logger.Logger -import nl.astraeus.tmpl.db.Database - -val log = Logger() - -val REPO_NAME = "dummy so the gitea template compiles, please remove" - -fun main() { - Thread.currentThread().uncaughtExceptionHandler = Thread.UncaughtExceptionHandler { t, e -> - log.warn(e) { - e.message - } - } - - Runtime.getRuntime().addShutdownHook( - object : Thread() { - override fun run() { - Database.vacuumDatabase() - Database.closeDatabase() - } - } - ) - - Class.forName("nl.astraeus.jdbc.Driver") - Database.initialize(HikariConfig().apply { - driverClassName = "nl.astraeus.jdbc.Driver" - jdbcUrl = "jdbc:stat:webServerPort=6001:jdbc:sqlite:data/markdown-parser.db" - username = "sa" - password = "" - maximumPoolSize = 25 - isAutoCommit = false - - validate() - }) - -} diff --git a/src/jvmMain/kotlin/nl/astraeus/tmpl/db/Database.kt b/src/jvmMain/kotlin/nl/astraeus/tmpl/db/Database.kt deleted file mode 100644 index 9df850a..0000000 --- a/src/jvmMain/kotlin/nl/astraeus/tmpl/db/Database.kt +++ /dev/null @@ -1,73 +0,0 @@ -package nl.astraeus.tmpl.db - -import com.zaxxer.hikari.HikariConfig -import com.zaxxer.hikari.HikariDataSource -import java.sql.Connection -import java.util.* -import java.util.concurrent.atomic.AtomicBoolean -import kotlin.collections.set -import kotlin.use - -private val currentConnection = ThreadLocal() - -fun transaction( - block: (Connection) -> T -): T { - val hasConnection = currentConnection.get() != null - var oldConnection: Connection? = null - - if (!hasConnection) { - currentConnection.set(Database.getConnection()) - } - - val connection = currentConnection.get() - - try { - val result = block(connection) - - connection.commit() - - return result - } finally { - if (!hasConnection) { - currentConnection.set(oldConnection) - connection.close() - } - } -} - -object Database { - - var ds: HikariDataSource? = null - - fun initialize(config: HikariConfig) { - val properties = Properties() - properties["journal_mode"] = "WAL" - - config.dataSourceProperties = properties - config.addDataSourceProperty("cachePrepStmts", "true") - config.addDataSourceProperty("prepStmtCacheSize", "250") - config.addDataSourceProperty("prepStmtCacheSqlLimit", "2048") - - ds = HikariDataSource(config) - Migrations.databaseVersionTableCreated = AtomicBoolean(false) - Migrations.updateDatabaseIfNeeded() - } - - fun getConnection() = ds?.connection ?: error("Database has not been initialized!") - - fun vacuumDatabase() { - getConnection().use { - it.autoCommit = true - - it.prepareStatement("VACUUM").use { ps -> - ps.executeUpdate() - } - } - } - - fun closeDatabase() { - ds?.close() - } - -} diff --git a/src/jvmMain/kotlin/nl/astraeus/tmpl/db/Migrations.kt b/src/jvmMain/kotlin/nl/astraeus/tmpl/db/Migrations.kt deleted file mode 100644 index 60d075b..0000000 --- a/src/jvmMain/kotlin/nl/astraeus/tmpl/db/Migrations.kt +++ /dev/null @@ -1,105 +0,0 @@ -package nl.astraeus.tmpl.db - -import nl.astraeus.tmpl.log -import java.sql.Connection -import java.sql.SQLException -import java.sql.Timestamp -import java.util.concurrent.atomic.AtomicBoolean - -sealed class Migration { - class Query( - val query: String - ) : Migration() { - override fun toString(): String { - return query - } - } - - class Code( - val code: (Connection) -> Unit - ) : Migration() { - override fun toString(): String { - return code.toString() - } - } -} - -val DATABASE_MIGRATIONS = arrayOf( - Migration.Query( - """ - CREATE TABLE DATABASE_VERSION ( - ID INTEGER PRIMARY KEY, - QUERY TEXT, - EXECUTED TIMESTAMP - ) - """.trimIndent() - ), - Migration.Query("SELECT sqlite_version()"), -) - -object Migrations { - var databaseVersionTableCreated = AtomicBoolean(false) - - fun updateDatabaseIfNeeded() { - try { - transaction { con -> - con.prepareStatement( - """ - SELECT MAX(ID) FROM DATABASE_VERSION - """.trimIndent() - ).use { ps -> - ps.executeQuery().use { rs -> - databaseVersionTableCreated.compareAndSet(false, true) - - if(rs.next()) { - val maxId = rs.getInt(1) - - for (index in maxId + 1.. - log.debug { - "Executing migration index - [DATABASE_MIGRATIONS[index]]" - } - val description = when( - val migration = DATABASE_MIGRATIONS[index] - ) { - is Migration.Query -> { - con.prepareStatement(migration.query).use { ps -> - ps.execute() - } - - migration.query - } - is Migration.Code -> { - migration.code(con) - - migration.code.toString() - } - } - con.prepareStatement("INSERT INTO DATABASE_VERSION VALUES (?, ?, ?)").use { ps -> - ps.setInt(1, index) - ps.setString(2, description) - ps.setTimestamp(3, Timestamp(System.currentTimeMillis())) - - ps.execute() - } - } - } - -}