cache-manager 0.1.0

Directory-based file cache utility with crate-root discovery and grouped subdirectories
Documentation

cache-manager

made-with-rust crates.io MIT licensed Apache 2.0 licensed Coverage

Directory-based cache and artifact path management with crate-root discovery, grouped cache paths, and optional eviction on directory initialization.

This crate is intentionally tool-agnostic — it only manages cache/artifact directory layout and paths and does not assume or depend on any specific consumer tooling. Any tool or library may use it to discover, create, and evict files in project-scoped cache directories.

If your utility reads or writes files at all, it can use cache-manager to compute and manage cache paths and apply eviction rules — the crate makes no assumptions about how callers read or write those files.

It has zero runtime dependencies (standard library only for library consumers).

It is suitable for:

  • Artifact storage (build outputs, generated files, intermediate data, etc.).
  • Monorepos or multi-crate workspaces that need centralized cache/artifact management via a shared root (for example with CacheRoot::from_root(...)).

Tested on macOS, Linux, and Windows.

Eviction Policy

Use EvictPolicy with:

  • CacheGroup::ensure_dir_with_policy(...)
  • CacheRoot::ensure_group_with_policy(...)
  • CacheGroup::eviction_report(...) to preview which files would be evicted.

Policy fields:

  • max_age: remove files older than or equal to the age threshold.
  • max_files: keep at most N files.
  • max_bytes: keep total file bytes at or below the threshold.

Eviction order is always:

  1. max_age
  2. max_files
  3. max_bytes

For max_files and max_bytes, files are evicted oldest-first by modified time (ascending), then by path for deterministic tie-breaking.

eviction_report(...) and ensure_*_with_policy(...) use the same selection logic.

How max_bytes works

  • Scans regular files recursively under the managed directory.
  • Sums metadata.len() across those files.
  • If total exceeds max_bytes, removes files oldest-first until total is <= max_bytes.
  • Directories are not counted as bytes.
  • Enforcement happens only during policy-aware ensure_*_with_policy calls (not continuously in the background).

Usage

Basic examples showing common operations:

The primary root type is CacheRoot, which represents a filesystem root under which cache groups (CacheGroup) live.

A CacheGroup represents a subdirectory under a CacheRoot and manages cache entries stored in that directory.

Discover a cache path for the current crate/workspace and resolve an entry path.

Note: discover_cache_path only computes a filesystem path — it does not create directories or files. Behavior:

  • Searches upward from the current working directory for a Cargo.toml and uses that crate root when found; otherwise it falls back to the current working directory.
  • The discovered root is canonicalized when possible to avoid surprising differences between logically-equal paths.
  • If the relative_path argument is absolute, it is returned unchanged.
use cache_manager::CacheRoot;

// Compute a path like <crate-root>/.cache/tool/data.bin without creating it.
let cache_path = CacheRoot::discover_cache_path(".cache", "tool/data.bin");
println!("cache path: {}", cache_path.display());

// If you already have an absolute entry path, it's returned unchanged:
let absolute = std::path::PathBuf::from("/tmp/custom/cache.json");
let kept = CacheRoot::discover_cache_path(".cache", &absolute);
assert_eq!(kept, absolute);

Create a CacheRoot from an explicit path and apply an eviction policy to a group:

use cache_manager::{CacheRoot, EvictPolicy};
use std::time::Duration;

let root = CacheRoot::from_root("/tmp/project");
let group = root.group("artifacts");

let policy = EvictPolicy {
	max_files: Some(100),
	max_age: Some(Duration::from_secs(60 * 60 * 24 * 30)), // 30 days
	..Default::default()
};

group.ensure_dir_with_policy(Some(&policy)).expect("ensure and evict");

Preview which files would be removed without applying deletions:

use cache_manager::{CacheRoot, EvictPolicy};

let root = CacheRoot::from_root("/tmp/project");
let group = root.group("artifacts");
let policy = EvictPolicy {
	max_files: Some(10),
	..Default::default()
};

let report = group.eviction_report(&policy).expect("eviction report");
for p in report.marked_for_eviction {
	println!("would remove: {}", p.display());
}

Create or update a cache entry (ensures parent directories exist):

use cache_manager::CacheRoot;

let root = CacheRoot::from_root("/tmp/project");
let group = root.group("artifacts/json");

let entry = group.touch("v1/index.bin").expect("touch entry");
println!("touched: {}", entry.display());

Per-subdirectory policies

Different subdirectories under the same CacheRoot can use independent policies; call ensure_dir_with_policy on each CacheGroup separately to apply per-group rules.

Get the root path

To obtain the underlying filesystem path for a CacheRoot, use path():

use cache_manager::CacheRoot;

let root = CacheRoot::from_root("/tmp/project");
let root_path = root.path();
println!("root path: {}", root_path.display());

Also obtain a CacheGroup path and resolve an entry path under that group:

use cache_manager::CacheRoot;

let root = CacheRoot::from_root("/tmp/project");
let group = root.group("artifacts");

let group_path = group.path();
println!("group path: {}", group_path.display());

let entry_path = group.entry_path("v1/index.bin");
println!("entry path: {}", entry_path.display());

License

cache-manager is primarily distributed under the terms of both the MIT license and the Apache License (Version 2.0).

See LICENSE-APACHE and LICENSE-MIT for details.