pyclean.traversal   A
last analyzed

Complexity

Total Complexity 17

Size/Duplication

Total Lines 73
Duplicated Lines 0 %

Importance

Changes 0
Metric Value
wmc 17
eloc 36
dl 0
loc 73
rs 10
c 0
b 0
f 0

3 Functions

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