63 lines
2.1 KiB
Python
63 lines
2.1 KiB
Python
from __future__ import annotations
|
|
|
|
import os
|
|
from dataclasses import dataclass
|
|
from pathlib import Path
|
|
from typing import Iterator
|
|
|
|
|
|
@dataclass(frozen=True)
|
|
class VaultPaths:
|
|
root: str
|
|
notes: str
|
|
attachments: str
|
|
kb: str
|
|
|
|
|
|
class Vault:
|
|
def __init__(self, root_path: str):
|
|
root = Path(root_path).expanduser().resolve()
|
|
object.__setattr__(
|
|
self,
|
|
"paths",
|
|
VaultPaths(
|
|
root=str(root),
|
|
notes=str(root / "notes"),
|
|
attachments=str(root / "attachments"),
|
|
kb=str(root / ".kb"),
|
|
),
|
|
)
|
|
|
|
def ensure_structure(self) -> None:
|
|
Path(self.paths.notes).mkdir(parents=True, exist_ok=True)
|
|
Path(self.paths.attachments).mkdir(parents=True, exist_ok=True)
|
|
Path(self.paths.kb).mkdir(parents=True, exist_ok=True)
|
|
|
|
def abspath(self, rel_path: str) -> str:
|
|
# If rel_path is absolute, Path(root) / rel_path will return rel_path as-is.
|
|
return str((Path(self.paths.root) / rel_path).resolve())
|
|
|
|
def relpath(self, abs_path: str) -> str:
|
|
return str(Path(abs_path).resolve().relative_to(Path(self.paths.root).resolve()))
|
|
|
|
def iter_markdown_files(self) -> Iterator[str]:
|
|
"""
|
|
Yield absolute paths to .md files under <vault>/notes recursively.
|
|
- Allows the vault root to be hidden or not.
|
|
- Skips hidden subdirectories within notes/ (names starting with '.').
|
|
- Skips hidden files (names starting with '.').
|
|
"""
|
|
notes_dir = Path(self.paths.notes)
|
|
if not notes_dir.exists():
|
|
return iter(())
|
|
|
|
# Walk manually to filter hidden dirs/files
|
|
for dirpath, dirnames, filenames in os.walk(notes_dir):
|
|
# Remove hidden subdirectories in-place (prevents os.walk from entering them)
|
|
dirnames[:] = [d for d in dirnames if not d.startswith(".")]
|
|
for fname in filenames:
|
|
if fname.startswith("."):
|
|
continue
|
|
if not fname.endswith(".md"):
|
|
continue
|
|
yield str(Path(dirpath, fname).resolve()) |