Skip to content

Commit 2eb48ba

Browse files
committed
Remove recursion from IFileTree::glob() and clean code.
1 parent 74cac72 commit 2eb48ba

File tree

1 file changed

+93
-45
lines changed

1 file changed

+93
-45
lines changed

src/ifiletree.cpp

Lines changed: 93 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
#include "ifiletree.h"
22

33
#include <algorithm>
4+
#include <ranges>
45
#include <span>
56
#include <stack>
67

@@ -181,13 +182,12 @@ void IFileTree::walk(
181182

182183
std::generator<std::shared_ptr<const FileTreeEntry>> IFileTree::walk() const
183184
{
184-
185185
std::stack<std::shared_ptr<const FileTreeEntry>> stack;
186186

187-
// We start by pushing all the entries in this tree, this avoid having to do extra
188-
// check later for avoid leading separator:
189-
for (auto rit = rbegin(); rit != rend(); ++rit) {
190-
stack.push(*rit);
187+
// we start by pushing all the entries in this tree, this avoid having to do extra
188+
// check later to avoid leading separator
189+
for (const auto& entry : entries() | std::views::reverse) {
190+
stack.push(entry);
191191
}
192192

193193
while (!stack.empty()) {
@@ -198,93 +198,141 @@ std::generator<std::shared_ptr<const FileTreeEntry>> IFileTree::walk() const
198198

199199
if (entry->isDir()) {
200200
auto tree = entry->astree();
201-
for (auto rit = tree->rbegin(); rit != tree->rend(); ++rit) {
202-
stack.push(*rit);
201+
for (const auto& child : tree->entries() | std::views::reverse) {
202+
stack.push(child);
203203
}
204204
}
205205
}
206206
}
207207

208208
namespace
209209
{
210+
using glob_stack_t = std::stack<
211+
std::pair<std::shared_ptr<const FileTreeEntry>, std::span<QRegularExpression>>>;
210212

213+
// check the given entry against the list of patterns and add new (entry, patterns) to
214+
// the given stack
215+
//
211216
std::generator<std::shared_ptr<const FileTreeEntry>>
212-
ifiletree_glob_impl(std::shared_ptr<const FileTreeEntry> entry,
213-
std::span<std::pair<QString, QRegularExpression>> const& patterns)
217+
ifiletree_glob_impl_step(glob_stack_t& glob_stack,
218+
std::shared_ptr<const FileTreeEntry> entry,
219+
std::span<QRegularExpression> const& patterns)
214220
{
221+
// no more patterns, nothing to do
215222
if (patterns.size() == 0) {
216223
co_return;
217224
}
218225

219-
if (patterns[0].first == "**") {
220-
//
226+
// special handling if the pattern is empty (correspond to a '**' glob)
227+
if (patterns[0].pattern() == "") {
228+
229+
// if there are more patterns after '**', we need to check the entry again, e.g.,
230+
// if the entry name is 'x' and the pattern is '**/x' to match it
221231
if (patterns.size() != 1) {
222-
co_yield std::ranges::elements_of(
223-
ifiletree_glob_impl(entry, patterns.subspan(1)));
232+
glob_stack.emplace(entry, patterns.subspan(1));
224233
}
225234

226-
if (entry->isDir()) {
227-
if (patterns.size() == 1) {
228-
co_yield entry;
229-
}
235+
// if the entry is a file, there is nothing to do with '**'
236+
if (entry->isFile()) {
237+
co_return;
238+
}
230239

231-
for (const auto& child : *entry->astree()) {
232-
const auto subPatterns = child->isDir() ? patterns : patterns.subspan(1);
233-
co_yield std::ranges::elements_of(ifiletree_glob_impl(child, subPatterns));
234-
}
240+
// if this is the end of the patterns list, we need to yield the current entry
241+
// since it is a directory
242+
if (patterns.size() == 1) {
243+
co_yield entry;
235244
}
236245

237-
} else if (patterns[0].second.match(entry->name()).hasMatch()) {
238-
// only one thing remain so we yield it
246+
// recurse over childs, but for directories, we need to keep the leading '**' in
247+
// the list of patterns since '**' can match multiple level of directories
248+
auto tree = entry->astree();
249+
for (auto rit = tree->rbegin(); rit != tree->rend(); ++rit) {
250+
glob_stack.emplace(*rit, (*rit)->isDir() ? patterns : patterns.subspan(1));
251+
}
252+
}
253+
// otherwise (if the first patterns is not '**'), we simply check if we have a match
254+
else if (patterns[0].match(entry->name()).hasMatch()) {
255+
// this was the last pattern and we have a match, so we yield the current entry,
256+
// not that this will yield intermediate matching directory, but this is expected
239257
if (patterns.size() == 1) {
240258
co_yield entry;
259+
}
241260

242-
} else if (entry->isDir()) {
261+
// if the entry is not a directory, we have nothing more to do
262+
if (entry->isFile()) {
263+
co_return;
264+
}
243265

244-
if (patterns.size() == 2 && patterns[1].first == "**") {
245-
co_yield entry;
246-
}
266+
// if all that remain after this pattern is a '**', we need to yield the current
267+
// entry since '**' can also match an empty succession of directories, e.g. 'a/b'
268+
// is matched by 'a/b/**'
269+
if (patterns.size() == 2 && patterns[1].pattern() == "") {
270+
co_yield entry;
271+
}
247272

248-
// if we have more patterns, we need to go deeper
249-
for (const auto& child : *entry->astree()) {
250-
co_yield std::ranges::elements_of(
251-
ifiletree_glob_impl(child, patterns.subspan(1)));
252-
}
273+
// we then need to recurse over
274+
auto tree = entry->astree();
275+
for (auto rit = tree->rbegin(); rit != tree->rend(); ++rit) {
276+
glob_stack.emplace(*rit, patterns.subspan(1));
253277
}
254278
}
255279
}
256280

281+
// main function for IFileTree::glob - this function simply manages the stack of of
282+
// entry / patterns to handle, and then falls back to ifiletree_glob_impl_step
283+
//
284+
std::generator<std::shared_ptr<const FileTreeEntry>>
285+
ifiletree_glob_impl(std::shared_ptr<const FileTreeEntry> entry,
286+
std::span<QRegularExpression> const& patterns)
287+
{
288+
glob_stack_t stack;
289+
stack.emplace(entry, patterns);
290+
291+
while (!stack.empty()) {
292+
const auto [entry, patterns] = stack.top();
293+
stack.pop();
294+
co_yield std::ranges::elements_of(
295+
ifiletree_glob_impl_step(stack, entry, patterns));
296+
}
297+
}
298+
299+
// actual implementation for IFileTree::glob
300+
//
257301
std::generator<std::shared_ptr<const FileTreeEntry>>
258302
ifiletree_glob_impl(std::shared_ptr<const IFileTree> tree, QString pattern)
259303
{
260-
// replace \\ by /
304+
// replace \\ by / to simply handling
261305
pattern = pattern.trimmed().replace("\\", "/");
262306

263-
// reduce **/** to ** when possible
307+
// reduce successions of **/** to **, this makes it easier to handle it in the
308+
// actual implementation
264309
pattern = pattern.replace(QRegularExpression("(\\*\\*/)*\\*\\*"), "**");
265310

266-
// handle special case
311+
// we are going to match directly starting from the child of the tree, so we need
312+
// to handle the root here
313+
//
314+
// note: this is the only pattern that can match the tree itself
267315
if (pattern == "**") {
268316
co_yield tree;
269317
}
270318

271-
// split pattern into blocks
272-
std::vector<std::pair<QString, QRegularExpression>> patterns;
319+
// split pattern into blocks, we keep
320+
const auto regexOptions = FileNameComparator::CaseSensitivity == Qt::CaseInsensitive
321+
? QRegularExpression::CaseInsensitiveOption
322+
: QRegularExpression::NoPatternOption;
323+
std::vector<QRegularExpression> patterns;
273324
for (const auto& part : pattern.split("/")) {
274325
if (part == "**") {
275-
patterns.emplace_back(part, QRegularExpression());
326+
// for '**' pattern, push an empty regex
327+
patterns.emplace_back();
276328
} else {
277-
QString regexStr = QRegularExpression::wildcardToRegularExpression(
329+
const auto regexStr = QRegularExpression::wildcardToRegularExpression(
278330
part, QRegularExpression::NonPathWildcardConversion);
279-
patterns.emplace_back(
280-
part,
281-
QRegularExpression(regexStr, FileNameComparator::CaseSensitivity ==
282-
Qt::CaseInsensitive
283-
? QRegularExpression::CaseInsensitiveOption
284-
: QRegularExpression::NoPatternOption));
331+
patterns.emplace_back(regexStr, regexOptions);
285332
}
286333
}
287334

335+
// fall back to the main glob function for the child of this tree
288336
for (const auto& child : *tree) {
289337
co_yield std::ranges::elements_of(ifiletree_glob_impl(child, patterns));
290338
}

0 commit comments

Comments
 (0)