Skip to content

Commit

Permalink
Add --exit-code flag to exit with a specified value if defaults were …
Browse files Browse the repository at this point in the history
…applied.
  • Loading branch information
dsully committed Sep 18, 2024
1 parent fc26efb commit fdd5255
Show file tree
Hide file tree
Showing 6 changed files with 47 additions and 36 deletions.
2 changes: 1 addition & 1 deletion Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ license = "MIT"
name = "macos-defaults"
readme = "README.md"
repository = "https://github.com/dsully/macos-defaults"
version = "0.1.1"
version = "0.2.0"

[dependencies]
camino = "1.1.9"
Expand Down
4 changes: 2 additions & 2 deletions src/cmd/apply.rs
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@ data:
#[derive(Debug, Default, Serialize, Deserialize)]
struct DefaultsConfig(HashMap<String, HashMap<String, plist::Value>>);

pub fn apply_defaults(path: &Utf8PathBuf) -> Result<()> {
pub fn apply_defaults(path: &Utf8PathBuf) -> Result<bool> {
//
let s = fs::read_to_string(path).map_err(|e| E::FileRead {
path: path.to_owned(),
Expand Down Expand Up @@ -129,7 +129,7 @@ pub fn apply_defaults(path: &Utf8PathBuf) -> Result<()> {
}

if errors.is_empty() {
return Ok(());
return Ok(changed);
}

for error in &errors {
Expand Down
4 changes: 2 additions & 2 deletions src/cmd/mod.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
pub mod apply;
pub mod dump;

pub use apply::*;
pub use dump::*;
pub use apply::{apply_defaults, process_path};
pub use dump::dump;
51 changes: 26 additions & 25 deletions src/defaults.rs
Original file line number Diff line number Diff line change
@@ -1,17 +1,18 @@
//! Utility functions for updating plist files.
//
// NB: Most of this code originated from: https://github.com/gibfahn/up-rs, MIT & Apache 2.0 licensed.
//

use std::collections::HashMap;
use std::fs::{self, File};
use std::io::Read;
use std::mem;

use camino::{Utf8Path, Utf8PathBuf};
use color_eyre::eyre::{eyre, Result};
use duct::cmd;
use log::{debug, info, trace, warn};
use plist::{Dictionary, Value};
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use std::fs::{self, File};
use std::io::Read;
use std::mem;

use super::errors::DefaultsError as E;

Expand Down Expand Up @@ -356,12 +357,14 @@ fn merge_value(new_value: &mut Value, old_value: Option<&Value>) {
/// But any duplicates between old and new values are removed, with the first value taking
/// precedence.
fn replace_ellipsis_array(new_value: &mut Value, old_value: Option<&Value>) {
//
let Value::Array(new_array) = new_value else {
trace!("Value isn't an array, skipping ellipsis replacement...");
return;
};

let ellipsis = plist::Value::from(ELLIPSIS);

let Some(position) = new_array.iter().position(|x| x == &ellipsis) else {
trace!("New value doesn't contain ellipsis, skipping ellipsis replacement...");
return;
Expand All @@ -376,6 +379,7 @@ fn replace_ellipsis_array(new_value: &mut Value, old_value: Option<&Value>) {
let array_copy: Vec<_> = std::mem::take(new_array);

trace!("Performing array ellipsis replacement...");

for element in array_copy {
if element == ellipsis {
for old_element in old_array {
Expand All @@ -390,30 +394,33 @@ fn replace_ellipsis_array(new_value: &mut Value, old_value: Option<&Value>) {
}
}

/// Recursively merge dictionaries, unless the new value is empty `{}`.
/// If a dictionary
/// * is empty `{}`
/// * contains a key `{}`
/// Then the merge step will be skipped for it and its children.
// Recursively merge dictionaries, unless the new value is empty `{}`.
// If a dictionary
// * is empty `{}`
// * contains a key `{}`
// Then the merge step will be skipped for it and its children.
fn deep_merge_dictionaries(new_value: &mut Value, old_value: Option<&Value>) {
//
let Value::Dictionary(new_dict) = new_value else {
trace!("New value is not a dictionary, Skipping merge...");
return;
};

if new_dict.is_empty() {
trace!("New value is an empty dictionary. Skipping merge...");
return;
}

// the "..." key is no longer used, and its merging behavior is performed by default. ignore it, for compatibility with older YAML.
new_dict.remove(ELLIPSIS);

let Some(old_dict) = old_value.and_then(plist::Value::as_dictionary) else {
trace!("Old value wasn't a dict. Skipping merge...");
return;
};

// for each value, recursively invoke this to merge any child dictionaries.
// also perform array ellipsis replacment.
// also perform array ellipsis replacement.
// this occurs even if "!" is present.
for (key, new_child_value) in &mut *new_dict {
let old_child_value = old_dict.get(key);
Expand All @@ -425,7 +432,9 @@ fn deep_merge_dictionaries(new_value: &mut Value, old_value: Option<&Value>) {
new_dict.remove(BANG);
return;
}

trace!("Performing deep merge...");

for (key, old_value) in old_dict {
if !new_dict.contains_key(key) {
new_dict.insert(key.clone(), old_value.clone());
Expand Down Expand Up @@ -497,10 +506,8 @@ mod tests {
use crate::defaults::deep_merge_dictionaries;

use super::{replace_ellipsis_array, NS_GLOBAL_DOMAIN};
// use serial_test::serial;

#[test]
// #[serial(home_dir)] // Test relies on or changes the $HOME env var.
fn plist_path_tests() -> TestResult {
let home_dir = dirs::home_dir().expect("Expected to be able to calculate the user's home directory.");

Expand Down Expand Up @@ -585,7 +592,7 @@ mod tests {
}

#[test]
fn test_deep_merge_dictionaries() -> TestResult {
fn test_deep_merge_dictionaries() {
use plist::{Dictionary, Value};

let old_value = Dictionary::from_iter([
Expand All @@ -612,12 +619,10 @@ mod tests {
.into();

assert_eq!(new_value, expected);

Ok(())
}

#[test]
fn test_replace_ellipsis_dict_nested() -> TestResult {
fn test_replace_ellipsis_dict_nested() {
use plist::{Dictionary, Value};

let old_value = Dictionary::from_iter([(
Expand Down Expand Up @@ -660,12 +665,10 @@ mod tests {
.into();

assert_eq!(new_value, expected);

Ok(())
}

#[test]
fn test_replace_ellipsis_dict_nested_bang() -> TestResult {
fn test_replace_ellipsis_dict_nested_bang() {
use plist::{Dictionary, Value};

let old_value = Dictionary::from_iter([(
Expand Down Expand Up @@ -702,12 +705,10 @@ mod tests {
.into();

assert_eq!(new_value, expected);

Ok(())
}

#[test]
fn test_replace_ellipsis_array() -> TestResult {
fn test_replace_ellipsis_array() {
let old_value = vec![
10.into(), // !
20.into(), // !
Expand Down Expand Up @@ -736,7 +737,7 @@ mod tests {
50.into(),
]
.into();

assert_eq!(new_value, expected);
Ok(())
}
}
20 changes: 15 additions & 5 deletions src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,10 @@ pub(crate) enum Commands {
/// Sets the input file or path to use.
#[arg(required = true, value_hint = ValueHint::FilePath)]
path: Utf8PathBuf,

/// If changes were applied, exit with this return code.
#[clap(short, long, default_value = "0")]
exit_code: i32,
},

/// Generate shell completions to stdout.
Expand Down Expand Up @@ -103,18 +107,22 @@ fn main() -> Result<()> {
env_logger::Builder::new().filter_level(cli.verbose.log_level_filter()).init();

match cli.command {
Commands::Apply { path } => {
Commands::Apply { path, exit_code } => {
//
let mut changed = false;

for p in process_path(path)? {
fs::metadata(&p).map_err(|e| E::FileRead { path: p.clone(), source: e })?;

apply_defaults(&p)?;
if apply_defaults(&p)? {
changed = true;
}
}

Ok(())
std::process::exit(if changed { exit_code } else { 0 });
}
Commands::Completions { shell } => {
generate(shell, &mut CLI::command(), "macos-defaults", &mut io::stdout().lock());

Ok(())
}
Commands::Dump {
Expand All @@ -123,5 +131,7 @@ fn main() -> Result<()> {
global_domain,
domain,
} => dump(current_host, path, global_domain, domain),
}
}?;

std::process::exit(0);
}

0 comments on commit fdd5255

Please sign in to comment.