fx-bin 2.2.2__tar.gz → 2.4.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.
- {fx_bin-2.2.2 → fx_bin-2.4.0}/PKG-INFO +1 -1
- {fx_bin-2.2.2 → fx_bin-2.4.0}/fx_bin/__init__.py +1 -1
- fx_bin-2.4.0/fx_bin/backup_utils.py +123 -0
- fx_bin-2.4.0/fx_bin/cli.py +1215 -0
- {fx_bin-2.2.2 → fx_bin-2.4.0}/fx_bin/common.py +16 -7
- fx_bin-2.4.0/fx_bin/common_functional.py +415 -0
- fx_bin-2.4.0/fx_bin/errors.py +160 -0
- {fx_bin-2.2.2 → fx_bin-2.4.0}/fx_bin/files.py +2 -2
- {fx_bin-2.2.2 → fx_bin-2.4.0}/fx_bin/find_files.py +0 -1
- fx_bin-2.4.0/fx_bin/lib.py +118 -0
- fx_bin-2.4.0/fx_bin/organize.py +389 -0
- fx_bin-2.4.0/fx_bin/organize_functional.py +936 -0
- {fx_bin-2.2.2 → fx_bin-2.4.0}/fx_bin/py_fx_bin.py +0 -1
- {fx_bin-2.2.2 → fx_bin-2.4.0}/fx_bin/replace.py +31 -30
- fx_bin-2.4.0/fx_bin/replace_functional.py +415 -0
- fx_bin-2.4.0/fx_bin/shared_types.py +54 -0
- {fx_bin-2.2.2 → fx_bin-2.4.0}/fx_bin/size.py +2 -2
- {fx_bin-2.2.2 → fx_bin-2.4.0}/fx_bin/today.py +9 -4
- {fx_bin-2.2.2 → fx_bin-2.4.0}/pyproject.toml +11 -1
- fx_bin-2.2.2/fx_bin/cli.py +0 -590
- fx_bin-2.2.2/fx_bin/common_functional.py +0 -259
- fx_bin-2.2.2/fx_bin/errors.py +0 -96
- fx_bin-2.2.2/fx_bin/lib.py +0 -42
- fx_bin-2.2.2/fx_bin/replace_functional.py +0 -304
- {fx_bin-2.2.2 → fx_bin-2.4.0}/README.md +0 -0
- {fx_bin-2.2.2 → fx_bin-2.4.0}/fx_bin/backup.py +0 -0
- {fx_bin-2.2.2 → fx_bin-2.4.0}/fx_bin/filter.py +0 -0
- {fx_bin-2.2.2 → fx_bin-2.4.0}/fx_bin/realpath.py +0 -0
- {fx_bin-2.2.2 → fx_bin-2.4.0}/fx_bin/root.py +0 -0
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
"""Shared backup utilities for file operations.
|
|
2
|
+
|
|
3
|
+
This module provides reusable backup functionality that can be used
|
|
4
|
+
across different modules (replace.py, replace_functional.py, etc.).
|
|
5
|
+
|
|
6
|
+
Functions follow functional patterns with IOResult for error handling.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
import os
|
|
10
|
+
import shutil
|
|
11
|
+
import stat
|
|
12
|
+
|
|
13
|
+
from loguru import logger as L
|
|
14
|
+
from returns.io import IOResult
|
|
15
|
+
|
|
16
|
+
from fx_bin.errors import IOError as FxIOError
|
|
17
|
+
from fx_bin.shared_types import FileBackup
|
|
18
|
+
|
|
19
|
+
# Re-export for backward compatibility
|
|
20
|
+
__all__ = ["FileBackup", "create_backup", "restore_from_backup", "cleanup_backup"]
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def create_backup(filename: str) -> IOResult[FileBackup, FxIOError]:
|
|
24
|
+
"""Create a backup of the file.
|
|
25
|
+
|
|
26
|
+
Creates a backup copy of the specified file with preserved metadata.
|
|
27
|
+
The backup file is created with a ".backup" suffix.
|
|
28
|
+
|
|
29
|
+
Args:
|
|
30
|
+
filename: Path to the file to backup
|
|
31
|
+
|
|
32
|
+
Returns:
|
|
33
|
+
IOResult containing:
|
|
34
|
+
- Success: FileBackup object with backup metadata
|
|
35
|
+
- Failure: FxIOError if backup creation fails
|
|
36
|
+
|
|
37
|
+
Example:
|
|
38
|
+
>>> from fx_bin.lib import unsafe_ioresult_unwrap
|
|
39
|
+
>>> result = create_backup("/path/to/file.txt")
|
|
40
|
+
>>> backup = unsafe_ioresult_unwrap(result)
|
|
41
|
+
>>> print(backup.backup_path)
|
|
42
|
+
/path/to/file.txt.backup
|
|
43
|
+
"""
|
|
44
|
+
try:
|
|
45
|
+
# Get original file stats
|
|
46
|
+
original_stat = os.stat(filename)
|
|
47
|
+
original_mode = original_stat.st_mode
|
|
48
|
+
|
|
49
|
+
# Create backup path
|
|
50
|
+
backup_path = f"{filename}.backup"
|
|
51
|
+
|
|
52
|
+
# Copy file with metadata
|
|
53
|
+
shutil.copy2(filename, backup_path)
|
|
54
|
+
|
|
55
|
+
return IOResult.from_value(
|
|
56
|
+
FileBackup(
|
|
57
|
+
original_path=filename,
|
|
58
|
+
backup_path=backup_path,
|
|
59
|
+
original_mode=original_mode,
|
|
60
|
+
)
|
|
61
|
+
)
|
|
62
|
+
except Exception as e:
|
|
63
|
+
return IOResult.from_failure(FxIOError(f"Failed to create backup: {e}"))
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
def restore_from_backup(backup: FileBackup) -> IOResult[None, FxIOError]:
|
|
67
|
+
"""Restore original file from backup on failure.
|
|
68
|
+
|
|
69
|
+
Restores the original file from the backup copy and restores
|
|
70
|
+
the original file permissions. Gracefully handles missing backup
|
|
71
|
+
files (already cleaned up).
|
|
72
|
+
|
|
73
|
+
Args:
|
|
74
|
+
backup: FileBackup object containing backup metadata
|
|
75
|
+
|
|
76
|
+
Returns:
|
|
77
|
+
IOResult containing:
|
|
78
|
+
- Success: None (restore succeeded or backup already cleaned up)
|
|
79
|
+
- Failure: FxIOError if restore operation fails
|
|
80
|
+
|
|
81
|
+
Example:
|
|
82
|
+
>>> backup = FileBackup(...)
|
|
83
|
+
>>> result = restore_from_backup(backup)
|
|
84
|
+
"""
|
|
85
|
+
try:
|
|
86
|
+
if os.path.exists(backup.backup_path):
|
|
87
|
+
# Restore the backup
|
|
88
|
+
shutil.move(backup.backup_path, backup.original_path)
|
|
89
|
+
# Restore permissions
|
|
90
|
+
os.chmod(backup.original_path, stat.S_IMODE(backup.original_mode))
|
|
91
|
+
return IOResult.from_value(None)
|
|
92
|
+
except Exception as e:
|
|
93
|
+
return IOResult.from_failure(FxIOError(f"Failed to restore from backup: {e}"))
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
def cleanup_backup(backup: FileBackup) -> IOResult[None, FxIOError]:
|
|
97
|
+
"""Remove backup file after successful operation.
|
|
98
|
+
|
|
99
|
+
Cleans up the backup file after a successful operation.
|
|
100
|
+
Gracefully handles missing backup files (already cleaned up).
|
|
101
|
+
This is a non-critical operation - failures are logged but don't
|
|
102
|
+
fail the overall operation.
|
|
103
|
+
|
|
104
|
+
Args:
|
|
105
|
+
backup: FileBackup object containing backup metadata
|
|
106
|
+
|
|
107
|
+
Returns:
|
|
108
|
+
IOResult containing:
|
|
109
|
+
- Success: None (cleanup succeeded or backup already gone)
|
|
110
|
+
- Failure: Never fails - errors logged but returns Success
|
|
111
|
+
|
|
112
|
+
Example:
|
|
113
|
+
>>> backup = FileBackup(...)
|
|
114
|
+
>>> result = cleanup_backup(backup)
|
|
115
|
+
"""
|
|
116
|
+
try:
|
|
117
|
+
if os.path.exists(backup.backup_path):
|
|
118
|
+
os.unlink(backup.backup_path)
|
|
119
|
+
return IOResult.from_value(None)
|
|
120
|
+
except Exception as e:
|
|
121
|
+
# Non-critical error - log but don't fail
|
|
122
|
+
L.warning(f"Could not remove backup {backup.backup_path}: {e}")
|
|
123
|
+
return IOResult.from_value(None)
|