diff --git a/build.sbt b/build.sbt index 5e508df..9276ce8 100644 --- a/build.sbt +++ b/build.sbt @@ -8,7 +8,7 @@ externalResolvers := Seq( ) val ProjectName: String = "sbt-gs-calver" -val Description: String = "SBT 1.9.0+ plugin for Git-based semantic versioning." +val Description: String = "SBT 1.9.0+ plugin for calendar versioning." def getProperty[A]( name: String, diff --git a/project/build.properties b/project/build.properties index abbbce5..04267b1 100644 --- a/project/build.properties +++ b/project/build.properties @@ -1 +1 @@ -sbt.version=1.9.8 +sbt.version=1.9.9 diff --git a/src/main/scala/gs/CalVer.scala b/src/main/scala/gs/CalVer.scala index 0bf3204..ec18869 100644 --- a/src/main/scala/gs/CalVer.scala +++ b/src/main/scala/gs/CalVer.scala @@ -1,6 +1,169 @@ package gs -case class CalVer(value: String) +import java.time.LocalDate +import scala.collection.mutable.ListBuffer + +/** + * Representation of a rendered Calendar Version. + * + * @param value The version string. + * @param pattern The pattern which rendered the version string. + */ +case class CalVer( + value: String, + pattern: CalVer.Pattern +) object CalVer { + /** + * Render the given pattern to a Calendar Version. + * + * @param pattern The pattern to render. + * @return The rendered CalVer value. + */ + def render(pattern: Pattern): CalVer = { + val today = LocalDate.now() + CalVer( + value = pattern.items.map { + case Left(component) => + component match { + case Component.YYYY => today.getYear().toString() + case Component.MM => today.getMonthValue().toString() + case Component.DD => today.getDayOfMonth().toString() + case Component.Label(label) => label + case Component.GitSha(length) => Git.getSha(length) + } + case Right(delimiter) => Delimiter.render(delimiter) + }.mkString, + pattern = pattern + ) + } + + object Defaults { + /** + * Default pattern for release versions. + */ + val ReleasePattern: Pattern = + Pattern + .builder(Component.YYYY) + .addDelimiter(Delimiter.Dot) + .addComponent(Component.MM) + .addDelimiter(Delimiter.Dot) + .addComponent(Component.DD) + .addDelimiter(Delimiter.Hyphen) + .addComponent(Component.GitSha(7)) + .build() + + /** + * Default pattern for pre-release versions. + */ + val PreReleasePattern: Pattern = + Pattern + .builder(Component.YYYY) + .addDelimiter(Delimiter.Dot) + .addComponent(Component.MM) + .addDelimiter(Delimiter.Dot) + .addComponent(Component.DD) + .addDelimiter(Delimiter.Hyphen) + .addComponent(Component.Label("SNAPSHOT")) + .build() + } + + sealed trait Component + + object Component { + /** + * CalVer Component: Year. + */ + case object YYYY extends Component + + /** + * CalVer Component: Month (1-12), without a leading zero. + */ + case object MM extends Component + + /** + * CalVer Component: Day of Month, without a leading zero. + */ + case object DD extends Component + + /** + * CalVer Component: Label. + * + * This component contains an arbitrary string value. + * + * @param value The value of the label. + */ + case class Label(value: String) extends Component + + /** + * CalVer Component. Git SHA. + * + * This component defines the length (first N characters) of the Git SHA to + * take from the current branch of the current repository. An empty string + * is used if lookup fails or no commits exist. + * + * @param length The number of characters of the Git SHA to use. + */ + case class GitSha(length: Int) extends Component + + def describe(component: Component): String = component match { + case YYYY => "YYYY" + case MM => "MM" + case DD => "DD" + case Label(value) => value + case GitSha(length) => s"SHA($length)" + } + } + + sealed abstract class Delimiter(val value: String) + + object Delimiter { + case object Dot extends Delimiter(".") + case object Hyphen extends Delimiter("-") + case object Plus extends Delimiter("+") + case object Underscore extends Delimiter("_") + + def render(delimiter: Delimiter): String = delimiter.value + } + + final class Pattern private (val items: List[Pattern.Item]) { + lazy val str: String = + items.map { + case Left(component) => Component.describe(component) + case Right(delimiter) => Delimiter.render(delimiter) + }.mkString + + override def toString(): String = str + } + + object Pattern { + private type Item = Either[Component, Delimiter] + + def builder(component: Component): Builder = Builder(component) + + final class Builder private () { + private val buffer: ListBuffer[Item] = new ListBuffer() + + def addComponent(component: Component): Builder = { + val _ = buffer.append(Left(component)) + this + } + + def addDelimiter(delimiter: Delimiter): Builder = { + val _ = buffer.append(Right(delimiter)) + this + } + + def build(): Pattern = new Pattern(buffer.toList) + } + + object Builder { + def apply(component: Component): Builder = { + val b = new Builder() + b.addComponent(component) + b + } + } + } } diff --git a/src/main/scala/gs/CalVerKeys.scala b/src/main/scala/gs/CalVerKeys.scala index f777948..57fdfb5 100644 --- a/src/main/scala/gs/CalVerKeys.scala +++ b/src/main/scala/gs/CalVerKeys.scala @@ -12,6 +12,31 @@ object CalVerKeys { "Calculated CalVer." ) + /** + * SBT setting which defines the CalVer pattern used to calculate the final + * version. This should not be set manually, please use + * `calVerReleasePattern` and `calVerPreReleasePattern`. + */ + lazy val calVerPattern = settingKey[CalVer.Pattern]( + "Selected CalVer pattern." + ) + + /** + * User-defined SBT setting which defines the CalVer pattern to use for a + * release build. + */ + lazy val calVerReleasePattern = settingKey[Option[CalVer.Pattern]]( + "Pattern to use for calculating CalVer for release builds." + ) + + /** + * User-defined SBT setting which defines the CalVer pattern to use for a + * pre-release build. + */ + lazy val calVerPreReleasePattern = settingKey[Option[CalVer.Pattern]]( + "Pattern to use for calculating CalVer for pre-release builds." + ) + /** Task which emits the current version at the informational log level. */ lazy val calVerInfo = taskKey[Unit]( diff --git a/src/main/scala/gs/CalVerPlugin.scala b/src/main/scala/gs/CalVerPlugin.scala index 436d042..58397ff 100644 --- a/src/main/scala/gs/CalVerPlugin.scala +++ b/src/main/scala/gs/CalVerPlugin.scala @@ -11,9 +11,22 @@ object CalVerPlugin extends AutoPlugin { // Perform all version calculations and expose as a variable. lazy val calVerDefaults: Seq[Setting[_]] = { - // Expose the relevant values as setting keys. + val isRelease = PluginProperties.isRelease() Seq( - calVer := "" + calVerReleasePattern := calVerReleasePattern.value.orElse( + Some(CalVer.Defaults.ReleasePattern) + ), + calVerPreReleasePattern := calVerPreReleasePattern.value.orElse( + Some(CalVer.Defaults.PreReleasePattern) + ), + calVerPattern := { + if (isRelease) { + calVerReleasePattern.value.getOrElse(CalVer.Defaults.ReleasePattern) + } else { + calVerPreReleasePattern.value.getOrElse(CalVer.Defaults.PreReleasePattern) + } + } , + calVer := CalVer.render(calVerPattern.value).value ) } @@ -25,6 +38,7 @@ object CalVerPlugin extends AutoPlugin { calVerInfo := { val log = Keys.streams.value.log log.info(s"[CalVer] Version: ${calVer.value}") + log.info(s"[CalVer] Pattern: ${calVerPattern.value}") } ) diff --git a/src/main/scala/gs/Git.scala b/src/main/scala/gs/Git.scala index f8ff8d9..ad26702 100644 --- a/src/main/scala/gs/Git.scala +++ b/src/main/scala/gs/Git.scala @@ -1,15 +1,16 @@ package gs -//import java.io.File -//import scala.util.Try - object Git { - - /*def getLatestSemVer(): SemVer = { - // Suppress all error output. Suppress exceptions for the failed process. - // Capture the standard output and parse it if the command succeeds. + /** + * Get the latest SHA. If there are no commits on this branch, the empty + * string will be returned. + * + * @param length The number of characters to take from the SHA. + * @return The latest SHA. + */ + def getSha(length: Int): String = { val result = os - .proc("git", "describe", "--tags", "--abbrev=0") + .proc("git", "rev-parse", "HEAD") .call( stderr = os.ProcessOutput( ( @@ -21,9 +22,8 @@ object Git { ) if (result.exitCode != 0) - SemVer.DefaultVersion + result.out.text().take(length) else - SemVer.parse(result.out.text()).getOrElse(SemVer.DefaultVersion) - }*/ - + "" + } } diff --git a/src/main/scala/gs/PluginProperties.scala b/src/main/scala/gs/PluginProperties.scala index fa2bc46..51d6eb7 100644 --- a/src/main/scala/gs/PluginProperties.scala +++ b/src/main/scala/gs/PluginProperties.scala @@ -24,9 +24,8 @@ object PluginProperties { /** Use one of the following to calculate an incremented release version: * - * - `sbt -Drelease=major` - * - `sbt -Drelease=minor` - * - `sbt -Drelease=patch` + * - `sbt -Drelease=true` + * - `sbt -Drelease=false` */ val ReleaseProperty: String = "release"