patchdiff 0.3.4__py3-none-any.whl → 0.3.6__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
patchdiff/diff.py
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
from
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
2
3
|
from typing import Dict, List, Set, Tuple
|
|
3
4
|
|
|
4
5
|
from .pointer import Pointer
|
|
@@ -6,86 +7,129 @@ from .types import Diffable
|
|
|
6
7
|
|
|
7
8
|
|
|
8
9
|
def diff_lists(input: List, output: List, ptr: Pointer) -> Tuple[List, List]:
|
|
9
|
-
|
|
10
|
+
m, n = len(input), len(output)
|
|
11
|
+
|
|
12
|
+
# Build DP table bottom-up (iterative approach)
|
|
13
|
+
# dp[i][j] = cost of transforming input[0:i] to output[0:j]
|
|
14
|
+
dp = [[0] * (n + 1) for _ in range(m + 1)]
|
|
10
15
|
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
16
|
+
# Initialize base cases
|
|
17
|
+
for i in range(1, m + 1):
|
|
18
|
+
dp[i][0] = i # Cost of deleting all elements
|
|
19
|
+
for j in range(1, n + 1):
|
|
20
|
+
dp[0][j] = j # Cost of adding all elements
|
|
21
|
+
|
|
22
|
+
# Fill DP table
|
|
23
|
+
for i in range(1, m + 1):
|
|
24
|
+
for j in range(1, n + 1):
|
|
25
|
+
if input[i - 1] == output[j - 1]:
|
|
26
|
+
# Elements match, no operation needed
|
|
27
|
+
dp[i][j] = dp[i - 1][j - 1]
|
|
15
28
|
else:
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
29
|
+
# Take minimum of three operations
|
|
30
|
+
dp[i][j] = min(
|
|
31
|
+
dp[i - 1][j] + 1, # Remove from input
|
|
32
|
+
dp[i][j - 1] + 1, # Add from output
|
|
33
|
+
dp[i - 1][j - 1] + 1, # Replace
|
|
34
|
+
)
|
|
35
|
+
|
|
36
|
+
# Traceback to extract operations
|
|
37
|
+
ops = []
|
|
38
|
+
rops = []
|
|
39
|
+
i, j = m, n
|
|
40
|
+
|
|
41
|
+
while i > 0 or j > 0:
|
|
42
|
+
if i > 0 and j > 0 and input[i - 1] == output[j - 1]:
|
|
43
|
+
# Elements match, no operation
|
|
44
|
+
i -= 1
|
|
45
|
+
j -= 1
|
|
46
|
+
elif i > 0 and (j == 0 or dp[i][j] == dp[i - 1][j] + 1):
|
|
47
|
+
# Remove from input
|
|
48
|
+
ops.append({"op": "remove", "idx": i - 1})
|
|
49
|
+
rops.append({"op": "add", "idx": j - 1, "value": input[i - 1]})
|
|
50
|
+
i -= 1
|
|
51
|
+
elif j > 0 and (i == 0 or dp[i][j] == dp[i][j - 1] + 1):
|
|
52
|
+
# Add from output
|
|
53
|
+
ops.append({"op": "add", "idx": i - 1, "value": output[j - 1]})
|
|
54
|
+
rops.append({"op": "remove", "idx": j - 1})
|
|
55
|
+
j -= 1
|
|
56
|
+
else:
|
|
57
|
+
# Replace
|
|
58
|
+
ops.append(
|
|
59
|
+
{
|
|
60
|
+
"op": "replace",
|
|
61
|
+
"idx": i - 1,
|
|
62
|
+
"original": input[i - 1],
|
|
63
|
+
"value": output[j - 1],
|
|
64
|
+
}
|
|
65
|
+
)
|
|
66
|
+
rops.append(
|
|
67
|
+
{
|
|
68
|
+
"op": "replace",
|
|
69
|
+
"idx": j - 1,
|
|
70
|
+
"original": output[j - 1],
|
|
71
|
+
"value": input[i - 1],
|
|
72
|
+
}
|
|
73
|
+
)
|
|
74
|
+
i -= 1
|
|
75
|
+
j -= 1
|
|
76
|
+
|
|
77
|
+
# Apply padding to operations (using explicit loops instead of reduce)
|
|
78
|
+
padded_ops = []
|
|
79
|
+
padding = 0
|
|
80
|
+
# Iterate in reverse to get correct order (traceback extracts operations backwards)
|
|
81
|
+
for op in reversed(ops):
|
|
66
82
|
if op["op"] == "add":
|
|
67
83
|
padded_idx = op["idx"] + 1 + padding
|
|
68
|
-
idx_token = padded_idx if padded_idx < len(
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
84
|
+
idx_token = padded_idx if padded_idx < len(input) + padding else "-"
|
|
85
|
+
padded_ops.append(
|
|
86
|
+
{
|
|
87
|
+
"op": "add",
|
|
88
|
+
"path": ptr.append(idx_token),
|
|
89
|
+
"value": op["value"],
|
|
90
|
+
}
|
|
91
|
+
)
|
|
92
|
+
padding += 1
|
|
75
93
|
elif op["op"] == "remove":
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
94
|
+
padded_ops.append(
|
|
95
|
+
{
|
|
96
|
+
"op": "remove",
|
|
97
|
+
"path": ptr.append(op["idx"] + padding),
|
|
98
|
+
}
|
|
99
|
+
)
|
|
100
|
+
padding -= 1
|
|
101
|
+
else: # replace
|
|
82
102
|
replace_ptr = ptr.append(op["idx"] + padding)
|
|
83
103
|
replace_ops, _ = diff(op["original"], op["value"], replace_ptr)
|
|
84
|
-
|
|
104
|
+
padded_ops.extend(replace_ops)
|
|
85
105
|
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
106
|
+
padded_rops = []
|
|
107
|
+
padding = 0
|
|
108
|
+
# Iterate in reverse to get correct order (traceback extracts operations backwards)
|
|
109
|
+
for op in reversed(rops):
|
|
110
|
+
if op["op"] == "add":
|
|
111
|
+
padded_idx = op["idx"] + 1 + padding
|
|
112
|
+
idx_token = padded_idx if padded_idx < len(output) + padding else "-"
|
|
113
|
+
padded_rops.append(
|
|
114
|
+
{
|
|
115
|
+
"op": "add",
|
|
116
|
+
"path": ptr.append(idx_token),
|
|
117
|
+
"value": op["value"],
|
|
118
|
+
}
|
|
119
|
+
)
|
|
120
|
+
padding += 1
|
|
121
|
+
elif op["op"] == "remove":
|
|
122
|
+
padded_rops.append(
|
|
123
|
+
{
|
|
124
|
+
"op": "remove",
|
|
125
|
+
"path": ptr.append(op["idx"] + padding),
|
|
126
|
+
}
|
|
127
|
+
)
|
|
128
|
+
padding -= 1
|
|
129
|
+
else: # replace
|
|
130
|
+
replace_ptr = ptr.append(op["idx"] + padding)
|
|
131
|
+
replace_ops, _ = diff(op["original"], op["value"], replace_ptr)
|
|
132
|
+
padded_rops.extend(replace_ops)
|
|
89
133
|
|
|
90
134
|
return padded_ops, padded_rops
|
|
91
135
|
|
|
@@ -125,7 +169,9 @@ def diff_sets(input: Set, output: Set, ptr: Pointer) -> Tuple[List, List]:
|
|
|
125
169
|
return ops, rops
|
|
126
170
|
|
|
127
171
|
|
|
128
|
-
def diff(
|
|
172
|
+
def diff(
|
|
173
|
+
input: Diffable, output: Diffable, ptr: Pointer | None = None
|
|
174
|
+
) -> Tuple[List, List]:
|
|
129
175
|
if input == output:
|
|
130
176
|
return [], []
|
|
131
177
|
if ptr is None:
|
patchdiff/pointer.py
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
1
3
|
import re
|
|
2
|
-
from typing import Any, Hashable,
|
|
4
|
+
from typing import Any, Hashable, Iterable, Tuple
|
|
3
5
|
|
|
4
6
|
from .types import Diffable
|
|
5
7
|
|
|
6
|
-
|
|
7
8
|
tilde0_re = re.compile("~0")
|
|
8
9
|
tilde1_re = re.compile("~1")
|
|
9
10
|
tilde_re = re.compile("~")
|
|
@@ -19,7 +20,7 @@ def escape(token: str) -> str:
|
|
|
19
20
|
|
|
20
21
|
|
|
21
22
|
class Pointer:
|
|
22
|
-
def __init__(self, tokens:
|
|
23
|
+
def __init__(self, tokens: Iterable[Hashable] | None = None) -> None:
|
|
23
24
|
if tokens is None:
|
|
24
25
|
tokens = []
|
|
25
26
|
self.tokens = tuple(tokens)
|
|
@@ -33,7 +34,7 @@ class Pointer:
|
|
|
33
34
|
return "/" + "/".join(escape(str(t)) for t in self.tokens)
|
|
34
35
|
|
|
35
36
|
def __repr__(self) -> str:
|
|
36
|
-
return f"Pointer({
|
|
37
|
+
return f"Pointer({list(self.tokens)!r})"
|
|
37
38
|
|
|
38
39
|
def __hash__(self) -> int:
|
|
39
40
|
return hash(self.tokens)
|
|
@@ -62,4 +63,4 @@ class Pointer:
|
|
|
62
63
|
|
|
63
64
|
def append(self, token: Hashable) -> "Pointer":
|
|
64
65
|
"""append, creating new Pointer"""
|
|
65
|
-
return Pointer(self.tokens
|
|
66
|
+
return Pointer((*self.tokens, token))
|
|
@@ -1,16 +1,10 @@
|
|
|
1
|
-
Metadata-Version: 2.
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
2
|
Name: patchdiff
|
|
3
|
-
Version: 0.3.
|
|
3
|
+
Version: 0.3.6
|
|
4
4
|
Summary: MIT
|
|
5
|
-
|
|
6
|
-
Author: Korijn van Golen
|
|
7
|
-
|
|
8
|
-
Requires-Python: >=3.7
|
|
9
|
-
Classifier: Programming Language :: Python :: 3
|
|
10
|
-
Classifier: Programming Language :: Python :: 3.10
|
|
11
|
-
Classifier: Programming Language :: Python :: 3.7
|
|
12
|
-
Classifier: Programming Language :: Python :: 3.8
|
|
13
|
-
Classifier: Programming Language :: Python :: 3.9
|
|
5
|
+
Project-URL: Homepage, https://github.com/fork-tongue/patchdiff
|
|
6
|
+
Author-email: Korijn van Golen <korijn@gmail.com>, Berend Klein Haneveld <berendkleinhaneveld@gmail.com>
|
|
7
|
+
Requires-Python: >=3.8
|
|
14
8
|
Description-Content-Type: text/markdown
|
|
15
9
|
|
|
16
10
|
[](https://badge.fury.io/py/patchdiff)
|
|
@@ -58,4 +52,3 @@ print(to_json(ops, indent=4))
|
|
|
58
52
|
# }
|
|
59
53
|
# ]
|
|
60
54
|
```
|
|
61
|
-
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
patchdiff/__init__.py,sha256=uQAwiWM9rTop0zZC6Spn0y_v74cfAzuhCxcEFOrAaXY,110
|
|
2
|
+
patchdiff/apply.py,sha256=25TfOTZTYFIjy70mBus5yCzmbJRawbguDffv_4yHjqE,1366
|
|
3
|
+
patchdiff/diff.py,sha256=JPERnHGR0oyV_E-Q4GDnjrg2dfofCrGcfrrJbNfd50c,6489
|
|
4
|
+
patchdiff/pointer.py,sha256=5bcOYUGPJ-bo1dEWBcQpXVsjhV46EJtDyG3RPw0VwTI,1812
|
|
5
|
+
patchdiff/serialize.py,sha256=N0S9e0P49TBMb7ghM--h13MsF59ybiscjZ_auAErTq8,295
|
|
6
|
+
patchdiff/types.py,sha256=BVKXOl3tnQOOml3VI_epTrLn79agi6sD5vNr2acC-yE,77
|
|
7
|
+
patchdiff-0.3.6.dist-info/METADATA,sha256=Xw0FpdVK-G5SZTtfQv_J3cj4AnySJt5A_OF7thAT0tU,1634
|
|
8
|
+
patchdiff-0.3.6.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
|
9
|
+
patchdiff-0.3.6.dist-info/RECORD,,
|
patchdiff-0.3.4.dist-info/RECORD
DELETED
|
@@ -1,9 +0,0 @@
|
|
|
1
|
-
patchdiff/__init__.py,sha256=uQAwiWM9rTop0zZC6Spn0y_v74cfAzuhCxcEFOrAaXY,110
|
|
2
|
-
patchdiff/apply.py,sha256=25TfOTZTYFIjy70mBus5yCzmbJRawbguDffv_4yHjqE,1366
|
|
3
|
-
patchdiff/diff.py,sha256=JHEut2x4uLztJ72FuHFH_qwNbBr-U5aWhvJWsLAFB7U,5424
|
|
4
|
-
patchdiff/pointer.py,sha256=8woatYj2inseiyb027HwwsTFTV4yNQomynLZF6TuXZ4,1767
|
|
5
|
-
patchdiff/serialize.py,sha256=N0S9e0P49TBMb7ghM--h13MsF59ybiscjZ_auAErTq8,295
|
|
6
|
-
patchdiff/types.py,sha256=BVKXOl3tnQOOml3VI_epTrLn79agi6sD5vNr2acC-yE,77
|
|
7
|
-
patchdiff-0.3.4.dist-info/WHEEL,sha256=DA86_h4QwwzGeRoz62o1svYt5kGEXpoUTuTtwzoTb30,83
|
|
8
|
-
patchdiff-0.3.4.dist-info/METADATA,sha256=7Idsz1U3AFalNosjnBX7EG3y-nm7PBVlNv-IFqdpEyU,1823
|
|
9
|
-
patchdiff-0.3.4.dist-info/RECORD,,
|