nicepython 0.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.
- nicepy/__init__.py +4 -0
- nicepy/logger.py +51 -0
- nicepy/nicepath/__init__.py +1 -0
- nicepy/nicepath/core.py +702 -0
- nicepy/nicepath/exceptios.py +28 -0
- nicepython-0.1.0.dist-info/METADATA +320 -0
- nicepython-0.1.0.dist-info/RECORD +10 -0
- nicepython-0.1.0.dist-info/WHEEL +5 -0
- nicepython-0.1.0.dist-info/licenses/license +9 -0
- nicepython-0.1.0.dist-info/top_level.txt +1 -0
nicepy/__init__.py
ADDED
nicepy/logger.py
ADDED
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
from functools import wraps
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
logger = logging.getLogger("nicepy")
|
|
6
|
+
|
|
7
|
+
if not logger.handlers:
|
|
8
|
+
handler = logging.StreamHandler()
|
|
9
|
+
formatter = logging.Formatter(
|
|
10
|
+
"(%(filename)s-%(lineno)d)[%(levelname)s] %(name)s | %(message)s"
|
|
11
|
+
)
|
|
12
|
+
handler.setFormatter(formatter)
|
|
13
|
+
logger.addHandler(handler)
|
|
14
|
+
|
|
15
|
+
logger.setLevel(30)
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
from functools import wraps
|
|
20
|
+
from nicepy.logger import logger
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def log_operation(func):
|
|
24
|
+
@wraps(func)
|
|
25
|
+
def wrapper(self ):
|
|
26
|
+
logger.debug(f"{func.__name__} started -> {self._path}")
|
|
27
|
+
try:
|
|
28
|
+
result = func(self)
|
|
29
|
+
logger.info(f"{func.__name__} success -> {self._path}")
|
|
30
|
+
return result
|
|
31
|
+
except Exception as e:
|
|
32
|
+
logger.exception(
|
|
33
|
+
f"{func.__name__} failed -> {self._path} | Reason: {e}"
|
|
34
|
+
)
|
|
35
|
+
raise
|
|
36
|
+
return wrapper
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
from nicepy.logger import logger
|
|
40
|
+
|
|
41
|
+
def log_start(instance, func_name):
|
|
42
|
+
path_info = getattr(instance, "_path", "unknown")
|
|
43
|
+
logger.debug(f"{func_name} started -> {path_info}")
|
|
44
|
+
|
|
45
|
+
def log_end(instance, func_name):
|
|
46
|
+
path_info = getattr(instance, "_path", "unknown")
|
|
47
|
+
logger.info(f"{func_name} finished -> {path_info}")
|
|
48
|
+
|
|
49
|
+
def log_error(instance, func_name, e):
|
|
50
|
+
path_info = getattr(instance, "_path", "unknown")
|
|
51
|
+
logger.exception(f"{func_name} failed -> {path_info} | Reason: {e}")
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
from .core import NicePath
|
nicepy/nicepath/core.py
ADDED
|
@@ -0,0 +1,702 @@
|
|
|
1
|
+
# nicepath/core.py
|
|
2
|
+
from __future__ import annotations
|
|
3
|
+
from dataclasses import dataclass
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
import inspect
|
|
6
|
+
import shutil
|
|
7
|
+
from datetime import datetime
|
|
8
|
+
import re
|
|
9
|
+
from nicepy.logger import logger
|
|
10
|
+
from nicepy.nicepath.exceptios import (
|
|
11
|
+
NicePathError,
|
|
12
|
+
NotADirectoryError,
|
|
13
|
+
NotAFileError,
|
|
14
|
+
PathNotFoundError,
|
|
15
|
+
WriteError,
|
|
16
|
+
DeleteError
|
|
17
|
+
)
|
|
18
|
+
|
|
19
|
+
@dataclass
|
|
20
|
+
class NicePath:
|
|
21
|
+
"""
|
|
22
|
+
đ NicePath: A powerful, user-friendly wrapper around pathlib.Path with logging, smart search, and tree view.
|
|
23
|
+
|
|
24
|
+
Hint (English):
|
|
25
|
+
- _input_path: str or Path - input path for initialization
|
|
26
|
+
- base_dir: Optional base path for relative paths
|
|
27
|
+
|
|
28
|
+
عاŮŮŮ
Ř§Ű ŮاعسŰ:
|
|
29
|
+
- _input_path: Ů
ŘłŰŘą ŮاŰŮ/ŮžŮŘ´Ů ŮŘąŮŘŻŰ (str Űا Path)
|
|
30
|
+
- base_dir: Ů
ŘłŰŘą ٞاŰŮ Ř¨ŘąŘ§Ű Ů
ŘłŰŘąŮŘ§Ű ŮسبŰ
|
|
31
|
+
"""
|
|
32
|
+
_input_path: str | Path
|
|
33
|
+
base_dir: Path | None = None
|
|
34
|
+
|
|
35
|
+
def __post_init__(self):
|
|
36
|
+
"""Initialize internal resolved path"""
|
|
37
|
+
if isinstance(self._input_path, Path):
|
|
38
|
+
self._path: Path = self._input_path.resolve()
|
|
39
|
+
return
|
|
40
|
+
|
|
41
|
+
input_path = Path(self._input_path)
|
|
42
|
+
if input_path.is_absolute():
|
|
43
|
+
self._path = input_path.resolve()
|
|
44
|
+
return
|
|
45
|
+
|
|
46
|
+
# relative path based on caller file
|
|
47
|
+
caller_file = inspect.stack()[1].filename
|
|
48
|
+
base = Path(caller_file).resolve().parent if self.base_dir is None else Path(self.base_dir).resolve()
|
|
49
|
+
self._path: Path = (base / input_path).resolve()
|
|
50
|
+
|
|
51
|
+
# -------------------------
|
|
52
|
+
# Logger helpers
|
|
53
|
+
# -------------------------
|
|
54
|
+
def _log_start(self, action: str):
|
|
55
|
+
logger.debug(f"{action} started -> {self._path}")
|
|
56
|
+
|
|
57
|
+
def _log_success(self, action: str):
|
|
58
|
+
logger.info(f"{action} success -> {self._path}")
|
|
59
|
+
|
|
60
|
+
def _log_error(self, action: str, e: Exception):
|
|
61
|
+
logger.exception(f"{action} failed -> {self._path} | Reason: {e}")
|
|
62
|
+
|
|
63
|
+
# -------------------------
|
|
64
|
+
# Info Properties
|
|
65
|
+
# -------------------------
|
|
66
|
+
@property
|
|
67
|
+
def path(self) -> Path:
|
|
68
|
+
"""Return the internal resolved Path object / بعگعداŮŘŻŮ Ů
ŘłŰŘą"""
|
|
69
|
+
return self._path
|
|
70
|
+
|
|
71
|
+
@property
|
|
72
|
+
def name(self) -> str:
|
|
73
|
+
"""File or folder name (with extension) / ŮاŮ
ŮاŰŮ Űا ŮžŮŘ´Ů"""
|
|
74
|
+
return self._path.name
|
|
75
|
+
|
|
76
|
+
@property
|
|
77
|
+
def stem(self) -> str:
|
|
78
|
+
"""File name without extension / ŮاŮ
ŮاŰ٠بدŮ٠ٞسŮŮŘŻ"""
|
|
79
|
+
return self._path.stem
|
|
80
|
+
|
|
81
|
+
@property
|
|
82
|
+
def suffix(self) -> str:
|
|
83
|
+
"""File extension / ٞسŮŮŘŻ ŮاŰŮ"""
|
|
84
|
+
return self._path.suffix
|
|
85
|
+
|
|
86
|
+
@property
|
|
87
|
+
def parent(self) -> NicePath:
|
|
88
|
+
"""Return parent folder as NicePath / ŮžŮŘ´Ů ŮاŮŘŻ ب٠ؾŮعت NicePath"""
|
|
89
|
+
return NicePath(self._path.parent)
|
|
90
|
+
|
|
91
|
+
@property
|
|
92
|
+
def exists(self) -> bool:
|
|
93
|
+
"""Check if path exists / Ř¨ŘąŘąŘłŰ ŮŘŹŮŘŻ Ů
ŘłŰŘą"""
|
|
94
|
+
return self._path.exists()
|
|
95
|
+
|
|
96
|
+
@property
|
|
97
|
+
def is_file(self) -> bool:
|
|
98
|
+
"""Check if path is a file / Ř¨ŘąŘąŘłŰ Ř§ŰŮÚŠŮ Ů
ŘłŰŘą ŮاŰ٠است"""
|
|
99
|
+
return self._path.is_file()
|
|
100
|
+
|
|
101
|
+
@property
|
|
102
|
+
def is_dir(self) -> bool:
|
|
103
|
+
"""Check if path is a directory / Ř¨ŘąŘąŘłŰ Ř§ŰŮÚŠŮ Ů
ŘłŰŘą ŮžŮش٠است"""
|
|
104
|
+
return self._path.is_dir()
|
|
105
|
+
|
|
106
|
+
@property
|
|
107
|
+
def size(self) -> int:
|
|
108
|
+
"""Return file size or total folder size in bytes / ŘŘŹŮ
ŮاŰŮ Űا ÚŠŮ ŮžŮŘ´Ů"""
|
|
109
|
+
if self.is_file:
|
|
110
|
+
return self._path.stat().st_size
|
|
111
|
+
return sum(p.stat().st_size for p in self._path.rglob("*") if p.is_file())
|
|
112
|
+
|
|
113
|
+
@property
|
|
114
|
+
def created_at(self) -> datetime:
|
|
115
|
+
"""Return creation datetime / زŮ
ا٠اŰ؏اد ŮاŰŮ Űا ŮžŮŘ´Ů"""
|
|
116
|
+
return datetime.fromtimestamp(self._path.stat().st_ctime)
|
|
117
|
+
|
|
118
|
+
@property
|
|
119
|
+
def modified_at(self) -> datetime:
|
|
120
|
+
"""Return last modification datetime / زŮ
ا٠آ؎عŰŮ ŮŰعاŰŘ´"""
|
|
121
|
+
return datetime.fromtimestamp(self._path.stat().st_mtime)
|
|
122
|
+
# -------------------------
|
|
123
|
+
# File Actions
|
|
124
|
+
# -------------------------
|
|
125
|
+
def read(self, encoding: str = "utf-8") -> str:
|
|
126
|
+
"""
|
|
127
|
+
Read file content / ŘŽŮاŮŘŻŮ Ů
ŘŘŞŮŘ§Ű ŮاŰŮ
|
|
128
|
+
|
|
129
|
+
Args:
|
|
130
|
+
encoding: File encoding / اŮÚŠŮŘŻŰŮÚŻ ŮاŰŮ
|
|
131
|
+
|
|
132
|
+
Returns:
|
|
133
|
+
str: file content / Ů
ŘŘŞŮŘ§Ű ŮاŰŮ
|
|
134
|
+
"""
|
|
135
|
+
self._log_start("read")
|
|
136
|
+
if not self.exists:
|
|
137
|
+
self._log_error("read", PathNotFoundError(f"{self._path} not found"))
|
|
138
|
+
raise PathNotFoundError(f"File not found: {self._path}")
|
|
139
|
+
|
|
140
|
+
if not self.is_file:
|
|
141
|
+
self._log_error("read", NotAFileError(f"{self._path} is not a file"))
|
|
142
|
+
raise NotAFileError(f"Cannot read because it is not a file: {self._path}")
|
|
143
|
+
|
|
144
|
+
try:
|
|
145
|
+
content = self._path.read_text(encoding=encoding)
|
|
146
|
+
self._log_success("read")
|
|
147
|
+
return content
|
|
148
|
+
except Exception as e:
|
|
149
|
+
self._log_error("read", e)
|
|
150
|
+
raise
|
|
151
|
+
|
|
152
|
+
def write(self, data: str, encoding: str = "utf-8") -> NicePath:
|
|
153
|
+
"""
|
|
154
|
+
Write text to file / ŮŮŘ´ŘŞŮ Ů
ŘŞŮ ŘŻŘą ŮاŰŮ
|
|
155
|
+
|
|
156
|
+
Returns:
|
|
157
|
+
self / ŘŽŮŘŻ Ř´ŰŘĄ Ř¨ŘąŘ§Ű Ř˛ŮŘŹŰع٠Ů
ŘŞŘŻŮا
|
|
158
|
+
"""
|
|
159
|
+
self._log_start("write")
|
|
160
|
+
try:
|
|
161
|
+
self._path.parent.mkdir(parents=True, exist_ok=True)
|
|
162
|
+
self._path.write_text(data, encoding=encoding)
|
|
163
|
+
self._log_success("write")
|
|
164
|
+
return self
|
|
165
|
+
except Exception as e:
|
|
166
|
+
self._log_error("write", e)
|
|
167
|
+
raise WriteError(f"Failed to write file: {self._path}") from e
|
|
168
|
+
|
|
169
|
+
def append(self, data: str, encoding: str = "utf-8") -> NicePath:
|
|
170
|
+
"""
|
|
171
|
+
Append text to file / ا؜اŮ٠ڊعد٠Ů
ت٠ب٠ŮاŰŮ
|
|
172
|
+
"""
|
|
173
|
+
self._log_start("append")
|
|
174
|
+
try:
|
|
175
|
+
self._path.parent.mkdir(parents=True, exist_ok=True)
|
|
176
|
+
with self._path.open("a", encoding=encoding) as f:
|
|
177
|
+
f.write(data)
|
|
178
|
+
self._log_success("append")
|
|
179
|
+
return self
|
|
180
|
+
except Exception as e:
|
|
181
|
+
self._log_error("append", e)
|
|
182
|
+
raise WriteError(f"Failed to append file: {self._path}") from e
|
|
183
|
+
|
|
184
|
+
def mkdir(self, parents: bool = True, exist_ok: bool = True) -> NicePath:
|
|
185
|
+
"""
|
|
186
|
+
Create directory / اŰ؏اد ŮžŮŘ´Ů
|
|
187
|
+
"""
|
|
188
|
+
self._log_start("mkdir")
|
|
189
|
+
try:
|
|
190
|
+
self._path.mkdir(parents=parents, exist_ok=exist_ok)
|
|
191
|
+
self._log_success("mkdir")
|
|
192
|
+
return self
|
|
193
|
+
except Exception as e:
|
|
194
|
+
self._log_error("mkdir", e)
|
|
195
|
+
raise NicePathError(f"Failed to create directory: {self._path}") from e
|
|
196
|
+
|
|
197
|
+
def delete(self) -> NicePath:
|
|
198
|
+
"""
|
|
199
|
+
Delete file or directory / Řذ٠ŮاŰŮ Űا ŮžŮŘ´Ů
|
|
200
|
+
"""
|
|
201
|
+
self._log_start("delete")
|
|
202
|
+
if not self.exists:
|
|
203
|
+
self._log_error("delete", PathNotFoundError(f"{self._path} not found"))
|
|
204
|
+
raise PathNotFoundError(f"Path not found: {self._path}")
|
|
205
|
+
|
|
206
|
+
try:
|
|
207
|
+
if self.is_file:
|
|
208
|
+
self._path.unlink()
|
|
209
|
+
elif self.is_dir:
|
|
210
|
+
shutil.rmtree(self._path)
|
|
211
|
+
self._log_success("delete")
|
|
212
|
+
return self
|
|
213
|
+
except Exception as e:
|
|
214
|
+
self._log_error("delete", e)
|
|
215
|
+
raise DeleteError(f"Failed to delete: {self._path}") from e
|
|
216
|
+
|
|
217
|
+
def copy_to(self, destination: NicePath) -> NicePath:
|
|
218
|
+
"""
|
|
219
|
+
Copy file or folder to destination / ÚŠŮžŰ ŮاŰŮ Űا ŮžŮŘ´Ů
|
|
220
|
+
"""
|
|
221
|
+
self._log_start("copy_to")
|
|
222
|
+
try:
|
|
223
|
+
dest = Path(destination._path)
|
|
224
|
+
if self.is_file:
|
|
225
|
+
shutil.copy2(self._path, dest)
|
|
226
|
+
else:
|
|
227
|
+
shutil.copytree(self._path, dest, dirs_exist_ok=True)
|
|
228
|
+
self._log_success("copy_to")
|
|
229
|
+
return NicePath(dest)
|
|
230
|
+
except Exception as e:
|
|
231
|
+
self._log_error("copy_to", e)
|
|
232
|
+
raise NicePathError(f"Failed to copy to {destination._path}") from e
|
|
233
|
+
|
|
234
|
+
def move_to(self, destination: NicePath) -> NicePath:
|
|
235
|
+
"""
|
|
236
|
+
Move file or folder to destination / اŮŘŞŮا٠ŮاŰŮ Űا ŮžŮŘ´Ů
|
|
237
|
+
"""
|
|
238
|
+
self._log_start("move_to")
|
|
239
|
+
try:
|
|
240
|
+
dest = shutil.move(self._path, destination._path)
|
|
241
|
+
self._log_success("move_to")
|
|
242
|
+
return NicePath(dest)
|
|
243
|
+
except Exception as e:
|
|
244
|
+
self._log_error("move_to", e)
|
|
245
|
+
raise NicePathError(f"Failed to move to {destination._path}") from e
|
|
246
|
+
|
|
247
|
+
# -------------------------
|
|
248
|
+
# Tree View
|
|
249
|
+
# -------------------------
|
|
250
|
+
def tree(
|
|
251
|
+
self,
|
|
252
|
+
recursive: bool = True,
|
|
253
|
+
ignore_hidden: bool = True,
|
|
254
|
+
ignore_venv: bool = True,
|
|
255
|
+
) -> str:
|
|
256
|
+
"""
|
|
257
|
+
EN:
|
|
258
|
+
Generate a visual tree representation of the directory structure.
|
|
259
|
+
|
|
260
|
+
FA:
|
|
261
|
+
ŘŞŮŮŰŘŻ ŮŮ
اŰŘ´ گعاŮŰÚŠŰ ŘŻŘąŘŽŘŞŰ Ř§Ř˛ سا؎تاع ŮاŰŮâŮا Ů ŮžŮŘ´ŮâŮا.
|
|
262
|
+
|
|
263
|
+
Parameters
|
|
264
|
+
----------
|
|
265
|
+
recursive : bool, default=True
|
|
266
|
+
EN: If True, traverses subdirectories recursively.
|
|
267
|
+
FA: اگع True Ř¨Ř§Ř´ŘŻŘ Ř˛ŰعٞŮŘ´ŮâŮا عا ŮŰز ب٠ؾŮعت Ř¨Ř§Ř˛ÚŻŘ´ŘŞŰ Ř¨ŘąŘąŘłŰ Ů
ŰâÚŠŮŘŻ.
|
|
268
|
+
|
|
269
|
+
ignore_hidden : bool, default=True
|
|
270
|
+
EN: If True, hidden files and folders (starting with ".") are ignored.
|
|
271
|
+
FA: اگع True Ř¨Ř§Ř´ŘŻŘ ŮاŰŮâŮا Ů ŮžŮŘ´ŮâŮŘ§Ű Ů
ŘŽŮŰ ŮادŰŘŻŮ ÚŻŘąŮŘŞŮ Ů
ŰâŘ´ŮŮŘŻ.
|
|
272
|
+
|
|
273
|
+
ignore_venv : bool, default=True
|
|
274
|
+
EN: If True, any folder named 'venv' and its entire subtree will be skipped.
|
|
275
|
+
FA: اگع True Ř¨Ř§Ř´ŘŻŘ ŮžŮŘ´Ů venv Ů ŘŞŮ
اŮ
زŰŘąŮ
ŘŹŮ
Ůؚ٠آ٠ŮادŰŘŻŮ ÚŻŘąŮŘŞŮ Ů
ŰâŘ´ŮŘŻ.
|
|
276
|
+
|
|
277
|
+
Returns
|
|
278
|
+
-------
|
|
279
|
+
str
|
|
280
|
+
EN: A formatted tree string.
|
|
281
|
+
FA: عشتŮâŘ§Ű Ř´Ř§Ů
٠سا؎تاع ŘŻŘąŘŽŘŞŰ Ů
ŘłŰŘą.
|
|
282
|
+
|
|
283
|
+
Notes
|
|
284
|
+
-----
|
|
285
|
+
EN:
|
|
286
|
+
- Safe by default (venv ignored)
|
|
287
|
+
- Does not raise error if path does not exist (returns empty string)
|
|
288
|
+
|
|
289
|
+
FA:
|
|
290
|
+
- ب٠ؾŮعت ŮžŰŘ´âŮع؜ اŰŮ
٠است (venv ŮادŰŘŻŮ ÚŻŘąŮŘŞŮ Ů
ŰâŘ´ŮŘŻ)
|
|
291
|
+
- اگع Ů
ŘłŰŘą ŮŘŹŮŘŻ Ůداشت٠باشد ؎ءا ŮŮ
ŰâŘŻŮŘŻ ٠عشت٠؎اŮŰ Ř¨ŘąŮ
ŰâگعداŮŘŻ
|
|
292
|
+
"""
|
|
293
|
+
|
|
294
|
+
self._log_start("tree")
|
|
295
|
+
|
|
296
|
+
if not self.exists:
|
|
297
|
+
logger.warning(f"Tree skipped (path not found) -> {self._path}")
|
|
298
|
+
return ""
|
|
299
|
+
|
|
300
|
+
try:
|
|
301
|
+
lines: list[str] = []
|
|
302
|
+
|
|
303
|
+
def build_tree(path: Path, prefix=""):
|
|
304
|
+
entries = list(path.iterdir())
|
|
305
|
+
|
|
306
|
+
# ---- Filters ----
|
|
307
|
+
filtered: list[Path] = []
|
|
308
|
+
for e in entries:
|
|
309
|
+
if ignore_hidden and e.name.startswith("."):
|
|
310
|
+
continue
|
|
311
|
+
if ignore_venv and e.is_dir() and e.name.lower() == "venv":
|
|
312
|
+
logger.debug(f"tree skipped venv -> {e}")
|
|
313
|
+
continue
|
|
314
|
+
filtered.append(e)
|
|
315
|
+
|
|
316
|
+
filtered.sort(key=lambda x: (x.is_file(), x.name.lower()))
|
|
317
|
+
|
|
318
|
+
for index, entry in enumerate(filtered):
|
|
319
|
+
connector = "âââ " if index == len(filtered) - 1 else "âââ "
|
|
320
|
+
lines.append(prefix + connector + entry.name)
|
|
321
|
+
|
|
322
|
+
if entry.is_dir() and recursive:
|
|
323
|
+
extension = " " if index == len(filtered) - 1 else "â "
|
|
324
|
+
build_tree(entry, prefix + extension)
|
|
325
|
+
|
|
326
|
+
lines.append(self._path.name)
|
|
327
|
+
|
|
328
|
+
if self.is_dir:
|
|
329
|
+
build_tree(self._path)
|
|
330
|
+
|
|
331
|
+
self._log_success("tree")
|
|
332
|
+
return "\n".join(lines)
|
|
333
|
+
|
|
334
|
+
except Exception as e:
|
|
335
|
+
self._log_error("tree", e)
|
|
336
|
+
return ""
|
|
337
|
+
|
|
338
|
+
# -------------------------
|
|
339
|
+
# Smart Search
|
|
340
|
+
# -------------------------
|
|
341
|
+
|
|
342
|
+
def search(
|
|
343
|
+
self,
|
|
344
|
+
name_contains: str | None = None,
|
|
345
|
+
suffix: str | None = None,
|
|
346
|
+
stem: str | None = None,
|
|
347
|
+
regex: str | None = None,
|
|
348
|
+
min_size: int | None = None,
|
|
349
|
+
max_size: int | None = None,
|
|
350
|
+
only_files: bool = False,
|
|
351
|
+
only_dirs: bool = False,
|
|
352
|
+
recursive: bool = True,
|
|
353
|
+
ignore_hidden: bool = True,
|
|
354
|
+
ignore_venv: bool = True,
|
|
355
|
+
) -> list["NicePath"]:
|
|
356
|
+
"""
|
|
357
|
+
EN:
|
|
358
|
+
Advanced file and directory search with multiple filters.
|
|
359
|
+
|
|
360
|
+
FA:
|
|
361
|
+
؏ست؏ŮŰ ŮžŰŘ´ŘąŮŘŞŮ ŮاŰŮ Ů ŮžŮش٠با ŮŰŮŘŞŘąŮŘ§Ű Ů
ŘŞŮŮŘš.
|
|
362
|
+
|
|
363
|
+
Parameters
|
|
364
|
+
----------
|
|
365
|
+
name_contains : str | None
|
|
366
|
+
EN: Return items whose name contains this substring (case-insensitive).
|
|
367
|
+
FA: ŮاŰŮâŮاŰŰ ÚŠŮ ŮاŮ
آŮâŮا شاŮ
٠اŰ٠عشت٠باشد.
|
|
368
|
+
|
|
369
|
+
suffix : str | None
|
|
370
|
+
EN: Filter by file extension (e.g. ".py").
|
|
371
|
+
FA: ŮŰŮŘŞŘą بع اساس ٞسŮŮŘŻ ŮاŰŮ (Ů
ŘŤŮا٠".py").
|
|
372
|
+
|
|
373
|
+
stem : str | None
|
|
374
|
+
EN: Match exact file name without extension.
|
|
375
|
+
FA: تءاب٠دŮŰŮ ŮاŮ
ŮاŰ٠بدŮ٠ٞسŮŮŘŻ.
|
|
376
|
+
|
|
377
|
+
regex : str | None
|
|
378
|
+
EN: Apply regex pattern to file name.
|
|
379
|
+
FA: اؚŮ
ا٠ؚباعت Ů
ŮظŮ
ŘąŮŰ ŮاŮ
ŮاŰŮ.
|
|
380
|
+
|
|
381
|
+
min_size : int | None
|
|
382
|
+
EN: Minimum file size in bytes.
|
|
383
|
+
FA: ŘداŮ٠اŮداز٠ŮاŰŮ (بع Řسب باŰŘŞ).
|
|
384
|
+
|
|
385
|
+
max_size : int | None
|
|
386
|
+
EN: Maximum file size in bytes.
|
|
387
|
+
FA: Řداڊ؍ع اŮداز٠ŮاŰŮ (بع Řسب باŰŘŞ).
|
|
388
|
+
|
|
389
|
+
only_files : bool, default=False
|
|
390
|
+
EN: Return only files.
|
|
391
|
+
FA: ŮŮء ŮاŰŮâŮا عا بعگعداŮŘŻ.
|
|
392
|
+
|
|
393
|
+
only_dirs : bool, default=False
|
|
394
|
+
EN: Return only directories.
|
|
395
|
+
FA: ŮŮء ŮžŮŘ´ŮâŮا عا بعگعداŮŘŻ.
|
|
396
|
+
|
|
397
|
+
recursive : bool, default=True
|
|
398
|
+
EN: Search recursively in subdirectories.
|
|
399
|
+
FA: ؏ست؏ŮŰ Ř¨Ř§Ř˛ÚŻŘ´ŘŞŰ ŘŻŘą زŰعٞŮŘ´ŮâŮا.
|
|
400
|
+
|
|
401
|
+
ignore_hidden : bool, default=True
|
|
402
|
+
EN: Ignore hidden files and folders.
|
|
403
|
+
FA: ŮادŰŘŻŮ ÚŻŘąŮŘŞŮ ŮاŰŮâŮا Ů ŮžŮŘ´ŮâŮŘ§Ű Ů
ŘŽŮŰ.
|
|
404
|
+
|
|
405
|
+
ignore_venv : bool, default=True
|
|
406
|
+
EN: Ignore any folder named 'venv' and its subtree.
|
|
407
|
+
FA: ŮادŰŘŻŮ ÚŻŘąŮŘŞŮ ŮžŮŘ´Ů venv Ů ŘŞŮ
اŮ
زŰŘąŮ
ŘŹŮ
Ůؚ٠آŮ.
|
|
408
|
+
|
|
409
|
+
Returns
|
|
410
|
+
-------
|
|
411
|
+
list[NicePath]
|
|
412
|
+
EN: A list of NicePath objects matching filters.
|
|
413
|
+
FA: ŮŰŘłŘŞŰ Ř§Ř˛ اشŰاإ NicePath Ů
ءاب٠با ŮŰŮŘŞŘąŮا.
|
|
414
|
+
|
|
415
|
+
Safety
|
|
416
|
+
------
|
|
417
|
+
EN:
|
|
418
|
+
- venv is ignored by default for safety and performance.
|
|
419
|
+
- Returns empty list if nothing is found (no exception).
|
|
420
|
+
|
|
421
|
+
FA:
|
|
422
|
+
- ب٠ؾŮعت ŮžŰŘ´âŮع؜ venv ŮادŰŘŻŮ ÚŻŘąŮŘŞŮ Ů
ŰâŘ´ŮŘŻ.
|
|
423
|
+
- ŘŻŘą ŘľŮعت ؚدŮ
ŰاŮŘŞŮ ŮŘŞŰŘŹŮŘ Ř˘ŘąŘ§Ű٠؎اŮŰ Ř¨ŘąŮ
ŰâگعداŮŘŻ ٠؎ءا ŮŮ
ŰâŘŻŮŘŻ.
|
|
424
|
+
"""
|
|
425
|
+
|
|
426
|
+
self._log_start("search")
|
|
427
|
+
|
|
428
|
+
if not self.exists:
|
|
429
|
+
logger.warning(f"Search skipped (path not found) -> {self._path}")
|
|
430
|
+
return []
|
|
431
|
+
|
|
432
|
+
candidates = (
|
|
433
|
+
[self._path]
|
|
434
|
+
if self.is_file
|
|
435
|
+
else (self._path.rglob("*") if recursive else self._path.glob("*"))
|
|
436
|
+
)
|
|
437
|
+
|
|
438
|
+
results: list[NicePath] = []
|
|
439
|
+
|
|
440
|
+
for p in candidates:
|
|
441
|
+
|
|
442
|
+
# ---- Skip hidden ----
|
|
443
|
+
if ignore_hidden and p.name.startswith("."):
|
|
444
|
+
continue
|
|
445
|
+
|
|
446
|
+
# ---- Skip venv folders and their children ----
|
|
447
|
+
if ignore_venv and any(part.lower() == "venv" for part in p.parts):
|
|
448
|
+
logger.debug(f"search skipped venv path -> {p}")
|
|
449
|
+
continue
|
|
450
|
+
|
|
451
|
+
if only_files and not p.is_file():
|
|
452
|
+
continue
|
|
453
|
+
|
|
454
|
+
if only_dirs and not p.is_dir():
|
|
455
|
+
continue
|
|
456
|
+
|
|
457
|
+
if name_contains and name_contains.lower() not in p.name.lower():
|
|
458
|
+
continue
|
|
459
|
+
|
|
460
|
+
if suffix and p.suffix.lower() != suffix.lower():
|
|
461
|
+
continue
|
|
462
|
+
|
|
463
|
+
if stem and stem.lower() != p.stem.lower():
|
|
464
|
+
continue
|
|
465
|
+
|
|
466
|
+
if regex:
|
|
467
|
+
try:
|
|
468
|
+
if not re.search(regex, p.name):
|
|
469
|
+
continue
|
|
470
|
+
except re.error as e:
|
|
471
|
+
self._log_error("search-regex", e)
|
|
472
|
+
continue
|
|
473
|
+
|
|
474
|
+
if p.is_file():
|
|
475
|
+
size = p.stat().st_size
|
|
476
|
+
if min_size is not None and size < min_size:
|
|
477
|
+
continue
|
|
478
|
+
if max_size is not None and size > max_size:
|
|
479
|
+
continue
|
|
480
|
+
|
|
481
|
+
results.append(NicePath(p))
|
|
482
|
+
|
|
483
|
+
self._log_success("search")
|
|
484
|
+
return results
|
|
485
|
+
|
|
486
|
+
# -------------------------
|
|
487
|
+
# logAll: Tree + Search + Save to file
|
|
488
|
+
# -------------------------
|
|
489
|
+
|
|
490
|
+
def logAll(
|
|
491
|
+
self,
|
|
492
|
+
file_output: "NicePath",
|
|
493
|
+
search_name_contains: str | None = None,
|
|
494
|
+
search_suffix: str | None = None,
|
|
495
|
+
search_stem: str | None = None,
|
|
496
|
+
search_regex: str | None = None,
|
|
497
|
+
search_only_files: bool = True,
|
|
498
|
+
search_only_dirs: bool = False,
|
|
499
|
+
search_recursive: bool = True,
|
|
500
|
+
search_ignore_hidden: bool = True,
|
|
501
|
+
max_files: int = 500, # safety: max number of files to log
|
|
502
|
+
max_total_size: int = 50_000_000, # safety: max total bytes (50MB)
|
|
503
|
+
ignore_venv: bool = True, # ignore virtual environments by default
|
|
504
|
+
ignore_libs: bool = True, # ignore internal library folders by default
|
|
505
|
+
encoding: str = "utf-8"
|
|
506
|
+
) -> str:
|
|
507
|
+
"""
|
|
508
|
+
Log full structure: Tree + search results into a file
|
|
509
|
+
|
|
510
|
+
EN:
|
|
511
|
+
Logs the tree of the current path and all matching search files line by line.
|
|
512
|
+
- Safety limits prevent logging too many files or too large total size.
|
|
513
|
+
- Automatically skips venv and library folders by default.
|
|
514
|
+
- If the original path does not exist, logs until last existing parent with a warning.
|
|
515
|
+
|
|
516
|
+
FA:
|
|
517
|
+
سا؎تاع ŘŻŘąŘŽŘŞŰ Ů
ŘłŰŘą Ů ŘŞŮ
اŮ
ŮاŰŮâŮŘ§Ű Ů
ءاب٠با ŘłŘąÚ ŘąŘ§ ؎ء ب٠؎ء Ůاگ Ů
ŰâÚŠŮŘŻ.
|
|
518
|
+
- Ů
ŘŘŻŮŘŻŰŘŞâŮŘ§Ű Ř§ŰŮ
ŮŰ Ř§Ř˛ Ůاگ بŰŘ´ از ŘŘŻ ŮاŰŮâŮا Űا ŘŘŹŮ
ŘŹŮŮÚŻŰŘąŰ Ů
ŰâÚŠŮŘŻ.
|
|
519
|
+
- ب٠ؾŮعت ŮžŰŘ´Ůع؜ ŮžŮŘ´ŮâŮŘ§Ű venv ٠ڊتاب؎اŮŮâŮا عا اŰÚŻŮŮŘą Ů
ŰâÚŠŮŘŻ.
|
|
520
|
+
- ŘŻŘą ŘľŮعت ŮŘŹŮŘŻ Ůداشت٠Ů
ŘłŰŘą اؾŮŰŘ ŘŞŘ§ آ؎عŰŮ Ů
ŘłŰŘą Ů
ŮŘŹŮŘŻ Ůاگ Ů
ŰâÚŠŮŘŻ Ů Ůشداع Ů
ŰâŘŻŮŘŻ.
|
|
521
|
+
|
|
522
|
+
Returns:
|
|
523
|
+
str: full text (tree + files) for printing or saving
|
|
524
|
+
|
|
525
|
+
------------------------------------------------------------
|
|
526
|
+
|
|
527
|
+
Parameters
|
|
528
|
+
----------
|
|
529
|
+
file_output : NicePath
|
|
530
|
+
EN: Destination file to store the generated log.
|
|
531
|
+
FA: ŮاŰŮ Ů
Ůؾد Ř¨ŘąŘ§Ű Ř°ŘŽŰع٠گزاعش.
|
|
532
|
+
|
|
533
|
+
search_name_contains : str | None
|
|
534
|
+
EN: Filter by substring in filename.
|
|
535
|
+
FA: ŮŰŮŘŞŘą بع اساس Ř¨ŘŽŘ´Ű Ř§Ř˛ ŮاŮ
ŮاŰŮ.
|
|
536
|
+
|
|
537
|
+
search_suffix : str | None
|
|
538
|
+
EN: Filter by file extension (e.g. ".py").
|
|
539
|
+
FA: ŮŰŮŘŞŘą بع اساس ٞسŮŮŘŻ ŮاŰŮ.
|
|
540
|
+
|
|
541
|
+
search_stem : str | None
|
|
542
|
+
EN: Match exact filename without extension.
|
|
543
|
+
FA: تءاب٠دŮŰŮ ŮاŮ
ŮاŰ٠بدŮ٠ٞسŮŮŘŻ.
|
|
544
|
+
|
|
545
|
+
search_regex : str | None
|
|
546
|
+
EN: Apply regex pattern to filename.
|
|
547
|
+
FA: اؚŮ
ا٠ؚباعت Ů
ŮظŮ
ŘąŮŰ ŮاŮ
ŮاŰŮ.
|
|
548
|
+
|
|
549
|
+
search_only_files : bool, default=True
|
|
550
|
+
EN: Include only files in search results.
|
|
551
|
+
FA: ŮŮء ŮاŰŮâŮا ŘŻŘą ŮتاŰŘŹ ؏ست؏٠ŮŘاظ Ř´ŮŮŘŻ.
|
|
552
|
+
|
|
553
|
+
search_only_dirs : bool, default=False
|
|
554
|
+
EN: Include only directories in search results.
|
|
555
|
+
FA: ŮŮء ŮžŮŘ´ŮâŮا ŘŻŘą ŮتاŰŘŹ ŮŘاظ Ř´ŮŮŘŻ.
|
|
556
|
+
|
|
557
|
+
search_recursive : bool, default=True
|
|
558
|
+
EN: Search subdirectories recursively.
|
|
559
|
+
FA: ؏ست؏ŮŰ Ř¨Ř§Ř˛ÚŻŘ´ŘŞŰ ŘŻŘą زŰعٞŮŘ´ŮâŮا.
|
|
560
|
+
|
|
561
|
+
search_ignore_hidden : bool, default=True
|
|
562
|
+
EN: Ignore hidden files and folders.
|
|
563
|
+
FA: ŮادŰŘŻŮ ÚŻŘąŮŘŞŮ ŮاŰŮâŮا Ů ŮžŮŘ´ŮâŮŘ§Ű Ů
ŘŽŮŰ.
|
|
564
|
+
|
|
565
|
+
safe_mode : bool, default=True
|
|
566
|
+
EN:
|
|
567
|
+
Enables protection limits:
|
|
568
|
+
- max_files
|
|
569
|
+
- max_file_size
|
|
570
|
+
- ignore_venv
|
|
571
|
+
FA:
|
|
572
|
+
ŮؚاŮâŘłŘ§Ř˛Ű Ů
ŘŘŻŮŘŻŰŘŞâŮŘ§Ű Ř§ŰŮ
ŮŰ Ř´Ř§Ů
Ů:
|
|
573
|
+
- Řداڊ؍ع تؚداد ŮاŰŮ
|
|
574
|
+
- Řداڊ؍ع ŘŘŹŮ
ŮاŰŮ
|
|
575
|
+
- ŮادŰŘŻŮ ÚŻŘąŮŘŞŮ venv
|
|
576
|
+
|
|
577
|
+
max_files : int, default=200
|
|
578
|
+
EN: Maximum number of files to log.
|
|
579
|
+
FA: Řداڊ؍ع تؚداد ŮاŰŮ Ůاب٠؍بت ŘŻŘą گزاعش.
|
|
580
|
+
|
|
581
|
+
max_file_size : int, default=1_000_000 (1MB)
|
|
582
|
+
EN: Maximum file size allowed for reading (in bytes).
|
|
583
|
+
FA: Řداڊ؍ع ŘŘŹŮ
ŮاŰŮ Ř¨ŘąŘ§Ű ŘŽŮاŮŘŻŮ (بع Řسب باŰŘŞ).
|
|
584
|
+
|
|
585
|
+
ignore_venv : bool, default=True
|
|
586
|
+
EN: Ignore 'venv' directories and their subtree.
|
|
587
|
+
FA: ŮادŰŘŻŮ ÚŻŘąŮŘŞŮ ŮžŮŘ´Ů venv ٠زŰŘąŮ
ŘŹŮ
Ůؚ٠آŮ.
|
|
588
|
+
|
|
589
|
+
encoding : str, default="utf-8"
|
|
590
|
+
EN: Encoding used when writing output file.
|
|
591
|
+
FA: ŮŮŘš اŮÚŠŮŘŻŰŮÚŻ Ř¨ŘąŘ§Ű ŮŮŘ´ŘŞŮ ŮاŰŮ ŘŽŘąŮŘŹŰ.
|
|
592
|
+
|
|
593
|
+
------------------------------------------------------------
|
|
594
|
+
|
|
595
|
+
Warning
|
|
596
|
+
-------
|
|
597
|
+
EN:
|
|
598
|
+
Disabling safe_mode on very large projects may cause
|
|
599
|
+
high memory usage and long execution time.
|
|
600
|
+
|
|
601
|
+
FA:
|
|
602
|
+
؎اŮ
ŮŘ´ ڊعد٠safe_mode ŘŻŘą ٞعŮÚŮâŮŘ§Ű Ř¨ŘłŰاع بزعگ
|
|
603
|
+
Ů
Ů
ڊ٠است باؚ؍ Ů
ؾع٠باŮŘ§Ű ŘاŮظ٠٠ڊŮŘŻŰ ŘłŰستŮ
Ř´ŮŘŻ.
|
|
604
|
+
|
|
605
|
+
"""
|
|
606
|
+
self._log_start("logAll")
|
|
607
|
+
|
|
608
|
+
try:
|
|
609
|
+
lines: list[str] = []
|
|
610
|
+
|
|
611
|
+
# ---------- Safety: find last existing parent ----------
|
|
612
|
+
path_to_use = self._path
|
|
613
|
+
while not path_to_use.exists() and path_to_use.parent != path_to_use:
|
|
614
|
+
path_to_use = path_to_use.parent
|
|
615
|
+
|
|
616
|
+
if path_to_use != self._path:
|
|
617
|
+
logger.warning(
|
|
618
|
+
f"logAll: Path {self._path} not found, logging stopped at {path_to_use}"
|
|
619
|
+
)
|
|
620
|
+
|
|
621
|
+
# ---------- Tree ----------
|
|
622
|
+
tree_text = NicePath(path_to_use).tree(
|
|
623
|
+
recursive=True, ignore_hidden=not search_ignore_hidden
|
|
624
|
+
)
|
|
625
|
+
lines.append("Tree Structure:\n")
|
|
626
|
+
lines.append(tree_text + "\n\n")
|
|
627
|
+
|
|
628
|
+
# ---------- Search ----------
|
|
629
|
+
search_results = NicePath(path_to_use).search(
|
|
630
|
+
name_contains=search_name_contains,
|
|
631
|
+
suffix=search_suffix,
|
|
632
|
+
stem=search_stem,
|
|
633
|
+
regex=search_regex,
|
|
634
|
+
only_files=search_only_files,
|
|
635
|
+
only_dirs=search_only_dirs,
|
|
636
|
+
recursive=search_recursive,
|
|
637
|
+
ignore_hidden=search_ignore_hidden,
|
|
638
|
+
)
|
|
639
|
+
|
|
640
|
+
# Apply safety filters
|
|
641
|
+
total_size = 0
|
|
642
|
+
logged_files = 0
|
|
643
|
+
output_lines: list[str] = []
|
|
644
|
+
|
|
645
|
+
for f in search_results:
|
|
646
|
+
if ignore_venv and "venv" in f.path.parts:
|
|
647
|
+
continue
|
|
648
|
+
if ignore_libs and "nicepy" in f.path.parts:
|
|
649
|
+
continue
|
|
650
|
+
|
|
651
|
+
try:
|
|
652
|
+
content = f.read()
|
|
653
|
+
except Exception as e:
|
|
654
|
+
content = f"[Could not read: {e}]"
|
|
655
|
+
|
|
656
|
+
file_size = len(content.encode(encoding))
|
|
657
|
+
total_size += file_size
|
|
658
|
+
logged_files += 1
|
|
659
|
+
|
|
660
|
+
if logged_files > max_files:
|
|
661
|
+
output_lines.append(
|
|
662
|
+
f"[Safety Limit] Skipped remaining files, max_files={max_files}\n"
|
|
663
|
+
)
|
|
664
|
+
break
|
|
665
|
+
if total_size > max_total_size:
|
|
666
|
+
output_lines.append(
|
|
667
|
+
f"[Safety Limit] Max total_size reached, {max_total_size} bytes\n"
|
|
668
|
+
)
|
|
669
|
+
break
|
|
670
|
+
|
|
671
|
+
output_lines.append(f"{f.path} -> {content}\n")
|
|
672
|
+
|
|
673
|
+
if output_lines:
|
|
674
|
+
lines.append("Search Results:\n")
|
|
675
|
+
lines.extend(output_lines)
|
|
676
|
+
else:
|
|
677
|
+
lines.append("Search Results: None\n")
|
|
678
|
+
|
|
679
|
+
# ---------- Write to file ----------
|
|
680
|
+
file_output.write("".join(lines), encoding=encoding)
|
|
681
|
+
|
|
682
|
+
self._log_success("logAll")
|
|
683
|
+
return "".join(lines)
|
|
684
|
+
except Exception as e:
|
|
685
|
+
self._log_error("logAll", e)
|
|
686
|
+
raise NicePathError(f"logAll failed for {self._path}") from e
|
|
687
|
+
|
|
688
|
+
# -------------------------
|
|
689
|
+
# Operator Overloads
|
|
690
|
+
# -------------------------
|
|
691
|
+
|
|
692
|
+
def __truediv__(self, other: str) -> "NicePath":
|
|
693
|
+
"""
|
|
694
|
+
Path division operator / استŮاد٠از / Ř¨ŘąŘ§Ű ŘłŘ§ŘŽŘŞ Ů
ŘłŰŘą ŘŹŘŻŰŘŻ
|
|
695
|
+
"""
|
|
696
|
+
return NicePath(self._path / other)
|
|
697
|
+
|
|
698
|
+
def __str__(self) -> str:
|
|
699
|
+
return str(self._path)
|
|
700
|
+
|
|
701
|
+
def __repr__(self) -> str:
|
|
702
|
+
return f"NicePath({self._path})"
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
|
|
2
|
+
# -------------------------
|
|
3
|
+
# Costume Expertions
|
|
4
|
+
# -------------------------
|
|
5
|
+
|
|
6
|
+
class NicePathError(Exception):
|
|
7
|
+
"""Base exception for nicepath"""
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class PathNotFoundError(NicePathError):
|
|
11
|
+
pass
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class NotAFileError(NicePathError):
|
|
15
|
+
pass
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class NotADirectoryError(NicePathError):
|
|
19
|
+
pass
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
class WriteError(NicePathError):
|
|
23
|
+
pass
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
class DeleteError(NicePathError):
|
|
27
|
+
pass
|
|
28
|
+
|
|
@@ -0,0 +1,320 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: nicepython
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Advanced OOP file and directory management library with powerful search, logging and chainable file operations.
|
|
5
|
+
Author-email: amin13m <aminm13aminm@gmail.com>
|
|
6
|
+
License: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/amin13m/nicepy
|
|
8
|
+
Project-URL: Repository, https://github.com/amin13m/nicepy
|
|
9
|
+
Project-URL: Issues, https://github.com/amin13m/nicepy/issues
|
|
10
|
+
Keywords: pathlib,filesystem,path,file,search,logging,oop
|
|
11
|
+
Classifier: Programming Language :: Python :: 3
|
|
12
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
13
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
15
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
16
|
+
Classifier: Operating System :: OS Independent
|
|
17
|
+
Requires-Python: >=3.9
|
|
18
|
+
Description-Content-Type: text/markdown
|
|
19
|
+
License-File: license
|
|
20
|
+
Dynamic: license-file
|
|
21
|
+
|
|
22
|
+
Tired of writing hundreds of lines with pathlib's verbose syntax? đ¤Ż
|
|
23
|
+
|
|
24
|
+
Meet NicePath.
|
|
25
|
+
|
|
26
|
+
A clean OOP path object with dozens of short, chainable methods and properties.
|
|
27
|
+
|
|
28
|
+
No try/except boilerplate.
|
|
29
|
+
Just .read(), .write(), .move_to() ⌠and it works.
|
|
30
|
+
|
|
31
|
+
⨠Smart search
|
|
32
|
+
đł Tree visualization
|
|
33
|
+
đ Automatic logging of hundreds of files with a single method
|
|
34
|
+
|
|
35
|
+
Less code. More clarity.
|
|
36
|
+
|
|
37
|
+
------------------------------------------------------------
|
|
38
|
+
|
|
39
|
+
đ NicePy
|
|
40
|
+
|
|
41
|
+
Advanced OOP File & Directory Management Library for Python
|
|
42
|
+
Built on top of pathlib with logging, search engine, tree view and smart utilities.
|
|
43
|
+
|
|
44
|
+
------------------------------------------------------------
|
|
45
|
+
|
|
46
|
+
⨠Why NicePy?
|
|
47
|
+
|
|
48
|
+
NicePath is a powerful wrapper around Pythonâs built-in pathlib, designed to make file management:
|
|
49
|
+
|
|
50
|
+
â Cleaner
|
|
51
|
+
â More readable
|
|
52
|
+
â More powerful
|
|
53
|
+
â Fully logged
|
|
54
|
+
â Searchable
|
|
55
|
+
â Tree-view ready
|
|
56
|
+
|
|
57
|
+
------------------------------------------------------------
|
|
58
|
+
|
|
59
|
+
đŚ Installation
|
|
60
|
+
|
|
61
|
+
đš Local Development Mode
|
|
62
|
+
|
|
63
|
+
pip install -e .
|
|
64
|
+
|
|
65
|
+
đš Future PyPI Installation
|
|
66
|
+
|
|
67
|
+
pip install nicepython
|
|
68
|
+
|
|
69
|
+
------------------------------------------------------------
|
|
70
|
+
|
|
71
|
+
đ Quick Start
|
|
72
|
+
|
|
73
|
+
from nicepy import NicePath
|
|
74
|
+
|
|
75
|
+
# Create path
|
|
76
|
+
p = NicePath("example.txt")
|
|
77
|
+
|
|
78
|
+
# Write data
|
|
79
|
+
p.write("Hello NicePy!")
|
|
80
|
+
|
|
81
|
+
# Read data
|
|
82
|
+
print(p.read())
|
|
83
|
+
|
|
84
|
+
# Append
|
|
85
|
+
p.append("\nNew Line")
|
|
86
|
+
|
|
87
|
+
# Show tree
|
|
88
|
+
root = NicePath(".")
|
|
89
|
+
print(root.tree())
|
|
90
|
+
|
|
91
|
+
# Search files
|
|
92
|
+
for f in root.search(suffix=".py"):
|
|
93
|
+
print(f.path)
|
|
94
|
+
|
|
95
|
+
------------------------------------------------------------
|
|
96
|
+
|
|
97
|
+
đł Tree Visualization
|
|
98
|
+
|
|
99
|
+
root = NicePath("my_project")
|
|
100
|
+
print(root.tree(ignore_hidden=False))
|
|
101
|
+
|
|
102
|
+
Example Output:
|
|
103
|
+
|
|
104
|
+
my_project
|
|
105
|
+
âââ main.py
|
|
106
|
+
âââ nicepy
|
|
107
|
+
â âââ core.py
|
|
108
|
+
âââ README.md
|
|
109
|
+
|
|
110
|
+
------------------------------------------------------------
|
|
111
|
+
|
|
112
|
+
đ Advanced Search
|
|
113
|
+
|
|
114
|
+
root.search(
|
|
115
|
+
name_contains="core",
|
|
116
|
+
suffix=".py",
|
|
117
|
+
recursive=True,
|
|
118
|
+
ignore_hidden=True
|
|
119
|
+
)
|
|
120
|
+
|
|
121
|
+
Supported Filters:
|
|
122
|
+
|
|
123
|
+
- name_contains
|
|
124
|
+
- suffix
|
|
125
|
+
- stem
|
|
126
|
+
- regex
|
|
127
|
+
- only_files
|
|
128
|
+
- only_dirs
|
|
129
|
+
- recursive
|
|
130
|
+
- ignore_hidden
|
|
131
|
+
|
|
132
|
+
If nothing is found â returns [] (never raises error)
|
|
133
|
+
|
|
134
|
+
------------------------------------------------------------
|
|
135
|
+
|
|
136
|
+
đ§ž logAll â Full Project Logger
|
|
137
|
+
|
|
138
|
+
Generate:
|
|
139
|
+
- Full tree structure
|
|
140
|
+
- Content of matched files
|
|
141
|
+
- Save everything into a file
|
|
142
|
+
|
|
143
|
+
project = NicePath("my_project")
|
|
144
|
+
output = NicePath("log.txt")
|
|
145
|
+
|
|
146
|
+
project.logAll(
|
|
147
|
+
file_output=output,
|
|
148
|
+
search_suffix=".py"
|
|
149
|
+
)
|
|
150
|
+
|
|
151
|
+
Useful for:
|
|
152
|
+
- Project snapshot
|
|
153
|
+
- Debug logging
|
|
154
|
+
- Code export
|
|
155
|
+
- Archiving structure
|
|
156
|
+
|
|
157
|
+
------------------------------------------------------------
|
|
158
|
+
|
|
159
|
+
đ Available Methods
|
|
160
|
+
|
|
161
|
+
write(data) â Write text to file
|
|
162
|
+
read() â Read file content
|
|
163
|
+
append(data) â Append to file
|
|
164
|
+
delete() â Remove file or directory
|
|
165
|
+
copy_to(dest) â Copy file/folder
|
|
166
|
+
move_to(dest) â Move file/folder
|
|
167
|
+
search(...) â Smart search engine
|
|
168
|
+
tree(...) â Visual tree display
|
|
169
|
+
logAll(...) â Full structured log export
|
|
170
|
+
|
|
171
|
+
Properties:
|
|
172
|
+
exists
|
|
173
|
+
is_file
|
|
174
|
+
is_dir
|
|
175
|
+
size
|
|
176
|
+
created_time
|
|
177
|
+
modified_time
|
|
178
|
+
|
|
179
|
+
------------------------------------------------------------
|
|
180
|
+
|
|
181
|
+
â NicePy vs pathlib
|
|
182
|
+
|
|
183
|
+
Feature | pathlib | NicePy
|
|
184
|
+
------------------------------------------------------------
|
|
185
|
+
Basic read/write | Yes | Yes
|
|
186
|
+
Append built-in | No | Yes
|
|
187
|
+
Tree view | No | Yes
|
|
188
|
+
Search engine | No | Yes
|
|
189
|
+
Regex search | No | Yes
|
|
190
|
+
Logging system | No | Yes
|
|
191
|
+
Full project log export | No | Yes
|
|
192
|
+
Unified OOP interface | Basic | Advanced
|
|
193
|
+
Custom exceptions | No | Yes
|
|
194
|
+
|
|
195
|
+
------------------------------------------------------------
|
|
196
|
+
|
|
197
|
+
## â ď¸ Safety Limits in NicePath
|
|
198
|
+
|
|
199
|
+
NicePath library includes powerful methods like logAll, tree, and search that can traverse large directories or read/write many files.
|
|
200
|
+
|
|
201
|
+
To prevent accidental overloads, these methods have default safety limits:
|
|
202
|
+
|
|
203
|
+
- logAll: limits the maximum number of files and total size it processes.
|
|
204
|
+
- Default max_files=500
|
|
205
|
+
- Default max_total_size=10_000_000 bytes (10 MB)
|
|
206
|
+
- tree and search:
|
|
207
|
+
- Can ignore virtual environments and library folders (ignore_venv=True by default)
|
|
208
|
+
- Can limit recursion depth or number of entries if needed.
|
|
209
|
+
|
|
210
|
+
Behavior when limits are reached:
|
|
211
|
+
|
|
212
|
+
- The operation does not crash.
|
|
213
|
+
- Partial results are written to the output file.
|
|
214
|
+
- A warning message is logged with logger.warning stating that safety limits were reached.
|
|
215
|
+
|
|
216
|
+
Customizing Safety:
|
|
217
|
+
|
|
218
|
+
You can override safety defaults in method calls:
|
|
219
|
+
|
|
220
|
+
`python
|
|
221
|
+
dir = NicePath("D:/Projects")
|
|
222
|
+
output_file = dir / "log.txt"
|
|
223
|
+
|
|
224
|
+
dir.logAll(
|
|
225
|
+
file_output=output_file,
|
|
226
|
+
search_suffix=".py",
|
|
227
|
+
max_files=1000, # increase limit
|
|
228
|
+
max_total_size=50_000_000, # 50 MB
|
|
229
|
+
ignore_venv=False # include virtual environments
|
|
230
|
+
)
|
|
231
|
+
|
|
232
|
+
đ Logging System
|
|
233
|
+
|
|
234
|
+
All critical operations are logged:
|
|
235
|
+
|
|
236
|
+
- Start
|
|
237
|
+
- Success
|
|
238
|
+
- Failure
|
|
239
|
+
- Error reason
|
|
240
|
+
|
|
241
|
+
Helps debugging and production stability.
|
|
242
|
+
|
|
243
|
+
------------------------------------------------------------
|
|
244
|
+
|
|
245
|
+
đ§Ş Testing
|
|
246
|
+
|
|
247
|
+
pytest -v
|
|
248
|
+
|
|
249
|
+
------------------------------------------------------------
|
|
250
|
+
|
|
251
|
+
đ Project Structure
|
|
252
|
+
|
|
253
|
+
nicepy_python
|
|
254
|
+
âââ pycache
|
|
255
|
+
â âââ init.pythonc
|
|
256
|
+
â âââ main.cpython-314.pyc
|
|
257
|
+
âââ nicepy
|
|
258
|
+
â âââ pycache
|
|
259
|
+
â â âââ init.pyc
|
|
260
|
+
â â âââ logger.cpython-314.pyc
|
|
261
|
+
â âââ nicepath
|
|
262
|
+
â â âââ pycache
|
|
263
|
+
â â â âââ init.pyc
|
|
264
|
+
â â â âââ core.cpython-314.pyc
|
|
265
|
+
â â â âââ exceptios.cpython-314.pyc
|
|
266
|
+
â â âââ init.py
|
|
267
|
+
â â âââ core.py
|
|
268
|
+
â â âââ exceptios.py
|
|
269
|
+
â âââ init.py
|
|
270
|
+
â âââ logger.py
|
|
271
|
+
âââ nicepy.egg-info
|
|
272
|
+
â âââ dependency_links.txt
|
|
273
|
+
â âââ PKG-INFO
|
|
274
|
+
â âââ SOURCES.txt
|
|
275
|
+
â âââ top_level.txt
|
|
276
|
+
âââ tests
|
|
277
|
+
â âââ pycache
|
|
278
|
+
â â âââ test_nicepath.cpython-314-pytest-9.0.2.pyc
|
|
279
|
+
â â âââ test_nicepath.cpython-314.pyc
|
|
280
|
+
â âââ newfolder
|
|
281
|
+
â â âââ ksc.txt
|
|
282
|
+
â â âââ tet.txt
|
|
283
|
+
â â âââ text.txt
|
|
284
|
+
â âââ test_nicepath.py
|
|
285
|
+
âââ init.py
|
|
286
|
+
âââ main.py
|
|
287
|
+
âââ pyproject.toml
|
|
288
|
+
âââ README.md
|
|
289
|
+
|
|
290
|
+
------------------------------------------------------------
|
|
291
|
+
|
|
292
|
+
đŽ Roadmap
|
|
293
|
+
|
|
294
|
+
[ ] PyPI release
|
|
295
|
+
[ ] Async support
|
|
296
|
+
[ ] Caching search engine
|
|
297
|
+
[ ] Watchdog integration
|
|
298
|
+
[ ] Colored tree output
|
|
299
|
+
[ ] CLI interface
|
|
300
|
+
|
|
301
|
+
------------------------------------------------------------
|
|
302
|
+
|
|
303
|
+
đ¤ Author
|
|
304
|
+
|
|
305
|
+
Amin
|
|
306
|
+
GitHub: https://github.com/amin13m
|
|
307
|
+
|
|
308
|
+
------------------------------------------------------------
|
|
309
|
+
|
|
310
|
+
đ License
|
|
311
|
+
|
|
312
|
+
MIT License
|
|
313
|
+
|
|
314
|
+
------------------------------------------------------------
|
|
315
|
+
|
|
316
|
+
đ Philosophy
|
|
317
|
+
|
|
318
|
+
Clean code.
|
|
319
|
+
Predictable behavior.
|
|
320
|
+
Zero surprise file handling.
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
nicepy/__init__.py,sha256=Jp-5qqGmZu3ATFx3OB22gOEDkclQP_UEgrb0qV5uDNI,123
|
|
2
|
+
nicepy/logger.py,sha256=PZb0abNyg8ufKekUX-P4A85ypA0zztkb6Vn7McXnHpE,1414
|
|
3
|
+
nicepy/nicepath/__init__.py,sha256=Mh94eCfVVfNHYg03UzvHAh1-wbua6Sy_IsIQhGfs-So,26
|
|
4
|
+
nicepy/nicepath/core.py,sha256=ofpekMNs4-NV9LTSK6hPF-VjiLcrMn1j770QkFNBSxQ,26383
|
|
5
|
+
nicepy/nicepath/exceptios.py,sha256=CqXucAoDn9IsGYMpdddO11tXLLHFE7nrfMOc-p65Jjg,417
|
|
6
|
+
nicepython-0.1.0.dist-info/licenses/license,sha256=wq4_Pe5i7o3kBG_NJJBio60kQaRFalOYHYxFPC9MleA,378
|
|
7
|
+
nicepython-0.1.0.dist-info/METADATA,sha256=w75FjEk4KrP-KvqfIrX8ukhmqXDXfZYjI-drEGKj3TA,8041
|
|
8
|
+
nicepython-0.1.0.dist-info/WHEEL,sha256=YCfwYGOYMi5Jhw2fU4yNgwErybb2IX5PEwBKV4ZbdBo,91
|
|
9
|
+
nicepython-0.1.0.dist-info/top_level.txt,sha256=YIbxzgo9858-SzJJHZcjdqzzXxIiSx3OvljR_aPZ7r8,7
|
|
10
|
+
nicepython-0.1.0.dist-info/RECORD,,
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 amin13m
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software.
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
nicepy
|