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 = """
+ [](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()
- }
- }
- }
-
-}