diffx-python 0.3.0__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,50 @@
1
+ # Rust
2
+ /target/
3
+ **/*.rs.bk
4
+
5
+ # IDEs
6
+ .idea/
7
+ .vscode/
8
+
9
+ # OS
10
+ .DS_Store
11
+ thumbs.db
12
+
13
+ # Cargo
14
+ Cargo.lock
15
+
16
+ # Test intermediate files (generated during tests)
17
+ /tests/output/
18
+ **/diff_report_v*.json
19
+
20
+ # Node.js
21
+ node_modules/
22
+ npm-debug.log*
23
+ yarn-debug.log*
24
+ yarn-error.log*
25
+ lerna-debug.log*
26
+ .pnpm-debug.log*
27
+ **/dist/
28
+ **/build/
29
+
30
+ # Python
31
+ __pycache__/
32
+ *.py[cod]
33
+ *~
34
+ *.so
35
+ .Python
36
+ .env
37
+ .venv
38
+ env/
39
+ venv/
40
+ ENV/
41
+ env.bak/
42
+ venv.bak/
43
+ *.egg-info/
44
+ .pytest_cache/
45
+ .coverage
46
+ htmlcov/
47
+
48
+ # Binary downloads
49
+ **/packages/*/bin/
50
+ **/packages/*/temp/
@@ -0,0 +1,134 @@
1
+ Metadata-Version: 2.4
2
+ Name: diffx-python
3
+ Version: 0.3.0
4
+ Summary: Python wrapper for diffx - semantic diff for structured data
5
+ Project-URL: Homepage, https://github.com/kako-jun/diffx
6
+ Project-URL: Repository, https://github.com/kako-jun/diffx
7
+ Project-URL: Issues, https://github.com/kako-jun/diffx/issues
8
+ Project-URL: Documentation, https://github.com/kako-jun/diffx/tree/main/docs
9
+ Author-email: kako-jun <kako.jun.42@gmail.com>
10
+ License-Expression: MIT
11
+ Keywords: comparison,csv,diff,ini,json,semantic,structured-data,toml,xml,yaml
12
+ Classifier: Development Status :: 4 - Beta
13
+ Classifier: Intended Audience :: Developers
14
+ Classifier: License :: OSI Approved :: MIT License
15
+ Classifier: Programming Language :: Python :: 3
16
+ Classifier: Programming Language :: Python :: 3.8
17
+ Classifier: Programming Language :: Python :: 3.9
18
+ Classifier: Programming Language :: Python :: 3.10
19
+ Classifier: Programming Language :: Python :: 3.11
20
+ Classifier: Programming Language :: Python :: 3.12
21
+ Classifier: Programming Language :: Python :: 3.13
22
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
23
+ Classifier: Topic :: Text Processing
24
+ Classifier: Topic :: Utilities
25
+ Requires-Python: >=3.8
26
+ Provides-Extra: dev
27
+ Requires-Dist: black; extra == 'dev'
28
+ Requires-Dist: isort; extra == 'dev'
29
+ Requires-Dist: mypy; extra == 'dev'
30
+ Requires-Dist: pytest-cov; extra == 'dev'
31
+ Requires-Dist: pytest>=6.0; extra == 'dev'
32
+ Requires-Dist: ruff; extra == 'dev'
33
+ Description-Content-Type: text/markdown
34
+
35
+ # diffx
36
+
37
+ Python wrapper for the `diffx` CLI tool - semantic diff for structured data.
38
+
39
+ ## Installation
40
+
41
+ ```bash
42
+ pip install diffx-py
43
+ ```
44
+
45
+ This will automatically download the appropriate `diffx` binary for your system from GitHub Releases.
46
+
47
+ ## Usage
48
+
49
+ ### Modern API (Recommended)
50
+
51
+ ```python
52
+ import diffx
53
+
54
+ # Compare two JSON files
55
+ result = diffx.diff('file1.json', 'file2.json')
56
+ print(result)
57
+
58
+ # Get structured output as JSON
59
+ json_result = diffx.diff(
60
+ 'config1.yaml',
61
+ 'config2.yaml',
62
+ diffx.DiffOptions(format='yaml', output='json')
63
+ )
64
+
65
+ for diff_item in json_result:
66
+ if diff_item.added:
67
+ print(f"Added: {diff_item.added}")
68
+ elif diff_item.modified:
69
+ print(f"Modified: {diff_item.modified}")
70
+
71
+ # Compare directory trees
72
+ dir_result = diffx.diff(
73
+ 'dir1/',
74
+ 'dir2/',
75
+ diffx.DiffOptions(recursive=True, path='config')
76
+ )
77
+
78
+ # Compare strings directly
79
+ json1 = '{"name": "Alice", "age": 30}'
80
+ json2 = '{"name": "Alice", "age": 31}'
81
+ string_result = diffx.diff_string(
82
+ json1, json2, 'json',
83
+ diffx.DiffOptions(output='json')
84
+ )
85
+ ```
86
+
87
+ ### Legacy API (Backward Compatibility)
88
+
89
+ ```python
90
+ from diffx import run_diffx
91
+
92
+ # Compare two JSON files (legacy)
93
+ result = run_diffx(["file1.json", "file2.json"])
94
+
95
+ if result.returncode == 0:
96
+ print("No differences found.")
97
+ else:
98
+ print("Differences found:")
99
+ print(result.stdout)
100
+ ```
101
+
102
+ ## Features
103
+
104
+ - **Multiple formats**: JSON, YAML, TOML, XML, INI, CSV
105
+ - **Smart diffing**: Understands structure, not just text
106
+ - **Flexible output**: CLI, JSON, YAML, unified diff formats
107
+ - **Advanced options**:
108
+ - Regex-based key filtering
109
+ - Floating-point tolerance
110
+ - Array element identification
111
+ - Path-based filtering
112
+ - **Cross-platform**: Automatically downloads the right binary
113
+
114
+ ## Development
115
+
116
+ To install in development mode with uv:
117
+
118
+ ```bash
119
+ uv venv
120
+ source .venv/bin/activate
121
+ uv pip install -e .[dev]
122
+ ```
123
+
124
+ ## Manual Binary Installation
125
+
126
+ If automatic download fails:
127
+
128
+ ```bash
129
+ diffx-download-binary
130
+ ```
131
+
132
+ ## License
133
+
134
+ This project is licensed under the MIT License.
@@ -0,0 +1,100 @@
1
+ # diffx
2
+
3
+ Python wrapper for the `diffx` CLI tool - semantic diff for structured data.
4
+
5
+ ## Installation
6
+
7
+ ```bash
8
+ pip install diffx-py
9
+ ```
10
+
11
+ This will automatically download the appropriate `diffx` binary for your system from GitHub Releases.
12
+
13
+ ## Usage
14
+
15
+ ### Modern API (Recommended)
16
+
17
+ ```python
18
+ import diffx
19
+
20
+ # Compare two JSON files
21
+ result = diffx.diff('file1.json', 'file2.json')
22
+ print(result)
23
+
24
+ # Get structured output as JSON
25
+ json_result = diffx.diff(
26
+ 'config1.yaml',
27
+ 'config2.yaml',
28
+ diffx.DiffOptions(format='yaml', output='json')
29
+ )
30
+
31
+ for diff_item in json_result:
32
+ if diff_item.added:
33
+ print(f"Added: {diff_item.added}")
34
+ elif diff_item.modified:
35
+ print(f"Modified: {diff_item.modified}")
36
+
37
+ # Compare directory trees
38
+ dir_result = diffx.diff(
39
+ 'dir1/',
40
+ 'dir2/',
41
+ diffx.DiffOptions(recursive=True, path='config')
42
+ )
43
+
44
+ # Compare strings directly
45
+ json1 = '{"name": "Alice", "age": 30}'
46
+ json2 = '{"name": "Alice", "age": 31}'
47
+ string_result = diffx.diff_string(
48
+ json1, json2, 'json',
49
+ diffx.DiffOptions(output='json')
50
+ )
51
+ ```
52
+
53
+ ### Legacy API (Backward Compatibility)
54
+
55
+ ```python
56
+ from diffx import run_diffx
57
+
58
+ # Compare two JSON files (legacy)
59
+ result = run_diffx(["file1.json", "file2.json"])
60
+
61
+ if result.returncode == 0:
62
+ print("No differences found.")
63
+ else:
64
+ print("Differences found:")
65
+ print(result.stdout)
66
+ ```
67
+
68
+ ## Features
69
+
70
+ - **Multiple formats**: JSON, YAML, TOML, XML, INI, CSV
71
+ - **Smart diffing**: Understands structure, not just text
72
+ - **Flexible output**: CLI, JSON, YAML, unified diff formats
73
+ - **Advanced options**:
74
+ - Regex-based key filtering
75
+ - Floating-point tolerance
76
+ - Array element identification
77
+ - Path-based filtering
78
+ - **Cross-platform**: Automatically downloads the right binary
79
+
80
+ ## Development
81
+
82
+ To install in development mode with uv:
83
+
84
+ ```bash
85
+ uv venv
86
+ source .venv/bin/activate
87
+ uv pip install -e .[dev]
88
+ ```
89
+
90
+ ## Manual Binary Installation
91
+
92
+ If automatic download fails:
93
+
94
+ ```bash
95
+ diffx-download-binary
96
+ ```
97
+
98
+ ## License
99
+
100
+ This project is licensed under the MIT License.
@@ -0,0 +1,85 @@
1
+ [build-system]
2
+ requires = ["hatchling"]
3
+ build-backend = "hatchling.build"
4
+
5
+ [project]
6
+ name = "diffx-python"
7
+ version = "0.3.0"
8
+ description = "Python wrapper for diffx - semantic diff for structured data"
9
+ readme = "README.md"
10
+ license = "MIT"
11
+ authors = [
12
+ { name = "kako-jun", email = "kako.jun.42@gmail.com" }
13
+ ]
14
+ classifiers = [
15
+ "Development Status :: 4 - Beta",
16
+ "Intended Audience :: Developers",
17
+ "License :: OSI Approved :: MIT License",
18
+ "Programming Language :: Python :: 3",
19
+ "Programming Language :: Python :: 3.8",
20
+ "Programming Language :: Python :: 3.9",
21
+ "Programming Language :: Python :: 3.10",
22
+ "Programming Language :: Python :: 3.11",
23
+ "Programming Language :: Python :: 3.12",
24
+ "Programming Language :: Python :: 3.13",
25
+ "Topic :: Software Development :: Libraries :: Python Modules",
26
+ "Topic :: Text Processing",
27
+ "Topic :: Utilities"
28
+ ]
29
+ keywords = [
30
+ "diff",
31
+ "semantic",
32
+ "json",
33
+ "yaml",
34
+ "toml",
35
+ "xml",
36
+ "ini",
37
+ "csv",
38
+ "structured-data",
39
+ "comparison"
40
+ ]
41
+ requires-python = ">=3.8"
42
+ dependencies = []
43
+
44
+ [project.urls]
45
+ Homepage = "https://github.com/kako-jun/diffx"
46
+ Repository = "https://github.com/kako-jun/diffx"
47
+ Issues = "https://github.com/kako-jun/diffx/issues"
48
+ Documentation = "https://github.com/kako-jun/diffx/tree/main/docs"
49
+
50
+ [project.optional-dependencies]
51
+ dev = [
52
+ "pytest >= 6.0",
53
+ "pytest-cov",
54
+ "black",
55
+ "isort",
56
+ "mypy",
57
+ "ruff"
58
+ ]
59
+
60
+ [project.scripts]
61
+ diffx-download-binary = "diffx.installer:main"
62
+
63
+ [tool.hatch.build.targets.wheel]
64
+ packages = ["src/diffx"]
65
+
66
+ [tool.hatch.build.targets.sdist]
67
+ include = [
68
+ "/src",
69
+ "/scripts",
70
+ "/README.md"
71
+ ]
72
+
73
+ [tool.ruff]
74
+ line-length = 88
75
+ target-version = "py38"
76
+
77
+ [tool.ruff.lint]
78
+ select = ["E", "F", "W", "I", "N", "UP", "YTT", "ANN", "S", "BLE", "FBT", "B", "A", "COM", "C4", "DTZ", "T10", "ISC", "ICN", "G", "PIE", "T20", "PYI", "PT", "Q", "RSE", "RET", "SLF", "SIM", "TID", "TCH", "ARG", "PTH", "ERA", "PGH", "PL", "TRY", "NPY", "RUF"]
79
+ ignore = ["ANN101", "ANN102", "COM812", "ISC001"]
80
+
81
+ [tool.mypy]
82
+ python_version = "3.8"
83
+ warn_return_any = true
84
+ warn_unused_configs = true
85
+ disallow_untyped_defs = true
@@ -0,0 +1,33 @@
1
+ """
2
+ diffx: Python wrapper for the diffx CLI tool
3
+
4
+ This package provides a Python interface to the diffx CLI tool for semantic
5
+ diffing of structured data formats like JSON, YAML, TOML, XML, INI, and CSV.
6
+ """
7
+
8
+ from .diffx import (
9
+ diff,
10
+ diff_string,
11
+ is_diffx_available,
12
+ DiffOptions,
13
+ DiffResult,
14
+ DiffError,
15
+ Format,
16
+ OutputFormat,
17
+ )
18
+
19
+ # For backward compatibility with existing diffx_python users
20
+ from .compat import run_diffx
21
+
22
+ __version__ = "0.3.0"
23
+ __all__ = [
24
+ "diff",
25
+ "diff_string",
26
+ "is_diffx_available",
27
+ "DiffOptions",
28
+ "DiffResult",
29
+ "DiffError",
30
+ "Format",
31
+ "OutputFormat",
32
+ "run_diffx", # Backward compatibility
33
+ ]
@@ -0,0 +1,56 @@
1
+ """
2
+ Backward compatibility layer for existing diffx_python users
3
+ """
4
+
5
+ import os
6
+ import subprocess
7
+ import sys
8
+ import platform
9
+ from pathlib import Path
10
+
11
+
12
+ def run_diffx(args):
13
+ """
14
+ Run diffx command with given arguments (backward compatibility)
15
+
16
+ This function maintains compatibility with the original diffx_python API.
17
+
18
+ Args:
19
+ args: List of command line arguments for diffx
20
+
21
+ Returns:
22
+ subprocess.CompletedProcess object with stdout, stderr, and returncode
23
+
24
+ Examples:
25
+ >>> result = run_diffx(["file1.json", "file2.json"])
26
+ >>> print(result.stdout)
27
+ """
28
+ # Determine the path to the diffx binary
29
+ package_dir = Path(__file__).parent.parent.parent
30
+ binary_name = "diffx.exe" if platform.system() == "Windows" else "diffx"
31
+ diffx_binary_path = package_dir / "bin" / binary_name
32
+
33
+ # Fall back to system PATH if local binary doesn't exist
34
+ if not diffx_binary_path.exists():
35
+ diffx_binary_path = "diffx"
36
+
37
+ command = [str(diffx_binary_path)] + args
38
+
39
+ try:
40
+ result = subprocess.run(command, capture_output=True, text=True, check=False)
41
+
42
+ if result.returncode != 0 and result.stderr:
43
+ print(f"Error running diffx: {result.stderr}", file=sys.stderr)
44
+
45
+ return result
46
+ except FileNotFoundError:
47
+ # Create a mock result object for consistency
48
+ class MockResult:
49
+ def __init__(self):
50
+ self.stdout = ""
51
+ self.stderr = "diffx binary not found. Please ensure the package is installed correctly."
52
+ self.returncode = -1
53
+
54
+ result = MockResult()
55
+ print(f"Error: {result.stderr}", file=sys.stderr)
56
+ return result
@@ -0,0 +1,244 @@
1
+ """
2
+ Main diffx wrapper implementation
3
+ """
4
+
5
+ import json
6
+ import subprocess
7
+ import tempfile
8
+ import os
9
+ import platform
10
+ from pathlib import Path
11
+ from typing import Union, List, Dict, Any, Optional, Literal
12
+ from dataclasses import dataclass
13
+
14
+
15
+ # Type definitions
16
+ Format = Literal["json", "yaml", "toml", "xml", "ini", "csv"]
17
+ OutputFormat = Literal["cli", "json", "yaml", "unified"]
18
+
19
+
20
+ @dataclass
21
+ class DiffOptions:
22
+ """Options for the diff operation"""
23
+ format: Optional[Format] = None
24
+ output: Optional[OutputFormat] = None
25
+ recursive: bool = False
26
+ path: Optional[str] = None
27
+ ignore_keys_regex: Optional[str] = None
28
+ epsilon: Optional[float] = None
29
+ array_id_key: Optional[str] = None
30
+
31
+
32
+ class DiffResult:
33
+ """Result of a diff operation when output format is 'json'"""
34
+ def __init__(self, data: Dict[str, Any]):
35
+ self.data = data
36
+
37
+ @property
38
+ def added(self) -> Optional[tuple]:
39
+ """Get Added result if present"""
40
+ return tuple(self.data["Added"]) if "Added" in self.data else None
41
+
42
+ @property
43
+ def removed(self) -> Optional[tuple]:
44
+ """Get Removed result if present"""
45
+ return tuple(self.data["Removed"]) if "Removed" in self.data else None
46
+
47
+ @property
48
+ def modified(self) -> Optional[tuple]:
49
+ """Get Modified result if present"""
50
+ return tuple(self.data["Modified"]) if "Modified" in self.data else None
51
+
52
+ @property
53
+ def type_changed(self) -> Optional[tuple]:
54
+ """Get TypeChanged result if present"""
55
+ return tuple(self.data["TypeChanged"]) if "TypeChanged" in self.data else None
56
+
57
+ def __repr__(self) -> str:
58
+ return f"DiffResult({self.data})"
59
+
60
+
61
+ class DiffError(Exception):
62
+ """Error thrown when diffx command fails"""
63
+ def __init__(self, message: str, exit_code: int, stderr: str):
64
+ super().__init__(message)
65
+ self.exit_code = exit_code
66
+ self.stderr = stderr
67
+
68
+
69
+ def _get_diffx_binary_path() -> str:
70
+ """Get the path to the diffx binary"""
71
+ # Check if local binary exists (installed via postinstall)
72
+ package_dir = Path(__file__).parent.parent.parent
73
+ binary_name = "diffx.exe" if platform.system() == "Windows" else "diffx"
74
+ local_binary_path = package_dir / "bin" / binary_name
75
+
76
+ if local_binary_path.exists():
77
+ return str(local_binary_path)
78
+
79
+ # Fall back to system PATH
80
+ return "diffx"
81
+
82
+
83
+ def _execute_diffx(args: List[str]) -> tuple[str, str]:
84
+ """Execute diffx command and return stdout, stderr"""
85
+ diffx_path = _get_diffx_binary_path()
86
+
87
+ try:
88
+ result = subprocess.run(
89
+ [diffx_path] + args,
90
+ capture_output=True,
91
+ text=True,
92
+ check=True
93
+ )
94
+ return result.stdout, result.stderr
95
+ except subprocess.CalledProcessError as e:
96
+ raise DiffError(
97
+ f"diffx exited with code {e.returncode}",
98
+ e.returncode,
99
+ e.stderr or ""
100
+ )
101
+ except FileNotFoundError:
102
+ raise DiffError(
103
+ "diffx command not found. Please install diffx CLI tool.",
104
+ -1,
105
+ ""
106
+ )
107
+
108
+
109
+ def diff(
110
+ input1: str,
111
+ input2: str,
112
+ options: Optional[DiffOptions] = None
113
+ ) -> Union[str, List[DiffResult]]:
114
+ """
115
+ Compare two files or directories using diffx
116
+
117
+ Args:
118
+ input1: Path to first file/directory or '-' for stdin
119
+ input2: Path to second file/directory
120
+ options: Comparison options
121
+
122
+ Returns:
123
+ String output for CLI format, or list of DiffResult for JSON format
124
+
125
+ Examples:
126
+ >>> result = diff('file1.json', 'file2.json')
127
+ >>> print(result)
128
+
129
+ >>> json_result = diff('config1.yaml', 'config2.yaml',
130
+ ... DiffOptions(format='yaml', output='json'))
131
+ >>> for diff_item in json_result:
132
+ ... print(diff_item)
133
+
134
+ >>> dir_result = diff('dir1/', 'dir2/',
135
+ ... DiffOptions(recursive=True, path='config'))
136
+ """
137
+ if options is None:
138
+ options = DiffOptions()
139
+
140
+ args = [input1, input2]
141
+
142
+ # Add format option
143
+ if options.format:
144
+ args.extend(["--format", options.format])
145
+
146
+ # Add output format option
147
+ if options.output:
148
+ args.extend(["--output", options.output])
149
+
150
+ # Add recursive option
151
+ if options.recursive:
152
+ args.append("--recursive")
153
+
154
+ # Add path filter option
155
+ if options.path:
156
+ args.extend(["--path", options.path])
157
+
158
+ # Add ignore keys regex option
159
+ if options.ignore_keys_regex:
160
+ args.extend(["--ignore-keys-regex", options.ignore_keys_regex])
161
+
162
+ # Add epsilon option
163
+ if options.epsilon is not None:
164
+ args.extend(["--epsilon", str(options.epsilon)])
165
+
166
+ # Add array ID key option
167
+ if options.array_id_key:
168
+ args.extend(["--array-id-key", options.array_id_key])
169
+
170
+ stdout, stderr = _execute_diffx(args)
171
+
172
+ # If output format is JSON, parse the result
173
+ if options.output == "json":
174
+ try:
175
+ json_data = json.loads(stdout)
176
+ return [DiffResult(item) for item in json_data]
177
+ except json.JSONDecodeError as e:
178
+ raise DiffError(f"Failed to parse JSON output: {e}", -1, "")
179
+
180
+ # Return raw output for other formats
181
+ return stdout
182
+
183
+
184
+ def diff_string(
185
+ content1: str,
186
+ content2: str,
187
+ format: Format,
188
+ options: Optional[DiffOptions] = None
189
+ ) -> Union[str, List[DiffResult]]:
190
+ """
191
+ Compare two strings directly (writes to temporary files)
192
+
193
+ Args:
194
+ content1: First content string
195
+ content2: Second content string
196
+ format: Content format
197
+ options: Comparison options
198
+
199
+ Returns:
200
+ String output for CLI format, or list of DiffResult for JSON format
201
+
202
+ Examples:
203
+ >>> json1 = '{"name": "Alice", "age": 30}'
204
+ >>> json2 = '{"name": "Alice", "age": 31}'
205
+ >>> result = diff_string(json1, json2, 'json',
206
+ ... DiffOptions(output='json'))
207
+ >>> print(result)
208
+ """
209
+ if options is None:
210
+ options = DiffOptions()
211
+
212
+ # Ensure format is set
213
+ options.format = format
214
+
215
+ # Create temporary files
216
+ with tempfile.TemporaryDirectory() as tmp_dir:
217
+ tmp_file1 = Path(tmp_dir) / f"file1.{format}"
218
+ tmp_file2 = Path(tmp_dir) / f"file2.{format}"
219
+
220
+ # Write content to temporary files
221
+ tmp_file1.write_text(content1, encoding="utf-8")
222
+ tmp_file2.write_text(content2, encoding="utf-8")
223
+
224
+ # Perform diff
225
+ return diff(str(tmp_file1), str(tmp_file2), options)
226
+
227
+
228
+ def is_diffx_available() -> bool:
229
+ """
230
+ Check if diffx command is available in the system
231
+
232
+ Returns:
233
+ True if diffx is available, False otherwise
234
+
235
+ Examples:
236
+ >>> if not is_diffx_available():
237
+ ... print("Please install diffx CLI tool")
238
+ ... exit(1)
239
+ """
240
+ try:
241
+ _execute_diffx(["--version"])
242
+ return True
243
+ except DiffError:
244
+ return False
@@ -0,0 +1,113 @@
1
+ #!/usr/bin/env python3
2
+ """Binary installer for diffx."""
3
+
4
+ import os
5
+ import platform
6
+ import shutil
7
+ import subprocess
8
+ import sys
9
+ import tarfile
10
+ import tempfile
11
+ import urllib.request
12
+ import zipfile
13
+ from pathlib import Path
14
+
15
+ DIFFX_VERSION = "0.3.0"
16
+
17
+
18
+ def get_platform_info():
19
+ """Get platform-specific download information."""
20
+ system = platform.system().lower()
21
+ machine = platform.machine().lower()
22
+
23
+ if system == "windows":
24
+ return "diffx-windows-x86_64.zip", "diffx.exe"
25
+ elif system == "darwin":
26
+ if machine in ["arm64", "aarch64"]:
27
+ return "diffx-macos-aarch64.tar.gz", "diffx"
28
+ else:
29
+ return "diffx-macos-x86_64.tar.gz", "diffx"
30
+ elif system == "linux":
31
+ return "diffx-linux-x86_64.tar.gz", "diffx"
32
+ else:
33
+ raise RuntimeError(f"Unsupported platform: {system}-{machine}")
34
+
35
+
36
+ def download_file(url: str, dest_path: Path) -> None:
37
+ """Download a file from URL to destination."""
38
+ print(f"Downloading diffx binary from {url}...")
39
+
40
+ try:
41
+ with urllib.request.urlopen(url) as response:
42
+ with open(dest_path, 'wb') as dest_file:
43
+ shutil.copyfileobj(response, dest_file)
44
+ except Exception as e:
45
+ raise RuntimeError(f"Failed to download {url}: {e}")
46
+
47
+
48
+ def extract_archive(archive_path: Path, extract_dir: Path) -> None:
49
+ """Extract archive file."""
50
+ print("Extracting binary...")
51
+
52
+ if archive_path.suffix == '.zip':
53
+ with zipfile.ZipFile(archive_path, 'r') as zip_file:
54
+ zip_file.extractall(extract_dir)
55
+ elif archive_path.name.endswith('.tar.gz'):
56
+ with tarfile.open(archive_path, 'r:gz') as tar_file:
57
+ tar_file.extractall(extract_dir)
58
+ else:
59
+ raise RuntimeError(f"Unsupported archive format: {archive_path}")
60
+
61
+
62
+ def main():
63
+ """Main function to download and install diffx binary."""
64
+ try:
65
+ # Get the package directory (where this script will be installed)
66
+ if hasattr(sys, '_MEIPASS'):
67
+ # If running from PyInstaller bundle
68
+ package_dir = Path(sys._MEIPASS).parent
69
+ else:
70
+ # Normal installation
71
+ package_dir = Path(__file__).parent.parent.parent
72
+
73
+ bin_dir = package_dir / "bin"
74
+
75
+ # Get platform info
76
+ archive_name, binary_name = get_platform_info()
77
+ binary_path = bin_dir / binary_name
78
+
79
+ # Skip download if binary already exists
80
+ if binary_path.exists():
81
+ print("diffx binary already exists, skipping download.")
82
+ return 0
83
+
84
+ # Create bin directory
85
+ bin_dir.mkdir(exist_ok=True)
86
+
87
+ # Download URL
88
+ download_url = f"https://github.com/kako-jun/diffx/releases/download/v{DIFFX_VERSION}/{archive_name}"
89
+
90
+ # Download to temporary file
91
+ with tempfile.TemporaryDirectory() as temp_dir:
92
+ temp_path = Path(temp_dir)
93
+ archive_path = temp_path / archive_name
94
+
95
+ download_file(download_url, archive_path)
96
+ extract_archive(archive_path, bin_dir)
97
+
98
+ # Make binary executable on Unix systems
99
+ if platform.system() != "Windows":
100
+ binary_path.chmod(0o755)
101
+
102
+ print(f"✅ diffx binary installed successfully at {binary_path}")
103
+ return 0
104
+
105
+ except Exception as error:
106
+ print(f"❌ Failed to download diffx binary: {error}")
107
+ print("You may need to install diffx manually from: https://github.com/kako-jun/diffx/releases")
108
+ # Don't fail the installation, just warn
109
+ return 0
110
+
111
+
112
+ if __name__ == "__main__":
113
+ sys.exit(main())