diff --git a/Cargo.lock b/Cargo.lock index fd52f22..26b94e0 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -522,6 +522,7 @@ dependencies = [ "testresult", "thiserror", "yaml-rust", + "yaml-split", ] [[package]] @@ -1224,3 +1225,12 @@ checksum = "56c1936c4cc7a1c9ab21a1ebb602eb942ba868cbd44a99cb7cdc5892335e1c85" dependencies = [ "linked-hash-map", ] + +[[package]] +name = "yaml-split" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9dab2bfe3b9aa09e8424e0e5139526c6a3857c4bd334d66b0453a357dd80fc58" +dependencies = [ + "thiserror", +] diff --git a/Cargo.toml b/Cargo.toml index c3d7da5..126f7b8 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -42,6 +42,7 @@ shadow-rs = { version = "0.35.0", default-features = false } sysinfo = "0.31.4" thiserror = "1.0.63" yaml-rust = "0.4.5" +yaml-split = "0.4.0" [dev-dependencies] testresult = "0.4.1" diff --git a/README.md b/README.md index 7dc2ef9..1e6ba58 100644 --- a/README.md +++ b/README.md @@ -88,6 +88,16 @@ data: # Export photos in vCards ABIncludePhotosInVCard: true +--- +# Multiple yaml docs in single file. +description: Dock + +kill: ["Dock"] + +data: + # Automatically hide and show the Dock + com.apple.dock: + autohide: true ``` You may also use full paths to `.plist` files instead of domain names. This is the only way to set values in /Library/Preferences/. diff --git a/src/cmd/apply.rs b/src/cmd/apply.rs index a80f9f9..12beb45 100644 --- a/src/cmd/apply.rs +++ b/src/cmd/apply.rs @@ -1,6 +1,7 @@ use std::collections::HashMap; +use std::io::{BufReader, BufRead}; use std::ffi::OsStr; -use std::fs; +use std::fs::File; use std::os::unix::ffi::OsStrExt; use camino::Utf8PathBuf; @@ -9,6 +10,7 @@ use colored::Colorize; use log::{debug, error, trace}; use serde::{Deserialize, Serialize}; use sysinfo::{Signal, System}; +use yaml_split::DocumentIterator; use crate::defaults::{write_defaults_values, MacOSDefaults}; use crate::errors::DefaultsError as E; @@ -81,14 +83,30 @@ struct DefaultsConfig(HashMap>); pub fn apply_defaults(path: &Utf8PathBuf) -> Result { // - let s = fs::read_to_string(path).map_err(|e| E::FileRead { + let file = File::open(path).map_err(|e| E::FileRead { path: path.to_owned(), source: e, })?; - trace!("Task '{path}' contents: <<<{s}>>>"); + let reader = BufReader::new(file); - let config: MacOSDefaults = serde_yaml::from_str(&s).map_err(|e| E::InvalidYaml { + trace!("Processing YAML documents from file: {}", path); + + let mut any_changed = false; + + for doc in DocumentIterator::new(reader) { + let doc = doc.map_err(|e| E::YamlSplitError { + path: path.to_owned(), + source: e, + })?; + any_changed |= process_yaml_document(doc.as_bytes(), path)?; + } + + Ok(any_changed) +} + +fn process_yaml_document(doc: impl BufRead, path: &Utf8PathBuf) -> Result { + let config: MacOSDefaults = serde_yaml::from_reader(doc).map_err(|e| E::InvalidYaml { path: path.to_owned(), source: e, })?; @@ -104,26 +122,20 @@ pub fn apply_defaults(path: &Utf8PathBuf) -> Result { println!(" {} {}", "▶".green(), description.bold().white()); } - let (passed, errors): (Vec<_>, Vec<_>) = defaults - .0 + let results: Vec<_> = defaults.0 .into_iter() .map(|(domain, prefs)| write_defaults_values(&domain, prefs, config.current_host)) - .partition(Result::is_ok); - - debug!("Passed: {passed:?}"); - debug!("Errored: {errors:?}"); + .collect(); - let changed = passed.iter().flatten().any(|&value| value); + let (passed, errors): (Vec<_>, Vec<_>) = results.into_iter().partition(Result::is_ok); - let errors: Vec<_> = errors.into_iter().map(Result::unwrap_err).collect(); - let passed: Vec<_> = passed.into_iter().map(Result::unwrap).collect(); + let changed = passed.iter().any(|r| *r.as_ref().unwrap()); - if changed && passed.into_iter().any(|r| r) { + if changed { if let Some(kill) = config.kill { - for process in &kill { + for process in kill { println!(" {} Restarting: {}", "✖".blue(), process.white()); - - kill_process_by_name(process); + kill_process_by_name(&process); } } } @@ -138,7 +150,9 @@ pub fn apply_defaults(path: &Utf8PathBuf) -> Result { let mut errors_iter = errors.into_iter(); - Err(errors_iter.next().ok_or(E::UnexpectedNone)?).wrap_err_with(|| eyre!("{:?}", errors_iter.collect::>())) + let first_error = errors_iter.next().ok_or(E::UnexpectedNone)??; + + Err(eyre!("{:?}", errors_iter.collect::>())).wrap_err(first_error) } fn kill_process_by_name(name: &str) { diff --git a/src/errors.rs b/src/errors.rs index 403cf86..7f5c2c7 100644 --- a/src/errors.rs +++ b/src/errors.rs @@ -47,4 +47,7 @@ pub enum DefaultsError { #[error("Eyre error.")] EyreError { source: color_eyre::Report }, + + #[error("failed to split YAML file {path}")] + YamlSplitError { path: Utf8PathBuf, source: yaml_split::YamlSplitError }, }