• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1#!/usr/bin/env python3
2#
3# Copyright (C) 2022 The Android Open Source Project
4#
5# Licensed under the Apache License, Version 2.0 (the "License");
6# you may not use this file except in compliance with the License.
7# You may obtain a copy of the License at
8#
9#      http://www.apache.org/licenses/LICENSE-2.0
10#
11# Unless required by applicable law or agreed to in writing, software
12# distributed under the License is distributed on an "AS IS" BASIS,
13# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14# See the License for the specific language governing permissions and
15# limitations under the License.
16"""Module for finding files in a tree"""
17
18from collections.abc import Iterator
19
20import os
21import sys
22
23
24class FileFinder:
25    def __init__(self, filename: str, ignore_paths=(), prune_hidden_dirs=True):
26        """Lightweight Single-threaded Non-cached file finder
27
28        Arguments:
29            filename: filename, should not contain directory prefixes
30            ignore_paths: filepath of dirs to ignore (e.g.
31                          $ANDROID_BUILD_TOP/out)
32            prune_hidden_dirs: whether hidden dirs (beginning with a dot
33                          character) should be ignored
34        """
35        self.filename = filename
36        self.ignore_paths = ignore_paths
37        if prune_hidden_dirs and os.name != "posix":
38            raise Exception(
39                "Skipping hidden directories is not supported on this platform"
40            )
41        self.prune_hidden_dirs = prune_hidden_dirs
42
43    def _is_hidden_dir(self, dirname: str) -> bool:
44        """Return true if directory is a hidden directory"""
45        return self.prune_hidden_dirs and dirname.startswith(".")
46
47    def find(self, path: str, search_depth: int) -> Iterator[str]:
48        """Search directory rooted at <path>
49
50        Arguments:
51            path: Search root
52            search_depth: Search maxdepth (relative to root). Search will be
53                          pruned beyond this level
54
55        Returns:
56            An iterator of filepaths that match <filename>
57        """
58        if search_depth < 0:
59            return
60
61        subdirs = []
62        with os.scandir(path) as it:
63            for dirent in sorted(it, key=lambda x: x.name):
64                try:
65                    if dirent.is_file():
66                        if dirent.name == self.filename:
67                            yield dirent.path
68                    elif dirent.is_dir():
69                        if (dirent.path not in self.ignore_paths
70                                and not self._is_hidden_dir(dirent.name)):
71                            subdirs.append(dirent.path)
72                except OSError as ex:
73                    # Log the exception, but continue traversing
74                    print(ex, file=sys.stderr)
75        for subdir in subdirs:
76            yield from self.find(subdir, search_depth - 1)
77