absfuyu 5.0.0__py3-none-any.whl → 5.1.0__py3-none-any.whl

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.

Potentially problematic release.


This version of absfuyu might be problematic. Click here for more details.

Files changed (72) hide show
  1. absfuyu/__init__.py +1 -1
  2. absfuyu/__main__.py +2 -2
  3. absfuyu/cli/__init__.py +2 -2
  4. absfuyu/cli/color.py +30 -14
  5. absfuyu/cli/config_group.py +9 -2
  6. absfuyu/cli/do_group.py +13 -6
  7. absfuyu/cli/game_group.py +9 -2
  8. absfuyu/cli/tool_group.py +16 -9
  9. absfuyu/config/__init__.py +2 -2
  10. absfuyu/core/__init__.py +2 -2
  11. absfuyu/core/baseclass.py +449 -80
  12. absfuyu/core/baseclass2.py +2 -2
  13. absfuyu/core/decorator.py +69 -3
  14. absfuyu/core/docstring.py +25 -22
  15. absfuyu/core/dummy_cli.py +2 -2
  16. absfuyu/core/dummy_func.py +19 -6
  17. absfuyu/core/typings.py +40 -0
  18. absfuyu/dxt/__init__.py +2 -2
  19. absfuyu/dxt/dictext.py +2 -2
  20. absfuyu/dxt/dxt_support.py +2 -2
  21. absfuyu/dxt/intext.py +31 -3
  22. absfuyu/dxt/listext.py +28 -3
  23. absfuyu/dxt/strext.py +3 -3
  24. absfuyu/extra/__init__.py +2 -2
  25. absfuyu/extra/beautiful.py +3 -2
  26. absfuyu/extra/da/__init__.py +36 -0
  27. absfuyu/extra/da/dadf.py +1138 -0
  28. absfuyu/extra/da/dadf_base.py +186 -0
  29. absfuyu/extra/da/df_func.py +97 -0
  30. absfuyu/extra/da/mplt.py +219 -0
  31. absfuyu/extra/data_analysis.py +10 -1067
  32. absfuyu/fun/__init__.py +2 -2
  33. absfuyu/fun/tarot.py +2 -2
  34. absfuyu/game/__init__.py +2 -2
  35. absfuyu/game/game_stat.py +2 -2
  36. absfuyu/game/sudoku.py +2 -2
  37. absfuyu/game/tictactoe.py +2 -2
  38. absfuyu/game/wordle.py +2 -2
  39. absfuyu/general/__init__.py +4 -4
  40. absfuyu/general/content.py +2 -2
  41. absfuyu/general/human.py +2 -2
  42. absfuyu/general/shape.py +2 -2
  43. absfuyu/logger.py +2 -2
  44. absfuyu/pkg_data/__init__.py +2 -2
  45. absfuyu/pkg_data/deprecated.py +2 -2
  46. absfuyu/sort.py +2 -2
  47. absfuyu/tools/__init__.py +25 -2
  48. absfuyu/tools/checksum.py +27 -7
  49. absfuyu/tools/converter.py +93 -28
  50. absfuyu/{general → tools}/generator.py +2 -2
  51. absfuyu/tools/inspector.py +433 -0
  52. absfuyu/tools/keygen.py +2 -2
  53. absfuyu/tools/obfuscator.py +46 -8
  54. absfuyu/tools/passwordlib.py +88 -23
  55. absfuyu/tools/shutdownizer.py +2 -2
  56. absfuyu/tools/web.py +2 -2
  57. absfuyu/util/__init__.py +2 -2
  58. absfuyu/util/api.py +2 -2
  59. absfuyu/util/json_method.py +2 -2
  60. absfuyu/util/lunar.py +2 -2
  61. absfuyu/util/path.py +190 -82
  62. absfuyu/util/performance.py +4 -4
  63. absfuyu/util/shorten_number.py +40 -10
  64. absfuyu/util/text_table.py +272 -0
  65. absfuyu/util/zipped.py +6 -6
  66. absfuyu/version.py +59 -42
  67. {absfuyu-5.0.0.dist-info → absfuyu-5.1.0.dist-info}/METADATA +10 -3
  68. absfuyu-5.1.0.dist-info/RECORD +76 -0
  69. absfuyu-5.0.0.dist-info/RECORD +0 -68
  70. {absfuyu-5.0.0.dist-info → absfuyu-5.1.0.dist-info}/WHEEL +0 -0
  71. {absfuyu-5.0.0.dist-info → absfuyu-5.1.0.dist-info}/entry_points.txt +0 -0
  72. {absfuyu-5.0.0.dist-info → absfuyu-5.1.0.dist-info}/licenses/LICENSE +0 -0
@@ -3,13 +3,13 @@ Absfuyu: Passwordlib
3
3
  --------------------
4
4
  Password library
5
5
 
6
- Version: 5.0.0
7
- Date updated: 19/02/2025 (dd/mm/yyyy)
6
+ Version: 5.1.0
7
+ Date updated: 10/03/2025 (dd/mm/yyyy)
8
8
  """
9
9
 
10
10
  # Module level
11
11
  # ---------------------------------------------------------------------------
12
- __all__ = ["PasswordGenerator", "TOTP"]
12
+ __all__ = ["PasswordGenerator", "PasswordHash", "TOTP"]
13
13
 
14
14
 
15
15
  # Library
@@ -21,11 +21,12 @@ import re
21
21
  from typing import ClassVar, Literal, NamedTuple
22
22
  from urllib.parse import quote, urlencode
23
23
 
24
- from absfuyu.core import BaseClass, deprecated, versionadded
24
+ from absfuyu.core.baseclass import BaseClass
25
+ from absfuyu.core.docstring import deprecated, versionadded
25
26
  from absfuyu.dxt import DictExt, Text
26
- from absfuyu.general.generator import Charset, Generator
27
27
  from absfuyu.logger import logger
28
28
  from absfuyu.pkg_data import DataList, DataLoader
29
+ from absfuyu.tools.generator import Charset, Generator
29
30
  from absfuyu.util import set_min
30
31
 
31
32
 
@@ -88,6 +89,18 @@ def _password_check(password: str) -> bool:
88
89
  # Class
89
90
  # ---------------------------------------------------------------------------
90
91
  class PasswordHash(NamedTuple):
92
+ """
93
+ Password hash
94
+
95
+ Parameters
96
+ ----------
97
+ salt : bytes
98
+ Salt
99
+
100
+ key : bytes
101
+ Key
102
+ """
103
+
91
104
  salt: bytes
92
105
  key: bytes
93
106
 
@@ -102,7 +115,17 @@ class PasswordGenerator(BaseClass):
102
115
  @staticmethod
103
116
  def password_hash(password: str) -> PasswordHash:
104
117
  """
105
- Generate hash for password
118
+ Generate hash for password.
119
+
120
+ Parameters
121
+ ----------
122
+ password : str
123
+ Password string
124
+
125
+ Returns
126
+ -------
127
+ PasswordHash
128
+ Password hash contains salt and key
106
129
  """
107
130
  salt = os.urandom(32)
108
131
  key = hashlib.pbkdf2_hmac(
@@ -116,7 +139,19 @@ class PasswordGenerator(BaseClass):
116
139
 
117
140
  @staticmethod
118
141
  def password_check(password: str) -> dict:
119
- """Check password's characteristic"""
142
+ """
143
+ Check password's characteristic.
144
+
145
+ Parameters
146
+ ----------
147
+ password : str
148
+ Password string
149
+
150
+ Returns
151
+ -------
152
+ dict
153
+ Password's characteristic.
154
+ """
120
155
  data = Text(password).analyze()
121
156
  data = DictExt(data).apply(lambda x: True if x > 0 else False) # type: ignore
122
157
  data.__setitem__("length", len(password))
@@ -131,7 +166,7 @@ class PasswordGenerator(BaseClass):
131
166
  include_special: bool = True,
132
167
  ) -> str:
133
168
  r"""
134
- Generate a random password
169
+ Generate a random password.
135
170
 
136
171
  Parameters
137
172
  ----------
@@ -141,13 +176,13 @@ class PasswordGenerator(BaseClass):
141
176
  | (Default: ``8``)
142
177
 
143
178
  include_uppercase : bool
144
- Include uppercase character in the password (Default: ``True``)
179
+ Include uppercase character in the password, by default ``True``
145
180
 
146
181
  include_number : bool
147
- Include digit character in the password (Default: ``True``)
182
+ Include digit character in the password, by default ``True``
148
183
 
149
184
  include_special : bool
150
- Include special character in the password (Default: ``True``)
185
+ Include special character in the password, by default ``True``
151
186
 
152
187
  Returns
153
188
  -------
@@ -205,16 +240,16 @@ class PasswordGenerator(BaseClass):
205
240
  Parameters
206
241
  ----------
207
242
  num_of_blocks : int
208
- Number of word used (Default: ``5``)
243
+ Number of word used, by default ``5``
209
244
 
210
245
  block_divider : str
211
- Character symbol that between each word (Default: ``"-"``)
246
+ Character symbol that between each word, by default ``"-"``
212
247
 
213
248
  first_letter_cap : bool
214
- Capitalize first character of each word (Default: ``True``)
249
+ Capitalize first character of each word, by default ``True``
215
250
 
216
251
  include_number : bool
217
- Add number to the end of each word (Default: ``True``)
252
+ Add number to the end of each word, by default ``True``
218
253
 
219
254
  custom_word_list : list[str] | None
220
255
  Custom word list for passphrase generation, by default uses a list of 360K+ words
@@ -255,6 +290,33 @@ class PasswordGenerator(BaseClass):
255
290
  class TOTP(BaseClass):
256
291
  """
257
292
  A class to represent a Time-based One-Time Password (TOTP) generator.
293
+
294
+ Parameters
295
+ ----------
296
+ secret : str
297
+ The shared secret key used to generate the TOTP.
298
+
299
+ name : str, optional
300
+ | The name associated with the TOTP.
301
+ | If not provided, by default ``"None"``.
302
+
303
+ issuer : str, optional
304
+ The issuer of the TOTP.
305
+
306
+ algorithm : Literal["SHA1", "SHA256", "SHA512"], optional
307
+ | The hashing algorithm used to generate the TOTP.
308
+ | Must be one of ``"SHA1"``, ``"SHA256"``, or ``"SHA512"``.
309
+ | By default ``"SHA1"``.
310
+
311
+ digit : int, optional
312
+ | The number of digits in the generated TOTP.
313
+ | Must be greater than 0.
314
+ | By default ``6``.
315
+
316
+ period : int, optional
317
+ | The time step in seconds for TOTP generation.
318
+ | Must be greater than 0.
319
+ | by default ``30``.
258
320
  """
259
321
 
260
322
  URL_SCHEME: ClassVar[str] = "otpauth://totp/"
@@ -277,23 +339,26 @@ class TOTP(BaseClass):
277
339
  The shared secret key used to generate the TOTP.
278
340
 
279
341
  name : str, optional
280
- The name associated with the TOTP. If not provided, defaults to ``"None"``.
342
+ | The name associated with the TOTP.
343
+ | If not provided, by default ``"None"``.
281
344
 
282
345
  issuer : str, optional
283
346
  The issuer of the TOTP.
284
347
 
285
348
  algorithm : Literal["SHA1", "SHA256", "SHA512"], optional
286
- The hashing algorithm used to generate the TOTP.
287
- Must be one of ``"SHA1"``, ``"SHA256"``, or ``"SHA512"``.
288
- Defaults to ``"SHA1"``.
349
+ | The hashing algorithm used to generate the TOTP.
350
+ | Must be one of ``"SHA1"``, ``"SHA256"``, or ``"SHA512"``.
351
+ | By default ``"SHA1"``.
289
352
 
290
353
  digit : int, optional
291
- The number of digits in the generated TOTP. Must be greater than 0.
292
- Defaults to ``6``.
354
+ | The number of digits in the generated TOTP.
355
+ | Must be greater than 0.
356
+ | By default ``6``.
293
357
 
294
358
  period : int, optional
295
- The time step in seconds for TOTP generation. Must be greater than 0.
296
- Defaults to ``30``.
359
+ | The time step in seconds for TOTP generation.
360
+ | Must be greater than 0.
361
+ | by default ``30``.
297
362
  """
298
363
  self.secret = secret.upper()
299
364
  self.name = name if name else "None"
@@ -3,8 +3,8 @@ Absfuyu: Shutdownizer
3
3
  ---------------------
4
4
  This shutdowns
5
5
 
6
- Version: 5.0.0
7
- Date updated: 23/02/2025 (dd/mm/yyyy)
6
+ Version: 5.1.0
7
+ Date updated: 10/03/2025 (dd/mm/yyyy)
8
8
  """
9
9
 
10
10
  # Module level
absfuyu/tools/web.py CHANGED
@@ -3,8 +3,8 @@ Absfuyu: Web
3
3
  ------------
4
4
  Web, ``request``, ``BeautifulSoup`` stuff
5
5
 
6
- Version: 1.0.2
7
- Date updated: 05/04/2024 (dd/mm/yyyy)
6
+ Version: 5.1.0
7
+ Date updated: 10/03/2025 (dd/mm/yyyy)
8
8
  """
9
9
 
10
10
  # Library
absfuyu/util/__init__.py CHANGED
@@ -3,8 +3,8 @@ Absufyu: Utilities
3
3
  ------------------
4
4
  Some random utilities
5
5
 
6
- Version: 1.5.2
7
- Date updated: 25/11/2024 (dd/mm/yyyy)
6
+ Version: 5.1.0
7
+ Date updated: 10/03/2025 (dd/mm/yyyy)
8
8
  """
9
9
 
10
10
  # Library
absfuyu/util/api.py CHANGED
@@ -3,8 +3,8 @@ Absufyu: API
3
3
  ------------
4
4
  Fetch data stuff
5
5
 
6
- Version: 5.0.0
7
- Date updated: 13/02/2025 (dd/mm/yyyy)
6
+ Version: 5.1.0
7
+ Date updated: 10/03/2025 (dd/mm/yyyy)
8
8
  """
9
9
 
10
10
  # Module level
@@ -3,8 +3,8 @@ Absfuyu: Json Method
3
3
  --------------------
4
4
  ``.json`` file handling
5
5
 
6
- Version: 5.0.0
7
- Date updated: 25/02/2025 (dd/mm/yyyy)
6
+ Version: 5.1.0
7
+ Date updated: 10/03/2025 (dd/mm/yyyy)
8
8
  """
9
9
 
10
10
  # Module level
absfuyu/util/lunar.py CHANGED
@@ -4,8 +4,8 @@ Absfuyu: Lunar calendar
4
4
  -----------------------
5
5
  Convert to lunar calendar
6
6
 
7
- Version: 1.0.3
8
- Date updated: 15/11/2024 (dd/mm/yyyy)
7
+ Version: 5.1.0
8
+ Date updated: 10/03/2025 (dd/mm/yyyy)
9
9
 
10
10
  Source:
11
11
  -------
absfuyu/util/path.py CHANGED
@@ -3,8 +3,8 @@ Absfuyu: Path
3
3
  -------------
4
4
  Path related
5
5
 
6
- Version: 5.0.0
7
- Date updated: 13/02/2025 (dd/mm/yyyy)
6
+ Version: 5.1.0
7
+ Date updated: 10/03/2025 (dd/mm/yyyy)
8
8
 
9
9
  Feature:
10
10
  --------
@@ -16,8 +16,15 @@ Feature:
16
16
  # ---------------------------------------------------------------------------
17
17
  __all__ = [
18
18
  # Main
19
+ "DirectoryBase",
19
20
  "Directory",
20
21
  "SaveFileAs",
22
+ # Mixin
23
+ "DirectoryInfoMixin",
24
+ "DirectoryBasicOperationMixin",
25
+ "DirectoryArchiverMixin",
26
+ "DirectoryOrganizerMixin",
27
+ "DirectoryTreeMixin",
21
28
  # Support
22
29
  "FileOrFolderWithModificationTime",
23
30
  "DirectoryInfo",
@@ -32,9 +39,11 @@ import shutil
32
39
  from datetime import datetime
33
40
  from functools import partial
34
41
  from pathlib import Path
35
- from typing import Any, Literal, NamedTuple
42
+ from typing import Any, ClassVar, Literal, NamedTuple
36
43
 
37
- from absfuyu.core import versionadded
44
+ from absfuyu.core.baseclass import BaseClass
45
+ from absfuyu.core.decorator import add_subclass_methods_decorator
46
+ from absfuyu.core.docstring import deprecated, versionadded, versionchanged
38
47
  from absfuyu.logger import logger
39
48
 
40
49
 
@@ -53,21 +62,40 @@ class FileOrFolderWithModificationTime(NamedTuple):
53
62
  modification_time: datetime
54
63
 
55
64
 
65
+ @deprecated(
66
+ "5.1.0", reason="Support for ``DirectoryInfoMixin`` which is also deprecated"
67
+ )
56
68
  @versionadded("3.3.0")
57
69
  class DirectoryInfo(NamedTuple):
58
70
  """
59
71
  Information of a directory
60
72
  """
61
73
 
62
- files: int
63
- folders: int
64
74
  creation_time: datetime
65
75
  modification_time: datetime
66
76
 
67
77
 
68
- # Class - Directory | version 3.4.0: Remake Directory into modular class
78
+ # Class - Directory
69
79
  # ---------------------------------------------------------------------------
70
- class DirectoryBase:
80
+ @add_subclass_methods_decorator
81
+ class DirectoryBase(BaseClass):
82
+ """
83
+ Directory - Base
84
+
85
+ Parameters
86
+ ----------
87
+ source_path : str | Path
88
+ Source folder
89
+
90
+ create_if_not_exist : bool
91
+ Create directory when not exist,
92
+ by default ``False``
93
+ """
94
+
95
+ # Custom attribute
96
+ _METHOD_INCLUDE: ClassVar[bool] = True # Include in DIR_METHODS
97
+ SUBCLASS_METHODS: ClassVar[dict[str, list[str]]] = {}
98
+
71
99
  def __init__(
72
100
  self,
73
101
  source_path: str | Path,
@@ -80,80 +108,53 @@ class DirectoryBase:
80
108
  Source folder
81
109
 
82
110
  create_if_not_exist : bool
83
- Create directory when not exist
84
- (Default: ``False``)
111
+ Create directory when not exist,
112
+ by default ``False``
85
113
  """
86
114
  self.source_path = Path(source_path)
87
- if create_if_not_exist:
88
- if not self.source_path.exists():
115
+ if not self.source_path.exists():
116
+ if create_if_not_exist:
89
117
  self.source_path.mkdir(exist_ok=True, parents=True)
118
+ else:
119
+ raise FileNotFoundError("Directory not existed")
90
120
 
91
- def __str__(self) -> str:
92
- return self.source_path.__str__()
93
-
94
- def __repr__(self) -> str:
95
- return f"{self.__class__.__name__}({self.source_path})"
96
-
97
- def __format__(self, __format_spec: str) -> str:
98
- """
99
- Change format of an object.
100
- Avaiable option: ``info``
101
-
102
- Usage
103
- -----
104
- >>> print(f"{<object>:<format_spec>}")
105
- >>> print(<object>.__format__(<format_spec>))
106
- >>> print(format(<object>, <format_spec>))
107
- """
108
- # Show quick info
109
- if __format_spec.lower().startswith("info"):
110
- return self.quick_info().__repr__()
111
-
112
- # No format spec
113
- return self.__repr__()
114
121
 
115
- # Everything
116
- @property
117
- @versionadded("3.3.0")
118
- def everything(self) -> list[Path]:
119
- """
120
- Every folders and files in this Directory
121
- """
122
- return list(x for x in self.source_path.glob("**/*"))
123
-
124
- @versionadded("3.3.0")
125
- def _every_folder(self) -> list[Path]:
126
- """
127
- Every folders in this Directory
128
- """
129
- return list(x for x in self.source_path.glob("**/*") if x.is_dir())
122
+ class DirectoryInfoMixin(DirectoryBase):
123
+ """
124
+ Directory - Info
130
125
 
131
- @versionadded("3.3.0")
132
- def _every_file(self) -> list[Path]:
133
- """
134
- Every folders in this Directory
135
- """
136
- return list(x for x in self.source_path.glob("**/*") if x.is_file())
126
+ - Quick info
127
+ """
137
128
 
138
- # Quick information
129
+ @deprecated("5.1.0", reason="Not efficient")
139
130
  @versionadded("3.3.0")
140
131
  def quick_info(self) -> DirectoryInfo:
141
132
  """
142
133
  Quick information about this Directory
143
134
 
144
- :rtype: DirectoryInfo
135
+ Returns
136
+ -------
137
+ DirectoryInfo
138
+ DirectoryInfo
145
139
  """
146
140
  source_stat: os.stat_result = self.source_path.stat()
147
141
  out = DirectoryInfo(
148
- files=len(self._every_file()),
149
- folders=len(self._every_folder()),
150
142
  creation_time=datetime.fromtimestamp(source_stat.st_ctime),
151
143
  modification_time=datetime.fromtimestamp(source_stat.st_mtime),
152
144
  )
153
145
  return out
154
146
 
155
147
 
156
- class DirectoryBasicOperation(DirectoryBase):
148
+ class DirectoryBasicOperationMixin(DirectoryBase):
149
+ """
150
+ Directory - Basic operation
151
+
152
+ - Rename
153
+ - Copy
154
+ - Move
155
+ - Delete
156
+ """
157
+
157
158
  # Rename
158
159
  def rename(self, new_name: str) -> None:
159
160
  """
@@ -214,7 +215,7 @@ class DirectoryBasicOperation(DirectoryBase):
214
215
  shutil.move(self.source_path, Path(dst))
215
216
  logger.debug(f"Moving to {dst}...DONE")
216
217
 
217
- except shutil.Error as e: # File already exists
218
+ except OSError as e: # File already exists
218
219
  logger.error(e)
219
220
  logger.debug("Overwriting file...")
220
221
  if content_only:
@@ -316,31 +317,54 @@ class DirectoryBasicOperation(DirectoryBase):
316
317
  except Exception as e:
317
318
  logger.error(f"Removing {self.source_path}...FAILED\n{e}")
318
319
 
319
- # Zip
320
+
321
+ class DirectoryArchiverMixin(DirectoryBase):
322
+ """
323
+ Directory - Archiver/Compress
324
+
325
+ - Compress
326
+ - Decompress
327
+ - Register extra zip format <staticmethod>
328
+ """
329
+
330
+ @versionchanged("5.1.0", reason="Update funcionality (new parameter)")
320
331
  def compress(
321
- self, *, format: Literal["zip", "tar", "gztar", "bztar", "xztar"] = "zip"
332
+ self,
333
+ format: Literal["zip", "tar", "gztar", "bztar", "xztar"] = "zip",
334
+ delete_after_compress: bool = False,
335
+ move_inside: bool = True,
322
336
  ) -> Path | None:
323
337
  """
324
338
  Compress the directory (Default: Create ``.zip`` file)
325
339
 
326
340
  Parameters
327
341
  ----------
328
- format : Literal["zip", "tar", "gztar", "bztar", "xztar"]
342
+ format : Literal["zip", "tar", "gztar", "bztar", "xztar"], optional
343
+ By default ``"zip"``
329
344
  - ``zip``: ZIP file (if the ``zlib`` module is available).
330
345
  - ``tar``: Uncompressed tar file. Uses POSIX.1-2001 pax format for new archives.
331
346
  - ``gztar``: gzip'ed tar-file (if the ``zlib`` module is available).
332
347
  - ``bztar``: bzip2'ed tar-file (if the ``bz2`` module is available).
333
348
  - ``xztar``: xz'ed tar-file (if the ``lzma`` module is available).
334
349
 
350
+ delete_after_compress : bool, optional
351
+ Delete directory after compress, by default ``False``
352
+
353
+ move_inside : bool, optional
354
+ Move the commpressed file inside the directory,
355
+ by default ``True``
356
+
335
357
  Returns
336
358
  -------
337
359
  Path
338
360
  Compressed path
361
+
339
362
  None
340
363
  When fail to compress
341
364
  """
342
365
  logger.debug(f"Zipping {self.source_path}...")
343
366
  try:
367
+ # Zip
344
368
  # zip_name = self.source_path.parent.joinpath(self.source_path.name).__str__()
345
369
  # shutil.make_archive(zip_name, format=format, root_dir=self.source_path)
346
370
  zip_path = shutil.make_archive(
@@ -348,26 +372,85 @@ class DirectoryBasicOperation(DirectoryBase):
348
372
  )
349
373
  logger.debug(f"Zipping {self.source_path}...DONE")
350
374
  logger.debug(f"Path: {zip_path}")
375
+
376
+ # Del
377
+ if delete_after_compress:
378
+ move_inside = False
379
+ shutil.rmtree(self.source_path)
380
+
381
+ # Move
382
+ if move_inside:
383
+ zf = Path(zip_path)
384
+ _move_path = self.source_path.joinpath(zf.name)
385
+ if _move_path.exists():
386
+ _move_path.unlink(missing_ok=True)
387
+ _move = zf.rename(_move_path)
388
+ return _move
389
+
351
390
  return Path(zip_path)
352
- except Exception as e:
391
+ except (FileExistsError, OSError) as e:
353
392
  logger.error(f"Zipping {self.source_path}...FAILED\n{e}")
354
393
  return None
355
394
 
395
+ @staticmethod
396
+ @versionadded("5.1.0")
397
+ def register_extra_zip_format() -> None:
398
+ """This register extra extension for zipfile"""
399
+ extra_extension = [".zip", ".cbz"]
400
+ shutil.unregister_unpack_format("zip")
401
+ shutil.register_unpack_format(
402
+ "zip",
403
+ extra_extension,
404
+ shutil._unpack_zipfile, # type: ignore
405
+ description="ZIP file",
406
+ )
356
407
 
357
- class DirectoryTree(DirectoryBase):
358
- pass
408
+ @versionadded("5.1.0")
409
+ def decompress(
410
+ self,
411
+ format: Literal["zip", "tar", "gztar", "bztar", "xztar"] | None = None,
412
+ delete_after_done: bool = False,
413
+ ) -> None:
414
+ """
415
+ Decompress compressed file in directory (first level only)
416
+
417
+ Parameters
418
+ ----------
419
+ format : Literal["zip", "tar", "gztar", "bztar", "xztar"] | None, optional
420
+ By default ``None``
421
+ - ``zip``: ZIP file (if the ``zlib`` module is available).
422
+ - ``tar``: Uncompressed tar file. Uses POSIX.1-2001 pax format for new archives.
423
+ - ``gztar``: gzip'ed tar-file (if the ``zlib`` module is available).
424
+ - ``bztar``: bzip2'ed tar-file (if the ``bz2`` module is available).
425
+ - ``xztar``: xz'ed tar-file (if the ``lzma`` module is available).
359
426
 
427
+ delete_after_done : bool, optional
428
+ Delete compressed file when extracted, by default ``False``
429
+ """
430
+ # Register extra extension
431
+ self.register_extra_zip_format()
432
+
433
+ # Decompress first level only
434
+ for path in self.source_path.glob("*"):
435
+ try:
436
+ shutil.unpack_archive(
437
+ path, path.parent.joinpath(path.stem), format=format
438
+ )
439
+ if delete_after_done and path.is_file():
440
+ path.unlink(missing_ok=True)
441
+ except OSError:
442
+ continue
360
443
 
361
- class Directory(DirectoryBasicOperation, DirectoryTree):
362
- """
363
- Some shortcuts for directory
364
444
 
365
- - list_structure
366
- - delete, rename, copy, move
367
- - zip
368
- - quick_info
445
+ class DirectoryOrganizerMixin(DirectoryBase):
369
446
  """
447
+ Directory - File organizer - SOON
448
+ """
449
+
450
+ pass
370
451
 
452
+
453
+ class DirectoryTreeMixin(DirectoryBase):
371
454
  # Directory structure
372
455
  def _list_dir(self, *ignore: str) -> list[Path]:
373
456
  """
@@ -504,10 +587,41 @@ class Directory(DirectoryBasicOperation, DirectoryTree):
504
587
  return self.list_structure("__pycache__", ".pyc")
505
588
 
506
589
 
590
+ class Directory(
591
+ DirectoryTreeMixin,
592
+ DirectoryOrganizerMixin,
593
+ DirectoryArchiverMixin,
594
+ DirectoryBasicOperationMixin,
595
+ DirectoryInfoMixin,
596
+ ):
597
+ """
598
+ Some shortcuts for directory
599
+
600
+ Parameters
601
+ ----------
602
+ source_path : str | Path
603
+ Source folder
604
+
605
+ create_if_not_exist : bool
606
+ Create directory when not exist,
607
+ by default ``False``
608
+
609
+
610
+ Example:
611
+ --------
612
+ >>> # For a list of method
613
+ >>> Directory.SUBCLASS_METHODS
614
+ """
615
+
616
+ pass
617
+
618
+
507
619
  # Class - SaveFileAs
508
620
  # ---------------------------------------------------------------------------
509
621
  class SaveFileAs:
510
- """File as multiple file type"""
622
+ """
623
+ File as multiple file type
624
+ """
511
625
 
512
626
  def __init__(self, data: Any, *, encoding: str | None = "utf-8") -> None:
513
627
  """
@@ -552,9 +666,3 @@ class SaveFileAs:
552
666
  # from absfuyu.util.json_method import JsonFile
553
667
  # temp = JsonFile(path, sort_keys=False)
554
668
  # temp.save_json()
555
-
556
-
557
- # Dev and Test new feature before get added to the main class
558
- # ---------------------------------------------------------------------------
559
- class _NewDirFeature(Directory):
560
- pass