/
dirs.py
730 lines (551 loc) · 24.6 KB
/
dirs.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
"""Manipulation of directories and/or file paths."""
import collections.abc
import copy
import errno
import importlib.resources
import os
import pathlib
import re
import shutil
from ._cache import _check_rel_pathname, _confirmed
# ==================================================================================================
# Directory navigation
# ==================================================================================================
def cd(*subdir, mkdir=False, cwd=None, back_check=False, **kwargs):
"""
Get the full pathname of a directory (or file).
:param subdir: name of a directory or names of directories (and/or a filename)
:type subdir: str | os.PathLike[str] | bytes | os.Path[bytes]
:param mkdir: whether to create a directory, defaults to ``False``
:type mkdir: bool
:param cwd: current working directory, defaults to ``None``
:type cwd: str | os.PathLike[str] | bytes | os.Path[bytes] | None
:param back_check: whether to check if a parent directory exists, defaults to ``False``
:type back_check: bool
:param kwargs: [optional] parameters (e.g. ``mode=0o777``) of `os.makedirs`_
:return: full pathname of a directory or that of a file
:rtype: str
.. _`os.makedirs`: https://docs.python.org/3/library/os.html#os.makedirs
**Examples**::
>>> from pyhelpers.dirs import cd
>>> import os
>>> import pathlib
>>> current_wd = cd() # Current working directory
>>> os.path.relpath(current_wd)
'.'
>>> # The directory will be created if it does not exist
>>> path_to_tests_dir = cd("tests")
>>> os.path.relpath(path_to_tests_dir)
'tests'
>>> path_to_tests_dir = cd(pathlib.Path("tests"))
>>> os.path.relpath(path_to_tests_dir)
'tests'
"""
# Current working directory
path = os.getcwd() if cwd is None else copy.copy(cwd.decode() if isinstance(cwd, bytes) else cwd)
if back_check:
while not os.path.exists(path):
path = os.path.dirname(path)
for x in subdir:
if isinstance(x, bytes):
x = x.decode()
path = os.path.dirname(path) if x == ".." else os.path.join(path, x)
if mkdir:
path_to_file, ext = os.path.splitext(path)
kwargs.update({'exist_ok': True})
if ext == '':
os.makedirs(path_to_file, **kwargs)
else:
os.makedirs(os.path.dirname(path_to_file), **kwargs)
return path
def go_from_altered_cwd(dir_name, **kwargs):
"""
Get the full pathname of an altered working directory.
:param dir_name: name of a directory
:type dir_name: str | os.PathLike[str] | bytes | os.Path[bytes]
:param kwargs: [optional] parameters of the function :func:`pyhelpers.dirs.cd`
:return: full pathname of an altered working directory (changed from the directory ``path_to_dir``)
:rtype: str
**Examples**::
>>> from pyhelpers.dirs import go_from_altered_cwd
>>> import os
>>> init_cwd = os.getcwd()
>>> init_cwd
'<cwd>'
>>> # Change the current working directory to "/new_cwd"
>>> new_cwd = "\\new_cwd"
>>> os.mkdir(new_cwd)
>>> os.chdir(new_cwd)
>>> # Get the full path to a folder named "tests"
>>> path_to_tests = go_from_altered_cwd(path_to_dir="tests")
>>> path_to_tests
'<new_cwd>\\tests'
>>> # Get the full path to a directory one level above the current working directory
>>> path_to_tests_ = go_from_altered_cwd(path_to_dir="\\tests")
>>> path_to_tests_ == os.path.join(os.path.dirname(os.getcwd()), "tests")
True
>>> os.chdir(init_cwd)
>>> os.rmdir(new_cwd)
"""
dir_name_ = dir_name.decode() if isinstance(dir_name, bytes) else dir_name
target = cd(dir_name_, **kwargs)
if os.path.isdir(target):
altered_cwd = target
else:
original_cwd = os.path.dirname(target)
altered_cwd = os.path.join(original_cwd, dir_name_)
if altered_cwd == target:
pass
else:
while not os.path.isdir(altered_cwd):
original_cwd = os.path.dirname(original_cwd)
if original_cwd == cd():
break
else:
altered_cwd = os.path.join(original_cwd, dir_name_)
return altered_cwd
def cdd(*subdir, data_dir="data", mkdir=False, **kwargs):
"""
Get the full pathname of a directory (or file) under ``data_dir``.
:param subdir: name of directory or names of directories (and/or a filename)
:type subdir: str | os.PathLike[str] | bytes | os.Path[bytes]
:param data_dir: name of a directory where data is (or will be) stored, defaults to ``"data"``
:type data_dir: str | os.PathLike[str] | bytes | os.Path[bytes]
:param mkdir: whether to create a directory, defaults to ``False``
:type mkdir: bool
:param kwargs: [optional] parameters of the function :func:`pyhelpers.dirs.cd`
:return path: full pathname of a directory (or a file) under ``data_dir``
:rtype: str
**Examples**::
>>> from pyhelpers.dirs import cdd, delete_dir
>>> import os
>>> path_to_dat_dir = cdd()
>>> # As `mkdir=False`, `path_to_dat_dir` will NOT be created if it doesn't exist
>>> os.path.relpath(path_to_dat_dir)
'data'
>>> path_to_dat_dir = cdd(data_dir="test_cdd", mkdir=True)
>>> # As `mkdir=True`, `path_to_dat_dir` will be created if it doesn't exist
>>> os.path.relpath(path_to_dat_dir)
'test_cdd'
>>> # Delete the "test_cdd" folder
>>> delete_dir(path_to_dat_dir, verbose=True)
To delete the directory "test_cdd\\"
? [No]|Yes: yes
Deleting "test_cdd\\" ... Done.
>>> # Set `data_dir` to be `"tests"`
>>> path_to_dat_dir = cdd("data", data_dir="test_cdd", mkdir=True)
>>> os.path.relpath(path_to_dat_dir)
'test_cdd\\data'
>>> # Delete the "test_cdd" folder and the sub-folder "data"
>>> test_cdd = os.path.dirname(path_to_dat_dir)
>>> delete_dir(test_cdd, verbose=True)
The directory "test_cdd\\" is not empty.
Confirmed to delete it
? [No]|Yes: yes
Deleting "test_cdd\\" ... Done.
>>> # # Alternatively,
>>> # import shutil
>>> # shutil.rmtree(test_cdd)
"""
kwargs.update({'mkdir': mkdir})
path = cd(data_dir, *subdir, **kwargs)
return path
def cd_data(*subdir, data_dir="data", mkdir=False, **kwargs):
"""
Get the full pathname of a directory (or file) under ``data_dir`` of a package.
:param subdir: name of directory or names of directories (and/or a filename)
:type subdir: str | os.PathLike[str] | bytes | os.Path[bytes]
:param data_dir: name of a directory to store data, defaults to ``"data"``
:type data_dir: str | os.PathLike[str] | bytes | os.Path[bytes]
:param mkdir: whether to create a directory, defaults to ``False``
:type mkdir: bool
:param kwargs: [optional] parameters (e.g. ``mode=0o777``) of `os.makedirs`_
:return: full pathname of a directory or that of a file under ``data_dir`` of a package
:rtype: str
.. _`os.makedirs`: https://docs.python.org/3/library/os.html#os.makedirs
**Examples**::
>>> from pyhelpers.dirs import cd_data
>>> import os
>>> path_to_dat_dir = cd_data("tests", mkdir=False)
>>> os.path.relpath(path_to_dat_dir)
'pyhelpers\\data\\tests'
"""
data_dir_ = data_dir.decode() if isinstance(data_dir, bytes) else str(data_dir)
path = importlib.resources.files(__package__).joinpath(data_dir_)
for x in subdir:
path = os.path.join(path, x.decode() if isinstance(x, bytes) else x)
if mkdir:
path_to_file, ext = os.path.splitext(path)
kwargs.update({'exist_ok': True})
if ext == '' or ext == b'':
os.makedirs(path_to_file, **kwargs)
else:
os.makedirs(os.path.dirname(path_to_file), **kwargs)
return path
# ==================================================================================================
# Directory validation
# ==================================================================================================
def path2linux(path):
"""
Convert path to an uniformat (Linux) file path, which is executable in Windows, Linux and macOS.
- Format the file path to be used for cross-platform compatibility;
- Convert OS path to standard Linux path.
:param path: absolute or relative pathname
:type path: str | os.PathLike | bytes
:return: standard linux pathname
:rtype: str
**Examples**::
>>> from pyhelpers.dirs import path2linux
>>> import pathlib
>>> path2linux("tests\\data\\dat.csv")
'tests/data/dat.csv'
>>> path2linux(pathlib.Path("tests\\data\\dat.csv"))
'tests/data/dat.csv'
"""
# noinspection PyBroadException
try:
return path.replace("\\", "/")
except Exception:
return str(path).replace("\\", "/")
def uniform_pathname(pathname):
"""
An alternative to the function :func:`~pyhelpers.dirs.path2linux`.
:param pathname: absolute or relative pathname
:type pathname: str | pathlib.Path
:return: standard linux pathname
:rtype: str
**Examples**::
>>> from pyhelpers.dirs import uniform_pathname
>>> import pathlib
>>> uniform_pathname("tests\\data\\dat.csv")
'tests/data/dat.csv'
>>> uniform_pathname("tests//data/dat.csv")
'tests/data/dat.csv'
>>> uniform_pathname(pathlib.Path("tests\\data/dat.csv"))
'tests/data/dat.csv'
"""
pathname_ = re.sub(r"\\|\\\\|//", "/", str(pathname))
return pathname_
def is_dir(path_to_dir):
"""
Check whether a directory-like string is a (valid) directory name.
See also [`DIRS-IVD-1 <https://stackoverflow.com/questions/9532499/>`_] and
[`DIRS-IVD-2 <https://docs.microsoft.com/en-us/windows/win32/debug/system-error-codes--0-499->`_].
:param path_to_dir: pathname of a directory
:type path_to_dir: str | bytes
:return: whether the input is a path-like string that describes a directory name
:rtype: bool
**Examples**::
>>> from pyhelpers.dirs import cd, is_dir
>>> x = "tests"
>>> is_dir(x)
False
>>> x = "/tests"
>>> is_dir(x)
True
>>> x = cd("tests")
>>> is_dir(x)
True
"""
try:
root_dirname_, pathname_ = os.path.splitdrive(path_to_dir)
root_dirname = root_dirname_ if root_dirname_ else '.'
root_dirname += os.path.sep
for pathname_part in re.split(r'[\\/]', pathname_):
try:
os.lstat(root_dirname + pathname_part)
except OSError as exc:
if hasattr(exc, 'winerror'):
if exc.winerror == 123: # ERROR_INVALID_NAME
return False
elif exc.errno in {errno.ENAMETOOLONG, errno.ERANGE}:
return False
except TypeError:
return False
else:
return bool(os.path.dirname(path_to_dir))
def validate_dir(path_to_dir=None, subdir="", msg="Invalid input!", **kwargs):
"""
Validate the pathname of a directory.
:param path_to_dir: pathname of a data directory, defaults to ``None``
:type path_to_dir: str | os.PathLike[str] | bytes | os.Path[bytes] | None
:param subdir: name of a subdirectory to be examined if ``directory=None``, defaults to ``""``
:type subdir: str | os.PathLike[str] | bytes | os.Path[bytes]
:param msg: error message if ``data_dir`` is not a full pathname, defaults to ``"Invalid input!"``
:type msg: str
:param kwargs: [optional] parameters of the function :func:`pyhelpers.dirs.cd`
:return: valid full pathname of a directory
:rtype: str
**Examples**::
>>> from pyhelpers.dirs import validate_dir
>>> import os
>>> dat_dir = validate_dir()
>>> os.path.relpath(dat_dir)
'.'
>>> dat_dir = validate_dir("tests")
>>> os.path.relpath(dat_dir)
'tests'
>>> dat_dir = validate_dir(subdir="data")
>>> os.path.relpath(dat_dir)
'data'
"""
if path_to_dir:
if isinstance(path_to_dir, pathlib.Path):
path_to_dir_ = str(path_to_dir)
elif isinstance(path_to_dir, bytes):
path_to_dir_ = path_to_dir.decode()
else:
path_to_dir_ = path_to_dir
assert isinstance(path_to_dir_, str), msg
if not os.path.isabs(path_to_dir_): # Use default file directory
data_dir_ = cd(path_to_dir_.strip('.\\.'), **kwargs)
else:
assert os.path.isabs(path_to_dir_), msg
data_dir_ = os.path.realpath(path_to_dir_.lstrip('.\\.'))
else:
data_dir_ = cd(subdir, **kwargs) if subdir else cd()
return data_dir_
def validate_filename(file_pathname, suffix_num=1):
"""
If the filename exist, then create new filename with a suffix such as "(1)", "(2)", and so on.
:param file_pathname: pathname of a file
:type file_pathname: str
:param suffix_num: a number as a suffix appended to the filename, defaults to ``1``
:type suffix_num: int
:return: a validated file name
:rtype: str
**Examples**::
>>> from pyhelpers.dirs import validate_filename
>>> import os
>>> test_file_pathname = "tests/data/test.txt"
>>> # When the file does not exist, return the same file name
>>> os.path.exists(test_file_pathname)
False
>>> file_pathname_0 = validate_filename(test_file_pathname)
>>> os.path.relpath(file_pathname_0)
'tests\\data\\test.txt'
>>> # Create a file named "test.txt"
>>> open(test_file_pathname, 'w').close()
>>> os.path.exists(test_file_pathname)
True
>>> # As "test.txt" exists, the function returns a new pathname ending with "test(1).txt"
>>> file_pathname_1 = validate_filename(test_file_pathname)
>>> os.path.relpath(file_pathname_1)
'tests\\data\\test(1).txt'
>>> # When "test(1).txt" exists, the function returns a pathname of a file named "test(2).txt"
>>> open(file_pathname_1, 'w').close()
>>> os.path.exists(file_pathname_1)
True
>>> file_pathname_2 = validate_filename(test_file_pathname)
>>> os.path.relpath(file_pathname_2)
'tests\\data\\test(2).txt'
>>> # Remove the created files
>>> for x in [file_pathname_0, file_pathname_1]:
... os.remove(x)
"""
# convert the path to standard linux path
filename_abspath = path2linux(os.path.abspath(file_pathname))
# get the file suffix
file_suffix = filename_abspath.split(".")[-1]
file_without_suffix = filename_abspath[:-len(file_suffix) - 1]
# remove the suffix if the file name contains "("
if "(" in file_without_suffix:
file_without_suffix = file_without_suffix.split("(")[0]
# if the file does not exist, return the same file name
if os.path.exists(filename_abspath):
filename_update = f"{file_without_suffix}({suffix_num}).{file_suffix}"
return validate_filename(filename_update, suffix_num + 1)
return filename_abspath
# ==================================================================================================
# Directory/file control
# ==================================================================================================
def get_file_pathnames(path_to_dir, file_ext=None, incl_subdir=False):
"""
Get all files in the folder with the specified file type.
:param path_to_dir: a folder path
:type path_to_dir: str | os.PathLike
:param file_ext: exact file type to specify, if file_type is ``"*"`` or ``"all"``,
return all files in the folder; defaults to ``None``
:type file_ext: str | None
:param incl_subdir: whether to get files inside the subfolder, defaults to ``False``;
when ``incl_subdir=True``, the function traverses all subfolders
:type incl_subdir: bool
:return: a list of file paths
:rtype: list
**Examples**::
>>> from pyhelpers.dirs import get_file_pathnames
>>> test_dir_name = "tests/data"
>>> # Get all files in the folder (without sub-folders)
>>> get_file_pathnames(test_dir_name)
['tests/data/csr_mat.npz',
'tests/data/dat.csv',
'tests/data/dat.feather',
'tests/data/dat.joblib',
'tests/data/dat.json',
'tests/data/dat.pickle',
'tests/data/dat.txt',
'tests/data/dat.xlsx',
'tests/data/zipped',
'tests/data/zipped.7z',
'tests/data/zipped.txt',
'tests/data/zipped.zip']
>>> get_file_pathnames(test_dir_name, file_ext=".txt")
['tests/data/dat.txt', 'tests/data/zipped.txt']
>>> # Get absolute pathnames of all files contained in the folder (incl. all sub-folders)
>>> get_file_pathnames(test_dir_name, file_ext="txt", incl_subdir=True)
['tests/data/dat.txt', 'tests/data/zipped.txt', 'tests/data/zipped/zipped.txt']
"""
if incl_subdir:
files_list = []
for root, _, files in os.walk(path_to_dir):
files_list.extend([os.path.join(root, file) for file in files])
if file_ext in {None, "*", "all"}:
return [path2linux(file) for file in files_list]
return [path2linux(file) for file in files_list if file.endswith(file_ext)]
# Files in the first layer of the folder
if file_ext in {None, "*", "all"}:
return [path2linux(os.path.join(path_to_dir, file)) for file in os.listdir(path_to_dir)]
# noinspection PyTypeChecker
return [
path2linux(os.path.join(path_to_dir, file)) for file in os.listdir(path_to_dir)
if file.endswith(file_ext)]
def check_files_exist(filenames, path_to_dir):
"""
Check if queried files exist in a given directory.
:param filenames: a list of filenames
:type filenames: list
:param path_to_dir: a list of filenames in the directory
:type path_to_dir: str | os.PathLike
:return: ``True`` if all the queried files exist, ``False`` otherwise
:rtype: bool
**Examples**::
>>> from pyhelpers.dirs import check_files_exist
>>> test_dir_name = "tests/data"
>>> # Check if all required files exist in the directory
>>> check_files_exist(["dat.csv", "dat.txt"], test_dir_name)
True
>>> # If not all required files exist, print the missing files
>>> check_files_exist(["dat.csv", "dat.txt", "dat_0.txt"], test_dir_name)
Error: Required files are not satisfied, missing files are: ['dat_0.txt']
False
"""
dir_files = get_file_pathnames(path_to_dir, file_ext="*")
# Format the required file name to standard linux path
filenames = [path2linux(os.path.abspath(filename)) for filename in filenames]
required_files_short = [filename.split("/")[-1] for filename in filenames]
dir_files_short = [filename.split("/")[-1] for filename in dir_files]
# `mask` have the same length as `filenames`
mask = [file in dir_files_short for file in required_files_short]
if all(mask):
rslt = True
else:
err_prt_dat = [required_files_short[i] for i in range(len(required_files_short)) if not mask[i]]
err_msg = f"Error: Required files are not satisfied, missing files are: {err_prt_dat}"
print(err_msg)
rslt = False
return rslt
def _delete_dir(path_to_dir, confirmation_required=True, verbose=False, **kwargs):
"""
Delete a directory.
:param path_to_dir: pathname of a directory
:type path_to_dir: str | bytes | os.PathLike[str] | os.PathLike[bytes]
:param confirmation_required: whether to prompt a message for confirmation to proceed,
defaults to ``True``
:type confirmation_required: bool
:param verbose: whether to print relevant information in console as the function runs,
defaults to ``False``
:type verbose: bool | int
:param kwargs: [optional] parameters of `shutil.rmtree`_ or `os.rmdir`_
.. _`shutil.rmtree`: https://docs.python.org/3/library/shutil.html#shutil.rmtree
.. _`os.rmdir`: https://docs.python.org/3/library/os.html#os.rmdir
**Tests**::
>>> from pyhelpers.dirs import cd, _delete_dir
>>> import os
>>> dir_path = cd("test_dir", mkdir=True)
>>> rel_dir_path = os.path.relpath(dir_path)
>>> print('The directory "{}\\" exists? {}'.format(rel_dir_path, os.path.exists(dir_path)))
The directory "test_dir\\" exists? True
>>> _delete_dir(dir_path, verbose=True)
To delete the directory "test_dir\\"
? [No]|Yes: yes
Deleting "test_dir\\" ... Done.
>>> print('The directory "{}\\" exists? {}'.format(rel_dir_path, os.path.exists(dir_path)))
The directory "test_dir\\" exists? False
>>> dir_path = cd("test_dir", "folder", mkdir=True)
>>> rel_dir_path = os.path.relpath(dir_path)
>>> print('The directory "{}\\" exists? {}'.format(rel_dir_path, os.path.exists(dir_path)))
The directory "test_dir\\folder\\" exists? True
>>> _delete_dir(cd("test_dir"), verbose=True)
The directory "test_dir\\" is not empty.
Confirmed to delete it
? [No]|Yes: yes
Deleting "test_dir\\" ... Done.
>>> print('The directory "{}\\" exists? {}'.format(rel_dir_path, os.path.exists(dir_path)))
The directory "test_dir\\folder\\" exists? False
"""
dir_pathname = _check_rel_pathname(path_to_dir)
try:
if os.listdir(dir_pathname):
cfm_msg = f"The directory \"{dir_pathname}\\\" is not empty.\nConfirmed to delete it\n?"
func = shutil.rmtree
else:
cfm_msg = f"To delete the directory \"{dir_pathname}\\\"\n?"
func = os.rmdir
if _confirmed(prompt=cfm_msg, confirmation_required=confirmation_required):
if verbose:
print("Deleting \"{}\\\"".format(path_to_dir), end=" ... ")
func(dir_pathname, **kwargs)
if verbose:
if not os.path.exists(path_to_dir):
print("Done.")
else:
print("Cancelled.")
except Exception as e:
print("Failed. {}.".format(e))
def delete_dir(path_to_dir, confirmation_required=True, verbose=False, **kwargs):
"""
Delete a directory or directories.
:param path_to_dir: pathname (or pathnames) of a directory (or directories)
:type path_to_dir: str | bytes | os.PathLike[str] | os.PathLike[bytes] | collections.abc.Sequence
:param confirmation_required: whether to prompt a message for confirmation to proceed,
defaults to ``True``
:type confirmation_required: bool
:param verbose: whether to print relevant information in console as the function runs,
defaults to ``False``
:type verbose: bool | int
:param kwargs: [optional] parameters of `shutil.rmtree`_ or `os.rmdir`_
.. _`shutil.rmtree`: https://docs.python.org/3/library/shutil.html#shutil.rmtree
.. _`os.rmdir`: https://docs.python.org/3/library/os.html#os.rmdir
**Examples**::
>>> from pyhelpers.dirs import cd, delete_dir
>>> import os
>>> test_dirs = []
>>> for x in range(3):
... test_dirs.append(cd("tests", f"test_dir{x}", mkdir=True))
... if x == 0:
... cd("tests", f"test_dir{x}", "a_folder", mkdir=True)
... elif x == 1:
... open(cd("tests", f"test_dir{x}", "file"), 'w').close()
>>> delete_dir(path_to_dir=test_dirs, verbose=True)
To delete the following directories:
"tests\\test_dir0\\" (Not empty)
"tests\\test_dir1\\" (Not empty)
"tests\\test_dir2\\"
? [No]|Yes: yes
Deleting "tests\\test_dir0\\" ... Done.
Deleting "tests\\test_dir1\\" ... Done.
Deleting "tests\\test_dir2\\" ... Done.
"""
if isinstance(path_to_dir, collections.abc.Sequence) and not isinstance(path_to_dir, (str, bytes)):
dir_pathnames = [_check_rel_pathname(p) for p in path_to_dir]
else:
dir_pathnames = [_check_rel_pathname(path_to_dir)]
pn = ["\"" + p + ("\\\" (Not empty)" if os.listdir(p) else "\\\"") for p in dir_pathnames]
if len(pn) == 1:
cfm_msg = f"To delete the directory {pn[0]}\n?"
else:
temp = "\n\t".join(pn)
cfm_msg = f"To delete the following directories:\n\t{temp}\n?"
if _confirmed(prompt=cfm_msg, confirmation_required=confirmation_required):
for dir_pathname in dir_pathnames:
_delete_dir(dir_pathname, confirmation_required=False, verbose=verbose, **kwargs)