patchdiff 0.3.5__py3-none-any.whl → 0.3.7__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/__init__.py +4 -2
- patchdiff/apply.py +2 -0
- patchdiff/diff.py +152 -99
- patchdiff/pointer.py +12 -13
- {patchdiff-0.3.5.dist-info → patchdiff-0.3.7.dist-info}/METADATA +1 -1
- patchdiff-0.3.7.dist-info/RECORD +9 -0
- patchdiff-0.3.5.dist-info/RECORD +0 -9
- {patchdiff-0.3.5.dist-info → patchdiff-0.3.7.dist-info}/WHEEL +0 -0
patchdiff/__init__.py
CHANGED
patchdiff/apply.py
CHANGED
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,126 +7,178 @@ 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
|
|
|
92
136
|
|
|
93
137
|
def diff_dicts(input: Dict, output: Dict, ptr: Pointer) -> Tuple[List, List]:
|
|
94
138
|
ops, rops = [], []
|
|
95
|
-
input_keys = set(input.keys())
|
|
96
|
-
output_keys = set(output.keys())
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
139
|
+
input_keys = set(input.keys()) if input else set()
|
|
140
|
+
output_keys = set(output.keys()) if output else set()
|
|
141
|
+
if input_only := input_keys - output_keys:
|
|
142
|
+
for key in input_only:
|
|
143
|
+
key_ptr = ptr.append(key)
|
|
144
|
+
ops.append({"op": "remove", "path": key_ptr})
|
|
145
|
+
rops.insert(0, {"op": "add", "path": key_ptr, "value": input[key]})
|
|
146
|
+
if output_only := output_keys - input_keys:
|
|
147
|
+
for key in output_only:
|
|
148
|
+
key_ptr = ptr.append(key)
|
|
149
|
+
ops.append(
|
|
150
|
+
{
|
|
151
|
+
"op": "add",
|
|
152
|
+
"path": key_ptr,
|
|
153
|
+
"value": output[key],
|
|
154
|
+
}
|
|
155
|
+
)
|
|
156
|
+
rops.insert(0, {"op": "remove", "path": key_ptr})
|
|
157
|
+
if common := input_keys & output_keys:
|
|
158
|
+
for key in common:
|
|
159
|
+
key_ops, key_rops = diff(input[key], output[key], ptr.append(key))
|
|
160
|
+
ops.extend(key_ops)
|
|
161
|
+
key_rops.extend(rops)
|
|
162
|
+
rops = key_rops
|
|
114
163
|
return ops, rops
|
|
115
164
|
|
|
116
165
|
|
|
117
166
|
def diff_sets(input: Set, output: Set, ptr: Pointer) -> Tuple[List, List]:
|
|
118
167
|
ops, rops = [], []
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
168
|
+
if input_only := input - output:
|
|
169
|
+
for value in input_only:
|
|
170
|
+
ops.append({"op": "remove", "path": ptr.append(value)})
|
|
171
|
+
rops.insert(0, {"op": "add", "path": ptr.append("-"), "value": value})
|
|
172
|
+
if output_only := output - input:
|
|
173
|
+
for value in output_only:
|
|
174
|
+
ops.append({"op": "add", "path": ptr.append("-"), "value": value})
|
|
175
|
+
rops.insert(0, {"op": "remove", "path": ptr.append(value)})
|
|
125
176
|
return ops, rops
|
|
126
177
|
|
|
127
178
|
|
|
128
|
-
def diff(
|
|
179
|
+
def diff(
|
|
180
|
+
input: Diffable, output: Diffable, ptr: Pointer | None = None
|
|
181
|
+
) -> Tuple[List, List]:
|
|
129
182
|
if input == output:
|
|
130
183
|
return [], []
|
|
131
184
|
if ptr is None:
|
patchdiff/pointer.py
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
3
|
import re
|
|
4
|
-
from typing import Any, Hashable,
|
|
4
|
+
from typing import Any, Hashable, Iterable, Tuple
|
|
5
5
|
|
|
6
6
|
from .types import Diffable
|
|
7
7
|
|
|
@@ -20,7 +20,9 @@ def escape(token: str) -> str:
|
|
|
20
20
|
|
|
21
21
|
|
|
22
22
|
class Pointer:
|
|
23
|
-
|
|
23
|
+
__slots__ = ("tokens",)
|
|
24
|
+
|
|
25
|
+
def __init__(self, tokens: Iterable[Hashable] | None = None) -> None:
|
|
24
26
|
if tokens is None:
|
|
25
27
|
tokens = []
|
|
26
28
|
self.tokens = tuple(tokens)
|
|
@@ -40,7 +42,7 @@ class Pointer:
|
|
|
40
42
|
return hash(self.tokens)
|
|
41
43
|
|
|
42
44
|
def __eq__(self, other: "Pointer") -> bool:
|
|
43
|
-
if
|
|
45
|
+
if other.__class__ != self.__class__:
|
|
44
46
|
return False
|
|
45
47
|
return self.tokens == other.tokens
|
|
46
48
|
|
|
@@ -48,17 +50,14 @@ class Pointer:
|
|
|
48
50
|
key = ""
|
|
49
51
|
parent = None
|
|
50
52
|
cursor = obj
|
|
51
|
-
|
|
52
|
-
parent = cursor
|
|
53
|
-
if hasattr(parent, "add"): # set
|
|
54
|
-
break
|
|
55
|
-
if hasattr(parent, "append"): # list
|
|
56
|
-
if key == "-":
|
|
57
|
-
break
|
|
53
|
+
if tokens := self.tokens:
|
|
58
54
|
try:
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
55
|
+
for key in tokens:
|
|
56
|
+
parent = cursor
|
|
57
|
+
cursor = parent[key]
|
|
58
|
+
except (KeyError, TypeError):
|
|
59
|
+
# KeyError for dicts, TypeError for sets and lists
|
|
60
|
+
pass
|
|
62
61
|
return parent, key, cursor
|
|
63
62
|
|
|
64
63
|
def append(self, token: Hashable) -> "Pointer":
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
patchdiff/__init__.py,sha256=tXjBK8Oei1NjMt-U4gRhshdJ9yYoRq5Y-xf0m3nHLq8,163
|
|
2
|
+
patchdiff/apply.py,sha256=zxjJ4sCivcy4WOZulQGfv2iduVeY0xnjlvZJkHIZhtY,1405
|
|
3
|
+
patchdiff/diff.py,sha256=-Aj-8NGE1HRoLxVYNK1dFofACaY3fCCsIJo6ypkrl5U,6827
|
|
4
|
+
patchdiff/pointer.py,sha256=MBakmqcm__2vrorFtMeJekeA8bWS-5mh384UG0u4E0w,1790
|
|
5
|
+
patchdiff/serialize.py,sha256=N0S9e0P49TBMb7ghM--h13MsF59ybiscjZ_auAErTq8,295
|
|
6
|
+
patchdiff/types.py,sha256=BVKXOl3tnQOOml3VI_epTrLn79agi6sD5vNr2acC-yE,77
|
|
7
|
+
patchdiff-0.3.7.dist-info/METADATA,sha256=d35C8963EBLsfTkqYNNriTO1aXYFKnJNEPh3tVYF0Es,1634
|
|
8
|
+
patchdiff-0.3.7.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
|
9
|
+
patchdiff-0.3.7.dist-info/RECORD,,
|
patchdiff-0.3.5.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=AQuIhGDnZda-9IykepEHXcfrDJSEJPXuGe8SdLQoquU,5424
|
|
4
|
-
patchdiff/pointer.py,sha256=ZZqJtGYM2uK6P2saOBn0LWxpIb_qgdTFlcE4EQncnRU,1804
|
|
5
|
-
patchdiff/serialize.py,sha256=N0S9e0P49TBMb7ghM--h13MsF59ybiscjZ_auAErTq8,295
|
|
6
|
-
patchdiff/types.py,sha256=BVKXOl3tnQOOml3VI_epTrLn79agi6sD5vNr2acC-yE,77
|
|
7
|
-
patchdiff-0.3.5.dist-info/METADATA,sha256=pea0_lmfx8Y5g5RMNpQGfSJWfQiDQQHFv_yybEQUMkg,1634
|
|
8
|
-
patchdiff-0.3.5.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
|
9
|
-
patchdiff-0.3.5.dist-info/RECORD,,
|
|
File without changes
|