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::io;
|
||||||
use std::path::{Path, PathBuf};
|
use std::path::{Path, PathBuf};
|
||||||
|
use walkdir::WalkDir;
|
||||||
|
|
||||||
// ── VaultDocument ─────────────────────────────────────────────────────────────
|
// ── VaultDocument ─────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
@@ -83,6 +84,11 @@ pub fn is_within_vault(vault_path: &Path, candidate: &Path) -> bool {
|
|||||||
/// → matches "guides/getting-started.md"
|
/// → matches "guides/getting-started.md"
|
||||||
/// ```
|
/// ```
|
||||||
pub fn resolve_wiki_link(vault_path: &Path, raw_target: &str) -> Option<PathBuf> {
|
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")
|
// Split off subpath prefix if present: "guides/Getting Started" → ("guides", "Getting Started")
|
||||||
let (subdir, name) = match raw_target.rfind('/') {
|
let (subdir, name) = match raw_target.rfind('/') {
|
||||||
Some(i) => (&raw_target[..i], &raw_target[i + 1..]),
|
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
|
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