pyclean.traversal.descend_and_clean()   B
last analyzed

Complexity

Conditions 8

Size

Total Lines 19
Code Lines 12

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 12
dl 0
loc 19
rs 7.3333
c 0
b 0
f 0
cc 8
nop 3
1
# SPDX-FileCopyrightText: 2020 Peter Bittner <[email protected]>
2
#
3
# SPDX-License-Identifier: GPL-3.0-or-later
4
5
"""Directory traversal and ignore pattern matching."""
6
7
from __future__ import annotations
8
9
import logging
10
import os
11
from pathlib import Path
12
13
from .runner import Runner
14
15
log = logging.getLogger(__name__)
16
17
18
def normalize(path_pattern: str) -> str:
19
    """
20
    Normalize path separators in a pattern for cross-platform support.
21
22
    On Windows, both forward slash and backslash are valid path separators.
23
    On Unix/Posix, only forward slash is valid (backslash can be part of filename).
24
    """
25
    return path_pattern.replace(os.sep, os.altsep or os.sep)
26
27
28
def should_ignore(pathname: str, ignore_patterns: list[str] | None) -> bool:
29
    """
30
    Check if a path should be ignored based on ignore patterns.
31
32
    Patterns can be:
33
    - Simple names like 'bar': matches any directory with that name
34
    - Paths like 'foo/bar': matches 'bar' directory inside 'foo' directory
35
      and also ignores everything inside that directory
36
    """
37
    if not ignore_patterns:
38
        return False
39
40
    path = Path(pathname)
41
42
    for pattern in ignore_patterns:
43
        pattern_parts = Path(normalize(pattern)).parts
44
        if len(pattern_parts) > 1:
45
            if len(path.parts) < len(pattern_parts):
46
                continue
47
            for i in range(len(path.parts) - len(pattern_parts) + 1):
48
                path_slice = path.parts[i : i + len(pattern_parts)]
49
                if path_slice == pattern_parts:
50
                    return True
51
        elif path.name == pattern:
52
            return True
53
    return False
54
55
56
def descend_and_clean(directory, file_types, dir_names):
57
    """
58
    Walk and descend a directory tree, cleaning up files of a certain type
59
    along the way. Only delete directories if they are empty, in the end.
60
    """
61
    for child in sorted(os.scandir(directory), key=lambda e: e.name):
62
        if child.is_file():
63
            if Path(child.path).suffix in file_types:
64
                Runner.unlink(Path(child.path))
65
        elif child.is_dir():
66
            if should_ignore(child.path, Runner.ignore):
67
                log.debug('Skipping %s', child.name)
68
            else:
69
                descend_and_clean(child.path, file_types, dir_names)
70
71
            if child.name in dir_names:
72
                Runner.rmdir(Path(child.path))
73
        else:
74
            log.debug('Ignoring %s (neither a file nor a folder)', child.name)
75