Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,16 @@ When writing or running any Rust test, follow the
functions and hand-rolled loops, `proptest` for domain-wide properties. Run with
plain `cargo test -p <crate>` (no nextest/just wrapper).

## Documentation

When writing or editing any docblock, doc comment, or module header (`///`,
`//!`) — including on code you just changed — follow the
[`writing-interface-docblocks`](.claude/skills/writing-interface-docblocks/SKILL.md)
skill: a docblock is a contract, not a changelog. Terse, present-tense, no
prompt or diff leakage (`rather than`, `now returns`, `as requested`); document
the *when* of errors/panics, not the *why*; module docs name the domain, not the
one item currently inside them.

## DSL

The `harmont` Python package (pipeline DSL) lives inside `crates/hm-dsl-engine/harmont-py/` so it ships with the crate.
Expand Down
19 changes: 3 additions & 16 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 0 additions & 3 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ members = [
"crates/hm-config",
"crates/hm-plugin-protocol",
"crates/hm-pipeline-ir",
"crates/hm-util",
"crates/hm-plugin-cloud",
"crates/hm-dsl-engine",
"crates/hm-render",
Expand All @@ -19,7 +18,6 @@ default-members = [
"crates/hm-config",
"crates/hm-plugin-protocol",
"crates/hm-pipeline-ir",
"crates/hm-util",
"crates/hm-plugin-cloud",
"crates/hm-dsl-engine",
"crates/hm-render",
Expand All @@ -37,7 +35,6 @@ hm-exec = { path = "crates/hm-exec", version = "0.0.0-dev"
hm-plugin-protocol = { path = "crates/hm-plugin-protocol", version = "0.0.0-dev" }
hm-plugin-cloud = { path = "crates/hm-plugin-cloud", version = "0.0.0-dev" }
hm-pipeline-ir = { path = "crates/hm-pipeline-ir", version = "0.0.0-dev" }
hm-util = { path = "crates/hm-util", version = "0.0.0-dev" }
hm-config = { path = "crates/hm-config", version = "0.0.0-dev" }
hm-dsl-engine = { path = "crates/hm-dsl-engine", version = "0.0.0-dev" }
hm-render = { path = "crates/hm-render", version = "0.0.0-dev" }
Expand Down
12 changes: 10 additions & 2 deletions crates/hm-common/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -12,26 +12,34 @@ categories = ["command-line-utilities"]
path = "src/lib.rs"

[features]
app-runtime = []
sys-runtime = []

[dependencies]
anyhow = { workspace = true }
async-trait = { workspace = true }
chrono = { workspace = true }
derive_more = { workspace = true }
dirs = "6"
serde = { workspace = true }
serde_json = { workspace = true }
thiserror = { workspace = true }
bstr = "1"
include_dir = "0.7"
num-traits = { workspace = true }
tempfile = "3"
tokio = { version = "1", features = ["process", "fs"] }
tokio = { version = "1", features = ["process", "fs", "rt", "rt-multi-thread", "io-util"] }
tracing = "0.1"
unicode-width = { workspace = true }
unicode-segmentation = { workspace = true }
which = "7"

[target.'cfg(windows)'.dependencies.windows]
version = "0.62"
features = [
"Win32_Foundation",
"Win32_Storage_FileSystem",
]

[dev-dependencies]
tempfile = "3"
tokio = { version = "1", features = ["macros", "rt-multi-thread"] }
Expand Down
76 changes: 76 additions & 0 deletions crates/hm-common/src/dir_provider.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
//! Platform user directories.
//!
//! Exposes the operating system's per-user directory roots. Application-
//! agnostic: it knows nothing of Harmont's own `hm/` subdirectory — callers
//! join that (and any file name) onto these roots.
//!
//! On non-Windows the roots are `~/.config` and `~/.cache`; the `$XDG_*` env
//! vars are intentionally not honored, keeping paths predictable.

use std::path::{Path, PathBuf};

/// The platform per-user directory roots, resolved once at construction and
/// read as borrowed paths.
///
/// Build with [`DirProvider::new`], then read the `&Path` accessors. Attach a
/// process-wide instance to the system runtime and reach it via its `dirs()`
/// accessor instead of reconstructing one per call.
#[derive(Debug, Clone)]
pub struct DirProvider {
config: PathBuf,
cache: PathBuf,
}

impl DirProvider {
/// Resolve the platform directory roots, once.
///
/// Returns `None` if a root cannot be determined — e.g. there is no home
/// directory.
#[must_use]
pub fn new() -> Option<Self> {
#[cfg(windows)]
let (config, cache) = (dirs::config_dir()?, dirs::cache_dir()?);
#[cfg(not(windows))]
let (config, cache) = {
let home = dirs::home_dir()?;
(home.join(".config"), home.join(".cache"))
};

Some(Self { config, cache })
}

/// The user configuration root (`~/.config` on non-Windows).
#[must_use]
pub fn config(&self) -> &Path {
&self.config
}

/// The user cache root (`~/.cache` on non-Windows).
#[must_use]
pub fn cache(&self) -> &Path {
&self.cache
}
}

#[cfg(test)]
#[allow(clippy::unwrap_used, reason = "test setup and assertions")]
mod tests {
use super::*;
use rstest::rstest;

#[rstest]
fn config_is_the_platform_config_root() {
let dirs = DirProvider::new().unwrap();
assert!(dirs.config().is_absolute(), "got {:?}", dirs.config());
#[cfg(not(windows))]
assert!(dirs.config().ends_with(".config"), "got {:?}", dirs.config());
}

#[rstest]
fn cache_is_the_platform_cache_root() {
let dirs = DirProvider::new().unwrap();
assert!(dirs.cache().is_absolute(), "got {:?}", dirs.cache());
#[cfg(not(windows))]
assert!(dirs.cache().ends_with(".cache"), "got {:?}", dirs.cache());
}
}
Loading
Loading