antsibull-nox 0.0.1__py3-none-any.whl → 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.
antsibull_nox/paths.py ADDED
@@ -0,0 +1,201 @@
1
+ # Author: Felix Fontein <felix@fontein.de>
2
+ # GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or
3
+ # https://www.gnu.org/licenses/gpl-3.0.txt)
4
+ # SPDX-License-Identifier: GPL-3.0-or-later
5
+ # SPDX-FileCopyrightText: 2025, Ansible Project
6
+
7
+ """
8
+ Path utilities.
9
+ """
10
+
11
+ from __future__ import annotations
12
+
13
+ import atexit
14
+ import functools
15
+ import os
16
+ import shutil
17
+ import tempfile
18
+ from pathlib import Path
19
+
20
+ from antsibull_fileutils.copier import Copier, GitCopier
21
+ from antsibull_fileutils.vcs import detect_vcs, list_git_files
22
+
23
+
24
+ def find_data_directory() -> Path:
25
+ """
26
+ Retrieve the directory for antsibull_nox.data on disk.
27
+ """
28
+ return Path(__file__).parent / "data"
29
+
30
+
31
+ def match_path(path: str, is_file: bool, paths: list[str]) -> bool:
32
+ """
33
+ Check whether a path (that is a file or not) matches a given list of paths.
34
+ """
35
+ for check in paths:
36
+ if check == path:
37
+ return True
38
+ if not is_file:
39
+ if not check.endswith("/"):
40
+ check += "/"
41
+ if path.startswith(check):
42
+ return True
43
+ return False
44
+
45
+
46
+ def restrict_paths(paths: list[str], restrict: list[str]) -> list[str]:
47
+ """
48
+ Restrict a list of paths with a given set of restrictions.
49
+ """
50
+ result = []
51
+ for path in paths:
52
+ is_file = os.path.isfile(path)
53
+ if not is_file and not path.endswith("/"):
54
+ path += "/"
55
+ if not match_path(path, is_file, restrict):
56
+ if not is_file:
57
+ for check in restrict:
58
+ if check.startswith(path) and os.path.exists(check):
59
+ result.append(check)
60
+ continue
61
+ result.append(path)
62
+ return result
63
+
64
+
65
+ def _scan_remove_paths(
66
+ path: str, remove: list[str], extensions: list[str] | None
67
+ ) -> list[str]:
68
+ result = []
69
+ for root, dirs, files in os.walk(path, topdown=True):
70
+ if not root.endswith("/"):
71
+ root += "/"
72
+ if match_path(root, False, remove):
73
+ continue
74
+ if all(not check.startswith(root) for check in remove):
75
+ dirs[:] = []
76
+ result.append(root)
77
+ continue
78
+ for file in files:
79
+ if extensions and os.path.splitext(file)[1] not in extensions:
80
+ continue
81
+ filepath = os.path.normpath(os.path.join(root, file))
82
+ if not match_path(filepath, True, remove):
83
+ result.append(filepath)
84
+ for directory in list(dirs):
85
+ if directory == "__pycache__":
86
+ continue
87
+ dirpath = os.path.normpath(os.path.join(root, directory))
88
+ if match_path(dirpath, False, remove):
89
+ dirs.remove(directory)
90
+ continue
91
+ return result
92
+
93
+
94
+ def remove_paths(
95
+ paths: list[str], remove: list[str], extensions: list[str] | None
96
+ ) -> list[str]:
97
+ """
98
+ Restrict a list of paths by removing paths.
99
+
100
+ If ``extensions`` is specified, only files matching this extension
101
+ will be considered when files need to be explicitly enumerated.
102
+ """
103
+ result = []
104
+ for path in paths:
105
+ is_file = os.path.isfile(path)
106
+ if not is_file and not path.endswith("/"):
107
+ path += "/"
108
+ if match_path(path, is_file, remove):
109
+ continue
110
+ if not is_file and any(check.startswith(path) for check in remove):
111
+ result.extend(_scan_remove_paths(path, remove, extensions))
112
+ continue
113
+ result.append(path)
114
+ return result
115
+
116
+
117
+ def filter_paths(
118
+ paths: list[str],
119
+ /,
120
+ remove: list[str] | None = None,
121
+ restrict: list[str] | None = None,
122
+ extensions: list[str] | None = None,
123
+ ) -> list[str]:
124
+ """
125
+ Modifies a list of paths by restricting to and/or removing paths.
126
+ """
127
+ if restrict:
128
+ paths = restrict_paths(paths, restrict)
129
+ if remove:
130
+ paths = remove_paths(paths, remove, extensions)
131
+ return [path for path in paths if os.path.exists(path)]
132
+
133
+
134
+ @functools.cache
135
+ def list_all_files() -> list[Path]:
136
+ """
137
+ List all files of interest in the repository.
138
+ """
139
+ directory = Path.cwd()
140
+ vcs = detect_vcs(directory)
141
+ if vcs == "git":
142
+ return [directory / path.decode("utf-8") for path in list_git_files(directory)]
143
+ result = []
144
+ for root, dirs, files in os.walk(directory, topdown=True):
145
+ root_path = Path(root)
146
+ for file in files:
147
+ result.append(root_path / file)
148
+ if root_path == directory and ".nox" in dirs:
149
+ dirs.remove(".nox")
150
+ return result
151
+
152
+
153
+ def remove_path(path: Path) -> None:
154
+ """
155
+ Delete a path.
156
+ """
157
+ if not path.is_symlink() and path.is_dir():
158
+ shutil.rmtree(path)
159
+ elif path.exists():
160
+ path.unlink()
161
+
162
+
163
+ def copy_collection(source: Path, destination: Path) -> None:
164
+ """
165
+ Copy a collection from source to destination.
166
+
167
+ Automatically detect supported VCSs and use their information to avoid
168
+ copying ignored files.
169
+ """
170
+ if destination.exists():
171
+ remove_path(destination)
172
+ vcs = detect_vcs(source)
173
+ copier = {
174
+ "none": Copier,
175
+ "git": GitCopier,
176
+ }.get(vcs, Copier)()
177
+ copier.copy(source, destination, exclude_root=[".nox", ".tox"])
178
+
179
+
180
+ def create_temp_directory(basename: str) -> Path:
181
+ """
182
+ Create a temporary directory outside the nox tree.
183
+ """
184
+ directory = tempfile.mkdtemp(prefix=basename)
185
+ path = Path(directory)
186
+
187
+ def cleanup() -> None:
188
+ remove_path(path)
189
+
190
+ atexit.register(cleanup)
191
+ return path
192
+
193
+
194
+ __all__ = [
195
+ "copy_collection",
196
+ "create_temp_directory",
197
+ "filter_paths",
198
+ "find_data_directory",
199
+ "list_all_files",
200
+ "remove_path",
201
+ ]