4949import shutil
5050import stat
5151import sys
52+ import tempfile
5253from 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
5455
5556# this package
57+ from domdf_python_tools .compat import nullcontext
5658from domdf_python_tools .typing import JsonLibrary , PathLike
5759
5860__all__ = [
7577 "traverse_to_file" ,
7678 "matchglob" ,
7779 "unwanted_dirs" ,
80+ "TemporaryPathPlus" ,
7881 ]
7982
8083newline_default = object ()
@@ -449,6 +452,8 @@ def write_lines(
449452 data : Iterable [str ],
450453 encoding : Optional [str ] = "UTF-8" ,
451454 errors : Optional [str ] = None ,
455+ * ,
456+ trailing_whitespace : bool = False
452457 ) -> None :
453458 """
454459 Write the given list of lines to the file without trailing whitespace.
@@ -458,9 +463,19 @@ def write_lines(
458463 :param data:
459464 :param encoding: The encoding to write to the file in.
460465 :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.
461469 """
462470
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 )
464479
465480 def read_text (
466481 self ,
@@ -925,3 +940,52 @@ def matchglob(filename: PathLike, pattern: str):
925940 continue
926941 else :
927942 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