diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 9c3f67e..4a52513 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -9,9 +9,9 @@ name: Continuous Integration on: pull_request: - branches: ['*'] + branches: ['**'] push: - branches: ['*'] + branches: ['**'] tags: [v*] env: @@ -27,43 +27,45 @@ jobs: strategy: matrix: os: [ubuntu-latest] - scala: [2.12.13, 2.13.6, 3.0.0] - java: [openjdk@1.11.0] + scala: [2.12.20, 2.13.16, 3.3.6] + java: [zulu@8, temurin@17] runs-on: ${{ matrix.os }} steps: - name: Checkout current branch (full) - uses: actions/checkout@v2 + uses: actions/checkout@v6 with: fetch-depth: 0 - - name: Setup Java and Scala - uses: olafurpg/setup-scala@v10 + - name: Setup Java (zulu@8) + if: matrix.java == 'zulu@8' + uses: actions/setup-java@v5 with: - java-version: ${{ matrix.java }} + distribution: zulu + java-version: 8 + cache: sbt - - name: Cache sbt - uses: actions/cache@v2 + - name: Setup Java (temurin@17) + if: matrix.java == 'temurin@17' + uses: actions/setup-java@v5 with: - path: | - ~/.sbt - ~/.ivy2/cache - ~/.coursier/cache/v1 - ~/.cache/coursier/v1 - ~/AppData/Local/Coursier/Cache/v1 - ~/Library/Caches/Coursier/v1 - key: ${{ runner.os }}-sbt-cache-v2-${{ hashFiles('**/*.sbt') }}-${{ hashFiles('project/build.properties') }} + distribution: temurin + java-version: 17 + cache: sbt + + - name: Setup sbt + uses: sbt/setup-sbt@v1 - name: Check that workflows are up to date - run: sbt ++${{ matrix.scala }} githubWorkflowCheck + run: sbt '++ ${{ matrix.scala }}' githubWorkflowCheck - name: Build project - run: sbt ++${{ matrix.scala }} test + run: sbt '++ ${{ matrix.scala }}' test - name: Compress target directories run: tar cf targets.tar target stringdiff/.jvm/target stringdiff/.js/target project/target - name: Upload target directories - uses: actions/upload-artifact@v2 + uses: actions/upload-artifact@v5 with: name: target-${{ matrix.os }}-${{ matrix.scala }}-${{ matrix.java }} path: targets.tar @@ -75,60 +77,62 @@ jobs: strategy: matrix: os: [ubuntu-latest] - scala: [2.13.6] - java: [openjdk@1.11.0] + scala: [2.13.16] + java: [zulu@8] runs-on: ${{ matrix.os }} steps: - name: Checkout current branch (full) - uses: actions/checkout@v2 + uses: actions/checkout@v6 with: fetch-depth: 0 - - name: Setup Java and Scala - uses: olafurpg/setup-scala@v10 + - name: Setup Java (zulu@8) + if: matrix.java == 'zulu@8' + uses: actions/setup-java@v5 with: - java-version: ${{ matrix.java }} + distribution: zulu + java-version: 8 + cache: sbt - - name: Cache sbt - uses: actions/cache@v2 + - name: Setup Java (temurin@17) + if: matrix.java == 'temurin@17' + uses: actions/setup-java@v5 with: - path: | - ~/.sbt - ~/.ivy2/cache - ~/.coursier/cache/v1 - ~/.cache/coursier/v1 - ~/AppData/Local/Coursier/Cache/v1 - ~/Library/Caches/Coursier/v1 - key: ${{ runner.os }}-sbt-cache-v2-${{ hashFiles('**/*.sbt') }}-${{ hashFiles('project/build.properties') }} - - - name: Download target directories (2.12.13) - uses: actions/download-artifact@v2 + distribution: temurin + java-version: 17 + cache: sbt + + - name: Setup sbt + uses: sbt/setup-sbt@v1 + + - name: Download target directories (2.12.20) + uses: actions/download-artifact@v6 with: - name: target-${{ matrix.os }}-2.12.13-${{ matrix.java }} + name: target-${{ matrix.os }}-2.12.20-${{ matrix.java }} - - name: Inflate target directories (2.12.13) + - name: Inflate target directories (2.12.20) run: | tar xf targets.tar rm targets.tar - - name: Download target directories (2.13.6) - uses: actions/download-artifact@v2 + - name: Download target directories (2.13.16) + uses: actions/download-artifact@v6 with: - name: target-${{ matrix.os }}-2.13.6-${{ matrix.java }} + name: target-${{ matrix.os }}-2.13.16-${{ matrix.java }} - - name: Inflate target directories (2.13.6) + - name: Inflate target directories (2.13.16) run: | tar xf targets.tar rm targets.tar - - name: Download target directories (3.0.0) - uses: actions/download-artifact@v2 + - name: Download target directories (3.3.6) + uses: actions/download-artifact@v6 with: - name: target-${{ matrix.os }}-3.0.0-${{ matrix.java }} + name: target-${{ matrix.os }}-3.3.6-${{ matrix.java }} - - name: Inflate target directories (3.0.0) + - name: Inflate target directories (3.3.6) run: | tar xf targets.tar rm targets.tar - - run: sbt ++${{ matrix.scala }} ci-release \ No newline at end of file + - run: sbt ci-release diff --git a/.github/workflows/clean.yml b/.github/workflows/clean.yml index b535fcc..4bb28c8 100644 --- a/.github/workflows/clean.yml +++ b/.github/workflows/clean.yml @@ -9,6 +9,9 @@ name: Clean on: push +permissions: + actions: write + jobs: delete-artifacts: name: Delete Artifacts @@ -17,6 +20,7 @@ jobs: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} steps: - name: Delete artifacts + shell: bash {0} run: | # Customize those three lines with your repository and credentials: REPO=${GITHUB_API_URL}/repos/${{ github.repository }} @@ -25,7 +29,7 @@ jobs: ghapi() { curl --silent --location --user _:$GITHUB_TOKEN "$@"; } # A temporary file which receives HTTP response headers. - TMPFILE=/tmp/tmp.$$ + TMPFILE=$(mktemp) # An associative array, key: artifact name, value: number of artifacts of that name. declare -A ARTCOUNT @@ -56,4 +60,4 @@ jobs: printf "Deleting '%s' #%d, %'d bytes\n" $name ${ARTCOUNT[$name]} $size ghapi -X DELETE $REPO/actions/artifacts/$id done - done \ No newline at end of file + done diff --git a/build.sbt b/build.sbt index b3d3285..890d056 100644 --- a/build.sbt +++ b/build.sbt @@ -3,7 +3,7 @@ inThisBuild( organization := "app.tulz", homepage := Some(url("https://github.com/tulz-app/stringdiff")), licenses := List("MIT" -> url("https://github.com/tulz-app/stringdiff/blob/main/LICENSE.md")), - scmInfo := Some(ScmInfo(url("https://github.com/tulz-app/stringdiff"), "scm:git@github.com/tulz-app/laminext.git")), + scmInfo := Some(ScmInfo(url("https://github.com/tulz-app/stringdiff"), "scm:git@github.com/tulz-app/stringdiff.git")), developers := List(Developer("yurique", "Iurii Malchenko", "i@yurique.com", url("https://github.com/yurique"))), scalaVersion := ScalaVersions.v213, description := "String diff for Scala", @@ -14,7 +14,7 @@ inThisBuild( ), Test / publishArtifact := false, Test / parallelExecution := false, - githubWorkflowJavaVersions := Seq("openjdk@1.11.0"), + githubWorkflowJavaVersions += JavaSpec.temurin("17"), githubWorkflowTargetTags ++= Seq("v*"), githubWorkflowPublishTargetBranches := Seq(RefPredicate.StartsWith(Ref.Tag("v"))), githubWorkflowPublish := Seq(WorkflowStep.Sbt(List("ci-release"))), @@ -41,8 +41,7 @@ lazy val stringdiff = .settings( ScalaOptions.fixOptions, libraryDependencies ++= Seq( - "org.scala-lang.modules" %%% "scala-collection-compat" % "2.4.4", - "org.scalatest" %%% "scalatest" % "3.2.9" % Test + "org.scalatest" %%% "scalatest" % "3.2.9" % Test ) ) diff --git a/project/ScalaVersions.scala b/project/ScalaVersions.scala index 53de1b3..0de9b7c 100644 --- a/project/ScalaVersions.scala +++ b/project/ScalaVersions.scala @@ -1,5 +1,5 @@ object ScalaVersions { - val v212 = "2.12.13" - val v213 = "2.13.6" - val v3 = "3.0.0" + val v212 = "2.12.20" + val v213 = "2.13.16" + val v3 = "3.3.6" } diff --git a/project/build.properties b/project/build.properties index 19479ba..30b7fd9 100644 --- a/project/build.properties +++ b/project/build.properties @@ -1 +1 @@ -sbt.version=1.5.2 +sbt.version=1.12.0 diff --git a/project/plugins.sbt b/project/plugins.sbt index 487b05c..aeac1b8 100644 --- a/project/plugins.sbt +++ b/project/plugins.sbt @@ -1,17 +1,13 @@ logLevel := Level.Warn -addSbtPlugin("org.scala-js" % "sbt-scalajs" % "1.5.1") +addSbtPlugin("org.scala-js" % "sbt-scalajs" % "1.19.0") -addSbtPlugin("org.portable-scala" % "sbt-scalajs-crossproject" % "1.0.0") +addSbtPlugin("org.portable-scala" % "sbt-scalajs-crossproject" % "1.3.2") addSbtPlugin("org.scalameta" % "sbt-scalafmt" % "2.4.2") -addSbtPlugin("com.github.sbt" % "sbt-pgp" % "2.1.2") +addSbtPlugin("com.github.sbt" % "sbt-github-actions" % "0.29.0") -addSbtPlugin("org.xerial.sbt" % "sbt-sonatype" % "3.9.7") +addSbtPlugin("org.typelevel" % "sbt-tpolecat" % "0.5.2") -addSbtPlugin("com.codecommit" % "sbt-github-actions" % "0.10.1") - -addSbtPlugin("io.github.davidgregory084" % "sbt-tpolecat" % "0.1.17") - -addSbtPlugin("com.geirsson" % "sbt-ci-release" % "1.5.7") +addSbtPlugin("com.github.sbt" % "sbt-ci-release" % "1.11.2") diff --git a/stringdiff/src/main/scala-2.12/app/tulz/diff/compat/IndexedSeqView.scala b/stringdiff/src/main/scala-2.12/app/tulz/diff/compat/IndexedSeqView.scala deleted file mode 100644 index 5baeb03..0000000 --- a/stringdiff/src/main/scala-2.12/app/tulz/diff/compat/IndexedSeqView.scala +++ /dev/null @@ -1,28 +0,0 @@ -package app.tulz.diff -package compat - -import scala.collection.SeqView -import scala.collection.immutable.Range - -class IndexedSeqView[A]( - private[compat] val underlying: SeqView[A, _] -) { - def apply(index: Int): A = underlying(index) - - def size: Int = underlying.length - - def isEmpty: Boolean = underlying.isEmpty - - def concat(other: IndexedSeqView[A]): IndexedSeqView[A] = - new IndexedSeqView((underlying ++ other.underlying).view) - - def take(n: Int): IndexedSeqView[A] = new IndexedSeqView(underlying.take(n)) - - def slice(from: Int, until: Int): IndexedSeqView[A] = new IndexedSeqView(underlying.slice(from, until)) - - def toIndexedSeq: IndexedSeq[A] = underlying.toIndexedSeq - - def indices: Range = underlying.indices - - def forall(predicate: A => Boolean): Boolean = underlying.forall(predicate) -} diff --git a/stringdiff/src/main/scala-2.12/app/tulz/diff/compat/IndexedSeqViewOfCharOps.scala b/stringdiff/src/main/scala-2.12/app/tulz/diff/compat/IndexedSeqViewOfCharOps.scala deleted file mode 100644 index 60462d8..0000000 --- a/stringdiff/src/main/scala-2.12/app/tulz/diff/compat/IndexedSeqViewOfCharOps.scala +++ /dev/null @@ -1,9 +0,0 @@ -package app.tulz.diff.compat - -class IndexedSeqViewOfCharOps( - underlying: IndexedSeqView[Char] -) { - - def mkString: String = underlying.underlying.mkString - -} diff --git a/stringdiff/src/main/scala-2.12/app/tulz/diff/compat/IndexedSeqViewOfStringOps.scala b/stringdiff/src/main/scala-2.12/app/tulz/diff/compat/IndexedSeqViewOfStringOps.scala deleted file mode 100644 index 3cbd4bc..0000000 --- a/stringdiff/src/main/scala-2.12/app/tulz/diff/compat/IndexedSeqViewOfStringOps.scala +++ /dev/null @@ -1,9 +0,0 @@ -package app.tulz.diff.compat - -class IndexedSeqViewOfStringOps( - underlying: IndexedSeqView[String] -) { - - def mkString: String = underlying.underlying.mkString - -} diff --git a/stringdiff/src/main/scala-2.12/app/tulz/diff/compat/package.scala b/stringdiff/src/main/scala-2.12/app/tulz/diff/compat/package.scala deleted file mode 100644 index 239e657..0000000 --- a/stringdiff/src/main/scala-2.12/app/tulz/diff/compat/package.scala +++ /dev/null @@ -1,24 +0,0 @@ -package app.tulz.diff - -import scala.collection.SeqView -import scala.language.implicitConversions - -package object compat { - - implicit def indexedSeq_ViewToIndexedSeqView[A]( - underlying: SeqView[A, IndexedSeq[A]] - ): IndexedSeqView[A] = new IndexedSeqView[A](underlying) - - implicit def string_ViewToIndexedSeqView( - underlying: SeqView[Char, String] - ): IndexedSeqView[Char] = new IndexedSeqView[Char](underlying) - - implicit def indexedSeqViewOfCharOps( - underlying: IndexedSeqView[Char] - ): IndexedSeqViewOfCharOps = new IndexedSeqViewOfCharOps(underlying) - - implicit def indexedSeqViewOfStringOps( - underlying: IndexedSeqView[String] - ): IndexedSeqViewOfStringOps = new IndexedSeqViewOfStringOps(underlying) - -} diff --git a/stringdiff/src/main/scala-2.13/app/tulz/diff/compat/package.scala b/stringdiff/src/main/scala-2.13/app/tulz/diff/compat/package.scala deleted file mode 100644 index 6cb4b64..0000000 --- a/stringdiff/src/main/scala-2.13/app/tulz/diff/compat/package.scala +++ /dev/null @@ -1,7 +0,0 @@ -package app.tulz.diff - -package object compat { - - type IndexedSeqView[+A] = scala.collection.IndexedSeqView[A] - -} diff --git a/stringdiff/src/main/scala-3/app/tulz/diff/compat/package.scala b/stringdiff/src/main/scala-3/app/tulz/diff/compat/package.scala deleted file mode 100644 index 6cb4b64..0000000 --- a/stringdiff/src/main/scala-3/app/tulz/diff/compat/package.scala +++ /dev/null @@ -1,7 +0,0 @@ -package app.tulz.diff - -package object compat { - - type IndexedSeqView[+A] = scala.collection.IndexedSeqView[A] - -} diff --git a/stringdiff/src/main/scala/app/tulz/diff/MyersDiff.scala b/stringdiff/src/main/scala/app/tulz/diff/MyersDiff.scala index a6dadad..6cb6588 100644 --- a/stringdiff/src/main/scala/app/tulz/diff/MyersDiff.scala +++ b/stringdiff/src/main/scala/app/tulz/diff/MyersDiff.scala @@ -1,18 +1,16 @@ package app.tulz.diff -import compat._ - object MyersDiff { - def diff[A](ss1: IndexedSeqView[A], ss2: IndexedSeqView[A]): Seq[Operation] = { + def diff[A](ss1: IndexedSeq[A], ss2: IndexedSeq[A]): Seq[Operation] = { def mod(a: Int, b: Int): Int = { (b + (a % b)) % b } def rec( - ss1: IndexedSeqView[A], - ss2: IndexedSeqView[A], + ss1: IndexedSeq[A], + ss2: IndexedSeq[A], i: Int, j: Int ): Seq[Operation] = { @@ -24,10 +22,13 @@ object MyersDiff { val w = N - M val g = Array.fill(Z)(0) val p = Array.fill(Z)(0) - for (h <- 0 until (L / 2 + mod(L, 2)) + 1) { - for (r <- 0 until 2) { + var h = 0 + while (h < (L / 2 + mod(L, 2)) + 1) { + var r = 0 + while (r < 2) { val (c, d, o, m) = if (r == 0) (g, p, 1, 1) else (p, g, 0, -1) - for (k <- -(h - 2 * Math.max(0, h - M)) until h - 2 * Math.max(0, h - N) + 1 by 2) { + var k = -(h - 2 * Math.max(0, h - M)) + while (k < h - 2 * Math.max(0, h - N) + 1) { var a = if (k == -h || k != h && c(mod(k - 1, Z)) < c(mod(k + 1, Z))) { c(mod(k + 1, Z)) @@ -61,8 +62,11 @@ object MyersDiff { return Seq.empty } } + k += 2 } + r += 1 } + h += 1 } throw new RuntimeException("should never have reached here") } else if (N > 0) { diff --git a/stringdiff/src/main/scala/app/tulz/diff/MyersInterpret.scala b/stringdiff/src/main/scala/app/tulz/diff/MyersInterpret.scala index fa150b2..d90bdfc 100644 --- a/stringdiff/src/main/scala/app/tulz/diff/MyersInterpret.scala +++ b/stringdiff/src/main/scala/app/tulz/diff/MyersInterpret.scala @@ -3,7 +3,6 @@ package app.tulz.diff import app.tulz.diff.MyersDiff.Operation import scala.collection.mutable.ListBuffer -import compat._ object MyersInterpret { @@ -12,13 +11,13 @@ object MyersInterpret { def apply[A]( ops: Seq[Operation], - s1: IndexedSeqView[A], - s2: IndexedSeqView[A] - ): List[DiffElement[IndexedSeqView[A]]] = + s1: IndexedSeq[A], + s2: IndexedSeq[A] + ): List[DiffElement[IndexedSeq[A]]] = if (ops.isEmpty) { List(DiffElement.InBoth(s1)) } else { - val buffer = new ListBuffer[DiffElement[IndexedSeqView[A]]] + val buffer = new ListBuffer[DiffElement[IndexedSeq[A]]] buffer.sizeHint(ops.length) (Start +: ops).zip(ops :+ End).foreach { case (Start, Delete(deleteFrom, deleteCount)) => diff --git a/stringdiff/src/main/scala/app/tulz/diff/SeqDiff.scala b/stringdiff/src/main/scala/app/tulz/diff/SeqDiff.scala index 08d9531..a840e4c 100644 --- a/stringdiff/src/main/scala/app/tulz/diff/SeqDiff.scala +++ b/stringdiff/src/main/scala/app/tulz/diff/SeqDiff.scala @@ -1,7 +1,6 @@ package app.tulz.diff import app.tulz.diff.util.DiffCollapse -import compat._ object SeqDiff { @@ -10,13 +9,13 @@ object SeqDiff { s2: IndexedSeq[A], collapse: Boolean = true ): List[DiffElement[IndexedSeq[A]]] = - apply(s1.view, s2.view, collapse).map(_.map(_.toIndexedSeq)) + apply(s1, s2, collapse) def apply[A]( - s1: IndexedSeqView[A], - s2: IndexedSeqView[A], + s1: IndexedSeq[A], + s2: IndexedSeq[A], collapse: Boolean = true - ): List[DiffElement[IndexedSeqView[A]]] = { + ): List[DiffElement[IndexedSeq[A]]] = { val myersDiff = MyersDiff.diff(s1, s2) val diff = MyersInterpret(myersDiff, s1, s2) if (collapse) { diff --git a/stringdiff/src/main/scala/app/tulz/diff/StringDiff.scala b/stringdiff/src/main/scala/app/tulz/diff/StringDiff.scala index d685a5f..b1e8c2e 100644 --- a/stringdiff/src/main/scala/app/tulz/diff/StringDiff.scala +++ b/stringdiff/src/main/scala/app/tulz/diff/StringDiff.scala @@ -2,7 +2,6 @@ package app.tulz.diff import app.tulz.diff.format.DiffFormat import app.tulz.diff.util.DiffCollapse -import compat._ object StringDiff { @@ -38,8 +37,10 @@ object StringDiff { s2: String, collapse: Boolean = true ): List[DiffElement[String]] = { - val myersDiff = MyersDiff.diff(s1.view, s2.view) - val diff = MyersInterpret(myersDiff, s1.view, s2.view) + val v1 = s1.toIndexedSeq + val v2 = s2.toIndexedSeq + val myersDiff = MyersDiff.diff(v1, v2) + val diff = MyersInterpret(myersDiff, v1, v2) val result = if (collapse) { DiffCollapse(diff) } else { diff --git a/stringdiff/src/main/scala/app/tulz/diff/TokenDiff.scala b/stringdiff/src/main/scala/app/tulz/diff/TokenDiff.scala index 2f90974..3e29771 100644 --- a/stringdiff/src/main/scala/app/tulz/diff/TokenDiff.scala +++ b/stringdiff/src/main/scala/app/tulz/diff/TokenDiff.scala @@ -4,7 +4,6 @@ import app.tulz.diff.format.DiffFormat import app.tulz.diff.util.DiffCollapse import app.tulz.diff.util.DiffPrettier import app.tulz.diff.util.DiffTokenize -import compat._ import scala.collection.mutable.ListBuffer object TokenDiff { @@ -23,7 +22,7 @@ object TokenDiff { if (pos < s.length) { buffer.append(s.substring(pos)) } - buffer.toIndexedSeq + buffer.toVector } def apply( @@ -54,8 +53,8 @@ object TokenDiff { s2: String ): List[DiffElement[String]] = { val diff = SeqDiff( - TokenDiff.tokenize(s1).view, - TokenDiff.tokenize(s2).view, + TokenDiff.tokenize(s1), + TokenDiff.tokenize(s2), collapse = false ) var in = diff diff --git a/stringdiff/src/main/scala/app/tulz/diff/util/DiffCollapse.scala b/stringdiff/src/main/scala/app/tulz/diff/util/DiffCollapse.scala index fe771ba..1287299 100644 --- a/stringdiff/src/main/scala/app/tulz/diff/util/DiffCollapse.scala +++ b/stringdiff/src/main/scala/app/tulz/diff/util/DiffCollapse.scala @@ -5,13 +5,12 @@ import DiffElement.Diff import DiffElement.InBoth import DiffElement.InFirst import DiffElement.InSecond -import compat._ private[diff] object DiffCollapse { def apply[A]( - diff: List[DiffElement[IndexedSeqView[A]]] - ): List[DiffElement[IndexedSeqView[A]]] = + diff: List[DiffElement[IndexedSeq[A]]] + ): List[DiffElement[IndexedSeq[A]]] = ListScan(diff) { case InBoth(both) :: tail if both.isEmpty => Nil -> tail @@ -32,7 +31,7 @@ private[diff] object DiffCollapse { (InSecond(second) :: Nil) -> tail case InBoth(both1) :: InBoth(both2) :: tail => - Nil -> (InBoth(both1.concat(both2)) :: tail) + Nil -> (InBoth(both1 ++ both2) :: tail) case InSecond(second) :: InFirst(first) :: tail => Nil -> (Diff(first, second) :: tail) @@ -41,22 +40,22 @@ private[diff] object DiffCollapse { Nil -> (Diff(first, second) :: tail) case InFirst(first1) :: InFirst(first2) :: tail => - Nil -> (InFirst(first1.concat(first2)) :: tail) + Nil -> (InFirst(first1 ++ first2) :: tail) case InSecond(second1) :: InSecond(second2) :: tail => - Nil -> (InSecond(second1.concat(second2)) :: tail) + Nil -> (InSecond(second1 ++ second2) :: tail) case InFirst(first1) :: Diff(first2, second) :: tail => - Nil -> (Diff(first1.concat(first2), second) :: tail) + Nil -> (Diff(first1 ++ first2, second) :: tail) case InSecond(second1) :: Diff(first, second2) :: tail => - Nil -> (Diff(first, second1.concat(second2)) :: tail) + Nil -> (Diff(first, second1 ++ second2) :: tail) case Diff(first1, second) :: InFirst(first2) :: tail => - Nil -> (Diff(first1.concat(first2), second) :: tail) + Nil -> (Diff(first1 ++ first2, second) :: tail) case Diff(first, second1) :: InSecond(second2) :: tail => - Nil -> (Diff(first, second1.concat(second2)) :: tail) + Nil -> (Diff(first, second1 ++ second2) :: tail) case head :: tail => (head :: Nil) -> tail diff --git a/stringdiff/src/main/scala/app/tulz/diff/util/DiffLog.scala b/stringdiff/src/main/scala/app/tulz/diff/util/DiffLog.scala deleted file mode 100644 index f467cc2..0000000 --- a/stringdiff/src/main/scala/app/tulz/diff/util/DiffLog.scala +++ /dev/null @@ -1,17 +0,0 @@ -package app.tulz.diff -package util - -import format.DiffFormat -import compat._ - -private[diff] object DiffLog { - - def log(description: => String, diff: => List[DiffElement[IndexedSeqView[String]]]): Unit = { - println(s"${description}:${" " * (20 - description.length)}${DiffFormat.ansi(diff.map(_.map(_.mkString)))}") - } - - def logS(description: => String, diff: => List[DiffElement[String]]): Unit = { - println(s"${description}:${" " * (20 - description.length)}${DiffFormat.ansi(diff)}") - } - -} diff --git a/stringdiff/src/main/scala/app/tulz/diff/util/DiffPrettier.scala b/stringdiff/src/main/scala/app/tulz/diff/util/DiffPrettier.scala index e4821b5..332b553 100644 --- a/stringdiff/src/main/scala/app/tulz/diff/util/DiffPrettier.scala +++ b/stringdiff/src/main/scala/app/tulz/diff/util/DiffPrettier.scala @@ -3,7 +3,6 @@ package app.tulz.diff.util import app.tulz.diff.DiffElement import app.tulz.diff.DiffElement.Diff import app.tulz.diff.DiffElement.InBoth -import scala.collection.compat._ private[diff] object DiffPrettier { @@ -49,9 +48,10 @@ private[diff] object DiffPrettier { s1: String, s2: String ): (String, String, String) = { - val samePrefixLength = s1.indices - .takeWhile(i => i < s2.length && s1.charAt(i) == s2.charAt(i) && s1.charAt(i).isWhitespace) - .maxOption.fold(0)(_ + 1) + val samePrefixLength = + s1.indices + .takeWhile(i => i < s2.length && s1.charAt(i) == s2.charAt(i) && s1.charAt(i).isWhitespace) + .lastOption.fold(0)(_ + 1) (s1.take(samePrefixLength), s1.drop(samePrefixLength), s2.drop(samePrefixLength)) } @@ -62,7 +62,7 @@ private[diff] object DiffPrettier { ): (String, String, String) = { val sameSuffixLength = s1.indices .takeWhile(i => i < s2.length && s1.charAt(s1.length - 1 - i) == s2.charAt(s2.length - 1 - i) && s1.charAt(i).isWhitespace) - .maxOption.fold(0)(_ + 1) + .lastOption.fold(0)(_ + 1) (s1.dropRight(sameSuffixLength), s2.dropRight(sameSuffixLength), s1.takeRight(sameSuffixLength)) } diff --git a/stringdiff/src/main/scala/app/tulz/diff/util/DiffTokenize.scala b/stringdiff/src/main/scala/app/tulz/diff/util/DiffTokenize.scala index c2e660f..b50e1d3 100644 --- a/stringdiff/src/main/scala/app/tulz/diff/util/DiffTokenize.scala +++ b/stringdiff/src/main/scala/app/tulz/diff/util/DiffTokenize.scala @@ -6,17 +6,15 @@ import DiffElement.InFirst import DiffElement.InSecond import scala.collection.mutable.ListBuffer -import scala.collection.compat._ -import compat._ private[diff] object DiffTokenize { private def partitionFirstSecond( - diff: List[DiffElement[IndexedSeqView[String]]], - p: DiffElement[IndexedSeqView[String]] => Boolean, - transform: (List[DiffElement[IndexedSeqView[String]]], List[DiffElement[IndexedSeqView[String]]]) => Seq[DiffElement[IndexedSeqView[String]]] - ): List[DiffElement[IndexedSeqView[String]]] = { - ListScan.withBuffer[DiffElement[IndexedSeqView[String]], DiffElement[IndexedSeqView[String]]](diff) { (list, buffer) => + diff: List[DiffElement[IndexedSeq[String]]], + p: DiffElement[IndexedSeq[String]] => Boolean, + transform: (List[DiffElement[IndexedSeq[String]]], List[DiffElement[IndexedSeq[String]]]) => Seq[DiffElement[IndexedSeq[String]]] + ): List[DiffElement[IndexedSeq[String]]] = { + ListScan.withBuffer[DiffElement[IndexedSeq[String]], DiffElement[IndexedSeq[String]]](diff) { (list, buffer) => val (nonFirstSecond, firstSecondAndRest) = list.span(!_.inFirstOrSecond) buffer.appendAll(nonFirstSecond) val (firstSecond, rest) = firstSecondAndRest.span(_.inFirstOrSecond) @@ -32,13 +30,13 @@ private[diff] object DiffTokenize { } private def samePrefix( - group1: List[DiffElement[IndexedSeqView[String]]], - group2: List[DiffElement[IndexedSeqView[String]]] + group1: List[DiffElement[IndexedSeq[String]]], + group2: List[DiffElement[IndexedSeq[String]]] ): Option[ ( - List[DiffElement[IndexedSeqView[String]]], - List[DiffElement[IndexedSeqView[String]]], - List[DiffElement[IndexedSeqView[String]]] + List[DiffElement[IndexedSeq[String]]], + List[DiffElement[IndexedSeq[String]]], + List[DiffElement[IndexedSeq[String]]] ) ] = { group1.indices @@ -46,11 +44,11 @@ private[diff] object DiffTokenize { .takeWhile(i => i <= group2.size && ((group1(i - 1), group2(i - 1)) match { - case (InFirst(first), InSecond(second)) => same(first, second) - case (InSecond(second), InFirst(first)) => same(first, second) + case (InFirst(first), InSecond(second)) => first == second + case (InSecond(second), InFirst(first)) => first == second case _ => false }) - ).maxOption.map { samePrefixLength => + ).lastOption.map { samePrefixLength => val prefix = group1.take(samePrefixLength).collect { case InFirst(first) => InBoth(first) case InSecond(first) => InBoth(first) @@ -60,13 +58,13 @@ private[diff] object DiffTokenize { } private def sameSuffix( - group1: List[DiffElement[IndexedSeqView[String]]], - group2: List[DiffElement[IndexedSeqView[String]]] + group1: List[DiffElement[IndexedSeq[String]]], + group2: List[DiffElement[IndexedSeq[String]]] ): Option[ ( - List[DiffElement[IndexedSeqView[String]]], - List[DiffElement[IndexedSeqView[String]]], - List[DiffElement[IndexedSeqView[String]]] + List[DiffElement[IndexedSeq[String]]], + List[DiffElement[IndexedSeq[String]]], + List[DiffElement[IndexedSeq[String]]] ) ] = { group1.indices @@ -74,11 +72,11 @@ private[diff] object DiffTokenize { .takeWhile(i => i <= group2.size && ((group1(group1.length - i), group2(group2.size - i)) match { - case (InFirst(first), InSecond(second)) => same(first, second) - case (InSecond(second), InFirst(first)) => same(first, second) + case (InFirst(first), InSecond(second)) => first == second + case (InSecond(second), InFirst(first)) => first == second case _ => false }) - ).maxOption.map { sameSuffixLength => + ).lastOption.map { sameSuffixLength => val suffix = group1.takeRight(sameSuffixLength).collect { case InFirst(first) => InBoth(first) case InSecond(second) => InBoth(second) @@ -88,13 +86,13 @@ private[diff] object DiffTokenize { } private def prefixIsSuffix( - group1: List[DiffElement[IndexedSeqView[String]]], - group2: List[DiffElement[IndexedSeqView[String]]] + group1: List[DiffElement[IndexedSeq[String]]], + group2: List[DiffElement[IndexedSeq[String]]] ): Option[ ( - List[DiffElement[IndexedSeqView[String]]], - List[DiffElement[IndexedSeqView[String]]], - List[DiffElement[IndexedSeqView[String]]] + List[DiffElement[IndexedSeq[String]]], + List[DiffElement[IndexedSeq[String]]], + List[DiffElement[IndexedSeq[String]]] ) ] = { group1.indices @@ -102,11 +100,11 @@ private[diff] object DiffTokenize { .takeWhile(i => i <= group2.size && group1.take(i).zip(group2.takeRight(i)).forall { - case (InFirst(first), InSecond(second)) => same(first, second) - case (InSecond(second), InFirst(first)) => same(first, second) + case (InFirst(first), InSecond(second)) => first == second + case (InSecond(second), InFirst(first)) => first == second case _ => false } - ).maxOption.map { prefixSuffixLength => + ).lastOption.map { prefixSuffixLength => val prefix = group1.take(prefixSuffixLength).collect { case InFirst(first) => InBoth(first) case InSecond(first) => InBoth(first) @@ -116,13 +114,13 @@ private[diff] object DiffTokenize { } private def suffixIsPrefix( - group1: List[DiffElement[IndexedSeqView[String]]], - group2: List[DiffElement[IndexedSeqView[String]]] + group1: List[DiffElement[IndexedSeq[String]]], + group2: List[DiffElement[IndexedSeq[String]]] ): Option[ ( - List[DiffElement[IndexedSeqView[String]]], - List[DiffElement[IndexedSeqView[String]]], - List[DiffElement[IndexedSeqView[String]]] + List[DiffElement[IndexedSeq[String]]], + List[DiffElement[IndexedSeq[String]]], + List[DiffElement[IndexedSeq[String]]] ) ] = { group1.indices @@ -130,11 +128,11 @@ private[diff] object DiffTokenize { .takeWhile(i => i <= group2.size && group1.takeRight(i).zip(group2.take(i)).forall { - case (InFirst(first), InSecond(second)) => same(first, second) - case (InSecond(second), InFirst(first)) => same(first, second) + case (InFirst(first), InSecond(second)) => first == second + case (InSecond(second), InFirst(first)) => first == second case _ => false } - ).maxOption.map { suffixPrefixLength => + ).lastOption.map { suffixPrefixLength => val suffix = group1.takeRight(suffixPrefixLength).collect { case InFirst(first) => InBoth(first) case InSecond(first) => InBoth(first) @@ -144,19 +142,19 @@ private[diff] object DiffTokenize { } private def processFirstSecondGroups( - diff: List[DiffElement[IndexedSeqView[String]]], - p: DiffElement[IndexedSeqView[String]] => Boolean - ): List[DiffElement[IndexedSeqView[String]]] = + diff: List[DiffElement[IndexedSeq[String]]], + p: DiffElement[IndexedSeq[String]] => Boolean + ): List[DiffElement[IndexedSeq[String]]] = partitionFirstSecond( diff, p, (group1, group2) => { - val buffer = ListBuffer.empty[DiffElement[IndexedSeqView[String]]] - val bufferSuffix = ListBuffer.empty[DiffElement[IndexedSeqView[String]]] + val buffer = ListBuffer.empty[DiffElement[IndexedSeq[String]]] + val bufferSuffix = ListBuffer.empty[DiffElement[IndexedSeq[String]]] var work1 = group1 var work2 = group2 - samePrefix(work1, work2).map { case (prefix, rest1, rest2) => + samePrefix(work1, work2).foreach { case (prefix, rest1, rest2) => work1 = rest1 work2 = rest2 buffer.appendAll(prefix) @@ -196,26 +194,23 @@ private[diff] object DiffTokenize { ) def firstsGoFirst( - diff: List[DiffElement[IndexedSeqView[String]]] - ): List[DiffElement[IndexedSeqView[String]]] = + diff: List[DiffElement[IndexedSeq[String]]] + ): List[DiffElement[IndexedSeq[String]]] = processFirstSecondGroups(diff, _.inFirst) def secondsGoFirst( - diff: List[DiffElement[IndexedSeqView[String]]] - ): List[DiffElement[IndexedSeqView[String]]] = + diff: List[DiffElement[IndexedSeq[String]]] + ): List[DiffElement[IndexedSeq[String]]] = processFirstSecondGroups(diff, _.inSecond) - private def same(s1: IndexedSeqView[String], s2: IndexedSeqView[String]): Boolean = - s1.size == s2.size && s1.indices.forall(i => s1(i) == s2(i)) - - def join(diff: List[DiffElement[IndexedSeqView[String]]]): List[DiffElement[IndexedSeqView[String]]] = + def join(diff: List[DiffElement[IndexedSeq[String]]]): List[DiffElement[IndexedSeq[String]]] = ListScan(diff) { - case InSecond(second) :: InFirst(first) :: tail if same(first, second) => + case InSecond(second) :: InFirst(first) :: tail if first == second => Nil -> ( InBoth(first) :: tail ) - case InFirst(first) :: InSecond(second) :: tail if same(first, second) => + case InFirst(first) :: InSecond(second) :: tail if first == second => Nil -> ( InBoth(first) :: tail ) @@ -227,8 +222,8 @@ private[diff] object DiffTokenize { } def moveWhitespace( - diff: List[DiffElement[IndexedSeqView[String]]] - ): List[DiffElement[IndexedSeqView[String]]] = + diff: List[DiffElement[IndexedSeq[String]]] + ): List[DiffElement[IndexedSeq[String]]] = ListScan(diff) { case InSecond(second) :: InBoth(Whitespace(both)) :: InFirst(first) :: tail if first == second => @@ -264,13 +259,13 @@ private[diff] object DiffTokenize { case head :: tail => (head :: Nil) -> tail - case Nil => Nil -> Nil + case Nil => + Nil -> Nil } - private val whitespace = "\\s+".r private object Whitespace { - def unapply(s: IndexedSeqView[String]): Option[IndexedSeqView[String]] = { - Some(s).filter(_.forall(whitespace.findFirstMatchIn(_).isDefined)) + def unapply(s: IndexedSeq[String]): Option[IndexedSeq[String]] = { + if (s.forall(_.forall(_.isWhitespace))) Some(s) else None } } diff --git a/stringdiff/src/test/scala/app/tulz/diff/DiffTests.scala b/stringdiff/src/test/scala/app/tulz/diff/DiffTests.scala index 369b18b..5f150d1 100644 --- a/stringdiff/src/test/scala/app/tulz/diff/DiffTests.scala +++ b/stringdiff/src/test/scala/app/tulz/diff/DiffTests.scala @@ -1,8 +1,10 @@ package app.tulz.diff import app.tulz.diff.format.AnsiDiffFormat +import org.scalactic.source.Position import org.scalatest.funsuite.AnyFunSuite import org.scalatest.matchers.should.Matchers + import scala.Console._ class DiffTests extends AnyFunSuite with Matchers { @@ -20,7 +22,7 @@ class DiffTests extends AnyFunSuite with Matchers { s1: String, s2: String, expectedDiff: List[DiffElement[String]] - ): Unit = { + )(implicit pos: Position): Unit = { val dashes = 80 test(name) { println(s"--- ${name} ${"-" * (dashes - name.length - 5)}") @@ -136,7 +138,7 @@ class DiffTests extends AnyFunSuite with Matchers { "extra prefix in s1, two extra tokens in s2", "prefix-1 match-1 match-2", " match-1 diff-1 diff-2 match-2", - List(InFirst("prefix-1 "), InBoth("match-1"), InSecond(" diff-1 diff-2"), InBoth(" match-2")) + List(InFirst("prefix-1 "), InBoth("match-1 "), InSecond("diff-1 diff-2 "), InBoth("match-2")) ) doTest(