Skip to content

Commit e4f2fd4

Browse files
authored
feat: Port changes from java-diff-utils 4.10 (#21)
* Port `VerifyChunk` * Port `ConflictOutput` * Update README.md
1 parent ee4170b commit e4f2fd4

File tree

8 files changed

+177
-23
lines changed

8 files changed

+177
-23
lines changed

README.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ This is a port of [java-diff-utils](https://github.com/java-diff-utils/java-diff
1111
with multiplatform support. All credit for the implementation goes to original authors.
1212

1313
## Features
14-
All features from version 4.9 of the original library are present, except for:
14+
All features from version 4.10 of the original library are present, except for:
1515
* Unified diff, which heavily uses file read/write and therefore needs a more complicated rewrite for kotlin-multiplatform
1616
* diff-utils-jgit, which uses JVM-only jgit library
1717

src/commonMain/kotlin/io/github/petertrr/diffutils/patch/Chunk.kt

+10-10
Original file line numberDiff line numberDiff line change
@@ -46,17 +46,17 @@ public data class Chunk<T>(
4646
* @param target the sequence to verify against.
4747
* @throws PatchFailedException
4848
*/
49-
@Throws(PatchFailedException::class)
50-
public fun verify(target: List<T>) {
51-
if (position > target.size || last() > target.size) {
52-
throw PatchFailedException("Incorrect Chunk: the position of chunk > target size")
53-
}
54-
for (i in 0 until size()) {
55-
if (target[position + i] != lines[i]) {
56-
throw PatchFailedException(
57-
"Incorrect Chunk: the chunk content doesn't match the target"
58-
)
49+
public fun verify(target: List<T>): VerifyChunk {
50+
return if (position > target.size || last() > target.size) {
51+
VerifyChunk.POSITION_OUT_OF_TARGET
52+
} else if (
53+
(0 until size()).any { i ->
54+
target[position + i] != lines[i]
5955
}
56+
) {
57+
VerifyChunk.CONTENT_DOES_NOT_MATCH_TARGET
58+
} else {
59+
VerifyChunk.OK
6060
}
6161
}
6262

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
/*
2+
* Copyright 2021 Peter Trifanov.
3+
* Copyright 2021 java-diff-utils.
4+
*
5+
* Licensed under the Apache License, Version 2.0 (the "License");
6+
* you may not use this file except in compliance with the License.
7+
* You may obtain a copy of the License at
8+
*
9+
* http://www.apache.org/licenses/LICENSE-2.0
10+
*
11+
* Unless required by applicable law or agreed to in writing, software
12+
* distributed under the License is distributed on an "AS IS" BASIS,
13+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
* See the License for the specific language governing permissions and
15+
* limitations under the License.
16+
*
17+
* This file has been modified by Peter Trifanov when porting from Java to Kotlin.
18+
*/
19+
package io.github.petertrr.diffutils.patch
20+
21+
public fun interface ConflictOutput<T> {
22+
@Throws(PatchFailedException::class)
23+
public fun processConflict(verifyChunk: VerifyChunk, delta: Delta<T>, result: MutableList<T>)
24+
}
25+
26+
public class ExceptionProducingConflictOutput<T> : ConflictOutput<T> {
27+
override fun processConflict(verifyChunk: VerifyChunk, delta: Delta<T>, result: MutableList<T>) {
28+
throw PatchFailedException(
29+
"could not apply patch due to $verifyChunk"
30+
)
31+
}
32+
}
33+
34+
public class ConflictProducingConflictOutput : ConflictOutput<String> {
35+
override fun processConflict(verifyChunk: VerifyChunk, delta: Delta<String>, result: MutableList<String>) {
36+
if (result.size > delta.source.position) {
37+
val orgData = mutableListOf<String>()
38+
(0 until delta.source.size()).forEach { _ ->
39+
orgData.add(
40+
result.removeAt(delta.source.position)
41+
)
42+
}
43+
orgData.add(0, "<<<<<< HEAD")
44+
orgData.add("======")
45+
orgData.addAll(delta.source.lines)
46+
orgData.add(">>>>>>> PATCH")
47+
result.addAll(delta.source.position, orgData)
48+
} else {
49+
TODO("Not supported yet.")
50+
}
51+
}
52+
}

src/commonMain/kotlin/io/github/petertrr/diffutils/patch/Delta.kt

+16-10
Original file line numberDiff line numberDiff line change
@@ -62,12 +62,21 @@ public sealed class Delta<T>(public val type: DeltaType) {
6262
* @throws PatchFailedException
6363
*/
6464
@Throws(PatchFailedException::class)
65-
protected open fun verifyChunk(target: List<T>) {
66-
source.verify(target)
65+
protected open fun verifyChunkToFitTarget(target: List<T>): VerifyChunk {
66+
return source.verify(target)
6767
}
6868

6969
@Throws(PatchFailedException::class)
70-
public abstract fun applyTo(target: MutableList<T>)
70+
public open fun verifyAndApplyTo(target: MutableList<T>): VerifyChunk {
71+
val verify: VerifyChunk = verifyChunkToFitTarget(target)
72+
if (verify == VerifyChunk.OK) {
73+
applyTo(target)
74+
}
75+
return verify
76+
}
77+
78+
@Throws(PatchFailedException::class)
79+
protected abstract fun applyTo(target: MutableList<T>)
7180

7281
public abstract fun restore(target: MutableList<T>)
7382

@@ -77,8 +86,7 @@ public sealed class Delta<T>(public val type: DeltaType) {
7786
public abstract fun withChunks(original: Chunk<T>, revised: Chunk<T>): Delta<T>
7887
}
7988
public data class ChangeDelta<T>(override val source: Chunk<T>, override val target: Chunk<T>) : Delta<T>(DeltaType.CHANGE) {
80-
override fun applyTo(target: MutableList<T>) {
81-
verifyChunk(target)
89+
protected override fun applyTo(target: MutableList<T>) {
8290
val position: Int = source.position
8391
val size: Int = source.size()
8492
for (i in 0 until size) {
@@ -104,8 +112,7 @@ public data class ChangeDelta<T>(override val source: Chunk<T>, override val tar
104112
}
105113

106114
public data class DeleteDelta<T>(override val source: Chunk<T>, override val target: Chunk<T>) : Delta<T>(DeltaType.DELETE) {
107-
override fun applyTo(target: MutableList<T>) {
108-
verifyChunk(target)
115+
protected override fun applyTo(target: MutableList<T>) {
109116
val position = source.position
110117
for (i in 0 until source.size()) {
111118
target.removeAt(position)
@@ -124,8 +131,7 @@ public data class DeleteDelta<T>(override val source: Chunk<T>, override val tar
124131
}
125132

126133
public data class InsertDelta<T>(override val source: Chunk<T>, override val target: Chunk<T>) : Delta<T>(DeltaType.INSERT) {
127-
override fun applyTo(target: MutableList<T>) {
128-
verifyChunk(target)
134+
protected override fun applyTo(target: MutableList<T>) {
129135
val position = this.source.position
130136
this.target.lines.forEachIndexed { i, line ->
131137
target.add(position + i, line)
@@ -143,7 +149,7 @@ public data class InsertDelta<T>(override val source: Chunk<T>, override val tar
143149
}
144150

145151
public data class EqualDelta<T>(override val source: Chunk<T>, override val target: Chunk<T>) : Delta<T>(DeltaType.EQUAL) {
146-
override fun applyTo(target: MutableList<T>): Unit = verifyChunk(target)
152+
protected override fun applyTo(target: MutableList<T>): Unit = Unit
147153

148154
override fun restore(target: MutableList<T>): Unit = Unit
149155

src/commonMain/kotlin/io/github/petertrr/diffutils/patch/Patch.kt

+11-2
Original file line numberDiff line numberDiff line change
@@ -24,8 +24,11 @@ import io.github.petertrr.diffutils.algorithm.Change
2424
* Describes the patch holding all deltas between the original and revised texts.
2525
*
2626
* @param T The type of the compared elements in the 'lines'.
27+
* @property conflictOutput Alter normal conflict output behaviour to e.g. inclide some conflict statements in the result, like git does it.
2728
*/
28-
public class Patch<T> {
29+
public class Patch<T>(
30+
private var conflictOutput: ConflictOutput<T> = ExceptionProducingConflictOutput(),
31+
) {
2932
public var deltas: MutableList<Delta<T>> = arrayListOf()
3033
get() {
3134
field.sortBy { it.source.position }
@@ -43,7 +46,9 @@ public class Patch<T> {
4346
val it = deltas.listIterator(deltas.size)
4447
while (it.hasPrevious()) {
4548
val delta = it.previous()
46-
delta.applyTo(result)
49+
delta.verifyAndApplyTo(result).takeIf { it != VerifyChunk.OK }?.let {
50+
conflictOutput.processConflict(it, delta, result)
51+
}
4752
}
4853
return result
4954
}
@@ -75,6 +80,10 @@ public class Patch<T> {
7580
return "Patch{deltas=$deltas}"
7681
}
7782

83+
public fun withConflictOutput(conflictOutput: ConflictOutput<T>) {
84+
this.conflictOutput = conflictOutput
85+
}
86+
7887
public companion object {
7988
public fun <T> generate(original: List<T>, revised: List<T>, changes: List<Change>): Patch<T> {
8089
return generate(original, revised, changes, false)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
/*
2+
* Copyright 2021 Peter Trifanov.
3+
* Copyright 2021 java-diff-utils.
4+
*
5+
* Licensed under the Apache License, Version 2.0 (the "License");
6+
* you may not use this file except in compliance with the License.
7+
* You may obtain a copy of the License at
8+
*
9+
* http://www.apache.org/licenses/LICENSE-2.0
10+
*
11+
* Unless required by applicable law or agreed to in writing, software
12+
* distributed under the License is distributed on an "AS IS" BASIS,
13+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
* See the License for the specific language governing permissions and
15+
* limitations under the License.
16+
*
17+
* This file has been modified by Peter Trifanov when porting from Java to Kotlin.
18+
*/
19+
package io.github.petertrr.diffutils.patch
20+
21+
public enum class VerifyChunk {
22+
OK,
23+
POSITION_OUT_OF_TARGET,
24+
CONTENT_DOES_NOT_MATCH_TARGET,
25+
;
26+
}

src/commonTest/kotlin/io/github/petertrr/diffutils/patch/PatchTest.kt

+30
Original file line numberDiff line numberDiff line change
@@ -60,4 +60,34 @@ class PatchTest {
6060
fail(e.message)
6161
}
6262
}
63+
64+
@Test
65+
fun testPatch_Change_withExceptionProcessor() {
66+
val changeTest_from = listOf("aaa", "bbb", "ccc", "ddd")
67+
val changeTest_to = listOf("aaa", "bxb", "cxc", "ddd")
68+
val patch: Patch<String> = diff(changeTest_from, changeTest_to)
69+
patch.withConflictOutput(ConflictProducingConflictOutput())
70+
try {
71+
val data: List<String> = patch(
72+
changeTest_from.toMutableList().apply { this[2] = "CDC" },
73+
patch
74+
)
75+
assertEquals(9, data.size)
76+
assertEquals(
77+
listOf(
78+
"aaa",
79+
"<<<<<< HEAD",
80+
"bbb",
81+
"CDC",
82+
"======",
83+
"bbb",
84+
"ccc",
85+
">>>>>>> PATCH",
86+
"ddd"
87+
), data
88+
)
89+
} catch (e: PatchFailedException) {
90+
fail(e.message)
91+
}
92+
}
6393
}

src/commonTest/kotlin/io/github/petertrr/diffutils/text/DiffRowGeneratorTest.kt

+31
Original file line numberDiff line numberDiff line change
@@ -683,4 +683,35 @@ Bengal tiger panther but singapura but bombay munchkin for cougar. And more.""".
683683
)
684684
assertEquals(DiffRow(DiffRow.Tag.DELETE, "~B~", ""), rows[1])
685685
}
686+
687+
@Test
688+
fun testIssue119WrongContextLength() {
689+
val original: String =
690+
"""
691+
const world: string = 'world',
692+
p: number | undefined = 42;
693+
694+
console.log(`Hello, ${'$'}world}!`);
695+
""".trimIndent()
696+
val revised: String =
697+
"""
698+
const world: string = 'world';
699+
const p: number | undefined = 42;
700+
701+
console.log(`Hello, ${'$'}world}!`);
702+
""".trimIndent()
703+
val generator = DiffRowGenerator(
704+
showInlineDiffs = true,
705+
mergeOriginalRevised = true,
706+
inlineDiffByWord = true,
707+
oldTag = { _, _ -> "~" },
708+
newTag = { _, _ -> "**" },
709+
)
710+
val rows: List<DiffRow> = generator.generateDiffRows(
711+
original.split("\n"),
712+
revised.split("\n")
713+
)
714+
rows.filter { it.tag != DiffRow.Tag.EQUAL }
715+
.forEach { println(it) }
716+
}
686717
}

0 commit comments

Comments
 (0)