pyclean.traversal   A
last analyzed

Complexity

Total Complexity 17

Size/Duplication

Total Lines 75
Duplicated Lines 0 %

Importance

Changes 0
Metric Value
wmc 17
eloc 37
dl 0
loc 75
rs 10
c 0
b 0
f 0

3 Functions

Rating   Name   Duplication   Size   Complexity  
B should_ignore() 0 26 8
B descend_and_clean() 0 19 8
A normalize() 0 8 1
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