Skip to content

Instantly share code, notes, and snippets.

@ywett02
Last active April 24, 2020 13:40
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save ywett02/580c81bf782e2b589b06a4e3848a8227 to your computer and use it in GitHub Desktop.
Save ywett02/580c81bf782e2b589b06a4e3848a8227 to your computer and use it in GitHub Desktop.
package com.livepenalty.android
import com.livepenalty.android.CommitRange.BiDirectional
import com.livepenalty.android.CommitRange.UniDirectional
import com.livepenalty.android.ConventionalCommit.chore
import com.livepenalty.android.ConventionalCommit.docs
import com.livepenalty.android.ConventionalCommit.feat
import com.livepenalty.android.ConventionalCommit.fix
import com.livepenalty.android.ConventionalCommit.other
import com.livepenalty.android.ConventionalCommit.refactor
import com.livepenalty.android.ConventionalCommit.style
import com.livepenalty.android.ConventionalCommit.test
import org.gradle.api.DefaultTask
import org.gradle.api.tasks.Input
import org.gradle.api.tasks.TaskAction
import java.io.ByteArrayOutputStream
import java.io.File
import java.text.SimpleDateFormat
import java.util.Date
import java.util.Locale
fun providerAuthorityForFlavor(prefix: String, suffix: String?): String =
if (suffix == null) {
"$prefix.fileprovider"
} else {
"$prefix$suffix.fileprovider"
}
fun nowFormatted(pattern: String) = SimpleDateFormat(pattern, Locale.getDefault()).format(Date())
fun envOrDefaultValue(propertyName: String, defaultValue: Int): Int {
val property = System.getenv(propertyName)
return if (property?.trim().isNullOrEmpty()) {
defaultValue
} else {
property.toInt()
}
}
fun generateVersionCode(majorVersion: Int, minorVersion: Int, patchVersion: Int, buildVersion: Int) =
majorVersion * 10000000 + minorVersion * 100000 + patchVersion * 1000 + buildVersion
fun generateVersionName(majorVersion: Int, minorVersion: Int, patchVersion: Int) =
"${majorVersion}.${minorVersion}.${patchVersion}"
fun isNonStableVersion(version: String): Boolean {
val stableKeyword = listOf("RELEASE", "FINAL", "GA").any { version.toUpperCase().contains(it) }
val regex = "^[0-9,.v-]+(-r)?$".toRegex()
val isStable = stableKeyword || regex.matches(version)
return isStable.not()
}
enum class ConventionalCommit(val title: String) {
feat("Features ⚽"),
fix("Fixes \uD83D\uDC1B"),
docs("Documentation"),
style("Formatting"),
refactor("Refactoring"),
test("Testing"),
chore("Chore"),
other("Other");
}
inline class GitTag(val name: String)
inline class GitCommit(val message: String)
sealed class CommitRange {
abstract val range: String
data class BiDirectional(val start: GitTag, val end: GitTag) : CommitRange() {
override val range: String = "${start.name}..${end.name}"
}
data class UniDirectional(val end: GitTag) : CommitRange() {
override val range: String = end.name
}
}
open class Changelog : DefaultTask() {
@Input
lateinit var flavor: String
@TaskAction
fun generateChangelog() {
val lastTag = lastTag()
println("Current version: $lastTag")
val lastMajorVersion = lastMajorVersion(flavor)
println("Last major Version: $lastMajorVersion")
val range = if (lastMajorVersion == null) UniDirectional(lastTag) else BiDirectional(lastMajorVersion, lastTag)
println("Range: ${range.range}")
val commits = changelogCommits(range)
println(commits.joinToString(separator = "\n"))
val changelogString = changelogString(commits)
println(changelogString)
File("app/build/changelog.md").apply {
writeText(if (changelogString.isEmpty()) "No changes" else changelogString)
}
}
private fun changelogString(commits: List<GitCommit>): String {
val map = hashMapOf<ConventionalCommit, List<String>>()
commits.map { commit -> commit.message.split(":") }.forEach { message ->
val header = message.first()
when {
message.size != 2 -> map.addItem(other, message.joinToString(separator = " "))
header.startsWith(feat.name, true) -> map.addItem(feat, message[1])
header.startsWith(fix.name, true) -> map.addItem(fix, message[1])
header.startsWith(docs.name, true) -> map.addItem(docs, message[1])
header.startsWith(style.name, true) -> map.addItem(style, message[1])
header.startsWith(refactor.name, true) -> map.addItem(refactor, message[1])
header.startsWith(test.name, true) -> map.addItem(test, message[1])
header.startsWith(chore.name, true) -> map.addItem(chore, message[1])
else -> map.addItem(other, message[1])
}
}
return buildString {
map.forEach { entry ->
append("\n")
append("### ${entry.key.title}")
append("\n")
append(entry.value.map { message -> "- ${message.trim()}" }.joinToString(separator = "\n"))
append("\n")
}
}
}
private fun changelogCommits(commitRange: CommitRange): List<GitCommit> {
val commitsOutput = ByteArrayOutputStream()
project.exec {
isIgnoreExitValue = true
commandLine(
"sh",
"-c",
"git log ${commitRange.range} --first-parent --merges --pretty=format:'%b (%h)' | grep -v \"'master' into\""
)
standardOutput = commitsOutput
}
return if (commitsOutput.toString().isBlank()) emptyList() else commitsOutput.toString().split("\n")
.takeWhile { it.isNotBlank() }.map { message -> GitCommit(message) }
}
private fun lastTag(): GitTag {
val tagOutput = ByteArrayOutputStream()
project.exec {
commandLine("git", "describe", "--abbrev=0", "--tags")
standardOutput = tagOutput
}
return GitTag(tagOutput.toString().trim())
}
private fun lastMajorVersion(flavor: String): GitTag? {
val tagsOutput = ByteArrayOutputStream()
project.exec {
commandLine("git", "tag", "-l", "v[0-9]*-$flavor")
standardOutput = tagsOutput
}
val tags = tagsOutput.toString().split("\n").takeWhile { input -> input.isNotEmpty() }
return if (tags.size > 1) GitTag(tags[tags.size - 2]) else null
}
private fun <K, V> HashMap<K, List<V>>.addItem(key: K, item: V) {
this[key] = this.getOrDefault(key, listOf()).plus(item)
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment