peter-diff 0.1.1__tar.gz
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.
- peter_diff-0.1.1/MANIFEST.in +2 -0
- peter_diff-0.1.1/PKG-INFO +58 -0
- peter_diff-0.1.1/README.md +50 -0
- peter_diff-0.1.1/pyproject.toml +15 -0
- peter_diff-0.1.1/setup.cfg +4 -0
- peter_diff-0.1.1/src/peter-diff/__init__.py +1 -0
- peter_diff-0.1.1/src/peter-diff/cli.py +169 -0
- peter_diff-0.1.1/src/peter_diff.egg-info/PKG-INFO +58 -0
- peter_diff-0.1.1/src/peter_diff.egg-info/SOURCES.txt +10 -0
- peter_diff-0.1.1/src/peter_diff.egg-info/dependency_links.txt +1 -0
- peter_diff-0.1.1/src/peter_diff.egg-info/entry_points.txt +2 -0
- peter_diff-0.1.1/src/peter_diff.egg-info/top_level.txt +1 -0
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: peter-diff
|
|
3
|
+
Version: 0.1.1
|
|
4
|
+
Summary: A simple CLI tool to diff two text files.
|
|
5
|
+
License: MIT
|
|
6
|
+
Requires-Python: >=3.7
|
|
7
|
+
Description-Content-Type: text/markdown
|
|
8
|
+
|
|
9
|
+
# peter-diff
|
|
10
|
+
|
|
11
|
+
A simple Python CLI tool to compare two text files and output their differences, similar to the Unix `diff` command.
|
|
12
|
+
|
|
13
|
+
## Installation
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
pip install -e .
|
|
17
|
+
pip uninstall peter-diff
|
|
18
|
+
```
|
|
19
|
+
OR
|
|
20
|
+
|
|
21
|
+
```sh
|
|
22
|
+
pip install peter-diff
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
## Usage
|
|
26
|
+
|
|
27
|
+
```sh
|
|
28
|
+
pydiff file1.txt file2.txt
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
## Publishing to PyPI
|
|
32
|
+
|
|
33
|
+
```
|
|
34
|
+
pip install build twine
|
|
35
|
+
python -m build
|
|
36
|
+
twine upload dist/*
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
When prompted, use `__token__` as the username and your PyPI API token as the password.
|
|
40
|
+
|
|
41
|
+
To skip the prompt, create a `~/.pypirc` file:
|
|
42
|
+
|
|
43
|
+
```ini
|
|
44
|
+
[pypi]
|
|
45
|
+
username = __token__
|
|
46
|
+
password = pypi-YOUR-TOKEN-HERE
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
> **Note:** Bump the `version` in `pyproject.toml` before each upload — PyPI rejects duplicate version numbers.
|
|
50
|
+
|
|
51
|
+
## Requirements
|
|
52
|
+
|
|
53
|
+
- Python >= 3.9
|
|
54
|
+
- No external dependencies
|
|
55
|
+
|
|
56
|
+
## License
|
|
57
|
+
|
|
58
|
+
MIT
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
# peter-diff
|
|
2
|
+
|
|
3
|
+
A simple Python CLI tool to compare two text files and output their differences, similar to the Unix `diff` command.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
pip install -e .
|
|
9
|
+
pip uninstall peter-diff
|
|
10
|
+
```
|
|
11
|
+
OR
|
|
12
|
+
|
|
13
|
+
```sh
|
|
14
|
+
pip install peter-diff
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
## Usage
|
|
18
|
+
|
|
19
|
+
```sh
|
|
20
|
+
pydiff file1.txt file2.txt
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
## Publishing to PyPI
|
|
24
|
+
|
|
25
|
+
```
|
|
26
|
+
pip install build twine
|
|
27
|
+
python -m build
|
|
28
|
+
twine upload dist/*
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
When prompted, use `__token__` as the username and your PyPI API token as the password.
|
|
32
|
+
|
|
33
|
+
To skip the prompt, create a `~/.pypirc` file:
|
|
34
|
+
|
|
35
|
+
```ini
|
|
36
|
+
[pypi]
|
|
37
|
+
username = __token__
|
|
38
|
+
password = pypi-YOUR-TOKEN-HERE
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
> **Note:** Bump the `version` in `pyproject.toml` before each upload — PyPI rejects duplicate version numbers.
|
|
42
|
+
|
|
43
|
+
## Requirements
|
|
44
|
+
|
|
45
|
+
- Python >= 3.9
|
|
46
|
+
- No external dependencies
|
|
47
|
+
|
|
48
|
+
## License
|
|
49
|
+
|
|
50
|
+
MIT
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "peter-diff"
|
|
3
|
+
version = "0.1.1"
|
|
4
|
+
description = "A simple CLI tool to diff two text files."
|
|
5
|
+
license = { text = "MIT" }
|
|
6
|
+
readme = "README.md"
|
|
7
|
+
requires-python = ">=3.7"
|
|
8
|
+
dependencies = []
|
|
9
|
+
|
|
10
|
+
[project.scripts]
|
|
11
|
+
peter-diff = "peter_diff.cli:main"
|
|
12
|
+
|
|
13
|
+
[build-system]
|
|
14
|
+
requires = ["setuptools>=61.0"]
|
|
15
|
+
build-backend = "setuptools.build_meta"
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
# peter-diff package
|
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
import sys
|
|
2
|
+
import difflib
|
|
3
|
+
import shutil
|
|
4
|
+
|
|
5
|
+
# ANSI codes
|
|
6
|
+
_RESET = "\033[0m"
|
|
7
|
+
_RED = "\033[41m" # deleted (left)
|
|
8
|
+
_GREEN = "\033[42m" # inserted (right)
|
|
9
|
+
_YELLOW = "\033[43m" # changed chars
|
|
10
|
+
_BOLD = "\033[1m"
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def _color_ok():
|
|
14
|
+
return sys.stdout.isatty()
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def _pad(text, width):
|
|
18
|
+
"""Truncate or pad *text* to exactly *width* visible characters."""
|
|
19
|
+
if len(text) > width:
|
|
20
|
+
return text[: width - 1] + "…"
|
|
21
|
+
return text.ljust(width)
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def _colored(text, code):
|
|
25
|
+
if _color_ok():
|
|
26
|
+
return code + text + _RESET
|
|
27
|
+
return text
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def _marker(tag, side):
|
|
31
|
+
"""Return a single visible marker char for no-colour terminals."""
|
|
32
|
+
if tag == "equal":
|
|
33
|
+
return " "
|
|
34
|
+
if tag == "delete":
|
|
35
|
+
return "<" if side == "left" else " "
|
|
36
|
+
if tag == "insert":
|
|
37
|
+
return " " if side == "left" else ">"
|
|
38
|
+
return "|"
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def _char_diff(left, right, col):
|
|
42
|
+
"""
|
|
43
|
+
Return (left_str, right_str) with only the differing characters highlighted,
|
|
44
|
+
each padded/truncated to exactly *col* visible characters.
|
|
45
|
+
"""
|
|
46
|
+
matcher = difflib.SequenceMatcher(None, left, right, autojunk=False)
|
|
47
|
+
l_segs, r_segs = [], []
|
|
48
|
+
for tag, i1, i2, j1, j2 in matcher.get_opcodes():
|
|
49
|
+
ls, rs = left[i1:i2], right[j1:j2]
|
|
50
|
+
if tag == "equal":
|
|
51
|
+
l_segs.append((ls, None))
|
|
52
|
+
r_segs.append((rs, None))
|
|
53
|
+
elif tag == "replace":
|
|
54
|
+
l_segs.append((ls, _YELLOW))
|
|
55
|
+
r_segs.append((rs, _YELLOW))
|
|
56
|
+
elif tag == "delete":
|
|
57
|
+
l_segs.append((ls, _RED))
|
|
58
|
+
elif tag == "insert":
|
|
59
|
+
r_segs.append((rs, _GREEN))
|
|
60
|
+
|
|
61
|
+
def render(segs):
|
|
62
|
+
out, vis = [], 0
|
|
63
|
+
for text, code in segs:
|
|
64
|
+
if vis >= col:
|
|
65
|
+
break
|
|
66
|
+
room = col - vis
|
|
67
|
+
if len(text) > room:
|
|
68
|
+
text = text[: room - 1] + "…"
|
|
69
|
+
out.append((code + text + _RESET) if code else text)
|
|
70
|
+
vis += len(text)
|
|
71
|
+
if vis < col:
|
|
72
|
+
out.append(" " * (col - vis))
|
|
73
|
+
return "".join(out)
|
|
74
|
+
|
|
75
|
+
return render(l_segs), render(r_segs)
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
def side_by_side_diff(lines1, lines2, file1, file2, only_diff=False):
|
|
79
|
+
term_w = shutil.get_terminal_size((160, 40)).columns
|
|
80
|
+
# layout: "NNNN M content … │ NNNN M content …"
|
|
81
|
+
# 4 + 1+1+1 = 7 chars overhead each side + 3 for " │ "
|
|
82
|
+
overhead = 7 + 3 + 7
|
|
83
|
+
col = max(20, (term_w - overhead) // 2)
|
|
84
|
+
|
|
85
|
+
sep = "─" * (col + 8) + "┼" + "─" * (col + 7)
|
|
86
|
+
use_color = _color_ok()
|
|
87
|
+
|
|
88
|
+
# Header
|
|
89
|
+
print(sep)
|
|
90
|
+
lh = (_BOLD if use_color else "") + _pad(file1, col) + (_RESET if use_color else "")
|
|
91
|
+
rh = (_BOLD if use_color else "") + _pad(file2, col) + (_RESET if use_color else "")
|
|
92
|
+
print(f"{'':4} {'':1} {lh} │ {'':4} {'':1} {rh}")
|
|
93
|
+
print(sep)
|
|
94
|
+
|
|
95
|
+
def emit(lnum, rnum, l_raw, r_raw, tag):
|
|
96
|
+
lnum_s = str(lnum) if lnum else ""
|
|
97
|
+
rnum_s = str(rnum) if rnum else ""
|
|
98
|
+
lm = _marker(tag, "left")
|
|
99
|
+
rm = _marker(tag, "right")
|
|
100
|
+
|
|
101
|
+
if use_color and tag == "replace":
|
|
102
|
+
# character-level highlighting
|
|
103
|
+
l_pad, r_pad = _char_diff(l_raw, r_raw, col)
|
|
104
|
+
else:
|
|
105
|
+
l_pad = _pad(l_raw, col)
|
|
106
|
+
r_pad = _pad(r_raw, col)
|
|
107
|
+
if use_color:
|
|
108
|
+
if tag == "delete":
|
|
109
|
+
l_pad = _colored(l_pad, _RED)
|
|
110
|
+
elif tag == "insert":
|
|
111
|
+
r_pad = _colored(r_pad, _GREEN)
|
|
112
|
+
|
|
113
|
+
print(f"{lnum_s:<4} {lm} {l_pad} │ {rnum_s:<4} {rm} {r_pad}")
|
|
114
|
+
|
|
115
|
+
matcher = difflib.SequenceMatcher(None, lines1, lines2, autojunk=False)
|
|
116
|
+
lnum = rnum = 0
|
|
117
|
+
|
|
118
|
+
for tag, i1, i2, j1, j2 in matcher.get_opcodes():
|
|
119
|
+
if tag == "equal":
|
|
120
|
+
for l, r in zip(lines1[i1:i2], lines2[j1:j2]):
|
|
121
|
+
lnum += 1; rnum += 1
|
|
122
|
+
if not only_diff:
|
|
123
|
+
emit(lnum, rnum, l.rstrip("\n"), r.rstrip("\n"), "equal")
|
|
124
|
+
|
|
125
|
+
elif tag == "replace":
|
|
126
|
+
lc, rc = lines1[i1:i2], lines2[j1:j2]
|
|
127
|
+
for k in range(max(len(lc), len(rc))):
|
|
128
|
+
l_raw = r_raw = ""
|
|
129
|
+
ln = rn = None
|
|
130
|
+
if k < len(lc):
|
|
131
|
+
lnum += 1; ln = lnum; l_raw = lc[k].rstrip("\n")
|
|
132
|
+
if k < len(rc):
|
|
133
|
+
rnum += 1; rn = rnum; r_raw = rc[k].rstrip("\n")
|
|
134
|
+
emit(ln, rn, l_raw, r_raw, "replace")
|
|
135
|
+
|
|
136
|
+
elif tag == "delete":
|
|
137
|
+
for l in lines1[i1:i2]:
|
|
138
|
+
lnum += 1
|
|
139
|
+
emit(lnum, None, l.rstrip("\n"), "", "delete")
|
|
140
|
+
|
|
141
|
+
elif tag == "insert":
|
|
142
|
+
for r in lines2[j1:j2]:
|
|
143
|
+
rnum += 1
|
|
144
|
+
emit(None, rnum, "", r.rstrip("\n"), "insert")
|
|
145
|
+
|
|
146
|
+
print(sep)
|
|
147
|
+
|
|
148
|
+
|
|
149
|
+
def main():
|
|
150
|
+
args = sys.argv[1:]
|
|
151
|
+
only_diff = "-o" in args
|
|
152
|
+
args = [a for a in args if a != "-o"]
|
|
153
|
+
if len(args) != 2:
|
|
154
|
+
print("Usage: peter-diff [-o] file1 file2", file=sys.stderr)
|
|
155
|
+
sys.exit(1)
|
|
156
|
+
file1, file2 = args[0], args[1]
|
|
157
|
+
try:
|
|
158
|
+
with open(file1) as f1, open(file2) as f2:
|
|
159
|
+
lines1 = f1.readlines()
|
|
160
|
+
lines2 = f2.readlines()
|
|
161
|
+
except Exception as e:
|
|
162
|
+
print(f"Error: {e}", file=sys.stderr)
|
|
163
|
+
sys.exit(1)
|
|
164
|
+
side_by_side_diff(lines1, lines2, file1, file2, only_diff=only_diff)
|
|
165
|
+
|
|
166
|
+
|
|
167
|
+
if __name__ == "__main__":
|
|
168
|
+
main()
|
|
169
|
+
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: peter-diff
|
|
3
|
+
Version: 0.1.1
|
|
4
|
+
Summary: A simple CLI tool to diff two text files.
|
|
5
|
+
License: MIT
|
|
6
|
+
Requires-Python: >=3.7
|
|
7
|
+
Description-Content-Type: text/markdown
|
|
8
|
+
|
|
9
|
+
# peter-diff
|
|
10
|
+
|
|
11
|
+
A simple Python CLI tool to compare two text files and output their differences, similar to the Unix `diff` command.
|
|
12
|
+
|
|
13
|
+
## Installation
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
pip install -e .
|
|
17
|
+
pip uninstall peter-diff
|
|
18
|
+
```
|
|
19
|
+
OR
|
|
20
|
+
|
|
21
|
+
```sh
|
|
22
|
+
pip install peter-diff
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
## Usage
|
|
26
|
+
|
|
27
|
+
```sh
|
|
28
|
+
pydiff file1.txt file2.txt
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
## Publishing to PyPI
|
|
32
|
+
|
|
33
|
+
```
|
|
34
|
+
pip install build twine
|
|
35
|
+
python -m build
|
|
36
|
+
twine upload dist/*
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
When prompted, use `__token__` as the username and your PyPI API token as the password.
|
|
40
|
+
|
|
41
|
+
To skip the prompt, create a `~/.pypirc` file:
|
|
42
|
+
|
|
43
|
+
```ini
|
|
44
|
+
[pypi]
|
|
45
|
+
username = __token__
|
|
46
|
+
password = pypi-YOUR-TOKEN-HERE
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
> **Note:** Bump the `version` in `pyproject.toml` before each upload — PyPI rejects duplicate version numbers.
|
|
50
|
+
|
|
51
|
+
## Requirements
|
|
52
|
+
|
|
53
|
+
- Python >= 3.9
|
|
54
|
+
- No external dependencies
|
|
55
|
+
|
|
56
|
+
## License
|
|
57
|
+
|
|
58
|
+
MIT
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
MANIFEST.in
|
|
2
|
+
README.md
|
|
3
|
+
pyproject.toml
|
|
4
|
+
src/peter-diff/__init__.py
|
|
5
|
+
src/peter-diff/cli.py
|
|
6
|
+
src/peter_diff.egg-info/PKG-INFO
|
|
7
|
+
src/peter_diff.egg-info/SOURCES.txt
|
|
8
|
+
src/peter_diff.egg-info/dependency_links.txt
|
|
9
|
+
src/peter_diff.egg-info/entry_points.txt
|
|
10
|
+
src/peter_diff.egg-info/top_level.txt
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
peter-diff
|