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.
@@ -0,0 +1,2 @@
1
+ include README.md
2
+ recursive-include src/python_diff *.py
@@ -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,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -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,2 @@
1
+ [console_scripts]
2
+ peter-diff = peter_diff.cli:main
@@ -0,0 +1 @@
1
+ peter-diff