bread-ecosystem/bread-utils/src/tomlcfg.rs
Breadway 853ee33415 Add bread-utils and bread-onnx: shared crates for ecosystem-wide duplication
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.
2026-07-17 09:15:54 +08:00

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);
}
}