]> git.frustrated-labs.net Git - gradle-changelog-plugin.git/commitdiff
feat: implement markdown parser + add tests
authorAlexander Goussas <[email protected]>
Fri, 24 Oct 2025 04:57:28 +0000 (23:57 -0500)
committerAlexander Goussas <[email protected]>
Fri, 24 Oct 2025 04:57:28 +0000 (23:57 -0500)
14 files changed:
.gitignore
app/build.gradle.kts
changelog-plugin/build.gradle.kts
changelog-plugin/src/main/kotlin/io/github/aloussase/changelog/Changelog.kt
changelog-plugin/src/main/kotlin/io/github/aloussase/changelog/ChangelogEntry.kt
changelog-plugin/src/main/kotlin/io/github/aloussase/changelog/ChangelogPlugin.kt
changelog-plugin/src/main/kotlin/io/github/aloussase/changelog/config/ChangelogPluginExtension.kt
changelog-plugin/src/main/kotlin/io/github/aloussase/changelog/config/Config.kt
changelog-plugin/src/main/kotlin/io/github/aloussase/changelog/formatter/ChangelogFormatter.kt [new file with mode: 0644]
changelog-plugin/src/main/kotlin/io/github/aloussase/changelog/formatter/ChangelogFormatterFactory.kt [new file with mode: 0644]
changelog-plugin/src/main/kotlin/io/github/aloussase/changelog/formatter/MarkdownFormatter.kt [new file with mode: 0644]
changelog-plugin/src/main/kotlin/io/github/aloussase/changelog/parser/ChangelogParserFactory.kt [new file with mode: 0644]
changelog-plugin/src/main/kotlin/io/github/aloussase/changelog/parser/MarkdownChangelogParser.kt
changelog-plugin/src/test/kotlin/io/github/aloussase/changelog/parser/MarkdownChangelogParserTests.kt [new file with mode: 0644]

index 38e0d8cda732aeb1f27889c07182272c8db7b1ba..ec06a0846fe3487c30e3acd73f324c97c2b5651f 100644 (file)
@@ -45,3 +45,5 @@ bin/
 .DS_Store
 
 .idea
+
+CHANGELOG.md
index d4ec185b90ab6d75b9c6588bb6833a69dd27db58..5669953155717b037265232669ab15ec54052188 100644 (file)
@@ -6,7 +6,7 @@ plugins {
 
 changelog {
     format = "markdown"
-    outputFileName = "CHANGELOG.md"
+    fileName = "CHANGELOG.md"
 
     git {
         baseBranch = "master"
index 17c85d3098eb9f36a72f583dff2d9c9724480ed6..dead3ef57459ee700746b3d34365d927f23bf300 100644 (file)
@@ -17,3 +17,12 @@ gradlePlugin {
         }
     }
 }
+
+dependencies {
+    testImplementation("org.junit.jupiter:junit-jupiter:5.10.0")
+    testImplementation("org.hamcrest:java-hamcrest:2.0.0.0")
+}
+
+tasks.withType<Test>().configureEach {
+    useJUnitPlatform()
+}
index 22a5fd5901681249d279c7acf36b27fc6f4fae55..971b9b25fed60888fe0be951781154883708fb71 100644 (file)
@@ -1,5 +1,5 @@
 package io.github.aloussase.changelog
 
 data class Changelog(
-    val entries: List<ChangelogEntry>
+    val entries: List<ChangelogEntry> = emptyList(),
 )
index 615decd65993d737905afaabfc9db72fbe93d6bb..4733a14dc49a8c225ea4e7c37be9e2b8fb6d24a0 100644 (file)
@@ -4,5 +4,5 @@ import io.github.aloussase.changelog.git.Commit
 
 data class ChangelogEntry(
     val branchName: String,
-    val commit: Commit,
+    val commits: List<Commit> = emptyList(),
 )
index a38e0e4fdfe61d11117cbfa8cd276995c0d1a485..b8d92240cb2fbf6090d8b355c7faac5f93ee6957 100644 (file)
@@ -2,8 +2,11 @@ package io.github.aloussase.changelog
 
 import io.github.aloussase.changelog.config.ChangelogPluginExtension
 import io.github.aloussase.changelog.config.Config
+import io.github.aloussase.changelog.formatter.ChangelogFormatterFactory
+import io.github.aloussase.changelog.parser.ChangelogParserFactory
 import org.gradle.api.Plugin
 import org.gradle.api.Project
+import java.io.File
 
 class ChangelogPlugin : Plugin<Project> {
     override fun apply(project: Project) {
@@ -11,10 +14,19 @@ class ChangelogPlugin : Plugin<Project> {
 
         extension.gitInfo.baseBranch.convention("master")
         extension.format.convention("markdown")
+        extension.fileName.convention("CHANGELOG.md")
 
         project.tasks.register("changelog") {
             val config = Config.from(extension)
-            println(config)
+            val parser = ChangelogParserFactory.createParser(config.documentFormat)
+            val changelogFile = File(config.fileName)
+            if (!changelogFile.exists()) {
+                changelogFile.createNewFile()
+            }
+            val document = changelogFile.readText()
+            val changelog = parser.parse(document).getOrThrow()
+            val formatter = ChangelogFormatterFactory.create(config.documentFormat)
+            changelogFile.writeText(formatter.format(changelog))
         }
     }
 }
index 907c997f9c27c39d650a70b36750f435f3e00af7..a6b60ea8c49d82f2b5d29be8637b2365c76b8216 100644 (file)
@@ -10,7 +10,7 @@ abstract class ChangelogPluginExtension {
 
     abstract val format: Property<String>
 
-    abstract val outputFileName: Property<String>
+    abstract val fileName: Property<String>
 
     fun git(action: Action<GitInfo>) = action.execute(gitInfo)
 }
index 3b5fd244db5387efb0f2dfe6e4f5746f62fb8391..927eda3d1f8db4b1eaff8cc27a47ce6c3bae532e 100644 (file)
@@ -4,7 +4,7 @@ import io.github.aloussase.changelog.formats.DocumentFormat
 import org.gradle.api.GradleException
 
 data class Config(
-    val outputFileName: String,
+    val fileName: String,
     val gitBranch: String,
     val documentFormat: DocumentFormat,
 ) {
@@ -20,7 +20,7 @@ data class Config(
                 throw GradleException("Branch name cannot be blank")
             }
 
-            val outputFileName = extension.outputFileName.get()
+            val outputFileName = extension.fileName.get()
             if (outputFileName.isBlank()) {
                 throw GradleException("Output file name cannot be blank")
             }
diff --git a/changelog-plugin/src/main/kotlin/io/github/aloussase/changelog/formatter/ChangelogFormatter.kt b/changelog-plugin/src/main/kotlin/io/github/aloussase/changelog/formatter/ChangelogFormatter.kt
new file mode 100644 (file)
index 0000000..4ab002b
--- /dev/null
@@ -0,0 +1,10 @@
+package io.github.aloussase.changelog.formatter
+
+import io.github.aloussase.changelog.Changelog
+
+interface ChangelogFormatter {
+    /**
+     * Format a changelog into a string.
+     */
+    fun format(changelog: Changelog): String
+}
diff --git a/changelog-plugin/src/main/kotlin/io/github/aloussase/changelog/formatter/ChangelogFormatterFactory.kt b/changelog-plugin/src/main/kotlin/io/github/aloussase/changelog/formatter/ChangelogFormatterFactory.kt
new file mode 100644 (file)
index 0000000..4e0e843
--- /dev/null
@@ -0,0 +1,14 @@
+package io.github.aloussase.changelog.formatter
+
+import io.github.aloussase.changelog.formats.DocumentFormat
+
+abstract class ChangelogFormatterFactory {
+
+    companion object {
+        fun create(format: DocumentFormat): ChangelogFormatter {
+            return when (format) {
+                DocumentFormat.Markdown -> MarkdownFormatter()
+            }
+        }
+    }
+}
diff --git a/changelog-plugin/src/main/kotlin/io/github/aloussase/changelog/formatter/MarkdownFormatter.kt b/changelog-plugin/src/main/kotlin/io/github/aloussase/changelog/formatter/MarkdownFormatter.kt
new file mode 100644 (file)
index 0000000..da6cbb7
--- /dev/null
@@ -0,0 +1,9 @@
+package io.github.aloussase.changelog.formatter
+
+import io.github.aloussase.changelog.Changelog
+
+class MarkdownFormatter : ChangelogFormatter {
+    override fun format(changelog: Changelog): String {
+        return changelog.entries.first().branchName
+    }
+}
diff --git a/changelog-plugin/src/main/kotlin/io/github/aloussase/changelog/parser/ChangelogParserFactory.kt b/changelog-plugin/src/main/kotlin/io/github/aloussase/changelog/parser/ChangelogParserFactory.kt
new file mode 100644 (file)
index 0000000..fcb976f
--- /dev/null
@@ -0,0 +1,13 @@
+package io.github.aloussase.changelog.parser
+
+import io.github.aloussase.changelog.formats.DocumentFormat
+
+abstract class ChangelogParserFactory {
+    companion object {
+        fun createParser(documentFormat: DocumentFormat): ChangelogParser {
+            return when (documentFormat) {
+                DocumentFormat.Markdown -> MarkdownChangelogParser()
+            }
+        }
+    }
+}
index b016edfedc137e3ac622525db79469751ec4e8a8..824dd04f0de1dcddba196d7512eee1d34afb3ef1 100644 (file)
@@ -1,12 +1,61 @@
 package io.github.aloussase.changelog.parser
 
 import io.github.aloussase.changelog.Changelog
+import io.github.aloussase.changelog.ChangelogEntry
+import io.github.aloussase.changelog.git.Commit
+import org.gradle.api.GradleException
 
 /**
  * A ChangelogParser that assumes it's input to be a Markdown document.
  */
 class MarkdownChangelogParser : ChangelogParser {
+
+    companion object {
+        private val BRANCH_NAME_REGEX = Regex("## [\\w-]+")
+        private val COMMIT_REGRX = Regex("- [\\w ]+")
+        private val DOC_TITLE_REGEX = Regex("# (Changelog|CHANGELOG)")
+    }
+
     override fun parse(doc: String): Result<Changelog> {
-        TODO("Not yet implemented")
+        if (doc.isEmpty()) {
+            return Result.success(Changelog())
+        }
+
+        val entries = arrayListOf<ChangelogEntry>()
+
+        for (blk in doc.split("\n\n")) {
+            if (DOC_TITLE_REGEX.matches(blk)) continue
+
+            val lines = blk.split("\n")
+            if (lines.isEmpty()) continue
+
+            val branchName = lines[0]
+            if (!BRANCH_NAME_REGEX.matches(branchName)) {
+                return Result.failure(
+                    GradleException("Expected a valid branch name, but got $branchName")
+                )
+            }
+
+            val commits = arrayListOf<Commit>()
+
+            for (line in lines.subList(1, lines.size)) {
+                if (COMMIT_REGRX.matches(line)) {
+                    commits.add(Commit(line.dropWhile { it == '-' || it == ' ' }))
+                } else {
+                    return Result.failure(
+                        GradleException("Expected a valid commit message, but got $line")
+                    )
+                }
+            }
+
+            entries.add(
+                ChangelogEntry(
+                    branchName.dropWhile { it == '#' || it == ' ' },
+                    commits
+                )
+            )
+        }
+
+        return Result.success(Changelog(entries))
     }
 }
diff --git a/changelog-plugin/src/test/kotlin/io/github/aloussase/changelog/parser/MarkdownChangelogParserTests.kt b/changelog-plugin/src/test/kotlin/io/github/aloussase/changelog/parser/MarkdownChangelogParserTests.kt
new file mode 100644 (file)
index 0000000..76f61a0
--- /dev/null
@@ -0,0 +1,76 @@
+package io.github.aloussase.changelog.parser
+
+import org.hamcrest.MatcherAssert.assertThat
+import org.hamcrest.Matchers.emptyIterable
+import org.hamcrest.Matchers.equalTo
+import org.hamcrest.collection.IsCollectionWithSize.hasSize
+import org.junit.jupiter.api.Test
+import org.junit.jupiter.params.ParameterizedTest
+import org.junit.jupiter.params.provider.ValueSource
+
+class MarkdownChangelogParserTests {
+
+    @Test
+    fun givenEmptyFileWhenParseIsInvokedThenReturnEmptyChangelog() {
+        val input = ""
+        val parser = MarkdownChangelogParser()
+
+        val result = parser.parse(input)
+
+        assertThat(result.isSuccess, equalTo(true))
+        assertThat(result.getOrThrow().entries, emptyIterable())
+    }
+
+    @ParameterizedTest
+    @ValueSource(strings = ["# Changelog", "# CHANGELOG"])
+    fun givenDocumentContainingOnlyHeaderWhenParseIsInvokedThenReturnEmptyChangelog(doc: String) {
+        val parser = MarkdownChangelogParser()
+
+        val result = parser.parse(doc)
+
+        assertThat(result.isSuccess, equalTo(true))
+        assertThat(result.getOrThrow().entries, emptyIterable())
+    }
+
+    @Test
+    fun givenDocumentContainingSingleEntryWithNoCommitsWhenParseisInvokedThenReturnChangelogWithThatEntryAndNoCommits() {
+        val doc = "# Changelog\n\n## LOYMAR-123"
+        val parser = MarkdownChangelogParser()
+
+        val result = parser.parse(doc)
+
+        assertThat(result.isSuccess, equalTo(true))
+        assertThat(result.getOrThrow().entries, hasSize(1))
+        assertThat(result.getOrThrow().entries.first().branchName, equalTo("LOYMAR-123"))
+        assertThat(result.getOrThrow().entries.first().commits, emptyIterable())
+    }
+
+    @Test
+    fun givenDocumentContainingSingleEntryWithCommitsWhenParseIsInvokedThenReturnChangelogWithSingleEntryAndCommits() {
+        val doc = "# Changelog\n\n## LOYMAR-123\n- first commit\n- second commit"
+        val parser = MarkdownChangelogParser()
+
+        val result = parser.parse(doc)
+
+        assertThat(result.isSuccess, equalTo(true))
+        assertThat(result.getOrThrow().entries, hasSize(1))
+        assertThat(result.getOrThrow().entries[0].commits, hasSize(2))
+        assertThat(result.getOrThrow().entries[0].commits[0].message, equalTo("first commit"))
+        assertThat(result.getOrThrow().entries[0].commits[1].message, equalTo("second commit"))
+    }
+
+    @Test
+    fun givenDocumentWithTwoEntriesWhenParseIsInvokedThenReturnChangelogWithTwoEntries() {
+        val doc = "# Changelog\n\n## LOYMAR-123\n- first commit\n- second commit\n\n## LOYMAR-456\n- third commit"
+        val parser = MarkdownChangelogParser()
+
+        val result = parser.parse(doc)
+
+        assertThat(result.isSuccess, equalTo(true))
+        assertThat(result.getOrThrow().entries, hasSize(2))
+        assertThat(result.getOrThrow().entries[0].commits, hasSize(2))
+        assertThat(result.getOrThrow().entries[1].commits, hasSize(1))
+        assertThat(result.getOrThrow().entries[1].commits[0].message, equalTo("third commit"))
+    }
+
+}