.DS_Store
.idea
+
+CHANGELOG.md
changelog {
format = "markdown"
- outputFileName = "CHANGELOG.md"
+ fileName = "CHANGELOG.md"
git {
baseBranch = "master"
}
}
}
+
+dependencies {
+ testImplementation("org.junit.jupiter:junit-jupiter:5.10.0")
+ testImplementation("org.hamcrest:java-hamcrest:2.0.0.0")
+}
+
+tasks.withType<Test>().configureEach {
+ useJUnitPlatform()
+}
package io.github.aloussase.changelog
data class Changelog(
- val entries: List<ChangelogEntry>
+ val entries: List<ChangelogEntry> = emptyList(),
)
data class ChangelogEntry(
val branchName: String,
- val commit: Commit,
+ val commits: List<Commit> = emptyList(),
)
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) {
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))
}
}
}
abstract val format: Property<String>
- abstract val outputFileName: Property<String>
+ abstract val fileName: Property<String>
fun git(action: Action<GitInfo>) = action.execute(gitInfo)
}
import org.gradle.api.GradleException
data class Config(
- val outputFileName: String,
+ val fileName: String,
val gitBranch: String,
val documentFormat: DocumentFormat,
) {
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")
}
--- /dev/null
+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
+}
--- /dev/null
+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()
+ }
+ }
+ }
+}
--- /dev/null
+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
+ }
+}
--- /dev/null
+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()
+ }
+ }
+ }
+}
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))
}
}
--- /dev/null
+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"))
+ }
+
+}