swdir
Swiftly traverse and scan directories recursively. Sway πͺ, swing π· or swim πͺΌ in directories.
swdir is a small crate that supplies the raw material for a
Directory Tree widget β path listings, recursive walks, typed entries.
It does not draw the tree, watch for file-change events, cache
results, or interpret file contents. Those responsibilities belong to
the GUI layer on top.
Two entry points
| Use case | API |
|---|---|
| Recursive walk (batch tools, CLIs) | Swdir::walk |
| Lazy-loading one-folder scan (GUIs) | scan_dir / scan_dir_with_options |
Both share the same SortOrder concept when a reproducible display
order matters.
Quick start β recursive walk
use Swdir;
Quick start β lazy loading (Directory Tree widgets)
scan_dir_with_options is the intended entry point for a GUI tree: call
it every time the user expands a folder. One directory per call, no
recursion, no hidden syscalls.
use Path;
use ;
Raw OS order? Use bare scan_dir(path) β no options, no sort, cheapest.
Ordering
Both Swdir::walk and scan_dir_with_options accept a SortOrder:
| Variant | Meaning |
|---|---|
SortOrder::Filesystem |
OS readdir order; cheapest, but not stable across runs / filesystems. |
SortOrder::NameAscDirsFirst |
Directories first, then files, each group sorted by name ascending. Default. |
use ;
Only two orderings, on purpose. If you need something else (size,
mtime, extensionβ¦), sort the returned Vec at the call site.
Recursion
Recurse is an enum with three meaningful states:
use ;
Filtering
Filtering is part of the default filter feature. The model is built
around two types:
FilterRuleβ the condition (hidden, extension, path prefix, kind, depth)Decisionβ what to do about a given entry, split into two axes:includeβ should it appear in the results?descendβ should the walker look inside it?
Rules compose with AND. Swdir::new() already installs one rule β
FilterRule::SkipHidden β so the common case needs no extra
configuration.
use ;
To see hidden entries, clear the default rules first:
use Swdir;
include vs descend
Separating the two axes means a rule can hide a directory from the
result tree while still descending through it. For instance,
FilterRule::only_kind(EntryKind::File) drops directories from the
output but keeps walking into them so nested files remain reachable β
which is what callers usually want when they say "give me just the
files under here".
What ships in 0.11
| Rule | Purpose |
|---|---|
FilterRule::SkipHidden |
Drop entries whose name starts with . (plus Windows hidden-bit). |
FilterRule::OnlyKinds(..) |
Keep only files / dirs / symlinks. |
FilterRule::ExtensionAllowlist(..) |
Keep files with these extensions. |
FilterRule::ExtensionDenylist(..) |
Drop files with these extensions. |
FilterRule::UnderPath(..) |
Restrict to entries under a path prefix. |
FilterRule::NotUnderPath(..) |
Exclude everything under a path prefix. |
FilterRule::MaxDepth(n) |
Cap depth of entries and descent. |
The enum is #[non_exhaustive], so future additions won't break
existing match statements. Advanced filter families (regex, glob,
metadata predicates, arbitrary closures) are deliberately out of scope
β bring those needs upstream if they come up in practice.
Error handling
Swdir::walk() returns a WalkReport. Unreadable directories go into
report.errors instead of being printed to stderr:
use Swdir;
scan_dir / scan_dir_with_options are atomic instead β on the
first I/O failure they return Err(ScanError::Io { .. }). The trade-off
matches the use case: batch walks want partial results, a single
GUI-node expansion wants all-or-nothing.
DirEntry β GUI-friendly helpers
DirEntry (the type returned by the scan functions) caches the
entry's FileType, so is_dir() / is_file() / is_symlink() never
re-syscall β even after the underlying file has been removed. 0.11 adds
two thin conveniences for tree widgets:
| Method | Returns | Notes |
|---|---|---|
DirEntry::display_name() |
&OsStr |
Borrowed, no allocation. |
DirEntry::relative_to(&self, root) |
Option<PathBuf> |
Pure path arithmetic, no I/O. |
That's the full list. The crate deliberately stops here β GUI tree data structures, routing, and state management are the widget's job.
iced lazy tree example
scan_dir_with_options is deliberately synchronous. To keep the iced
runtime responsive, wrap it in std::thread::spawn and drive it via
Task::perform:
use PathBuf;
use Task;
use ;
#
No tokio, no async-std, no feature flag β the crate stays
runtime-agnostic.
Feature flags
| Feature | Default | Purpose |
|---|---|---|
filter |
β | The FilterRule / Decision / EntryKind filter model. |
No advanced-filter. No async. No watcher. Scope creep is the
enemy.
Migrating from 0.10
0.11 is additive on top of 0.10. Existing 0.10 code keeps working unchanged. Optional adjustments to take advantage of the new surface:
| 0.10 | 0.11 (optional) |
|---|---|
Bespoke walk() + sort in your own code |
.sort_order(SortOrder::Filesystem) to skip the sort |
entry.path().strip_prefix(root).ok() |
entry.relative_to(root) |
entry.file_name() for GUI labels |
entry.display_name() (borrowed &OsStr, no alloc) |
scan_dir(path) (raw order) |
scan_dir_with_options(path, &ScanOptions::default()) |
See CHANGELOG.md for the full list. The Swdir, WalkReport,
FilterRule, Decision, Recurse, scan_dir, and DirEntry APIs
from 0.10 are unchanged.