Skip to content

Commit

Permalink
feat: Change log DSL
Browse files Browse the repository at this point in the history
  • Loading branch information
jmongard committed Sep 23, 2023
1 parent cdd531a commit ecd218c
Show file tree
Hide file tree
Showing 4 changed files with 185 additions and 162 deletions.
13 changes: 13 additions & 0 deletions src/main/kotlin/git/semver/plugin/changelog/ChangeLogContext.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package git.semver.plugin.changelog

class ChangeLogContext {
private val flaggedCommits = mutableSetOf<ChangeLogFormatter.CommitInfo>()

fun flagCommit(commit: ChangeLogFormatter.CommitInfo) {
flaggedCommits.add(commit)
}

fun isCommitFlagged(commit: ChangeLogFormatter.CommitInfo): Boolean {
return commit in flaggedCommits
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
package git.semver.plugin.changelog

import java.util.*

open class MarkDownDocumentBuilder {
val out = StringBuilder()

fun build(): String {
return out.toString()
}

fun heading1(text: String?) {
out.append("# ").appendLine(text)
}

fun heading2(text: String?) {
out.append("## ").appendLine(text)
}

fun heading3(text: String?) {
out.append("### ").appendLine(text)
}

fun heading4(text: String?) {
out.append("#### ").appendLine(text)
}

fun append(t: String?): MarkDownDocumentBuilder {
out.append(t)
return this
}

fun appendLine(t: String? = ""): MarkDownDocumentBuilder {
out.appendLine(t)
return this
}
}

open class ChangeLogDocumentBuilder(
private val commitInfos: List<ChangeLogFormatter.CommitInfo>,
private val context: ChangeLogContext
) : MarkDownDocumentBuilder() {
fun breakingChanges(block: ChangeLogDocumentBuilder.() -> Unit) {
filter(block) { it.isBreaking }
}

fun withScopes(vararg scopes: String, block: ChangeLogDocumentBuilder.() -> Unit) {
filter(block) { it.scope in scopes }
}

fun withoutScope(block: ChangeLogDocumentBuilder.() -> Unit) {
filter(block) { it.scope == null }
}

fun withTypes(vararg types: String, block: ChangeLogDocumentBuilder.() -> Unit) {
filter(block) { it.type in types }
}

fun withoutType(block: ChangeLogDocumentBuilder.() -> Unit) {
filter(block) { it.type == null }
}

fun with(filter: (ChangeLogFormatter.CommitInfo) -> Boolean, block: ChangeLogDocumentBuilder.() -> Unit) {
filter(block, filter)
}

private fun filter(block: ChangeLogDocumentBuilder.() -> Unit, filter: (ChangeLogFormatter.CommitInfo) -> Boolean) {
val filteredCommits = commitInfos().filter(filter)
if (filteredCommits.isNotEmpty()) {
val builder = ChangeLogDocumentBuilder(filteredCommits, context)
builder.block()
out.appendLine(builder.build())
}
}

fun groupByScope(block: ChangeLogGroupBuilder.() -> Unit) {
groupBy({ it.scope ?: "" }, block)
}

fun groupByType(block: ChangeLogGroupBuilder.() -> Unit) {
groupBy({ it.type ?: "" }, block)
}

fun groupBy(group: (ChangeLogFormatter.CommitInfo) -> String, block: ChangeLogGroupBuilder.() -> Unit) {
processGroups(commitInfos().groupBy(group), block)
}

fun groupBySorted(group: (ChangeLogFormatter.CommitInfo) -> String, block: ChangeLogGroupBuilder.() -> Unit) {
processGroups(commitInfos().groupByTo(TreeMap(), group), block)
}

private fun processGroups(
groupedByScope: Map<String, List<ChangeLogFormatter.CommitInfo>>,
block: ChangeLogGroupBuilder.() -> Unit
) {
for ((key, scopeCommits) in groupedByScope.filterKeys { it.isNotEmpty() }) {
processGroup(scopeCommits, key, block)
}
}

private fun processGroup(
scopeCommits: List<ChangeLogFormatter.CommitInfo>,
group: String,
block: ChangeLogGroupBuilder.() -> Unit
) {
val builder = ChangeLogGroupBuilder(scopeCommits, context, group)
block(builder)
out.appendLine(builder.build())
}

fun formatEach(block: ChangeLogTextFormatter.() -> String) {
for (commitInfo in commitInfos()) {
context.flagCommit(commitInfo)
val builder = ChangeLogTextFormatter(commitInfo)
out.appendLine(builder.block())
}
}

fun skip() {
for (commitInfo in commitInfos()) {
context.flagCommit(commitInfo)
}
}

private fun commitInfos() = commitInfos.filter { !context.isCommitFlagged(it) }
}

class ChangeLogGroupBuilder(
commitInfos: List<ChangeLogFormatter.CommitInfo>,
flagManager: ChangeLogContext,
val group: String
) : ChangeLogDocumentBuilder(commitInfos, flagManager)
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package git.semver.plugin.changelog

class ChangeLogTextFormatter(
private val commitInfo: ChangeLogFormatter.CommitInfo
) {

fun header() = (commitInfo.message ?: commitInfo.text).lineSequence().first()

fun body() = commitInfo.text.lineSequence()
.drop(1)
.dropWhile { it.isEmpty() }
.takeWhile { it.isNotEmpty() }

fun fullHeader() = commitInfo.text.lineSequence().first()

fun scope(format: String = "%s: ") = commitInfo.scope?.let { format.format(it) }.orEmpty()

fun type(format: String = "%s") = commitInfo.type?.let { format.format(it) }.orEmpty()

fun hash(format: String = "%s ", len: Int = 40) =
commitInfo.commits.joinToString("") { format.format(it.sha.take(len)) }
}
180 changes: 18 additions & 162 deletions src/test/kotlin/git/semver/plugin/changelog/TestApi.kt
Original file line number Diff line number Diff line change
@@ -1,161 +1,15 @@
import git.semver.plugin.changelog.ChangeLogContext
import git.semver.plugin.changelog.ChangeLogFormatter
import git.semver.plugin.changelog.ChangeLogFormatter.CommitInfo
import git.semver.plugin.changelog.ChangeLogSettings
import git.semver.plugin.changelog.ChangeLogDocumentBuilder
import git.semver.plugin.scm.Commit
import git.semver.plugin.semver.SemverSettings
import java.util.TreeMap

class ChangeLogContext {
private val flaggedCommits = mutableSetOf<Commit>()

fun flagCommit(commit: Commit) {
flaggedCommits.add(commit)
}

fun isCommitFlagged(commit: Commit): Boolean {
return commit in flaggedCommits
}
}

open class TextBuilder {
val out = StringBuilder()

fun build(): String {
return out.toString()
}

fun heading1(text: String?) {
out.append("# ").appendLine(text)
}

fun heading2(text: String?) {
out.append("## ").appendLine(text)
}

fun heading3(text: String?) {
out.append("### ").appendLine(text)
}

fun heading4(text: String?) {
out.append("#### ").appendLine(text)
}

fun append(t: String?): TextBuilder {
out.append(t)
return this
}

fun appendLine(t: String? = ""): TextBuilder {
out.appendLine(t)
return this
}
}

open class MarkdownDocumentBuilder(
private val commitInfos: List<CommitInfo>,
private val context: ChangeLogContext
) : TextBuilder() {
fun breakingChanges(block: MarkdownDocumentBuilder.() -> Unit) {
filter(block) { it.isBreaking }
}

fun withScopes(vararg scopes: String, block: MarkdownDocumentBuilder.() -> Unit) {
filter(block) { it.scope in scopes }
}

fun withoutScope(block: MarkdownDocumentBuilder.() -> Unit) {
filter(block) { it.scope == null }
}

fun withTypes(vararg types: String, block: MarkdownDocumentBuilder.() -> Unit) {
filter(block) { it.type in types }
}

fun withoutType(block: MarkdownDocumentBuilder.() -> Unit) {
filter(block) { it.type == null }
}

fun with(filter: (CommitInfo) -> Boolean, block: MarkdownDocumentBuilder.() -> Unit) {
filter(block, filter)
}

private fun filter(block: MarkdownDocumentBuilder.() -> Unit, filter: (CommitInfo) -> Boolean) {
val filteredCommits = commitInfos().filter(filter)
if (filteredCommits.isNotEmpty()) {
val builder = MarkdownDocumentBuilder(filteredCommits, context)
builder.block()
out.appendLine(builder.build())
}
}

fun groupByScope(block: GroupBuilder.() -> Unit) {
groupBy({ it.scope ?: "" }, block)
}

fun groupByType(block: GroupBuilder.() -> Unit) {
groupBy({ it.type ?: "" }, block)
}

fun groupBy(group: (CommitInfo) -> String, block: GroupBuilder.() -> Unit) {
apply(commitInfos().groupBy(group), block)
}

fun groupBySorted(group: (CommitInfo) -> String, block: GroupBuilder.() -> Unit) {
apply(commitInfos().groupByTo(TreeMap(), group), block)
}

private fun apply(
groupedByScope: Map<String, List<CommitInfo>>,
block: GroupBuilder.() -> Unit
) {
for ((key, scopeCommits) in groupedByScope.filterKeys { it.isNotEmpty() }) {
val builder = GroupBuilder(scopeCommits, context, key)
block(builder)
out.appendLine(builder.build())
}
}

fun changes(block: ChangeFormatter.() -> String) {
for (c in commitInfos()) {
context.flagCommit(c.commits[0])
val builder = ChangeFormatter(c)

out.appendLine(builder.block())
}
}

private fun commitInfos() = commitInfos.filter { !context.isCommitFlagged(it.commits[0]) }
}

class GroupBuilder(
commitInfos: List<CommitInfo>,
flagManager: ChangeLogContext,
val group: String
) : MarkdownDocumentBuilder(commitInfos, flagManager)

class ChangeFormatter(
private val commitInfo: CommitInfo
) {

fun messageHeader() = (commitInfo.message ?: commitInfo.text).lineSequence().first()

fun messageBody() = (commitInfo.message ?: commitInfo.text).lineSequence().drop(1).dropWhile { it.isEmpty() }

fun text() = commitInfo.text


fun scope(format: String = "%s: ") = commitInfo.scope?.let { format.format(it) }.orEmpty()

fun type(format: String = "%s") = commitInfo.type?.let { format.format(it) }.orEmpty()

fun hash(format: String = "%s ", len: Int = 40) =
commitInfo.commits.joinToString("") { format.format(it.sha.take(len)) }
}

fun markdownDocument(commitInfos: List<CommitInfo>, block: MarkdownDocumentBuilder.() -> Unit): String {
val documentBuilder = MarkdownDocumentBuilder(commitInfos, ChangeLogContext())
documentBuilder.block()
return documentBuilder.build()
fun markdownDocument(commitInfos: List<CommitInfo>, block: ChangeLogDocumentBuilder.() -> Unit): String {
val changeLogDocumentBuilder = ChangeLogDocumentBuilder(commitInfos, ChangeLogContext())
changeLogDocumentBuilder.block()
return changeLogDocumentBuilder.build()
}

fun main() {
Expand All @@ -168,14 +22,18 @@ fun main() {
val markdown = markdownDocument(commitInfos) {
appendLine(format.header).appendLine()

withTypes("release") {
skip()
}

breakingChanges {
heading3("Breaking Changes")
appendLine(format.breakingChangeHeader)
// groupByType {
// heading3(group)
// groupByScope {
// heading4(group)
changes {
"- ${hash()}${type()}${scope("(%s)")}: ${messageHeader()}"
formatEach {
"- ${hash()}${type()}${scope("(%s)")}: ${header()}"
}
// }
// }
Expand All @@ -201,13 +59,13 @@ fun main() {
// }


changes {
"- ${hash()}${scope()}${messageHeader()}"
formatEach {
"- ${hash()}${scope()}${header()}"
}
}


groupBySorted({ format.headerTexts[it.type] ?: format.otherChangeHeader }) {
groupBySorted({ format.otherChangeHeader }) {

appendLine(group)
// groupByScope {
Expand All @@ -219,12 +77,10 @@ fun main() {
// }


changes {
"- ${hash()}${scope()}${text()}"
formatEach {
"- ${hash()}${scope()}${fullHeader()}"
}
}

appendLine("End of Document")
}

println(markdown)
Expand Down

0 comments on commit ecd218c

Please sign in to comment.