absfuyu 3.2.0__py3-none-any.whl → 3.4.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.
- absfuyu/__init__.py +3 -10
- absfuyu/__main__.py +5 -250
- absfuyu/cli/__init__.py +51 -0
- absfuyu/cli/color.py +24 -0
- absfuyu/cli/config_group.py +56 -0
- absfuyu/cli/do_group.py +98 -0
- absfuyu/cli/game_group.py +109 -0
- absfuyu/config/__init__.py +55 -94
- absfuyu/config/config.json +0 -7
- absfuyu/core.py +5 -66
- absfuyu/everything.py +7 -9
- absfuyu/extensions/beautiful.py +30 -23
- absfuyu/extensions/dev/__init__.py +11 -8
- absfuyu/extensions/dev/password_hash.py +4 -2
- absfuyu/extensions/dev/passwordlib.py +7 -5
- absfuyu/extensions/dev/project_starter.py +4 -2
- absfuyu/extensions/dev/shutdownizer.py +148 -0
- absfuyu/extensions/extra/__init__.py +1 -2
- absfuyu/extensions/extra/data_analysis.py +110 -58
- absfuyu/fun/WGS.py +50 -26
- absfuyu/fun/__init__.py +6 -7
- absfuyu/fun/tarot.py +1 -1
- absfuyu/game/__init__.py +75 -81
- absfuyu/game/game_stat.py +36 -0
- absfuyu/game/sudoku.py +41 -48
- absfuyu/game/tictactoe.py +303 -548
- absfuyu/game/wordle.py +56 -47
- absfuyu/general/__init__.py +17 -7
- absfuyu/general/content.py +16 -15
- absfuyu/general/data_extension.py +314 -109
- absfuyu/general/generator.py +67 -67
- absfuyu/general/human.py +148 -78
- absfuyu/logger.py +94 -68
- absfuyu/pkg_data/__init__.py +29 -25
- absfuyu/py.typed +0 -0
- absfuyu/sort.py +61 -47
- absfuyu/tools/__init__.py +0 -1
- absfuyu/tools/converter.py +80 -62
- absfuyu/tools/keygen.py +62 -67
- absfuyu/tools/obfuscator.py +57 -53
- absfuyu/tools/stats.py +24 -24
- absfuyu/tools/web.py +10 -9
- absfuyu/util/__init__.py +38 -40
- absfuyu/util/api.py +53 -43
- absfuyu/util/json_method.py +25 -27
- absfuyu/util/lunar.py +20 -24
- absfuyu/util/path.py +362 -241
- absfuyu/util/performance.py +36 -98
- absfuyu/util/pkl.py +8 -8
- absfuyu/util/zipped.py +17 -19
- absfuyu/version.py +137 -148
- absfuyu-3.4.0.dist-info/METADATA +124 -0
- absfuyu-3.4.0.dist-info/RECORD +59 -0
- {absfuyu-3.2.0.dist-info → absfuyu-3.4.0.dist-info}/WHEEL +1 -2
- {absfuyu-3.2.0.dist-info → absfuyu-3.4.0.dist-info}/entry_points.txt +1 -0
- {absfuyu-3.2.0.dist-info → absfuyu-3.4.0.dist-info/licenses}/LICENSE +1 -1
- absfuyu/extensions/dev/pkglib.py +0 -98
- absfuyu/game/tictactoe2.py +0 -318
- absfuyu-3.2.0.dist-info/METADATA +0 -216
- absfuyu-3.2.0.dist-info/RECORD +0 -55
- absfuyu-3.2.0.dist-info/top_level.txt +0 -1
absfuyu/util/path.py
CHANGED
|
@@ -1,11 +1,10 @@
|
|
|
1
|
-
# -*- coding: utf-8 -*-
|
|
2
1
|
"""
|
|
3
2
|
Absfuyu: Path
|
|
4
3
|
-------------
|
|
5
4
|
Path related
|
|
6
5
|
|
|
7
|
-
Version: 1.5
|
|
8
|
-
Date updated:
|
|
6
|
+
Version: 1.6.5
|
|
7
|
+
Date updated: 10/04/2024 (dd/mm/yyyy)
|
|
9
8
|
|
|
10
9
|
Feature:
|
|
11
10
|
--------
|
|
@@ -13,138 +12,152 @@ Feature:
|
|
|
13
12
|
- SaveFileAs
|
|
14
13
|
"""
|
|
15
14
|
|
|
16
|
-
|
|
17
15
|
# Module level
|
|
18
16
|
###########################################################################
|
|
19
17
|
__all__ = [
|
|
20
|
-
#
|
|
18
|
+
# Main
|
|
21
19
|
"Directory",
|
|
22
|
-
"SaveFileAs"
|
|
20
|
+
"SaveFileAs",
|
|
21
|
+
# Support
|
|
22
|
+
"FileOrFolderWithModificationTime",
|
|
23
|
+
"DirectoryInfo",
|
|
23
24
|
]
|
|
24
25
|
|
|
25
26
|
|
|
26
27
|
# Library
|
|
27
28
|
###########################################################################
|
|
28
|
-
from datetime import datetime
|
|
29
|
-
from functools import partial
|
|
30
29
|
import os
|
|
31
|
-
from pathlib import Path
|
|
32
30
|
import re
|
|
33
31
|
import shutil
|
|
34
|
-
from
|
|
32
|
+
from datetime import datetime
|
|
33
|
+
from functools import partial
|
|
34
|
+
from pathlib import Path
|
|
35
|
+
from typing import Any, List, Literal, NamedTuple, Optional, Union
|
|
36
|
+
|
|
37
|
+
from deprecated.sphinx import versionadded
|
|
35
38
|
|
|
36
|
-
from absfuyu.logger import logger
|
|
39
|
+
from absfuyu.logger import LogLevel, logger
|
|
37
40
|
|
|
38
41
|
|
|
39
|
-
#
|
|
42
|
+
# Support Class
|
|
40
43
|
###########################################################################
|
|
41
|
-
|
|
44
|
+
@versionadded(version="3.3.0")
|
|
45
|
+
class FileOrFolderWithModificationTime(NamedTuple):
|
|
42
46
|
"""
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
try:
|
|
48
|
-
return os.path.abspath(os.path.dirname(__file__))
|
|
49
|
-
except:
|
|
50
|
-
return os.getcwd()
|
|
51
|
-
|
|
52
|
-
# return os.path.abspath(os.path.dirname(__file__))
|
|
53
|
-
|
|
54
|
-
def location_wrap(file_location: str): # Depreciated
|
|
55
|
-
"""
|
|
56
|
-
This function fix some `current working directory` error and return `abspath`
|
|
57
|
-
"""
|
|
58
|
-
assert isinstance(file_location, str), "Must be a string"
|
|
59
|
-
try:
|
|
60
|
-
here = here_location()
|
|
61
|
-
except:
|
|
62
|
-
here = ""
|
|
63
|
-
return os.path.join(here, file_location)
|
|
64
|
-
|
|
65
|
-
def get_all_file_path(folder: str, *file_type: str) -> list: # Depreciated
|
|
66
|
-
"""
|
|
67
|
-
Return a list of tuple: (path to choosen file type, filename)
|
|
68
|
-
|
|
69
|
-
- ``folder``: Folder path to search in
|
|
70
|
-
- ``file_type``: File type/extension without the ``"."`` symbol.
|
|
71
|
-
|
|
72
|
-
Support multiple file type (separate with ``","`` (coma))
|
|
73
|
-
(Example: ``jpg``, ``png``, ``npy``)
|
|
47
|
+
File or Folder with modification time
|
|
48
|
+
|
|
49
|
+
:param path: Original path
|
|
50
|
+
:param modification_time: Modification time
|
|
74
51
|
"""
|
|
75
|
-
# Check file type
|
|
76
|
-
# If no `file_type` entered then proceed to print available file type
|
|
77
|
-
if len(file_type) < 1:
|
|
78
|
-
available_file_type = []
|
|
79
|
-
for _, _, files in os.walk(folder):
|
|
80
|
-
for file in files:
|
|
81
|
-
temp = re.search(r"\b.*[.](\w+$)\b", file)
|
|
82
|
-
if temp is not None:
|
|
83
|
-
available_file_type.append(temp[1])
|
|
84
|
-
# print(f"Available file type: {set(available_file_type)}")
|
|
85
|
-
# return list(set(available_file_type))
|
|
86
|
-
# return None
|
|
87
|
-
raise ValueError(f"Available file type: {set(available_file_type)}")
|
|
88
|
-
|
|
89
|
-
# Generate regex pattern
|
|
90
|
-
temp_pattern = "|".join(f"[.]{x}" for x in file_type)
|
|
91
|
-
pattern = f"\\b^([\w ]+)({temp_pattern}$)\\b"
|
|
92
|
-
# print("Search pattern: ", pattern)
|
|
93
|
-
|
|
94
|
-
# Iter through each folder to find file
|
|
95
|
-
file_location = []
|
|
96
|
-
# for root, dirs, files in os.walk(folder):
|
|
97
|
-
for root, _, files in os.walk(folder):
|
|
98
|
-
for file in files:
|
|
99
|
-
result = re.search(pattern, file)
|
|
100
|
-
if result is not None:
|
|
101
|
-
file_location.append((os.path.join(root, file), result[1]))
|
|
102
|
-
return file_location
|
|
103
|
-
|
|
104
|
-
def here_sniplet():
|
|
105
|
-
"""Return current file location code"""
|
|
106
|
-
snip = """\
|
|
107
|
-
import os
|
|
108
|
-
here = os.path.abspath(os.path.dirname(__file__))
|
|
109
52
|
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
53
|
+
path: Path
|
|
54
|
+
modification_time: datetime
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
@versionadded(version="3.3.0")
|
|
58
|
+
class DirectoryInfo(NamedTuple):
|
|
59
|
+
"""Information of a directory"""
|
|
60
|
+
|
|
61
|
+
files: int
|
|
62
|
+
folders: int
|
|
63
|
+
creation_time: datetime
|
|
64
|
+
modification_time: datetime
|
|
114
65
|
|
|
115
66
|
|
|
116
|
-
# Class
|
|
67
|
+
# Class - Directory | version 3.4.0: Remake Directory into modular class
|
|
117
68
|
###########################################################################
|
|
118
|
-
class
|
|
119
|
-
"""
|
|
120
|
-
Some shortcuts for directory
|
|
121
|
-
|
|
122
|
-
- list_structure
|
|
123
|
-
- delete, rename, copy, move
|
|
124
|
-
- zip
|
|
125
|
-
"""
|
|
69
|
+
class DirectoryBase:
|
|
126
70
|
def __init__(
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
71
|
+
self,
|
|
72
|
+
source_path: Union[str, Path],
|
|
73
|
+
create_if_not_exist: bool = False,
|
|
74
|
+
) -> None:
|
|
131
75
|
"""
|
|
132
|
-
|
|
133
|
-
|
|
76
|
+
Parameters
|
|
77
|
+
----------
|
|
78
|
+
source_path : str | Path
|
|
79
|
+
Source folder
|
|
80
|
+
|
|
81
|
+
create_if_not_exist : bool
|
|
82
|
+
Create directory when not exist
|
|
83
|
+
(Default: ``False``)
|
|
134
84
|
"""
|
|
135
|
-
self.source_path = Path(source_path)
|
|
85
|
+
self.source_path = Path(source_path)
|
|
136
86
|
if create_if_not_exist:
|
|
137
|
-
self.source_path.
|
|
87
|
+
if not self.source_path.exists():
|
|
88
|
+
self.source_path.mkdir(exist_ok=True, parents=True)
|
|
89
|
+
|
|
138
90
|
def __str__(self) -> str:
|
|
139
|
-
return
|
|
91
|
+
return self.source_path.__str__()
|
|
92
|
+
|
|
140
93
|
def __repr__(self) -> str:
|
|
141
|
-
return self.
|
|
142
|
-
|
|
94
|
+
return f"{self.__class__.__name__}({self.source_path})"
|
|
95
|
+
|
|
96
|
+
def __format__(self, __format_spec: str) -> str:
|
|
97
|
+
"""
|
|
98
|
+
Change format of an object.
|
|
99
|
+
Avaiable option: ``info``
|
|
100
|
+
|
|
101
|
+
Usage
|
|
102
|
+
-----
|
|
103
|
+
>>> print(f"{<object>:<format_spec>}")
|
|
104
|
+
>>> print(<object>.__format__(<format_spec>))
|
|
105
|
+
>>> print(format(<object>, <format_spec>))
|
|
106
|
+
"""
|
|
107
|
+
# Show quick info
|
|
108
|
+
if __format_spec.lower().startswith("info"):
|
|
109
|
+
return self.quick_info().__repr__()
|
|
110
|
+
|
|
111
|
+
# No format spec
|
|
112
|
+
return self.__repr__()
|
|
113
|
+
|
|
114
|
+
# Everything
|
|
115
|
+
@property
|
|
116
|
+
@versionadded(version="3.3.0")
|
|
117
|
+
def everything(self) -> List[Path]:
|
|
118
|
+
"""
|
|
119
|
+
Every folders and files in this Directory
|
|
120
|
+
"""
|
|
121
|
+
return list(x for x in self.source_path.glob("**/*"))
|
|
122
|
+
|
|
123
|
+
@versionadded(version="3.3.0")
|
|
124
|
+
def _every_folder(self) -> List[Path]:
|
|
125
|
+
"""
|
|
126
|
+
Every folders in this Directory
|
|
127
|
+
"""
|
|
128
|
+
return list(x for x in self.source_path.glob("**/*") if x.is_dir())
|
|
129
|
+
|
|
130
|
+
@versionadded(version="3.3.0")
|
|
131
|
+
def _every_file(self) -> List[Path]:
|
|
132
|
+
"""
|
|
133
|
+
Every folders in this Directory
|
|
134
|
+
"""
|
|
135
|
+
return list(x for x in self.source_path.glob("**/*") if x.is_file())
|
|
136
|
+
|
|
137
|
+
# Quick information
|
|
138
|
+
@versionadded(version="3.3.0")
|
|
139
|
+
def quick_info(self) -> DirectoryInfo:
|
|
140
|
+
"""
|
|
141
|
+
Quick information about this Directory
|
|
142
|
+
|
|
143
|
+
:rtype: DirectoryInfo
|
|
144
|
+
"""
|
|
145
|
+
source_stat: os.stat_result = self.source_path.stat()
|
|
146
|
+
out = DirectoryInfo(
|
|
147
|
+
files=len(self._every_file()),
|
|
148
|
+
folders=len(self._every_folder()),
|
|
149
|
+
creation_time=datetime.fromtimestamp(source_stat.st_ctime),
|
|
150
|
+
modification_time=datetime.fromtimestamp(source_stat.st_mtime),
|
|
151
|
+
)
|
|
152
|
+
return out
|
|
153
|
+
|
|
154
|
+
|
|
155
|
+
class DirectoryBasicOperation(DirectoryBase):
|
|
143
156
|
# Rename
|
|
144
157
|
def rename(self, new_name: str) -> None:
|
|
145
158
|
"""
|
|
146
159
|
Rename directory
|
|
147
|
-
|
|
160
|
+
|
|
148
161
|
Parameters
|
|
149
162
|
----------
|
|
150
163
|
new_name : str
|
|
@@ -152,13 +165,12 @@ class Directory:
|
|
|
152
165
|
"""
|
|
153
166
|
try:
|
|
154
167
|
logger.debug(f"Renaming to {new_name}...")
|
|
155
|
-
self.source_path.rename(
|
|
156
|
-
self.source_path.parent.joinpath(new_name)
|
|
157
|
-
)
|
|
168
|
+
self.source_path.rename(self.source_path.with_name(new_name))
|
|
158
169
|
logger.debug(f"Renaming to {new_name}...DONE")
|
|
159
170
|
except Exception as e:
|
|
160
171
|
logger.error(e)
|
|
161
|
-
|
|
172
|
+
# return self.source_path
|
|
173
|
+
|
|
162
174
|
# Copy
|
|
163
175
|
def copy(self, dst: Path) -> None:
|
|
164
176
|
"""
|
|
@@ -173,14 +185,14 @@ class Directory:
|
|
|
173
185
|
try:
|
|
174
186
|
try:
|
|
175
187
|
shutil.copytree(self.source_path, Path(dst), dirs_exist_ok=True)
|
|
176
|
-
except:
|
|
188
|
+
except Exception:
|
|
177
189
|
shutil.copytree(self.source_path, Path(dst))
|
|
178
190
|
logger.debug(f"Copying to {dst}...DONE")
|
|
179
191
|
except Exception as e:
|
|
180
192
|
logger.error(e)
|
|
181
|
-
|
|
193
|
+
|
|
182
194
|
# Move
|
|
183
|
-
def move(self, dst: Path) -> None:
|
|
195
|
+
def move(self, dst: Path, content_only: bool = False) -> None:
|
|
184
196
|
"""
|
|
185
197
|
Move entire directory
|
|
186
198
|
|
|
@@ -188,99 +200,40 @@ class Directory:
|
|
|
188
200
|
----------
|
|
189
201
|
dst : Path
|
|
190
202
|
Destination
|
|
203
|
+
|
|
204
|
+
content_only : bool
|
|
205
|
+
Only move content inside the folder (Default: ``False``; Move entire folder)
|
|
191
206
|
"""
|
|
192
207
|
try:
|
|
193
208
|
logger.debug(f"Moving to {dst}...")
|
|
194
|
-
|
|
209
|
+
if content_only:
|
|
210
|
+
for x in self.source_path.iterdir():
|
|
211
|
+
shutil.move(x, Path(dst))
|
|
212
|
+
else:
|
|
213
|
+
shutil.move(self.source_path, Path(dst))
|
|
195
214
|
logger.debug(f"Moving to {dst}...DONE")
|
|
196
|
-
except Exception as e:
|
|
197
|
-
logger.error(e)
|
|
198
|
-
|
|
199
|
-
# Directory structure
|
|
200
|
-
def _list_dir(self, *ignore: str) -> List[Path]:
|
|
201
|
-
"""List all directories and files"""
|
|
202
|
-
logger.debug(f"Base folder: {self.source_path.name}")
|
|
203
|
-
|
|
204
|
-
temp = self.source_path.glob("**/*")
|
|
205
|
-
# No ignore rules
|
|
206
|
-
if len(ignore) == 0:
|
|
207
|
-
return [x.relative_to(self.source_path) for x in temp]
|
|
208
215
|
|
|
209
|
-
#
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
216
|
+
except shutil.Error as e: # File already exists
|
|
217
|
+
logger.error(e)
|
|
218
|
+
logger.debug("Overwriting file...")
|
|
219
|
+
if content_only:
|
|
220
|
+
for x in self.source_path.iterdir():
|
|
221
|
+
shutil.move(x, Path(dst).joinpath(x.name))
|
|
222
|
+
else:
|
|
223
|
+
shutil.move(self.source_path, Path(dst))
|
|
224
|
+
logger.debug("Overwriting file...DONE")
|
|
213
225
|
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
list_of_paths: List[Path],
|
|
217
|
-
*,
|
|
218
|
-
tab_symbol: str = None,
|
|
219
|
-
sub_dir_symbol: str = None
|
|
220
|
-
) -> List[str]:
|
|
226
|
+
# Delete folder
|
|
227
|
+
def _mtime_folder(self) -> List[FileOrFolderWithModificationTime]:
|
|
221
228
|
"""
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
list_of_paths: List of paths
|
|
225
|
-
tab_symbol: Tab symbol
|
|
226
|
-
sub_dir_symbol: Sub-directory symbol
|
|
229
|
+
Get modification time of file/folder (first level only)
|
|
227
230
|
"""
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
sub_dir_symbol = "|-- "
|
|
232
|
-
|
|
233
|
-
temp = sorted([str(x).split("/") for x in list_of_paths]) # Linux
|
|
234
|
-
if max(map(len, temp)) == 1:
|
|
235
|
-
temp = sorted([str(x).split("\\") for x in list_of_paths]) # Windows
|
|
236
|
-
|
|
237
|
-
return [f"{tab_symbol*(len(x)-1)}{sub_dir_symbol}{x[-1]}" for x in temp]
|
|
238
|
-
|
|
239
|
-
def list_structure(self, *ignore: str) -> str:
|
|
240
|
-
"""
|
|
241
|
-
List folder structure
|
|
242
|
-
|
|
243
|
-
Parameters
|
|
244
|
-
----------
|
|
245
|
-
ignore : str
|
|
246
|
-
Tuple contains patterns to ignore
|
|
247
|
-
|
|
248
|
-
Returns
|
|
249
|
-
-------
|
|
250
|
-
str
|
|
251
|
-
Directory structure
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
Example (For typical python library):
|
|
255
|
-
-------------------------------------
|
|
256
|
-
>>> test = Directory(<source path>)
|
|
257
|
-
>>> test.list_structure(
|
|
258
|
-
"__pycache__",
|
|
259
|
-
".pyc",
|
|
260
|
-
"__init__",
|
|
261
|
-
"__main__",
|
|
231
|
+
return [
|
|
232
|
+
FileOrFolderWithModificationTime(
|
|
233
|
+
path, datetime.fromtimestamp(path.stat().st_mtime)
|
|
262
234
|
)
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
temp = self._list_dir(*ignore)
|
|
266
|
-
out = self._separate_dir_and_files(temp)
|
|
267
|
-
return "\n".join(out)
|
|
268
|
-
|
|
269
|
-
def list_structure_pkg(self) -> str:
|
|
270
|
-
"""
|
|
271
|
-
List folder structure of a typical python package
|
|
272
|
-
|
|
273
|
-
Returns
|
|
274
|
-
-------
|
|
275
|
-
str
|
|
276
|
-
Directory structure
|
|
277
|
-
"""
|
|
278
|
-
return self.list_structure("__pycache__", ".pyc")
|
|
279
|
-
|
|
280
|
-
# Delete folder
|
|
281
|
-
def _mtime_folder(self) -> List[Tuple[Path, datetime]]:
|
|
282
|
-
"""Get modification time of file/folder"""
|
|
283
|
-
return [(x, datetime.fromtimestamp(x.stat().st_mtime)) for x in self.source_path.glob("*")]
|
|
235
|
+
for path in self.source_path.glob("*")
|
|
236
|
+
]
|
|
284
237
|
|
|
285
238
|
@staticmethod
|
|
286
239
|
def _delete_files(list_of_files: List[Path]) -> None:
|
|
@@ -295,39 +248,34 @@ class Directory:
|
|
|
295
248
|
shutil.rmtree(x)
|
|
296
249
|
else:
|
|
297
250
|
x.unlink()
|
|
298
|
-
logger.debug(f"Removing {x}...
|
|
299
|
-
except:
|
|
251
|
+
logger.debug(f"Removing {x}...SUCCEED")
|
|
252
|
+
except Exception:
|
|
300
253
|
logger.error(f"Removing {x}...FAILED")
|
|
301
254
|
|
|
302
255
|
@staticmethod
|
|
303
256
|
def _date_filter(
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
257
|
+
value: FileOrFolderWithModificationTime,
|
|
258
|
+
period: Literal["Y", "M", "D"] = "Y",
|
|
259
|
+
) -> bool:
|
|
307
260
|
"""
|
|
308
261
|
Filter out file with current Year|Month|Day
|
|
309
262
|
"""
|
|
310
|
-
period = period.upper().strip()
|
|
311
263
|
data = {
|
|
312
|
-
"Y": value
|
|
313
|
-
"M": value
|
|
314
|
-
"D": value
|
|
264
|
+
"Y": value.modification_time.year,
|
|
265
|
+
"M": value.modification_time.month,
|
|
266
|
+
"D": value.modification_time.day,
|
|
315
267
|
}
|
|
316
268
|
now = datetime.now()
|
|
317
|
-
ntime = {
|
|
318
|
-
"Y": now.year,
|
|
319
|
-
"M": now.month,
|
|
320
|
-
"D": now.day
|
|
321
|
-
}
|
|
269
|
+
ntime = {"Y": now.year, "M": now.month, "D": now.day}
|
|
322
270
|
return data[period] != ntime[period]
|
|
323
271
|
|
|
324
272
|
def delete(
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
273
|
+
self,
|
|
274
|
+
entire: bool = False,
|
|
275
|
+
*,
|
|
276
|
+
based_on_time: bool = False,
|
|
277
|
+
keep: Literal["Y", "M", "D"] = "Y",
|
|
278
|
+
) -> None:
|
|
331
279
|
"""
|
|
332
280
|
Deletes everything
|
|
333
281
|
|
|
@@ -337,36 +285,41 @@ class Directory:
|
|
|
337
285
|
| ``True``: Deletes the folder itself
|
|
338
286
|
| ``False``: Deletes content inside only
|
|
339
287
|
| (Default: ``False``)
|
|
340
|
-
|
|
288
|
+
|
|
341
289
|
based_on_time : bool
|
|
342
290
|
| ``True``: Deletes everything except ``keep`` period
|
|
343
291
|
| ``False``: Works normal
|
|
344
292
|
| (Default: ``False``)
|
|
345
|
-
|
|
293
|
+
|
|
346
294
|
keep : Literal["Y", "M", "D"]
|
|
347
295
|
Delete all file except current ``Year`` | ``Month`` | ``Day``
|
|
348
296
|
"""
|
|
349
297
|
try:
|
|
350
298
|
logger.info(f"Removing {self.source_path}...")
|
|
351
|
-
|
|
299
|
+
|
|
352
300
|
if entire:
|
|
353
301
|
shutil.rmtree(self.source_path)
|
|
354
302
|
else:
|
|
355
303
|
if based_on_time:
|
|
356
304
|
filter_func = partial(self._date_filter, period=keep)
|
|
357
|
-
self._delete_files([x[0] for x in filter(filter_func, self._mtime_folder())])
|
|
305
|
+
# self._delete_files([x[0] for x in filter(filter_func, self._mtime_folder())])
|
|
306
|
+
self._delete_files(
|
|
307
|
+
[x.path for x in filter(filter_func, self._mtime_folder())]
|
|
308
|
+
)
|
|
358
309
|
else:
|
|
359
|
-
self._delete_files(
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
logger.
|
|
310
|
+
self._delete_files(
|
|
311
|
+
map(lambda x: x.path, self._mtime_folder()) # type: ignore
|
|
312
|
+
)
|
|
313
|
+
|
|
314
|
+
logger.info(f"Removing {self.source_path}...SUCCEED")
|
|
315
|
+
except Exception as e:
|
|
316
|
+
logger.error(f"Removing {self.source_path}...FAILED\n{e}")
|
|
364
317
|
|
|
365
318
|
# Zip
|
|
366
|
-
def compress(self, *, format: str = "zip") -> None:
|
|
319
|
+
def compress(self, *, format: str = "zip") -> Union[Path, None]:
|
|
367
320
|
"""
|
|
368
321
|
Compress the directory (Default: Create ``.zip`` file)
|
|
369
|
-
|
|
322
|
+
|
|
370
323
|
Parameters
|
|
371
324
|
----------
|
|
372
325
|
format : Literal["zip", "tar", "gztar", "bztar", "xztar"]
|
|
@@ -375,38 +328,201 @@ class Directory:
|
|
|
375
328
|
- ``gztar``: gzip'ed tar-file (if the ``zlib`` module is available).
|
|
376
329
|
- ``bztar``: bzip2'ed tar-file (if the ``bz2`` module is available).
|
|
377
330
|
- ``xztar``: xz'ed tar-file (if the ``lzma`` module is available).
|
|
331
|
+
|
|
332
|
+
Returns
|
|
333
|
+
-------
|
|
334
|
+
Path
|
|
335
|
+
Compressed path
|
|
336
|
+
None
|
|
337
|
+
When fail to compress
|
|
378
338
|
"""
|
|
379
339
|
logger.debug(f"Zipping {self.source_path}...")
|
|
380
340
|
try:
|
|
381
|
-
zip_name = self.source_path.parent.joinpath(self.source_path.name)
|
|
382
|
-
shutil.make_archive(zip_name, format=format, root_dir=self.source_path)
|
|
341
|
+
# zip_name = self.source_path.parent.joinpath(self.source_path.name).__str__()
|
|
342
|
+
# shutil.make_archive(zip_name, format=format, root_dir=self.source_path)
|
|
343
|
+
zip_path = shutil.make_archive(
|
|
344
|
+
self.source_path.__str__(), format=format, root_dir=self.source_path
|
|
345
|
+
)
|
|
383
346
|
logger.debug(f"Zipping {self.source_path}...DONE")
|
|
384
|
-
|
|
385
|
-
|
|
347
|
+
logger.debug(f"Path: {zip_path}")
|
|
348
|
+
return Path(zip_path)
|
|
349
|
+
except Exception as e:
|
|
350
|
+
logger.error(f"Zipping {self.source_path}...FAILED\n{e}")
|
|
351
|
+
return None
|
|
352
|
+
|
|
353
|
+
|
|
354
|
+
class DirectoryTree(DirectoryBase):
|
|
355
|
+
pass
|
|
356
|
+
|
|
357
|
+
|
|
358
|
+
class Directory(DirectoryBasicOperation, DirectoryTree):
|
|
359
|
+
"""
|
|
360
|
+
Some shortcuts for directory
|
|
361
|
+
|
|
362
|
+
- list_structure
|
|
363
|
+
- delete, rename, copy, move
|
|
364
|
+
- zip
|
|
365
|
+
- quick_info
|
|
366
|
+
"""
|
|
367
|
+
|
|
368
|
+
# Directory structure
|
|
369
|
+
def _list_dir(self, *ignore: str) -> List[Path]:
|
|
370
|
+
"""
|
|
371
|
+
List all directories and files
|
|
372
|
+
|
|
373
|
+
Parameters
|
|
374
|
+
----------
|
|
375
|
+
ignore : str
|
|
376
|
+
List of pattern to ignore. Example: "__pycache__", ".pyc"
|
|
377
|
+
"""
|
|
378
|
+
logger.debug(f"Base folder: {self.source_path.name}")
|
|
386
379
|
|
|
380
|
+
list_of_path = self.source_path.glob("**/*")
|
|
387
381
|
|
|
382
|
+
# No ignore rules
|
|
383
|
+
if len(ignore) == 0: # No ignore pattern
|
|
384
|
+
return [path.relative_to(self.source_path) for path in list_of_path]
|
|
385
|
+
|
|
386
|
+
# With ignore rules
|
|
387
|
+
# ignore_pattern = "|".join(ignore)
|
|
388
|
+
ignore_pattern = re.compile("|".join(ignore))
|
|
389
|
+
logger.debug(f"Ignore pattern: {ignore_pattern}")
|
|
390
|
+
return [
|
|
391
|
+
path.relative_to(self.source_path)
|
|
392
|
+
for path in list_of_path
|
|
393
|
+
if re.search(ignore_pattern, path.name) is None
|
|
394
|
+
]
|
|
395
|
+
|
|
396
|
+
@staticmethod
|
|
397
|
+
@versionadded(version="3.3.0")
|
|
398
|
+
def _split_dir(list_of_path: List[Path]) -> List[List[str]]:
|
|
399
|
+
"""
|
|
400
|
+
Split pathname by ``os.sep``
|
|
401
|
+
|
|
402
|
+
Parameters
|
|
403
|
+
----------
|
|
404
|
+
list_of_path : list[Path]
|
|
405
|
+
List of Path
|
|
406
|
+
|
|
407
|
+
Returns
|
|
408
|
+
-------
|
|
409
|
+
list[list[str]]
|
|
410
|
+
List of splitted dir
|
|
411
|
+
|
|
412
|
+
|
|
413
|
+
Example:
|
|
414
|
+
--------
|
|
415
|
+
>>> test = [Path(test_root/test_not_root), ...]
|
|
416
|
+
>>> Directory._split_dir(test)
|
|
417
|
+
[[test_root, test_not_root], [...]...]
|
|
418
|
+
"""
|
|
419
|
+
|
|
420
|
+
return sorted([str(path).split(os.sep) for path in list_of_path])
|
|
421
|
+
|
|
422
|
+
def _separate_dir_and_files(
|
|
423
|
+
self,
|
|
424
|
+
list_of_path: List[Path],
|
|
425
|
+
*,
|
|
426
|
+
tab_symbol: Optional[str] = None,
|
|
427
|
+
sub_dir_symbol: Optional[str] = None,
|
|
428
|
+
) -> List[str]:
|
|
429
|
+
"""
|
|
430
|
+
Separate dir and file and transform into folder structure
|
|
431
|
+
|
|
432
|
+
Parameters
|
|
433
|
+
----------
|
|
434
|
+
list_of_path : list[Path]
|
|
435
|
+
List of paths
|
|
436
|
+
|
|
437
|
+
tab_symbol : str | None
|
|
438
|
+
Tab symbol
|
|
439
|
+
(Default: ``"\\t"``)
|
|
440
|
+
|
|
441
|
+
sub_dir_symbol : str | None
|
|
442
|
+
Sub-directory symbol
|
|
443
|
+
(Default: ``"|-- "``)
|
|
444
|
+
|
|
445
|
+
Returns
|
|
446
|
+
-------
|
|
447
|
+
list[str]
|
|
448
|
+
Folder structure ready to print
|
|
449
|
+
"""
|
|
450
|
+
# Check for tab and sub-dir symbol
|
|
451
|
+
if not tab_symbol:
|
|
452
|
+
tab_symbol = "\t"
|
|
453
|
+
if not sub_dir_symbol:
|
|
454
|
+
sub_dir_symbol = "|-- "
|
|
455
|
+
|
|
456
|
+
temp: List[List[str]] = self._split_dir(list_of_path)
|
|
457
|
+
|
|
458
|
+
return [ # Returns n-tab space with sub-dir-symbol for the last item in x
|
|
459
|
+
f"{tab_symbol * (len(x) - 1)}{sub_dir_symbol}{x[-1]}" for x in temp
|
|
460
|
+
]
|
|
461
|
+
|
|
462
|
+
def list_structure(self, *ignore: str) -> str:
|
|
463
|
+
"""
|
|
464
|
+
List folder structure
|
|
465
|
+
|
|
466
|
+
Parameters
|
|
467
|
+
----------
|
|
468
|
+
ignore : str
|
|
469
|
+
Tuple contains patterns to ignore
|
|
470
|
+
|
|
471
|
+
Returns
|
|
472
|
+
-------
|
|
473
|
+
str
|
|
474
|
+
Directory structure
|
|
475
|
+
|
|
476
|
+
|
|
477
|
+
Example (For typical python library):
|
|
478
|
+
-------------------------------------
|
|
479
|
+
>>> test = Directory(<source path>)
|
|
480
|
+
>>> test.list_structure(
|
|
481
|
+
"__pycache__",
|
|
482
|
+
".pyc",
|
|
483
|
+
"__init__",
|
|
484
|
+
"__main__",
|
|
485
|
+
)
|
|
486
|
+
...
|
|
487
|
+
"""
|
|
488
|
+
temp: List[Path] = self._list_dir(*ignore)
|
|
489
|
+
out: List[str] = self._separate_dir_and_files(temp)
|
|
490
|
+
return "\n".join(out) # Join the list
|
|
491
|
+
|
|
492
|
+
def list_structure_pkg(self) -> str:
|
|
493
|
+
"""
|
|
494
|
+
List folder structure of a typical python package
|
|
495
|
+
|
|
496
|
+
Returns
|
|
497
|
+
-------
|
|
498
|
+
str
|
|
499
|
+
Directory structure
|
|
500
|
+
"""
|
|
501
|
+
return self.list_structure("__pycache__", ".pyc")
|
|
502
|
+
|
|
503
|
+
|
|
504
|
+
# Class - SaveFileAs
|
|
505
|
+
###########################################################################
|
|
388
506
|
class SaveFileAs:
|
|
389
507
|
"""File as multiple file type"""
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
data: Any,
|
|
393
|
-
*,
|
|
394
|
-
encoding: Union[str, None] = "utf-8"
|
|
395
|
-
) -> None:
|
|
508
|
+
|
|
509
|
+
def __init__(self, data: Any, *, encoding: Union[str, None] = "utf-8") -> None:
|
|
396
510
|
"""
|
|
397
511
|
:param encoding: Default: utf-8
|
|
398
512
|
"""
|
|
399
513
|
self.data = data
|
|
400
514
|
self.encoding = encoding
|
|
515
|
+
|
|
401
516
|
def __str__(self) -> str:
|
|
402
517
|
return f"{self.__class__.__name__}()"
|
|
518
|
+
|
|
403
519
|
def __repr__(self) -> str:
|
|
404
520
|
return self.__str__()
|
|
405
521
|
|
|
406
522
|
def to_txt(self, path: Union[str, Path]) -> None:
|
|
407
523
|
"""
|
|
408
524
|
Save as ``.txt`` file
|
|
409
|
-
|
|
525
|
+
|
|
410
526
|
Parameters
|
|
411
527
|
----------
|
|
412
528
|
path : Path
|
|
@@ -414,11 +530,11 @@ class SaveFileAs:
|
|
|
414
530
|
"""
|
|
415
531
|
with open(path, "w", encoding=self.encoding) as file:
|
|
416
532
|
file.writelines(self.data)
|
|
417
|
-
|
|
533
|
+
|
|
418
534
|
# def to_pickle(self, path: Union[str, Path]) -> None:
|
|
419
535
|
# """
|
|
420
536
|
# Save as .pickle file
|
|
421
|
-
|
|
537
|
+
|
|
422
538
|
# :param path: Save location
|
|
423
539
|
# """
|
|
424
540
|
# from absfuyu.util.pkl import Pickler
|
|
@@ -427,7 +543,7 @@ class SaveFileAs:
|
|
|
427
543
|
# def to_json(self, path: Union[str, Path]) -> None:
|
|
428
544
|
# """
|
|
429
545
|
# Save as .json file
|
|
430
|
-
|
|
546
|
+
|
|
431
547
|
# :param path: Save location
|
|
432
548
|
# """
|
|
433
549
|
# from absfuyu.util.json_method import JsonFile
|
|
@@ -435,8 +551,13 @@ class SaveFileAs:
|
|
|
435
551
|
# temp.save_json()
|
|
436
552
|
|
|
437
553
|
|
|
554
|
+
# Dev and Test new feature before get added to the main class
|
|
555
|
+
###########################################################################
|
|
556
|
+
class _NewDirFeature(Directory):
|
|
557
|
+
pass
|
|
558
|
+
|
|
559
|
+
|
|
438
560
|
# Run
|
|
439
561
|
###########################################################################
|
|
440
562
|
if __name__ == "__main__":
|
|
441
|
-
logger.setLevel(
|
|
442
|
-
|
|
563
|
+
logger.setLevel(LogLevel.DEBUG)
|