bread-utils extracts genuinely duplicated logic found across breadbox, breadclip, breadmon, breadcrumbs, bos-settings, and breadhelp: - hypr: Hyprland socket1 request/response client (breadbox's get_active_workspace + breadclip's position.rs hyprctl_json were near-identical), socket2 path resolution (breadmon), and a version-tolerant `fullscreen` field parser (Hyprland has shipped both bool and int representations across versions). - singleton: correct flock-based single-instance toggle, replacing the TOCTOU-prone read-pid/check-proc/kill/write-pid pattern duplicated verbatim between breadbox and breadclip (breadclip's own comment says "matches breadbox pattern"). - proc: breadcrumbs' timeout-guarded subprocess runner, promoted verbatim as the one implementation in the ecosystem that already got this right. - atomic + xdg: atomic (temp-then-rename) file writes with an optional .bak-before-overwrite variant, and XDG path helpers that never fall back to a literal "~/..." string (the exact breadclip-core and breadpad-shared bug: PathBuf never expands `~`). - tomlcfg (feature "toml"): the load_doc/save_doc TOML-editing discipline bos-settings and breadhelp both implemented byte-for-byte identically in the same fix pass that introduced it. - gtk_popup (feature "gtk"): layer-shell overlay window setup, visible-row navigation, and click-outside-close, deduplicated from breadbox and breadclip (~150 duplicated lines, per both apps' own "same as breadbox" comments). bread-onnx extracts the embedding pipeline (tokenize -> tensor build -> mean-pool -> L2-normalize) duplicated near-verbatim between breadarr and breadsearch, a shared execution-provider session builder with loud EP- registration logging, and a model download+integrity helper. Defaults AMD iGPU acceleration to ort::ep::MIGraphX (not ROCm) per this machine's own breadsearch-gpu-backends lesson: ROCMExecutionProvider silently no-ops to CPU on distro ROCm onnxruntime builds compiled with --use_migraphx. Both crates build and pass their own test suites standalone. Consumer migrations follow in subsequent commits.
100 lines
3.7 KiB
Rust
100 lines
3.7 KiB
Rust
//! Non-destructive TOML config editing discipline.
|
|
//!
|
|
//! Extracted from `bos-settings/src/config/mod.rs` (`load_doc`/`save_doc`)
|
|
//! and `breadhelp/src/config.rs`, which re-implemented the exact same
|
|
//! function bodies in the same fix pass that introduced `bos-settings`'s
|
|
//! version — right down to the eprintln wording template. Both parse into a
|
|
//! `toml_edit::DocumentMut` (preserving keys/comments/formatting this app
|
|
//! doesn't model) and back up a file that exists but fails to parse, once,
|
|
//! before falling back to an empty document — so a bad edit is always
|
|
//! recoverable from `<path>.bak` instead of silently destroying whatever the
|
|
//! file used to hold.
|
|
//!
|
|
//! Requires the `toml` feature.
|
|
|
|
use std::path::Path;
|
|
use toml_edit::DocumentMut;
|
|
|
|
/// Load a TOML file into an editable document. A missing file yields an
|
|
/// empty document (normal for a fresh install). A file that *exists* but
|
|
/// fails to parse is backed up to `<path>.bak` once before falling back to
|
|
/// an empty document, so the next [`save_doc`] doesn't silently overwrite an
|
|
/// unparseable-but-recoverable file with only the caller's modelled keys.
|
|
///
|
|
/// `app` is used only to prefix the parse-failure log line (e.g.
|
|
/// `"breadhelp"`, `"bos-settings"`).
|
|
pub fn load_doc(app: &str, path: &Path) -> DocumentMut {
|
|
let Ok(text) = std::fs::read_to_string(path) else {
|
|
return DocumentMut::default();
|
|
};
|
|
match text.parse::<DocumentMut>() {
|
|
Ok(doc) => doc,
|
|
Err(e) => {
|
|
let backup = super::atomic::backup_path_for(path);
|
|
eprintln!(
|
|
"{app}: {} failed to parse ({e}); backed up to {} before falling back to defaults",
|
|
path.display(),
|
|
backup.display()
|
|
);
|
|
let _ = std::fs::write(&backup, &text);
|
|
DocumentMut::default()
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Write the document back to disk atomically (temp-then-rename), backing up
|
|
/// whatever was there before overwriting it — see
|
|
/// [`crate::atomic::write_atomic_backed_up`].
|
|
pub fn save_doc(path: &Path, doc: &DocumentMut) -> std::io::Result<()> {
|
|
super::atomic::write_atomic_backed_up(path, &doc.to_string())
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use super::*;
|
|
use toml_edit::value;
|
|
|
|
fn tmp_dir(name: &str) -> std::path::PathBuf {
|
|
let dir = std::env::temp_dir().join(format!("bread-utils-tomlcfg-test-{name}-{}", std::process::id()));
|
|
let _ = std::fs::remove_dir_all(&dir);
|
|
std::fs::create_dir_all(&dir).unwrap();
|
|
dir
|
|
}
|
|
|
|
#[test]
|
|
fn missing_file_yields_empty_document() {
|
|
let dir = tmp_dir("missing");
|
|
let doc = load_doc("test", &dir.join("nope.toml"));
|
|
assert!(doc.is_empty());
|
|
let _ = std::fs::remove_dir_all(&dir);
|
|
}
|
|
|
|
#[test]
|
|
fn save_then_load_round_trips() {
|
|
let dir = tmp_dir("roundtrip");
|
|
let path = dir.join("state.toml");
|
|
let mut doc = DocumentMut::default();
|
|
doc["general"]["mode"] = value("dad");
|
|
save_doc(&path, &doc).unwrap();
|
|
|
|
let loaded = load_doc("test", &path);
|
|
assert_eq!(
|
|
loaded.get("general").and_then(|t| t.get("mode")).and_then(|v| v.as_str()),
|
|
Some("dad")
|
|
);
|
|
let _ = std::fs::remove_dir_all(&dir);
|
|
}
|
|
|
|
#[test]
|
|
fn unparseable_existing_file_is_backed_up_before_falling_back() {
|
|
let dir = tmp_dir("bad-parse");
|
|
let path = dir.join("state.toml");
|
|
std::fs::write(&path, "this is not [ valid toml").unwrap();
|
|
|
|
let doc = load_doc("test", &path);
|
|
assert!(doc.is_empty());
|
|
let backup = dir.join("state.toml.bak");
|
|
assert_eq!(std::fs::read_to_string(&backup).unwrap(), "this is not [ valid toml");
|
|
let _ = std::fs::remove_dir_all(&dir);
|
|
}
|
|
}
|