Skip to main content

kaish_glob/
lib.rs

1//! kaish-glob: Glob matching and async file walking.
2//!
3//! Provides:
4//! - **glob_match**: Shell-style glob pattern matching with brace expansion
5//! - **GlobPath**: Path-aware glob matching with `**` (globstar) support
6//! - **FileWalker**: Async recursive directory walker, generic over `WalkerFs`
7//! - **IgnoreFilter**: Gitignore-style pattern filtering
8//! - **IncludeExclude**: rsync-style include/exclude filters
9//!
10//! The walker is generic over `WalkerFs`, a minimal read-only filesystem trait.
11//! Consumers implement `WalkerFs` to adapt their own filesystem abstraction.
12
13mod filter;
14pub mod glob;
15mod glob_path;
16mod ignore;
17mod walker;
18
19pub use filter::{FilterResult, IncludeExclude};
20pub use glob::{contains_glob, expand_braces, glob_match};
21pub use glob_path::{GlobPath, PathSegment, PatternError};
22pub use ignore::IgnoreFilter;
23pub use walker::{EntryTypes, ErrorCallback, FileWalker, WalkOptions};
24
25use async_trait::async_trait;
26use std::path::{Path, PathBuf};
27use thiserror::Error;
28
29/// Errors from filesystem operations within the walker.
30#[derive(Debug, Error)]
31pub enum WalkerError {
32    #[error("not found: {0}")]
33    NotFound(String),
34    #[error("permission denied: {0}")]
35    PermissionDenied(String),
36    #[error("io error: {0}")]
37    Io(String),
38    #[error("symlink cycle detected: {0}")]
39    SymlinkCycle(String),
40}
41
42/// Minimal read-only filesystem abstraction for the walker.
43///
44/// Implement this trait to adapt your project's filesystem layer
45/// (VFS, real FS, CRDT blocks, etc.) to `FileWalker` and `IgnoreFilter`.
46#[async_trait]
47pub trait WalkerFs: Send + Sync {
48    /// The directory entry type returned by `list_dir`.
49    type DirEntry: WalkerDirEntry;
50
51    /// List the entries in a directory.
52    async fn list_dir(&self, path: &Path) -> Result<Vec<Self::DirEntry>, WalkerError>;
53
54    /// Read the full contents of a file into memory.
55    ///
56    /// Currently used for loading `.gitignore` files. Implementations SHOULD
57    /// impose a reasonable size limit to prevent accidental multi-gigabyte reads.
58    async fn read_file(&self, path: &Path) -> Result<Vec<u8>, WalkerError>;
59
60    /// Check if a path is a directory.
61    async fn is_dir(&self, path: &Path) -> bool;
62
63    /// Check if a path exists.
64    async fn exists(&self, path: &Path) -> bool;
65
66    /// Return the canonical (resolved) path, following symlinks.
67    ///
68    /// Used by `FileWalker` for symlink cycle detection when `follow_symlinks`
69    /// is enabled. Implementations that support symlinks should resolve the path
70    /// to its real location. The default returns the path unchanged.
71    async fn canonicalize(&self, path: &Path) -> PathBuf {
72        path.to_path_buf()
73    }
74}
75
76/// A single entry returned by `WalkerFs::list_dir`.
77pub trait WalkerDirEntry: Send {
78    /// The entry name (file or directory name, not full path).
79    fn name(&self) -> &str;
80
81    /// True if this entry is a directory.
82    fn is_dir(&self) -> bool;
83
84    /// True if this entry is a regular file.
85    fn is_file(&self) -> bool;
86
87    /// True if this entry is a symbolic link.
88    fn is_symlink(&self) -> bool;
89}