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 ADDED
@@ -0,0 +1,4 @@
1
+ from .nicepath.core import NicePath
2
+ from .nicepath.exceptios import NicePathError
3
+
4
+ all = ["NicePath", "NicePathError"]
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
@@ -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,5 @@
1
+ Wheel-Version: 1.0
2
+ Generator: setuptools (82.0.0)
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
5
+
@@ -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