prefs 0.1.1

Type-safe macOS preferences library
use std::ffi::{CString, NulError};

use crate::error::Result;
use crate::key::{self, Key};
use crate::value::PreferenceValue;

/// Interface to a macOS preferences domain.
///
/// Each instance is tied to a single domain (e.g. `com.example.MyApp`).
/// All read and write operations are synchronous.
///
/// ```rust,no_run
/// use prefs::{Key, Preferences};
///
/// const VOLUME: Key<f64> = Key::new("volume");
///
/// # fn main() -> prefs::Result<()> {
/// let prefs = Preferences::new("com.example.MyApp")?;
/// prefs.set(VOLUME, &0.8)?;
/// # Ok(())
/// # }
/// ```
pub struct Preferences {
    domain: CString,
}

impl Preferences {
    /// Open the preferences domain with the given identifier.
    ///
    /// Returns an error if the domain contains a null byte.
    pub fn new(domain: &str) -> std::result::Result<Self, NulError> {
        Ok(Self {
            domain: CString::new(domain)?,
        })
    }

    /// Read a value for the given key.
    ///
    /// Returns `Ok(None)` when the key does not exist.
    pub fn get<T: PreferenceValue>(&self, key: Key<T>) -> Result<Option<T>> {
        let key = key::key_cstring(key.name());
        T::get(&self.domain, &key)
    }

    /// Read a value, returning `default` when the key does not exist.
    pub fn get_or<T: PreferenceValue>(&self, key: Key<T>, default: T) -> Result<T> {
        Ok(self.get(key)?.unwrap_or(default))
    }

    /// Write a value for the given key.
    #[allow(clippy::needless_pass_by_value)]
    pub fn set<T: PreferenceValue>(&self, key: Key<T>, value: &T) -> Result<()> {
        let key = key::key_cstring(key.name());
        T::set(&self.domain, &key, value)
    }

    /// Remove the key from the preferences domain.
    pub fn remove<T: PreferenceValue>(&self, key: Key<T>) -> Result<()> {
        let key = key::key_cstring(key.name());
        T::remove(&self.domain, &key)
    }

    /// Check whether the given key exists.
    pub fn contains<T: PreferenceValue>(&self, key: Key<T>) -> Result<bool> {
        let key = key::key_cstring(key.name());
        T::contains(&self.domain, &key)
    }
}

impl Clone for Preferences {
    fn clone(&self) -> Self {
        Self {
            domain: self.domain.clone(),
        }
    }
}

impl std::fmt::Debug for Preferences {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        f.debug_struct("Preferences")
            .field("domain", &self.domain.to_string_lossy())
            .finish()
    }
}