mod item;
mod parse;
use std::cell::OnceCell;
use std::path::PathBuf;
use indexmap::IndexMap;
use serde_derive::{Deserialize, Serialize};
use crate::cli::Cli;
use crate::config::item::Item;
use crate::error::Result;
use crate::ipc::get_socket_path;
use crate::theme::Theme;
use crate::util::sort_by_indices;
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct AppConfig {
#[serde(default)]
include: Vec<String>,
#[serde(default)]
pub theme: Theme,
pub items: Vec<Item>,
#[serde(rename = "socket")]
socket: Option<PathBuf>,
#[serde(skip)]
idx_to_name: OnceCell<IndexMap<usize, String>>,
}
impl AppConfig {
pub fn item_idx_to_name(&self) -> &IndexMap<usize, String> {
self.idx_to_name.get_or_init(|| {
let mut map = self
.items
.iter()
.enumerate()
.map(|(idx, item)| (idx, item.name().to_owned()))
.collect::<IndexMap<usize, String>>();
map.sort_keys();
map
})
}
pub fn socket(&self) -> PathBuf {
self.socket.clone().unwrap()
}
fn sort(mut items: &mut [Item]) {
let len = items.len();
let max = len.saturating_sub(1);
let mut item_order = (0..len).collect::<Vec<usize>>();
for (current_idx, item) in items.iter().enumerate() {
if let Some(target_idx) = item.common.index {
let idx = item_order.remove(current_idx);
item_order.insert(target_idx.clamp(0, max), idx);
}
}
sort_by_indices(&mut items, item_order);
}
fn validate_names(items: &[Item]) -> Result<()> {
for (i, a) in items.iter().enumerate().rev() {
for (j, b) in items.iter().enumerate() {
if i == j {
continue;
}
if let (Some(a), Some(b)) = (&a.common.name, &b.common.name) {
if a == b {
bail!(
"item names must be unique, item[{}] and item[{}] share the same name: {}",
i, j, a
);
}
}
}
}
Ok(())
}
pub async fn read(args: Cli) -> Result<AppConfig> {
let mut cfg = parse::parse(&args)?;
cfg.socket = Some(match args.socket {
Some(socket_path) => socket_path,
None => get_socket_path(cfg.socket.as_ref())?,
});
{
Self::sort(&mut cfg.items);
Self::validate_names(&cfg.items)?;
cfg.theme.validate()?;
}
Ok(cfg)
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::config::item::Common;
use crate::i3::I3Item;
macro_rules! item {
() => {
Item::new(Default::default(), I3Item::empty())
};
($name:expr) => {
Item::new(
Common {
name: Some($name.into()),
..Default::default()
},
I3Item::empty(),
)
};
($name:expr, $index:expr) => {
Item::new(
Common {
index: Some($index),
name: Some($name.into()),
..Default::default()
},
I3Item::empty(),
)
};
}
#[test]
fn validate_names() {
AppConfig::validate_names(&[item!(), item!()]).unwrap();
AppConfig::validate_names(&[item!("a"), item!("b")]).unwrap();
AppConfig::validate_names(&[item!(), item!("a"), item!("b"), item!("c"), item!()]).unwrap();
}
#[test]
#[should_panic(
expected = "item names must be unique, item[3] and item[1] share the same name: c"
)]
fn validate_names_duplicate() {
AppConfig::validate_names(&[item!("a"), item!("c"), item!("d"), item!("c")]).unwrap();
}
macro_rules! to_names {
($items:expr) => {
$items
.into_iter()
.map(|i| i.name().to_owned())
.collect::<Vec<_>>()
};
}
#[test]
fn sort_does_nothing() {
let mut items = [item!("a"), item!("b"), item!("c")];
AppConfig::sort(&mut items);
assert_eq!(to_names!(items), ["a", "b", "c"]);
}
#[test]
fn sort_one_index() {
let mut items = [item!("a"), item!("b", 0), item!("c")];
AppConfig::sort(&mut items);
assert_eq!(to_names!(items), ["b", "a", "c"]);
}
#[test]
fn sort_all_same_index() {
let mut items = [item!("a", 0), item!("b", 0), item!("c", 0)];
AppConfig::sort(&mut items);
assert_eq!(to_names!(items), ["c", "b", "a"]);
}
#[test]
fn sort_oob_index() {
let mut items = [item!("a", 42), item!("b", 1729), item!("c", 9001)];
AppConfig::sort(&mut items);
assert_eq!(to_names!(items), ["b", "a", "c"]);
}
#[test]
fn sort_reverse() {
let mut items = [item!("a", 2), item!("b", 1), item!("c", 0)];
AppConfig::sort(&mut items);
assert_eq!(to_names!(items), ["a", "b", "c"]);
}
}