Coverage for /usr/local/lib/python3.10/site-packages/opal_common-0.0.0-py3.10.egg/opal_common/paths.py: 30%
51 statements
« prev ^ index » next coverage.py v7.15.2, created at 2026-10-10 11:54 +0000
« prev ^ index » next coverage.py v7.15.2, created at 2026-10-10 11:54 +0000
1from pathlib import Path
2from typing import List, Set, Union
4from opal_common.utils import sorted_list_from_set
7class PathUtils:
8 @staticmethod
9 def intermediate_directories(paths: List[Path]) -> List[Path]:
10 """Returns the set of all parent directories for a list of paths.
12 i.e: calculate all partial paths that are directories.
13 """
14 directories = set()
15 for path in paths:
16 directories.update(path.parents)
17 return sorted_list_from_set(directories)
19 @staticmethod
20 def is_child_of_directories(path: Path, directories: Set[Path]) -> bool:
21 """Whether the input path is a child of one of the input
22 directories."""
23 return bool(directories & set(path.parents))
25 @staticmethod
26 def filter_children_paths_of_directories(
27 paths: List[Path], directories: Set[Path]
28 ) -> List[Path]:
29 """Returns only paths in :paths that are children of one of the paths
30 in :directories."""
31 return [
32 path
33 for path in paths
34 if PathUtils.is_child_of_directories(path, directories)
35 ]
37 @staticmethod
38 def non_intersecting_directories(paths: List[Path]) -> Set[Path]:
39 """Gets a list of paths (directories), and returns a set of directories
40 that are non-intersecting, meaning no directory in the set is a parent
41 of another directory in the set (i.e: parent directories "swallow"
42 their subdirectories)."""
43 output_paths = set()
44 for candidate in paths:
45 if set(candidate.parents) & output_paths:
46 # the next candidate is covered by a parent which is already in output -> SKIP
47 # or the next candidate is already in the list
48 continue
49 for out_path in list(output_paths):
50 # the next candidate can displace a child from the output
51 if candidate in list(out_path.parents):
52 output_paths.remove(out_path)
53 output_paths.add(candidate)
54 return output_paths
56 @staticmethod
57 def sort_paths_according_to_explicit_sorting(
58 unsorted_paths: List[Path], explicit_sorting: List[Path]
59 ) -> List[Path]:
60 """The way this sorting works, is assuming that explicit_sorting does
61 NOT necessarily contains all the paths found in the original list.
63 We must ensure that all items in unsorted_paths must also exist
64 in the output list.
65 """
66 unsorted = unsorted_paths.copy()
68 sorted_paths: List[Path] = []
69 for path in explicit_sorting:
70 try:
71 # we look for Path objects and not str for normalization of the path
72 found_path: Path = unsorted.pop(unsorted.index(path))
73 sorted_paths.append(found_path)
74 except ValueError:
75 continue # skip, not found in the original list
77 # add the remainder to the end of the sorted list
78 sorted_paths.extend(unsorted)
80 return sorted_paths
82 @staticmethod
83 def glob_style_match_path_to_list(path: str, match_paths: List[str]):
84 """
85 Check if given path matches any of the match_paths either via glob style matching or by being nested under - when the match path ends with "/**"
86 return the match path if there's a match, and None otherwise
87 """
88 # check if any of our ignore paths match the given path
89 for match_path in match_paths:
90 # if the path is the root "/", then it matches any path
91 if match_path == "/" or match_path == "/**":
92 return match_path
93 # if the path is indicated as a parent via "/**" at the end
94 if match_path.endswith("/**"):
95 # check if the path is under the parent
96 if (path + "/").startswith((match_path[:-3] + "/")):
97 return match_path
98 # otherwise check for simple (non-recursive glob matching)
99 else:
100 path_object = Path(path)
101 if path_object.match(match_path):
102 return match_path
103 # if no match - this path shouldn't be ignored
104 return None