pub struct ProjectRoot { /* private fields */ }Expand description
A canonical project root.
Implementations§
Source§impl ProjectRoot
impl ProjectRoot
Sourcepub fn discover(cwd: &Path) -> Option<ProjectRoot>
pub fn discover(cwd: &Path) -> Option<ProjectRoot>
CPM_PROJECT_ROOT if set, else the nearest ancestor of cwd (itself
included) containing .cpm-planner/ or .git.
Sourcepub fn from_path(root: &Path) -> Result<ProjectRoot, PlannerError>
pub fn from_path(root: &Path) -> Result<ProjectRoot, PlannerError>
Build a root from an existing directory (canonicalized).
pub fn root(&self) -> &Path
Sourcepub fn project_key(&self) -> String
pub fn project_key(&self) -> String
Canonical root as a UTF-8 string (lossy for non-UTF-8 paths).
pub fn plans_dir(&self) -> PathBuf
Sourcepub fn resolve_plan_file(&self, rel: &str) -> Result<PlanFileRef, PlannerError>
pub fn resolve_plan_file(&self, rel: &str) -> Result<PlanFileRef, PlannerError>
Validate a root-relative path of the exact form
.cpm-planner/plans/<name>/<variant>.json.
Sourcepub fn plan_file(
&self,
name: &str,
variant: &str,
) -> Result<PlanFileRef, PlannerError>
pub fn plan_file( &self, name: &str, variant: &str, ) -> Result<PlanFileRef, PlannerError>
Build and confinement-check the ref for <name>/<variant>.
Sourcepub fn list_plan_files(&self) -> Result<Vec<PlanFileRef>, PlannerError>
pub fn list_plan_files(&self) -> Result<Vec<PlanFileRef>, PlannerError>
All plan files, sorted by (name, variant). Non-conforming entries and symlinks are ignored. Empty only when the plans dir does not exist; other errors propagate.
Sourcepub fn read_graph(
&self,
f: &PlanFileRef,
) -> Result<(PlanGraph, String), PlannerError>
pub fn read_graph( &self, f: &PlanFileRef, ) -> Result<(PlanGraph, String), PlannerError>
Read a plan and the sha256 hex of the exact bytes parsed. Directories are walked with no-follow handles; the file is opened no-follow relative to the final directory handle, fstat’d to be a regular file, and read from that same handle.
Sourcepub fn file_hash(&self, f: &PlanFileRef) -> Result<String, PlannerError>
pub fn file_hash(&self, f: &PlanFileRef) -> Result<String, PlannerError>
The sha256 hex of a plan file’s bytes (the hash Self::read_graph
and Self::write_graph return), without parsing it. Read with the
same no-follow, regular-file-only rules as Self::read_graph.
Sourcepub fn read_bytes(&self, f: &PlanFileRef) -> Result<Vec<u8>, PlannerError>
pub fn read_bytes(&self, f: &PlanFileRef) -> Result<Vec<u8>, PlannerError>
The exact bytes of a plan file, read no-follow from a regular file.
A file larger than MAX_PLAN_FILE_BYTES is
INVALID_PATH: plan file exceeds 8 MiB.
Sourcepub fn remove_plan_file_if_unchanged(
&self,
f: &PlanFileRef,
expected_hash: &str,
)
pub fn remove_plan_file_if_unchanged( &self, f: &PlanFileRef, expected_hash: &str, )
Best-effort removal of a plan file this process just wrote: removed
only while its bytes still hash to expected_hash, walking the same
no-follow handles as every other operation. Errors are ignored.
Sourcepub fn write_graph(
&self,
f: &PlanFileRef,
g: &PlanGraph,
) -> Result<String, PlannerError>
pub fn write_graph( &self, f: &PlanFileRef, g: &PlanGraph, ) -> Result<String, PlannerError>
Atomically write pretty JSON plus a trailing newline; returns the content hash. All directory steps use no-follow handles and every file operation is relative to the final directory handle, so a parent swapped for a symlink mid-call cannot redirect the write.
Sourcepub fn write_new_graph(
&self,
f: &PlanFileRef,
g: &PlanGraph,
) -> Result<String, PlannerError>
pub fn write_new_graph( &self, f: &PlanFileRef, g: &PlanGraph, ) -> Result<String, PlannerError>
Self::write_graph that never replaces an existing file: the new
file is linked into place atomically, and an existing one (written
concurrently included) is INVALID_PATH: <rel_path> already exists.