fx-bin 2.0.0__tar.gz → 2.2.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.0.0 → fx_bin-2.2.0}/PKG-INFO +1 -1
- {fx_bin-2.0.0 → fx_bin-2.2.0}/fx_bin/__init__.py +1 -1
- {fx_bin-2.0.0 → fx_bin-2.2.0}/fx_bin/backup.py +31 -14
- {fx_bin-2.0.0 → fx_bin-2.2.0}/fx_bin/cli.py +6 -12
- {fx_bin-2.0.0 → fx_bin-2.2.0}/pyproject.toml +9 -2
- {fx_bin-2.0.0 → fx_bin-2.2.0}/README.md +0 -0
- {fx_bin-2.0.0 → fx_bin-2.2.0}/fx_bin/common.py +0 -0
- {fx_bin-2.0.0 → fx_bin-2.2.0}/fx_bin/common_functional.py +0 -0
- {fx_bin-2.0.0 → fx_bin-2.2.0}/fx_bin/errors.py +0 -0
- {fx_bin-2.0.0 → fx_bin-2.2.0}/fx_bin/files.py +0 -0
- {fx_bin-2.0.0 → fx_bin-2.2.0}/fx_bin/filter.py +0 -0
- {fx_bin-2.0.0 → fx_bin-2.2.0}/fx_bin/find_files.py +0 -0
- {fx_bin-2.0.0 → fx_bin-2.2.0}/fx_bin/lib.py +0 -0
- {fx_bin-2.0.0 → fx_bin-2.2.0}/fx_bin/py_fx_bin.py +0 -0
- {fx_bin-2.0.0 → fx_bin-2.2.0}/fx_bin/realpath.py +0 -0
- {fx_bin-2.0.0 → fx_bin-2.2.0}/fx_bin/replace.py +0 -0
- {fx_bin-2.0.0 → fx_bin-2.2.0}/fx_bin/replace_functional.py +0 -0
- {fx_bin-2.0.0 → fx_bin-2.2.0}/fx_bin/root.py +0 -0
- {fx_bin-2.0.0 → fx_bin-2.2.0}/fx_bin/size.py +0 -0
- {fx_bin-2.0.0 → fx_bin-2.2.0}/fx_bin/today.py +0 -0
|
@@ -15,6 +15,7 @@ import shutil
|
|
|
15
15
|
import tarfile
|
|
16
16
|
from datetime import datetime
|
|
17
17
|
from pathlib import Path
|
|
18
|
+
from typing import Optional
|
|
18
19
|
|
|
19
20
|
from .common import (
|
|
20
21
|
generate_timestamp,
|
|
@@ -47,14 +48,14 @@ def get_base_name(filename: str) -> str:
|
|
|
47
48
|
|
|
48
49
|
def backup_file(
|
|
49
50
|
source_path: str,
|
|
50
|
-
backup_dir: str =
|
|
51
|
+
backup_dir: Optional[str] = None,
|
|
51
52
|
timestamp_format: str = DEFAULT_TIMESTAMP_FORMAT,
|
|
52
53
|
) -> str:
|
|
53
54
|
"""Create a timestamped backup of a file.
|
|
54
55
|
|
|
55
56
|
Args:
|
|
56
57
|
source_path: Path to the file to backup
|
|
57
|
-
backup_dir: Directory to store the backup (default:
|
|
58
|
+
backup_dir: Directory to store the backup (default: None = same level as source)
|
|
58
59
|
timestamp_format: Format string for timestamp (default: '%Y%m%d%H%M%S')
|
|
59
60
|
|
|
60
61
|
Returns:
|
|
@@ -65,6 +66,8 @@ def backup_file(
|
|
|
65
66
|
|
|
66
67
|
Examples:
|
|
67
68
|
>>> backup_file("document.txt") # doctest: +SKIP
|
|
69
|
+
'document_20250104120000.txt'
|
|
70
|
+
>>> backup_file("document.txt", backup_dir="backups") # doctest: +SKIP
|
|
68
71
|
'backups/document_20250104120000.txt'
|
|
69
72
|
"""
|
|
70
73
|
if not os.path.exists(source_path):
|
|
@@ -78,8 +81,12 @@ def backup_file(
|
|
|
78
81
|
timestamp = generate_timestamp(timestamp_format, now=datetime.now())
|
|
79
82
|
backup_filename = f"{base_name}_{timestamp}{ext}"
|
|
80
83
|
|
|
81
|
-
|
|
82
|
-
|
|
84
|
+
# NEW DEFAULT: Same level as source, unless explicit backup_dir provided
|
|
85
|
+
if backup_dir is None:
|
|
86
|
+
backup_dir_path = source_path_obj.parent # Parent directory of source
|
|
87
|
+
else:
|
|
88
|
+
backup_dir_path = Path(backup_dir)
|
|
89
|
+
backup_dir_path.mkdir(parents=True, exist_ok=True)
|
|
83
90
|
|
|
84
91
|
backup_path = backup_dir_path / backup_filename
|
|
85
92
|
if backup_path.exists():
|
|
@@ -92,7 +99,7 @@ def backup_file(
|
|
|
92
99
|
|
|
93
100
|
def backup_directory(
|
|
94
101
|
source_path: str,
|
|
95
|
-
backup_dir: str =
|
|
102
|
+
backup_dir: Optional[str] = None,
|
|
96
103
|
timestamp_format: str = DEFAULT_TIMESTAMP_FORMAT,
|
|
97
104
|
compress: bool = False,
|
|
98
105
|
) -> str:
|
|
@@ -100,7 +107,7 @@ def backup_directory(
|
|
|
100
107
|
|
|
101
108
|
Args:
|
|
102
109
|
source_path: Path to the directory to backup
|
|
103
|
-
backup_dir: Directory to store the backup (default:
|
|
110
|
+
backup_dir: Directory to store the backup (default: None = same level as source)
|
|
104
111
|
timestamp_format: Format string for timestamp (default: '%Y%m%d%H%M%S')
|
|
105
112
|
compress: Whether to compress as .tar.xz (default: False)
|
|
106
113
|
|
|
@@ -116,9 +123,11 @@ def backup_directory(
|
|
|
116
123
|
|
|
117
124
|
Examples:
|
|
118
125
|
>>> backup_directory("mydir/") # doctest: +SKIP
|
|
119
|
-
'
|
|
126
|
+
'mydir_20250104120000/'
|
|
120
127
|
>>> backup_directory("mydir/", compress=True) # doctest: +SKIP
|
|
121
|
-
'
|
|
128
|
+
'mydir_20250104120000.tar.xz'
|
|
129
|
+
>>> backup_directory("mydir/", backup_dir="backups") # doctest: +SKIP
|
|
130
|
+
'backups/mydir_20250104120000/'
|
|
122
131
|
"""
|
|
123
132
|
if not os.path.exists(source_path):
|
|
124
133
|
raise FileNotFoundError(f"Source directory not found: {source_path}")
|
|
@@ -131,7 +140,7 @@ def backup_directory(
|
|
|
131
140
|
|
|
132
141
|
def _backup_directory_uncompressed(
|
|
133
142
|
source_path: str,
|
|
134
|
-
backup_dir: str,
|
|
143
|
+
backup_dir: Optional[str],
|
|
135
144
|
timestamp_format: str,
|
|
136
145
|
) -> str:
|
|
137
146
|
"""Create uncompressed directory backup."""
|
|
@@ -141,8 +150,12 @@ def _backup_directory_uncompressed(
|
|
|
141
150
|
timestamp = generate_timestamp(timestamp_format, now=datetime.now())
|
|
142
151
|
backup_dirname = f"{dir_name}_{timestamp}"
|
|
143
152
|
|
|
144
|
-
|
|
145
|
-
|
|
153
|
+
# NEW DEFAULT: Same level as source, unless explicit backup_dir provided
|
|
154
|
+
if backup_dir is None:
|
|
155
|
+
backup_dir_path = source_path_obj.parent # Parent directory of source
|
|
156
|
+
else:
|
|
157
|
+
backup_dir_path = Path(backup_dir)
|
|
158
|
+
backup_dir_path.mkdir(parents=True, exist_ok=True)
|
|
146
159
|
|
|
147
160
|
backup_path = backup_dir_path / backup_dirname
|
|
148
161
|
if backup_path.exists():
|
|
@@ -155,7 +168,7 @@ def _backup_directory_uncompressed(
|
|
|
155
168
|
|
|
156
169
|
|
|
157
170
|
def _backup_directory_compressed(
|
|
158
|
-
source_path: str, backup_dir: str, timestamp_format: str
|
|
171
|
+
source_path: str, backup_dir: Optional[str], timestamp_format: str
|
|
159
172
|
) -> str:
|
|
160
173
|
"""Create compressed directory backup as .tar.xz."""
|
|
161
174
|
source_path_obj = Path(source_path)
|
|
@@ -164,8 +177,12 @@ def _backup_directory_compressed(
|
|
|
164
177
|
timestamp = generate_timestamp(timestamp_format, now=datetime.now())
|
|
165
178
|
backup_filename = f"{dir_name}_{timestamp}.tar.xz"
|
|
166
179
|
|
|
167
|
-
|
|
168
|
-
|
|
180
|
+
# NEW DEFAULT: Same level as source, unless explicit backup_dir provided
|
|
181
|
+
if backup_dir is None:
|
|
182
|
+
backup_dir_path = source_path_obj.parent # Parent directory of source
|
|
183
|
+
else:
|
|
184
|
+
backup_dir_path = Path(backup_dir)
|
|
185
|
+
backup_dir_path.mkdir(parents=True, exist_ok=True)
|
|
169
186
|
|
|
170
187
|
backup_path = backup_dir_path / backup_filename
|
|
171
188
|
if backup_path.exists():
|
|
@@ -337,11 +337,6 @@ def replace(search_text, replace_text, filenames):
|
|
|
337
337
|
|
|
338
338
|
@cli.command()
|
|
339
339
|
@click.argument("path", type=click.Path(exists=True))
|
|
340
|
-
@click.option(
|
|
341
|
-
"--backup-dir",
|
|
342
|
-
default="backups",
|
|
343
|
-
help="Directory to store backups (default: 'backups')",
|
|
344
|
-
)
|
|
345
340
|
@click.option(
|
|
346
341
|
"--compress",
|
|
347
342
|
is_flag=True,
|
|
@@ -355,15 +350,16 @@ def replace(search_text, replace_text, filenames):
|
|
|
355
350
|
)
|
|
356
351
|
def backup(
|
|
357
352
|
path: str,
|
|
358
|
-
backup_dir: str,
|
|
359
353
|
compress: bool,
|
|
360
354
|
timestamp_format: Optional[str],
|
|
361
355
|
) -> int:
|
|
362
356
|
"""Create a timestamped backup of a file or directory.
|
|
363
357
|
|
|
364
358
|
Examples:
|
|
365
|
-
fx backup file.txt
|
|
366
|
-
fx backup mydir/ --compress
|
|
359
|
+
fx backup file.txt # Backup to file_timestamp.txt (same level)
|
|
360
|
+
fx backup mydir/ --compress # Backup to mydir_timestamp.tar.xz
|
|
361
|
+
|
|
362
|
+
Backups are created in the same directory as the source by default.
|
|
367
363
|
"""
|
|
368
364
|
from . import backup as backup_module
|
|
369
365
|
from pathlib import Path
|
|
@@ -373,11 +369,9 @@ def backup(
|
|
|
373
369
|
path_obj = Path(path)
|
|
374
370
|
|
|
375
371
|
if path_obj.is_file():
|
|
376
|
-
result = backup_module.backup_file(path,
|
|
372
|
+
result = backup_module.backup_file(path, None, ts_format)
|
|
377
373
|
else:
|
|
378
|
-
result = backup_module.backup_directory(
|
|
379
|
-
path, backup_dir, ts_format, compress
|
|
380
|
-
)
|
|
374
|
+
result = backup_module.backup_directory(path, None, ts_format, compress)
|
|
381
375
|
|
|
382
376
|
click.echo(f"Backup created: {result}")
|
|
383
377
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[tool.poetry]
|
|
2
2
|
name = "fx-bin"
|
|
3
|
-
version = "2.
|
|
3
|
+
version = "2.2.0"
|
|
4
4
|
description = "A common bin collection for my own usage"
|
|
5
5
|
authors = ["Frank Xu <frank@frankxu.me>"]
|
|
6
6
|
readme = "README.md"
|
|
@@ -157,10 +157,17 @@ version_variables = [
|
|
|
157
157
|
"fx_bin/__init__.py:__version__",
|
|
158
158
|
]
|
|
159
159
|
branch = "main"
|
|
160
|
-
upload_to_pypi = false
|
|
160
|
+
upload_to_pypi = false # We handle PyPI upload manually in workflow
|
|
161
161
|
build_command = "pip install poetry && poetry build"
|
|
162
162
|
commit_parser = "conventional"
|
|
163
163
|
|
|
164
|
+
# GitHub Release Settings
|
|
165
|
+
# Semantic-release creates GitHub Release with auto-generated changelog
|
|
166
|
+
upload_to_repository = true # Creates GitHub Release
|
|
167
|
+
repository_user = "frankyxhl"
|
|
168
|
+
repository_name = "fx_bin"
|
|
169
|
+
remove_dist = false # Keep dist files for manual upload to release
|
|
170
|
+
|
|
164
171
|
[tool.semantic_release.commit_parser_options]
|
|
165
172
|
minor_tags = ["feat"]
|
|
166
173
|
patch_tags = ["fix", "perf"]
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|