Repository navigation
Expand file tree
/
Copy pathdiff_dumps.py
More file actions
136 lines (110 loc) · 4.27 KB
/
Copy pathdiff_dumps.py
File metadata and controls
136 lines (110 loc) · 4.27 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
#!/usr/bin/env python3
"""Compare two BYD dump files and show what changed.
Usage
-----
python scripts/diff_dumps.py old.txt new.txt
python scripts/diff_dumps.py --include-raw old.txt new.txt
python scripts/diff_dumps.py --raw-only old.txt new.txt
"""
from __future__ import annotations
import argparse
import json
from pathlib import Path
from typing import Any
SKIP_KEYS = {"raw", "traceback"}
MAX_VAL_WIDTH = 60
MISSING = "<missing>"
def _truncate(val: Any, width: int = MAX_VAL_WIDTH) -> str:
s = str(val)
if len(s) <= width:
return s
return s[: width - 3] + "..."
def _diff(
old: Any,
new: Any,
path: str,
results: list[tuple[str, Any, Any]],
skip_keys: set[str],
) -> None:
if isinstance(old, dict) and isinstance(new, dict):
all_keys = dict.fromkeys(list(old.keys()) + list(new.keys()))
for key in all_keys:
if key in skip_keys:
continue
child_path = f"{path}.{key}" if path else key
if key not in old:
_collect_leaves(new[key], child_path, results, side="new", skip_keys=skip_keys)
elif key not in new:
_collect_leaves(old[key], child_path, results, side="old", skip_keys=skip_keys)
else:
_diff(old[key], new[key], child_path, results, skip_keys)
elif isinstance(old, list) and isinstance(new, list):
for i in range(max(len(old), len(new))):
child_path = f"{path}[{i}]"
if i >= len(old):
_collect_leaves(new[i], child_path, results, side="new", skip_keys=skip_keys)
elif i >= len(new):
_collect_leaves(old[i], child_path, results, side="old", skip_keys=skip_keys)
else:
_diff(old[i], new[i], child_path, results, skip_keys)
else:
if old != new:
results.append((path, old, new))
def _collect_leaves(
obj: Any,
path: str,
results: list[tuple[str, Any, Any]],
side: str,
skip_keys: set[str],
) -> None:
"""Collect all leaf values for an object that exists only on one side."""
if isinstance(obj, dict):
for key, val in obj.items():
if key in skip_keys:
continue
_collect_leaves(val, f"{path}.{key}", results, side, skip_keys)
elif isinstance(obj, list):
for i, val in enumerate(obj):
_collect_leaves(val, f"{path}[{i}]", results, side, skip_keys)
else:
if side == "new":
results.append((path, MISSING, obj))
else:
results.append((path, obj, MISSING))
def main() -> None:
parser = argparse.ArgumentParser(description="Diff two BYD dump files.")
parser.add_argument("old", help="Older dump file")
parser.add_argument("new", help="Newer dump file")
group = parser.add_mutually_exclusive_group()
group.add_argument("--include-raw", action="store_true", help="Include 'raw' sub-dicts in comparison")
group.add_argument("--raw-only", action="store_true", help="Only show differences within 'raw' sub-dicts")
args = parser.parse_args()
file_old, file_new = Path(args.old), Path(args.new)
print(f"Old: {file_old.name}")
print(f"New: {file_new.name}")
print()
old = json.loads(file_old.read_text(encoding="utf-8"))
new = json.loads(file_new.read_text(encoding="utf-8"))
skip_keys = set() if args.include_raw or args.raw_only else SKIP_KEYS
results: list[tuple[str, Any, Any]] = []
_diff(old, new, "", results, skip_keys)
if args.raw_only:
results = [(p, o, n) for p, o, n in results if ".raw." in p or ".raw[" in p]
if not results:
print("No differences found.")
return
# Calculate column widths
path_w = max(len(r[0]) for r in results)
old_w = max(len(_truncate(r[1])) for r in results)
new_w = max(len(_truncate(r[2])) for r in results)
path_w = max(path_w, 4)
old_w = max(old_w, 3)
new_w = max(new_w, 3)
header = f"{'Path':<{path_w}} {'Old':<{old_w}} {'New':<{new_w}}"
print(header)
print("─" * len(header))
for path, old_val, new_val in results:
print(f"{path:<{path_w}} {_truncate(old_val):<{old_w}} {_truncate(new_val):<{new_w}}")
print(f"\n{len(results)} difference(s) found.")
if __name__ == "__main__":
main()