use std::collections::BTreeMap;
use std::path::{Path, PathBuf};
pub const KEYFILE_BACKEND: &str = "keyfile";
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct GSettingEntry {
pub schema: String,
pub key: String,
pub value: String,
}
impl GSettingEntry {
pub fn new(
schema: impl Into<String>,
key: impl Into<String>,
value: impl Into<String>,
) -> Self {
Self {
schema: schema.into(),
key: key.into(),
value: value.into(),
}
}
}
#[derive(Debug, Clone)]
pub struct GSettingsConfig {
pub isolated: bool,
pub initial: Vec<GSettingEntry>,
}
impl Default for GSettingsConfig {
fn default() -> Self {
Self {
isolated: true,
initial: Vec::new(),
}
}
}
pub fn config_dir(runtime_dir: &Path) -> PathBuf {
runtime_dir.join("config")
}
fn keyfile_path(runtime_dir: &Path) -> PathBuf {
config_dir(runtime_dir).join("glib-2.0/settings/keyfile")
}
pub fn render_keyfile(entries: &[GSettingEntry]) -> String {
let mut groups: BTreeMap<String, BTreeMap<String, String>> = BTreeMap::new();
for e in entries {
let group = e.schema.replace('.', "/");
groups
.entry(group)
.or_default()
.insert(e.key.clone(), e.value.clone());
}
let mut out = String::new();
for (i, (group, kvs)) in groups.iter().enumerate() {
if i > 0 {
out.push('\n');
}
out.push('[');
out.push_str(group);
out.push_str("]\n");
for (k, v) in kvs {
out.push_str(k);
out.push('=');
out.push_str(v);
out.push('\n');
}
}
out
}
pub fn write_keyfile(runtime_dir: &Path, entries: &[GSettingEntry]) -> std::io::Result<()> {
let path = keyfile_path(runtime_dir);
if let Some(parent) = path.parent() {
std::fs::create_dir_all(parent)?;
}
std::fs::write(path, render_keyfile(entries))
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn default_is_isolated_with_no_seeds() {
let cfg = GSettingsConfig::default();
assert!(cfg.isolated);
assert!(cfg.initial.is_empty());
}
#[test]
fn config_dir_is_config_subdir_of_runtime() {
let dir = config_dir(Path::new("/run/user/1000/wd-session-abc"));
assert_eq!(dir, PathBuf::from("/run/user/1000/wd-session-abc/config"));
}
#[test]
fn render_groups_by_schema_path_with_dots_to_slashes() {
let out = render_keyfile(&[
GSettingEntry::new("org.gnome.mutter", "experimental-features", "['x']"),
GSettingEntry::new("org.gnome.desktop.interface", "text-scaling-factor", "1.5"),
]);
assert_eq!(
out,
"[org/gnome/desktop/interface]\ntext-scaling-factor=1.5\n\n\
[org/gnome/mutter]\nexperimental-features=['x']\n"
);
}
#[test]
fn render_last_write_wins_for_same_schema_key() {
let out = render_keyfile(&[
GSettingEntry::new("org.gnome.mutter", "experimental-features", "['default']"),
GSettingEntry::new("org.gnome.mutter", "experimental-features", "['override']"),
]);
assert_eq!(
out,
"[org/gnome/mutter]\nexperimental-features=['override']\n"
);
}
#[test]
fn render_empty_is_empty_string() {
assert_eq!(render_keyfile(&[]), "");
}
#[test]
fn write_keyfile_creates_nested_path() {
let dir = tempfile::tempdir().unwrap();
write_keyfile(
dir.path(),
&[GSettingEntry::new(
"org.gnome.mutter",
"experimental-features",
"['x']",
)],
)
.unwrap();
let written = std::fs::read_to_string(keyfile_path(dir.path())).unwrap();
assert_eq!(written, "[org/gnome/mutter]\nexperimental-features=['x']\n");
}
}