Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Revert 32 topic/lockstep updates #33

Merged
merged 2 commits into from
Sep 18, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
1 change: 0 additions & 1 deletion build.sbt
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,6 @@ lazy val core = myCrossProject("core")
Dependencies.catsEffect,
Dependencies.circeParser,
Dependencies.fs2Core,
Dependencies.refined,
Dependencies.scalaTest % Test
),
assembly / test := {}
Expand Down
103 changes: 34 additions & 69 deletions modules/core/src/main/scala/eu/timepit/scalasteward/model/Update.scala
Original file line number Diff line number Diff line change
Expand Up @@ -18,91 +18,57 @@ package eu.timepit.scalasteward.model

import cats.data.NonEmptyList
import cats.implicits._
import eu.timepit.refined.types.string.NonEmptyString
import eu.timepit.scalasteward.model.Update.{Group, Single}
import eu.timepit.scalasteward.util

import scala.util.matching.Regex

sealed trait Update extends Product with Serializable {
def groupId: String
def artifactId: String
def currentVersion: String
def newerVersions: NonEmptyList[String]
final case class Update(
groupId: String,
artifactId: String,
currentVersion: String,
newerVersions: NonEmptyList[String]
) {

/** Returns true if the changes of applying `other` would include the changes
* of applying `this`.
*/
def isImpliedBy(other: Update): Boolean =
groupId === other.groupId &&
artifactId =!= other.artifactId &&
artifactId.startsWith(Update.removeIgnorableSuffix(other.artifactId)) &&
currentVersion === other.currentVersion &&
newerVersions === other.newerVersions

def name: String =
if (Update.commonSuffixes.contains(artifactId))
groupId.split('.').lastOption.getOrElse(groupId)
else
artifactId
artifactId match {
case "core" => groupId.split('.').lastOption.getOrElse(groupId)
case _ => artifactId
}

def nextVersion: String =
newerVersions.head

def replaceAllIn(target: String): Option[String] = {
val quotedSearchTerms = searchTerms.map { term =>
def replaceAllIn(str: String): Option[String] = {
def normalize(searchTerm: String): String =
Regex
.quoteReplacement(Update.removeCommonSuffix(term))
.quoteReplacement(Update.removeIgnorableSuffix(searchTerm))
.replace("-", ".?")
}
val searchTerm = quotedSearchTerms.mkString_("(", "|", ")")
val regex = s"(?i)($searchTerm.*?)${Regex.quote(currentVersion)}".r

val regex =
s"(?i)(${normalize(name)}.*?)${Regex.quote(currentVersion)}".r
var updated = false
val result = regex.replaceAllIn(target, m => {
val result = regex.replaceAllIn(str, m => {
updated = true
m.group(1) + nextVersion
})
if (updated) Some(result) else None
}

def searchTerms: NonEmptyList[String] =
this match {
case s: Single => NonEmptyList.one(s.artifactId)
case g: Group => g.artifactIds.concat(g.artifactIdsPrefix.map(_.value).toList)
}

def show: String = {
val artifacts = this match {
case s: Single => s.artifactId
case g: Group => g.artifactIds.mkString_("{", ", ", "}")
}
val versions = (currentVersion :: newerVersions).mkString_("", " -> ", "")
s"$groupId:$artifacts : $versions"
}
def show: String =
s"$groupId:$artifactId : ${(currentVersion :: newerVersions).mkString_("", " -> ", "")}"
}

object Update {
final case class Single(
groupId: String,
artifactId: String,
currentVersion: String,
newerVersions: NonEmptyList[String]
) extends Update

final case class Group(
groupId: String,
artifactIds: NonEmptyList[String],
currentVersion: String,
newerVersions: NonEmptyList[String]
) extends Update {
override def artifactId: String =
artifactIds.head

def artifactIdsPrefix: Option[NonEmptyString] =
util.longestCommonNonEmptyPrefix(artifactIds)
}

///

def apply(
groupId: String,
artifactId: String,
currentVersion: String,
newerVersions: NonEmptyList[String]
): Single =
Single(groupId, artifactId, currentVersion, newerVersions)

def fromString(str: String): Either[Throwable, Single] =
def fromString(str: String): Either[Throwable, Update] =
Either.catchNonFatal {
val regex = """([^\s:]+):([^\s:]+)[^\s]*\s+:\s+([^\s]+)\s+->(.+)""".r
str match {
Expand All @@ -112,9 +78,8 @@ object Update {
}
}

val commonSuffixes: List[String] =
List("core", "server")

def removeCommonSuffix(str: String): String =
util.removeSuffix(str, commonSuffixes)
def removeIgnorableSuffix(str: String): String =
List("-core", "-server")
.find(suffix => str.endsWith(suffix))
.fold(str)(suffix => str.substring(0, str.length - suffix.length))
}
25 changes: 6 additions & 19 deletions modules/core/src/main/scala/eu/timepit/scalasteward/sbt.scala
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,6 @@ package eu.timepit.scalasteward
import better.files.File
import cats.effect.IO
import eu.timepit.scalasteward.model.Update
import cats.implicits._

object sbt {
def addGlobalPlugins(home: File): IO[Unit] =
Expand All @@ -39,29 +38,17 @@ object sbt {
io.firejail(sbtCmd :+ ";dependencyUpdates ;reload plugins; dependencyUpdates", dir)
.map(lines => sanitizeUpdates(toUpdates(lines)))

def sanitizeUpdates(updates: List[Update.Single]): List[Update] =
updates.distinct
.groupByNel(update => (update.groupId, update.currentVersion, update.newerVersions))
.values
.map { nel =>
val head = nel.head
if (nel.tail.nonEmpty)
Update.Group(
head.groupId,
nel.map(_.artifactId).sorted,
head.currentVersion,
head.newerVersions
)
else
head
}
.toList
def sanitizeUpdates(updates: List[Update]): List[Update] = {
val distinctUpdates = updates.distinct
distinctUpdates
.filterNot(update => distinctUpdates.exists(other => update.isImpliedBy(other)))
.sortBy(update => (update.groupId, update.artifactId))
}

val sbtCmd: List[String] =
List("sbt", "-no-colors")

def toUpdates(lines: List[String]): List[Update.Single] =
def toUpdates(lines: List[String]): List[Update] =
lines.flatMap { line =>
val trimmed = line.replace("[info]", "").trim
Update.fromString(trimmed).toSeq
Expand Down
20 changes: 0 additions & 20 deletions modules/core/src/main/scala/eu/timepit/scalasteward/util.scala
Original file line number Diff line number Diff line change
Expand Up @@ -17,29 +17,9 @@
package eu.timepit.scalasteward

import cats.Monad
import cats.data.NonEmptyList
import cats.implicits._
import eu.timepit.refined.types.string.NonEmptyString

object util {
def ifTrue[F[_]: Monad](fb: F[Boolean])(f: F[Unit]): F[Unit] =
fb.ifM(f, Monad[F].unit)

def longestCommonPrefix(s1: String, s2: String): String = {
var i = 0
val min = math.min(s1.length, s2.length)
while (i < min && s1(i) == s2(i)) i = i + 1
s1.substring(0, i)
}

def longestCommonPrefix(xs: NonEmptyList[String]): String =
xs.reduceLeft(longestCommonPrefix)

def longestCommonNonEmptyPrefix(xs: NonEmptyList[String]): Option[NonEmptyString] =
NonEmptyString.unapply(longestCommonPrefix(xs))

def removeSuffix(target: String, suffixes: List[String]): String =
suffixes
.find(suffix => target.endsWith(suffix))
.fold(target)(suffix => target.substring(0, target.length - suffix.length))
}
Original file line number Diff line number Diff line change
Expand Up @@ -106,30 +106,12 @@ class UpdateTest extends FunSuite with Matchers {
.replaceAllIn(original) shouldBe Some(expected)
}

test("replaceAllIn: group with prefix val") {
val original = """ val circe = "0.10.0-M1" """
val expected = """ val circe = "0.10.0-M2" """
Update
.Group(
"io.circe",
Nel.of("circe-generic", "circe-literal", "circe-parser", "circe-testing"),
"0.10.0-M1",
Nel.of("0.10.0-M2")
)
.replaceAllIn(original) shouldBe Some(expected)
}

test("replaceAllIn: group with repeated version") {
val original =
""" "com.pepegar" %% "hammock-core" % "0.8.1",
| "com.pepegar" %% "hammock-circe" % "0.8.1"
""".stripMargin.trim
val expected =
""" "com.pepegar" %% "hammock-core" % "0.8.5",
| "com.pepegar" %% "hammock-circe" % "0.8.5"
""".stripMargin.trim
Update
.Group("com.pepegar", Nel.of("hammock-core", "hammock-circe"), "0.8.1", Nel.of("0.8.5"))
.replaceAllIn(original) shouldBe Some(expected)
test("isImpliedBy") {
val update0 = Update("org.specs2", "specs2-core", "3.9.4", Nel.of("3.9.5"))
val update1 = update0.copy(artifactId = "specs2-scalacheck")
update0.isImpliedBy(update0) shouldBe false
update0.isImpliedBy(update1) shouldBe false
update1.isImpliedBy(update0) shouldBe true
update1.isImpliedBy(update1) shouldBe false
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,14 +8,7 @@ class sbtTest extends FunSuite with Matchers {
test("sanitizeUpdates") {
val update0 = Update("org.specs2", "specs2-core", "3.9.4", Nel.of("3.9.5"))
val update1 = update0.copy(artifactId = "specs2-scalacheck")
sbt.sanitizeUpdates(List(update0, update1)) shouldBe List(
Update.Group(
"org.specs2",
Nel.of("specs2-core", "specs2-scalacheck"),
"3.9.4",
Nel.of("3.9.5")
)
)
sbt.sanitizeUpdates(List(update0, update1)) shouldBe List(update0)
}

test("toUpdates") {
Expand Down
1 change: 0 additions & 1 deletion project/Dependencies.scala
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,5 @@ object Dependencies {
val catsEffect = "org.typelevel" %% "cats-effect" % "1.0.0"
val circeParser = "io.circe" %% "circe-parser" % "0.10.0-M2"
val fs2Core = "co.fs2" %% "fs2-core" % "1.0.0-M5"
val refined = "eu.timepit" %% "refined" % "0.9.2"
val scalaTest = "org.scalatest" %% "scalatest" % "3.0.5"
}