Skip to content

Commit 7210ae4

Browse files
refactor: merge hm-util into hm-common (#178)
1 parent 2218853 commit 7210ae4

36 files changed

Lines changed: 549 additions & 612 deletions

‎CLAUDE.md‎

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,16 @@ When writing or running any Rust test, follow the
1818
functions and hand-rolled loops, `proptest` for domain-wide properties. Run with
1919
plain `cargo test -p <crate>` (no nextest/just wrapper).
2020

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

2333
The `harmont` Python package (pipeline DSL) lives inside `crates/hm-dsl-engine/harmont-py/` so it ships with the crate.

‎Cargo.lock‎

Lines changed: 3 additions & 16 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

‎Cargo.toml‎

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@ members = [
66
"crates/hm-config",
77
"crates/hm-plugin-protocol",
88
"crates/hm-pipeline-ir",
9-
"crates/hm-util",
109
"crates/hm-plugin-cloud",
1110
"crates/hm-dsl-engine",
1211
"crates/hm-render",
@@ -19,7 +18,6 @@ default-members = [
1918
"crates/hm-config",
2019
"crates/hm-plugin-protocol",
2120
"crates/hm-pipeline-ir",
22-
"crates/hm-util",
2321
"crates/hm-plugin-cloud",
2422
"crates/hm-dsl-engine",
2523
"crates/hm-render",
@@ -37,7 +35,6 @@ hm-exec = { path = "crates/hm-exec", version = "0.0.0-dev"
3735
hm-plugin-protocol = { path = "crates/hm-plugin-protocol", version = "0.0.0-dev" }
3836
hm-plugin-cloud = { path = "crates/hm-plugin-cloud", version = "0.0.0-dev" }
3937
hm-pipeline-ir = { path = "crates/hm-pipeline-ir", version = "0.0.0-dev" }
40-
hm-util = { path = "crates/hm-util", version = "0.0.0-dev" }
4138
hm-config = { path = "crates/hm-config", version = "0.0.0-dev" }
4239
hm-dsl-engine = { path = "crates/hm-dsl-engine", version = "0.0.0-dev" }
4340
hm-render = { path = "crates/hm-render", version = "0.0.0-dev" }

‎crates/hm-common/Cargo.toml‎

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,26 +12,34 @@ categories = ["command-line-utilities"]
1212
path = "src/lib.rs"
1313

1414
[features]
15-
app-runtime = []
15+
sys-runtime = []
1616

1717
[dependencies]
1818
anyhow = { workspace = true }
1919
async-trait = { workspace = true }
2020
chrono = { workspace = true }
2121
derive_more = { workspace = true }
22+
dirs = "6"
2223
serde = { workspace = true }
2324
serde_json = { workspace = true }
2425
thiserror = { workspace = true }
2526
bstr = "1"
2627
include_dir = "0.7"
2728
num-traits = { workspace = true }
2829
tempfile = "3"
29-
tokio = { version = "1", features = ["process", "fs"] }
30+
tokio = { version = "1", features = ["process", "fs", "rt", "rt-multi-thread", "io-util"] }
3031
tracing = "0.1"
3132
unicode-width = { workspace = true }
3233
unicode-segmentation = { workspace = true }
3334
which = "7"
3435

36+
[target.'cfg(windows)'.dependencies.windows]
37+
version = "0.62"
38+
features = [
39+
"Win32_Foundation",
40+
"Win32_Storage_FileSystem",
41+
]
42+
3543
[dev-dependencies]
3644
tempfile = "3"
3745
tokio = { version = "1", features = ["macros", "rt-multi-thread"] }
Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
//! Platform user directories.
2+
//!
3+
//! Exposes the operating system's per-user directory roots. Application-
4+
//! agnostic: it knows nothing of Harmont's own `hm/` subdirectory — callers
5+
//! join that (and any file name) onto these roots.
6+
//!
7+
//! On non-Windows the roots are `~/.config` and `~/.cache`; the `$XDG_*` env
8+
//! vars are intentionally not honored, keeping paths predictable.
9+
10+
use std::path::{Path, PathBuf};
11+
12+
/// The platform per-user directory roots, resolved once at construction and
13+
/// read as borrowed paths.
14+
///
15+
/// Build with [`DirProvider::new`], then read the `&Path` accessors. Attach a
16+
/// process-wide instance to the system runtime and reach it via its `dirs()`
17+
/// accessor instead of reconstructing one per call.
18+
#[derive(Debug, Clone)]
19+
pub struct DirProvider {
20+
config: PathBuf,
21+
cache: PathBuf,
22+
}
23+
24+
impl DirProvider {
25+
/// Resolve the platform directory roots, once.
26+
///
27+
/// Returns `None` if a root cannot be determined — e.g. there is no home
28+
/// directory.
29+
#[must_use]
30+
pub fn new() -> Option<Self> {
31+
#[cfg(windows)]
32+
let (config, cache) = (dirs::config_dir()?, dirs::cache_dir()?);
33+
#[cfg(not(windows))]
34+
let (config, cache) = {
35+
let home = dirs::home_dir()?;
36+
(home.join(".config"), home.join(".cache"))
37+
};
38+
39+
Some(Self { config, cache })
40+
}
41+
42+
/// The user configuration root (`~/.config` on non-Windows).
43+
#[must_use]
44+
pub fn config(&self) -> &Path {
45+
&self.config
46+
}
47+
48+
/// The user cache root (`~/.cache` on non-Windows).
49+
#[must_use]
50+
pub fn cache(&self) -> &Path {
51+
&self.cache
52+
}
53+
}
54+
55+
#[cfg(test)]
56+
#[allow(clippy::unwrap_used, reason = "test setup and assertions")]
57+
mod tests {
58+
use super::*;
59+
use rstest::rstest;
60+
61+
#[rstest]
62+
fn config_is_the_platform_config_root() {
63+
let dirs = DirProvider::new().unwrap();
64+
assert!(dirs.config().is_absolute(), "got {:?}", dirs.config());
65+
#[cfg(not(windows))]
66+
assert!(dirs.config().ends_with(".config"), "got {:?}", dirs.config());
67+
}
68+
69+
#[rstest]
70+
fn cache_is_the_platform_cache_root() {
71+
let dirs = DirProvider::new().unwrap();
72+
assert!(dirs.cache().is_absolute(), "got {:?}", dirs.cache());
73+
#[cfg(not(windows))]
74+
assert!(dirs.cache().ends_with(".cache"), "got {:?}", dirs.cache());
75+
}
76+
}

0 commit comments

Comments
 (0)