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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: fx-bin
3
- Version: 2.0.0
3
+ Version: 2.2.0
4
4
  Summary: A common bin collection for my own usage
5
5
  Keywords: fx_bin
6
6
  Author: Frank Xu
@@ -15,4 +15,4 @@ try:
15
15
  __version__ = version("fx-bin")
16
16
  except Exception:
17
17
  # Fallback version in case package is not installed
18
- __version__ = "2.0.0"
18
+ __version__ = "2.2.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 = "backups",
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: 'backups')
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
- backup_dir_path = Path(backup_dir)
82
- backup_dir_path.mkdir(parents=True, exist_ok=True)
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 = "backups",
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: 'backups')
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
- 'backups/mydir_20250104120000/'
126
+ 'mydir_20250104120000/'
120
127
  >>> backup_directory("mydir/", compress=True) # doctest: +SKIP
121
- 'backups/mydir_20250104120000.tar.xz'
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
- backup_dir_path = Path(backup_dir)
145
- backup_dir_path.mkdir(parents=True, exist_ok=True)
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
- backup_dir_path = Path(backup_dir)
168
- backup_dir_path.mkdir(parents=True, exist_ok=True)
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 # Backup file.txt to backups/
366
- fx backup mydir/ --compress # Backup directory as .tar.xz
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, backup_dir, ts_format)
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.0.0"
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