feat(04-02): add [[Directory]] sentinel and list_vault_files() to vault.rs
- resolve_wiki_link() returns PathBuf::from("__directory__") for case-insensitive 'directory' target
- DirEntry struct with depth, name, is_dir, vault_path fields
- list_vault_files() using WalkDir::new().sort_by_file_name() for alphabetical tree
- Skips hidden entries (leading '.') and non-.md files silently
This commit is contained in:
@@ -1,5 +1,6 @@
|
||||
use std::io;
|
||||
use std::path::{Path, PathBuf};
|
||||
use walkdir::WalkDir;
|
||||
|
||||
// ── VaultDocument ─────────────────────────────────────────────────────────────
|
||||
|
||||
@@ -83,6 +84,11 @@ pub fn is_within_vault(vault_path: &Path, candidate: &Path) -> bool {
|
||||
/// → matches "guides/getting-started.md"
|
||||
/// ```
|
||||
pub fn resolve_wiki_link(vault_path: &Path, raw_target: &str) -> Option<PathBuf> {
|
||||
// Magic sentinel — [[Directory]] navigates to the virtual directory page
|
||||
if raw_target.eq_ignore_ascii_case("directory") {
|
||||
return Some(PathBuf::from("__directory__"));
|
||||
}
|
||||
|
||||
// Split off subpath prefix if present: "guides/Getting Started" → ("guides", "Getting Started")
|
||||
let (subdir, name) = match raw_target.rfind('/') {
|
||||
Some(i) => (&raw_target[..i], &raw_target[i + 1..]),
|
||||
@@ -182,3 +188,66 @@ pub fn resolve_standard_link(vault_path: &Path, current_doc: &str, dest: &str) -
|
||||
|
||||
None
|
||||
}
|
||||
|
||||
// ── Directory listing ─────────────────────────────────────────────────────────
|
||||
|
||||
/// Entry in the vault directory listing.
|
||||
pub struct DirEntry {
|
||||
/// Indentation depth (1-based, depth 1 = vault root children).
|
||||
pub depth: usize,
|
||||
/// Display name (without .md extension for files).
|
||||
pub name: String,
|
||||
/// True if this entry is a directory.
|
||||
pub is_dir: bool,
|
||||
/// Vault-relative path (only for .md files; None for directories).
|
||||
pub vault_path: Option<String>,
|
||||
}
|
||||
|
||||
/// List all markdown files and directories in the vault, sorted alphabetically.
|
||||
///
|
||||
/// Skips hidden files/directories (starting with '.') and non-.md files.
|
||||
/// Returns entries with depth for tree indentation.
|
||||
pub fn list_vault_files(vault_path: &Path) -> Vec<DirEntry> {
|
||||
let mut entries = Vec::new();
|
||||
for entry in WalkDir::new(vault_path)
|
||||
.sort_by_file_name()
|
||||
.into_iter()
|
||||
.filter_map(|e| e.ok())
|
||||
.skip(1) // skip vault root itself
|
||||
{
|
||||
let depth = entry.depth();
|
||||
let name = entry.file_name().to_string_lossy().to_string();
|
||||
|
||||
// Skip hidden files/dirs
|
||||
if name.starts_with('.') {
|
||||
continue;
|
||||
}
|
||||
|
||||
let is_dir = entry.file_type().is_dir();
|
||||
|
||||
if is_dir {
|
||||
entries.push(DirEntry {
|
||||
depth,
|
||||
name,
|
||||
is_dir: true,
|
||||
vault_path: None,
|
||||
});
|
||||
} else if name.ends_with(".md") {
|
||||
let display_name = name.strip_suffix(".md").unwrap_or(&name).to_string();
|
||||
let rel = entry
|
||||
.path()
|
||||
.strip_prefix(vault_path)
|
||||
.unwrap_or(entry.path())
|
||||
.to_string_lossy()
|
||||
.to_string();
|
||||
entries.push(DirEntry {
|
||||
depth,
|
||||
name: display_name,
|
||||
is_dir: false,
|
||||
vault_path: Some(rel),
|
||||
});
|
||||
}
|
||||
// Skip non-.md files silently
|
||||
}
|
||||
entries
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user