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
13pub mod file_types;
14mod filter;
15pub mod filetype;
16pub mod glob;
17mod glob_path;
18mod ignore;
19mod walker;
20
21pub use file_types::{build_file_types, list_file_types, FileTypeError};
22pub use filetype::{classify, detect, looks_like_text, Category, FileType, SNIFF_PREFIX_LEN};
23pub use filter::{FilterResult, IncludeExclude};
24pub use glob::{contains_glob, expand_braces, glob_match};
25pub use glob_path::{GlobPath, PathSegment, PatternError};
26pub use ignore::IgnoreFilter;
27pub use walker::{EntryTypes, ErrorCallback, FileWalker, WalkOptions};
28
29use async_trait::async_trait;
30use std::path::{Path, PathBuf};
31use thiserror::Error;
32
33/// Errors from filesystem operations within the walker.
34#[derive(Debug, Error)]
35pub enum WalkerError {
36    #[error("not found: {0}")]
37    NotFound(String),
38    #[error("permission denied: {0}")]
39    PermissionDenied(String),
40    #[error("io error: {0}")]
41    Io(String),
42    #[error("symlink cycle detected: {0}")]
43    SymlinkCycle(String),
44}
45
46/// Minimal read-only filesystem abstraction for the walker.
47///
48/// Implement this trait to adapt your project's filesystem layer
49/// (VFS, real FS, CRDT blocks, etc.) to `FileWalker` and `IgnoreFilter`.
50#[async_trait]
51pub trait WalkerFs: Send + Sync {
52    /// The directory entry type returned by `list_dir`.
53    type DirEntry: WalkerDirEntry;
54
55    /// List the entries in a directory.
56    async fn list_dir(&self, path: &Path) -> Result<Vec<Self::DirEntry>, WalkerError>;
57
58    /// Read the full contents of a file into memory.
59    ///
60    /// Currently used for loading `.gitignore` files. Implementations SHOULD
61    /// impose a reasonable size limit to prevent accidental multi-gigabyte reads.
62    async fn read_file(&self, path: &Path) -> Result<Vec<u8>, WalkerError>;
63
64    /// Check if a path is a directory.
65    async fn is_dir(&self, path: &Path) -> bool;
66
67    /// Check if a path exists.
68    async fn exists(&self, path: &Path) -> bool;
69
70    /// Return the canonical (resolved) path, following symlinks.
71    ///
72    /// Used by `FileWalker` for symlink cycle detection when `follow_symlinks`
73    /// is enabled. Implementations that support symlinks should resolve the path
74    /// to its real location. The default returns the path unchanged.
75    async fn canonicalize(&self, path: &Path) -> PathBuf {
76        path.to_path_buf()
77    }
78
79    /// Return the file size in bytes, or `None` if size cannot be determined.
80    ///
81    /// Used by `FileWalker` to honor `WalkOptions::max_filesize`.
82    /// Implementations that don't know file sizes (or for which a size query
83    /// is too expensive) may return `None` — the walker treats that as
84    /// "unknown size" and yields the file regardless of the limit.
85    async fn file_size(&self, _path: &Path) -> Option<u64> {
86        None
87    }
88}
89
90/// A single entry returned by `WalkerFs::list_dir`.
91pub trait WalkerDirEntry: Send {
92    /// The entry name (file or directory name, not full path).
93    fn name(&self) -> &str;
94
95    /// True if this entry is a directory.
96    fn is_dir(&self) -> bool;
97
98    /// True if this entry is a regular file.
99    fn is_file(&self) -> bool;
100
101    /// True if this entry is a symbolic link.
102    fn is_symlink(&self) -> bool;
103}