Compare commits
3 commits
646587585c
...
767e7f7434
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
767e7f7434 | ||
|
|
7b7fe680fe | ||
|
|
df7f51281a |
4 changed files with 95 additions and 53 deletions
44
Cargo.lock
generated
44
Cargo.lock
generated
|
|
@ -22,9 +22,9 @@ checksum = "f2032f911046de80f0a198e0901378627c33f59ea0ac00e363d481118bd70a53"
|
|||
|
||||
[[package]]
|
||||
name = "bitflags"
|
||||
version = "2.13.0"
|
||||
version = "2.13.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b4388bee8683e3d04af747c73422af53102d2bd24d9eadb6cbc100baef4b43f8"
|
||||
checksum = "b588b76d00fde79687d7646a9b5bdf3cc0f655e0bbd080335a95d7e96f3587da"
|
||||
|
||||
[[package]]
|
||||
name = "bread-theme"
|
||||
|
|
@ -37,12 +37,24 @@ dependencies = [
|
|||
"serde_json",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "bread-utils"
|
||||
version = "0.3.0"
|
||||
source = "git+https://git.breadway.dev/Breadway/bread-ecosystem?tag=v0.3.0#8e82d2d833e992ce939a5b836f910ee109f2e939"
|
||||
dependencies = [
|
||||
"dirs",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"toml_edit 0.22.27",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "breadhelp"
|
||||
version = "0.2.1"
|
||||
dependencies = [
|
||||
"async-channel",
|
||||
"bread-theme",
|
||||
"bread-utils",
|
||||
"gdk4",
|
||||
"glib",
|
||||
"gtk4",
|
||||
|
|
@ -167,24 +179,24 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "futures-channel"
|
||||
version = "0.3.32"
|
||||
version = "0.3.33"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "07bbe89c50d7a535e539b8c17bc0b49bdb77747034daa8087407d655f3f7cc1d"
|
||||
checksum = "262590f4fe6afeb0bc83be1daa64e52657fe185690a958af7f3ad0e92085c5ae"
|
||||
dependencies = [
|
||||
"futures-core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "futures-core"
|
||||
version = "0.3.32"
|
||||
version = "0.3.33"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7e3450815272ef58cec6d564423f6e755e25379b217b0bc688e295ba24df6b1d"
|
||||
checksum = "2cd50c473c80f6d7c3670a752354b8e569b1a7cbfdc0419ec88e5edad85e0dc7"
|
||||
|
||||
[[package]]
|
||||
name = "futures-executor"
|
||||
version = "0.3.32"
|
||||
version = "0.3.33"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "baf29c38818342a3b26b5b923639e7b1f4a61fc5e76102d4b1981c6dc7a7579d"
|
||||
checksum = "6754879cc9f2c66f88c6e5c35344bb0bdb0708b0352b1201815667c7eabc7458"
|
||||
dependencies = [
|
||||
"futures-core",
|
||||
"futures-task",
|
||||
|
|
@ -193,15 +205,15 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "futures-io"
|
||||
version = "0.3.32"
|
||||
version = "0.3.33"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cecba35d7ad927e23624b22ad55235f2239cfa44fd10428eecbeba6d6a717718"
|
||||
checksum = "4577ecaa3c4f96589d473f679a71b596316f6641bc350038b962a5daf0085d7a"
|
||||
|
||||
[[package]]
|
||||
name = "futures-macro"
|
||||
version = "0.3.32"
|
||||
version = "0.3.33"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e835b70203e41293343137df5c0664546da5745f82ec9b84d40be8336958447b"
|
||||
checksum = "2d6d3cde68c518367be28956066ddfef33813991b77a55005a69dae04bf3b10b"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
|
|
@ -210,15 +222,15 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "futures-task"
|
||||
version = "0.3.32"
|
||||
version = "0.3.33"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "037711b3d59c33004d3856fbdc83b99d4ff37a24768fa1be9ce3538a1cde4393"
|
||||
checksum = "b231ed28831efb4a61a08580c4bc233ec56bc009f4cd8f52da2c3cb97df0c109"
|
||||
|
||||
[[package]]
|
||||
name = "futures-util"
|
||||
version = "0.3.32"
|
||||
version = "0.3.33"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "389ca41296e6190b48053de0321d02a77f32f8a5d2461dd38762c0593805c6d6"
|
||||
checksum = "a77a90a256fce34da66415271e30f94ee91c57b04b8a2c042d9cf3220179deaa"
|
||||
dependencies = [
|
||||
"futures-core",
|
||||
"futures-macro",
|
||||
|
|
|
|||
|
|
@ -17,3 +17,4 @@ toml = "0.8"
|
|||
# Non-destructive state editing (mirrors bos-settings/src/config/mod.rs).
|
||||
toml_edit = "0.22"
|
||||
async-channel = "2"
|
||||
bread-utils = { git = "https://git.breadway.dev/Breadway/bread-ecosystem", tag = "v0.3.0", features = ["toml"] }
|
||||
|
|
|
|||
|
|
@ -1,20 +1,16 @@
|
|||
//! `~/.config/breadhelp/state.toml` — non-destructive TOML editing, same
|
||||
//! `load_doc`/`save_doc` discipline as `bos-settings/src/config/mod.rs`: a
|
||||
//! missing file yields defaults, a file that exists but fails to parse is
|
||||
//! backed up once before falling back, so a bad edit is always recoverable.
|
||||
//! `~/.config/breadhelp/state.toml` — non-destructive TOML editing via
|
||||
//! `bread_utils::tomlcfg`, the same `load_doc`/`save_doc` discipline
|
||||
//! `bos-settings/src/config/mod.rs` uses: a missing file yields defaults, a
|
||||
//! file that exists but fails to parse is backed up once before falling
|
||||
//! back, so a bad edit is always recoverable. This module used to carry its
|
||||
//! own byte-for-byte copy of that logic (introduced in the same fix pass
|
||||
//! that added it to bos-settings) — now both share one implementation.
|
||||
|
||||
use std::path::{Path, PathBuf};
|
||||
use toml_edit::{value, DocumentMut};
|
||||
|
||||
pub fn config_dir() -> PathBuf {
|
||||
if let Ok(xdg) = std::env::var("XDG_CONFIG_HOME") {
|
||||
let p = PathBuf::from(xdg);
|
||||
if p.is_absolute() {
|
||||
return p;
|
||||
}
|
||||
}
|
||||
let home = std::env::var("HOME").unwrap_or_else(|_| "/root".to_string());
|
||||
PathBuf::from(home).join(".config")
|
||||
bread_utils::xdg::config_home()
|
||||
}
|
||||
|
||||
fn state_path() -> PathBuf {
|
||||
|
|
@ -22,32 +18,11 @@ fn state_path() -> PathBuf {
|
|||
}
|
||||
|
||||
fn load_doc(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 = PathBuf::from(format!("{}.bak", path.display()));
|
||||
eprintln!(
|
||||
"breadhelp: {} failed to parse ({e}); backed up to {} before falling back to defaults",
|
||||
path.display(),
|
||||
backup.display()
|
||||
);
|
||||
let _ = std::fs::write(&backup, &text);
|
||||
DocumentMut::default()
|
||||
}
|
||||
}
|
||||
bread_utils::tomlcfg::load_doc("breadhelp", path)
|
||||
}
|
||||
|
||||
fn save_doc(path: &Path, doc: &DocumentMut) {
|
||||
if let Some(parent) = path.parent() {
|
||||
if let Err(e) = std::fs::create_dir_all(parent) {
|
||||
eprintln!("breadhelp: couldn't create {}: {e}", parent.display());
|
||||
return;
|
||||
}
|
||||
}
|
||||
if let Err(e) = std::fs::write(path, doc.to_string()) {
|
||||
if let Err(e) = bread_utils::tomlcfg::save_doc(path, doc) {
|
||||
eprintln!("breadhelp: couldn't write {}: {e}", path.display());
|
||||
}
|
||||
}
|
||||
|
|
@ -159,3 +134,56 @@ impl State {
|
|||
save_doc(&self.path, &self.doc);
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn state_toml_write_backs_up_previous_contents_and_no_tmp_file_left_behind() {
|
||||
// Exercises the same bread_utils::atomic::write_atomic_backed_up
|
||||
// path save_doc uses (via tomlcfg::save_doc), at the file-layout
|
||||
// breadhelp actually writes to — the deeper backup/no-leftover-tmp
|
||||
// behavior itself is covered by bread-utils' own test suite.
|
||||
let dir = std::env::temp_dir().join(format!("breadhelp-atomic-write-test-{}", std::process::id()));
|
||||
std::fs::create_dir_all(&dir).unwrap();
|
||||
let path = dir.join("state.toml");
|
||||
let backup = dir.join("state.toml.bak");
|
||||
|
||||
bread_utils::atomic::write_atomic_backed_up(&path, "first").unwrap();
|
||||
assert_eq!(std::fs::read_to_string(&path).unwrap(), "first");
|
||||
assert!(!backup.exists(), "no backup should be made when there's nothing to back up yet");
|
||||
|
||||
bread_utils::atomic::write_atomic_backed_up(&path, "second").unwrap();
|
||||
assert_eq!(std::fs::read_to_string(&path).unwrap(), "second");
|
||||
assert_eq!(std::fs::read_to_string(&backup).unwrap(), "first");
|
||||
|
||||
let leftover_tmp: Vec<_> = std::fs::read_dir(&dir)
|
||||
.unwrap()
|
||||
.filter_map(|e| e.ok())
|
||||
.map(|e| e.file_name().to_string_lossy().into_owned())
|
||||
.filter(|n| n.contains(".tmp."))
|
||||
.collect();
|
||||
assert!(leftover_tmp.is_empty(), "temp file should be renamed away, not left behind: {leftover_tmp:?}");
|
||||
|
||||
let _ = std::fs::remove_dir_all(&dir);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn save_doc_then_load_doc_round_trips_state() {
|
||||
let dir = std::env::temp_dir().join(format!("breadhelp-state-roundtrip-test-{}", std::process::id()));
|
||||
std::fs::create_dir_all(&dir).unwrap();
|
||||
let path = dir.join("state.toml");
|
||||
|
||||
let mut doc = DocumentMut::default();
|
||||
doc["onboarding"]["completed"] = value(true);
|
||||
doc["general"]["mode"] = value("dad");
|
||||
save_doc(&path, &doc);
|
||||
|
||||
let loaded = load_doc(&path);
|
||||
assert_eq!(loaded.get("onboarding").and_then(|t| t.get("completed")).and_then(|v| v.as_bool()), Some(true));
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -46,8 +46,9 @@ pub struct LayerClient {
|
|||
}
|
||||
|
||||
fn query_json(cmd: &str) -> Option<serde_json::Value> {
|
||||
let output = Command::new("hyprctl").args(["-j", cmd]).output().ok()?;
|
||||
output.status.success().then(|| serde_json::from_slice(&output.stdout).ok()).flatten()
|
||||
// Timeout-guarded: an unresponsive hyprctl (Hyprland wedged/reloading)
|
||||
// used to be able to block clients()/monitors()/layers() indefinitely.
|
||||
bread_utils::proc::run_json("hyprctl", &["-j", cmd], std::time::Duration::from_secs(3))
|
||||
}
|
||||
|
||||
pub fn clients() -> Vec<Client> {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue