1
1
#include " ifiletree.h"
2
2
3
3
#include < algorithm>
4
+ #include < ranges>
4
5
#include < span>
5
6
#include < stack>
6
7
@@ -181,13 +182,12 @@ void IFileTree::walk(
181
182
182
183
std::generator<std::shared_ptr<const FileTreeEntry>> IFileTree::walk () const
183
184
{
184
-
185
185
std::stack<std::shared_ptr<const FileTreeEntry>> stack;
186
186
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 );
191
191
}
192
192
193
193
while (!stack.empty ()) {
@@ -198,93 +198,141 @@ std::generator<std::shared_ptr<const FileTreeEntry>> IFileTree::walk() const
198
198
199
199
if (entry->isDir ()) {
200
200
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 );
203
203
}
204
204
}
205
205
}
206
206
}
207
207
208
208
namespace
209
209
{
210
+ using glob_stack_t = std::stack<
211
+ std::pair<std::shared_ptr<const FileTreeEntry>, std::span<QRegularExpression>>>;
210
212
213
+ // check the given entry against the list of patterns and add new (entry, patterns) to
214
+ // the given stack
215
+ //
211
216
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)
214
220
{
221
+ // no more patterns, nothing to do
215
222
if (patterns.size () == 0 ) {
216
223
co_return ;
217
224
}
218
225
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
221
231
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 ));
224
233
}
225
234
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
+ }
230
239
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;
235
244
}
236
245
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
239
257
if (patterns.size () == 1 ) {
240
258
co_yield entry;
259
+ }
241
260
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
+ }
243
265
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
+ }
247
272
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 ));
253
277
}
254
278
}
255
279
}
256
280
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
+ //
257
301
std::generator<std::shared_ptr<const FileTreeEntry>>
258
302
ifiletree_glob_impl (std::shared_ptr<const IFileTree> tree, QString pattern)
259
303
{
260
- // replace \\ by /
304
+ // replace \\ by / to simply handling
261
305
pattern = pattern.trimmed ().replace (" \\ " , " /" );
262
306
263
- // reduce **/** to ** when possible
307
+ // reduce successions of **/** to **, this makes it easier to handle it in the
308
+ // actual implementation
264
309
pattern = pattern.replace (QRegularExpression (" (\\ *\\ */)*\\ *\\ *" ), " **" );
265
310
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
267
315
if (pattern == " **" ) {
268
316
co_yield tree;
269
317
}
270
318
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;
273
324
for (const auto & part : pattern.split (" /" )) {
274
325
if (part == " **" ) {
275
- patterns.emplace_back (part, QRegularExpression ());
326
+ // for '**' pattern, push an empty regex
327
+ patterns.emplace_back ();
276
328
} else {
277
- QString regexStr = QRegularExpression::wildcardToRegularExpression (
329
+ const auto regexStr = QRegularExpression::wildcardToRegularExpression (
278
330
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);
285
332
}
286
333
}
287
334
335
+ // fall back to the main glob function for the child of this tree
288
336
for (const auto & child : *tree) {
289
337
co_yield std::ranges::elements_of (ifiletree_glob_impl (child, patterns));
290
338
}
0 commit comments