49
49
import shutil
50
50
import stat
51
51
import sys
52
+ import tempfile
52
53
from collections import deque
53
- from typing import IO , Any , Callable , Iterable , Iterator , List , Optional , TypeVar , Union
54
+ from typing import IO , Any , Callable , ContextManager , Iterable , Iterator , List , Optional , TypeVar , Union
54
55
55
56
# this package
57
+ from domdf_python_tools .compat import nullcontext
56
58
from domdf_python_tools .typing import JsonLibrary , PathLike
57
59
58
60
__all__ = [
75
77
"traverse_to_file" ,
76
78
"matchglob" ,
77
79
"unwanted_dirs" ,
80
+ "TemporaryPathPlus" ,
78
81
]
79
82
80
83
newline_default = object ()
@@ -449,6 +452,8 @@ def write_lines(
449
452
data : Iterable [str ],
450
453
encoding : Optional [str ] = "UTF-8" ,
451
454
errors : Optional [str ] = None ,
455
+ * ,
456
+ trailing_whitespace : bool = False
452
457
) -> None :
453
458
"""
454
459
Write the given list of lines to the file without trailing whitespace.
@@ -458,9 +463,19 @@ def write_lines(
458
463
:param data:
459
464
:param encoding: The encoding to write to the file in.
460
465
:param errors:
466
+ :param trailing_whitespace: If :py:obj:`True` trailing whitespace is preserved.
467
+
468
+ .. versionchanged:: 2.4.0 Added the ``trailing_whitespace`` option.
461
469
"""
462
470
463
- return self .write_clean ('\n ' .join (data ), encoding = encoding , errors = errors )
471
+ if trailing_whitespace :
472
+ data = list (data )
473
+ if data [- 1 ].strip ():
474
+ data .append ('' )
475
+
476
+ self .write_text ('\n ' .join (data ), encoding = encoding , errors = errors )
477
+ else :
478
+ self .write_clean ('\n ' .join (data ), encoding = encoding , errors = errors )
464
479
465
480
def read_text (
466
481
self ,
@@ -925,3 +940,52 @@ def matchglob(filename: PathLike, pattern: str):
925
940
continue
926
941
else :
927
942
return False
943
+
944
+
945
+ class TemporaryPathPlus (tempfile .TemporaryDirectory ):
946
+ """
947
+ Securely creates a temporary directory using the same rules as :func:`tempfile.mkdtemp`.
948
+ The resulting object can be used as a context manager.
949
+ On completion of the context or destruction of the object
950
+ the newly created temporary directory and all its contents are removed from the filesystem.
951
+
952
+ Unlike :func:`tempfile.TemporaryDirectory` this class is based around a :class:`~.PathPlus` object.
953
+
954
+ .. versionadded:: 2.4.0
955
+ """
956
+
957
+ name : PathPlus # type: ignore
958
+ """
959
+ The temporary directory itself.
960
+
961
+ This will be assigned to the target of the :keyword:`as` clause if the :class:`~.TemporaryPathPlus`
962
+ is used as a context manager.
963
+ """
964
+
965
+ def __init__ (
966
+ self ,
967
+ suffix : Optional [str ] = None ,
968
+ prefix : Optional [str ] = None ,
969
+ dir : Optional [PathLike ] = None , # noqa: A002 # pylint: disable=redefined-builtin
970
+ ) -> None :
971
+
972
+ super ().__init__ (suffix , prefix , dir )
973
+ self .name = PathPlus (self .name )
974
+
975
+ def cleanup (self ) -> None :
976
+ """
977
+ Cleanup the temporary directory by removing it and its contents.
978
+
979
+ If the :class:`~.TemporaryPathPlus` is used as a context manager
980
+ this is called when leaving the :keyword:`with` block.
981
+ """
982
+
983
+ context : ContextManager
984
+
985
+ if sys .platform == "win32" :
986
+ context = contextlib .suppress (PermissionError , NotADirectoryError )
987
+ else :
988
+ context = nullcontext ()
989
+
990
+ with context :
991
+ super ().cleanup ()
0 commit comments