• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

bleachbit / bleachbit / 29651580603

18 Jul 2026 04:14PM UTC coverage: 76.417% (+0.007%) from 76.41%
29651580603

push

github

web-flow
Merge pull request #2220 from XhmikosR/patch-1

Bootstrap.py: fix unescaped period character in regex

1 of 1 new or added line in 1 file covered. (100.0%)

331 existing lines in 10 files now uncovered.

8195 of 10724 relevant lines covered (76.42%)

1.32 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

93.85
/bleachbit/DeepScan.py
1
# vim: ts=4:sw=4:expandtab
2

3
# BleachBit
4
# Copyright (C) 2008-2025 Andrew Ziem
5
# https://www.bleachbit.org
6
#
7
# This program is free software: you can redistribute it and/or modify
8
# it under the terms of the GNU General Public License as published by
9
# the Free Software Foundation, either version 3 of the License, or
10
# (at your option) any later version.
11
#
12
# This program is distributed in the hope that it will be useful,
13
# but WITHOUT ANY WARRANTY; without even the implied warranty of
14
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15
# GNU General Public License for more details.
16
#
17
# You should have received a copy of the GNU General Public License
18
# along with this program.  If not, see <http://www.gnu.org/licenses/>.
19

20

21
"""
22
Scan directory tree for files to delete
23
"""
24

25
import logging
2✔
26
import os
2✔
27
import re
2✔
28
import time
2✔
29
import unicodedata
2✔
30
from collections import namedtuple
2✔
31
from bleachbit import FS_SCAN_RE_FLAGS, IS_MAC
2✔
32
from . import Command
2✔
33
from .FileUtilities import whitelisted
2✔
34

35

36
def normalized_walk(top, **kwargs):
2✔
37
    """
38
    macOS uses decomposed UTF-8 to store filenames. This functions
39
    is like `os.walk` but recomposes those decomposed filenames on
40
    macOS
41
    """
42
    try:
2✔
43
        from scandir import walk
2✔
44
    except:
2✔
45
        # there is a warning in FileUtilities, so don't warn again here
46
        from os import walk
2✔
47
    if IS_MAC:
2✔
UNCOV
48
        for dirpath, dirnames, filenames in walk(top, **kwargs):
×
49
            yield dirpath, dirnames, [
×
50
                unicodedata.normalize('NFC', fn)
51
                for fn in filenames
52
            ]
53
    else:
54
        yield from walk(top, **kwargs)
2✔
55

56

57
Search = namedtuple(
2✔
58
    'Search', ['command', 'regex', 'nregex', 'wholeregex', 'nwholeregex'])
59
Search.__new__.__defaults__ = (None,) * len(Search._fields)
2✔
60

61

62
class CompiledSearch:
2✔
63
    """Compiled search condition"""
64

65
    def __init__(self, search):
2✔
66
        self.command = search.command
2✔
67

68
        def re_compile(regex):
2✔
69
            return re.compile(regex, FS_SCAN_RE_FLAGS) if regex else None
2✔
70

71
        self.regex = re_compile(search.regex)
2✔
72
        self.nregex = re_compile(search.nregex)
2✔
73
        self.wholeregex = re_compile(search.wholeregex)
2✔
74
        self.nwholeregex = re_compile(search.nwholeregex)
2✔
75

76
    def match(self, dirpath, filename):
2✔
77
        if self.regex and not self.regex.search(filename):
2✔
78
            return None
2✔
79

80
        if self.nregex and self.nregex.search(filename):
2✔
UNCOV
81
            return None
×
82

83
        full_path = os.path.join(dirpath, filename)
2✔
84

85
        if self.wholeregex and not self.wholeregex.search(full_path):
2✔
86
            return None
2✔
87

88
        if self.nwholeregex and self.nwholeregex.search(full_path):
2✔
89
            return None
2✔
90

91
        return full_path
2✔
92

93

94
class DeepScan:
2✔
95

96
    """Advanced directory tree scan"""
97

98
    def __init__(self, searches):
2✔
99
        self.roots = []
2✔
100
        self.searches = searches
2✔
101

102
    def scan(self):
2✔
103
        """Perform requested searches and yield each match"""
104
        logging.getLogger(__name__).debug(
2✔
105
            'DeepScan.scan: searches=%s', str(self.searches))
106
        yield_time = time.time()
2✔
107

108
        for (top, searches) in self.searches.items():
2✔
109
            # This skips top-level directories that are in the keep list
110
            # to reduce unnecessary work.
111
            if whitelisted(top):
2✔
UNCOV
112
                continue
×
113
            compiled_searches = [CompiledSearch(s) for s in searches]
2✔
114
            for (dirpath, dirnames, filenames) in normalized_walk(top):
2✔
115
                # This filters out subdirectories that are in the keep list
116
                # to reduce unnecessary work.
117
                dirnames[:] = [
2✔
118
                    dirname
119
                    for dirname in dirnames
120
                    if not whitelisted(os.path.join(dirpath, dirname))
121
                ]
122
                for c in compiled_searches:
2✔
123
                    # fixme, don't match filename twice
124
                    for filename in filenames:
2✔
125
                        full_name = c.match(dirpath, filename)
2✔
126
                        if full_name is None:
2✔
127
                            continue
2✔
128
                        # fixme: support other commands
129
                        if c.command == 'delete':
2✔
130
                            yield Command.Delete(full_name)
2✔
131
                        elif c.command == 'shred':
2✔
132
                            yield Command.Shred(full_name)
2✔
133

134
                if time.time() - yield_time > 0.25:
2✔
135
                    # allow GTK+ to process the idle loop
136
                    yield True
2✔
137
                    yield_time = time.time()
2✔
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE TRIAL · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc