use std::{collections::HashMap, path::Path};
use anyhow::{anyhow, Context, Result};
use config::{Config, File, FileFormat, Value};
use lazy_static::lazy_static;
use tokio::sync::OnceCell;
#[cfg(feature = "battery")]
use crate::panels::Battery;
#[cfg(feature = "cpu")]
use crate::panels::Cpu;
#[cfg(feature = "custom")]
use crate::panels::Custom;
#[cfg(feature = "fanotify")]
use crate::panels::Fanotify;
#[cfg(feature = "github")]
use crate::panels::Github;
#[cfg(feature = "inotify")]
use crate::panels::Inotify;
#[cfg(feature = "memory")]
use crate::panels::Memory;
#[cfg(feature = "mpd")]
use crate::panels::Mpd;
#[cfg(feature = "network")]
use crate::panels::Network;
#[cfg(feature = "ping")]
use crate::panels::Ping;
#[cfg(feature = "pulseaudio")]
use crate::panels::Pulseaudio;
#[cfg(feature = "separator")]
use crate::panels::Separator;
#[cfg(feature = "temp")]
use crate::panels::Temp;
#[cfg(feature = "xwindow")]
use crate::panels::XWindow;
#[cfg(feature = "xworkspaces")]
use crate::panels::XWorkspaces;
#[cfg(feature = "clock")]
use crate::panels::{
precision::{Days, Hours, Minutes, Seconds},
Clock,
};
use crate::{
builders::BarConfigBuilder, cleanup, get_table_from_config,
remove_string_from_config, Alignment, Attrs, BarConfig, Margins,
PanelConfig, Position,
};
lazy_static! {
pub static ref ATTRS: OnceCell<HashMap<String, Value>> =
OnceCell::new();
pub static ref RAMPS: OnceCell<HashMap<String, Value>> =
OnceCell::new();
pub static ref BGS: OnceCell<HashMap<String, Value>> =
OnceCell::new();
pub static ref CONSTS: OnceCell<HashMap<String, Value>> =
OnceCell::new();
pub static ref IMAGES: OnceCell<HashMap<String, Value>> =
OnceCell::new();
}
pub fn parse(bar_name: &str, config: &Path) -> Result<BarConfig> {
let config = Config::builder()
.add_source(
File::new(
config.to_str().unwrap_or_else(|| {
log::error!("Invalid config path");
cleanup::exit(None, 101)
}),
FileFormat::Toml,
)
.required(true),
)
.build()
.unwrap_or_else(|e| {
log::error!("Error parsing config file: {e}");
cleanup::exit(None, 101)
});
log::info!("Read config file");
ATTRS
.set(config.get_table("attrs").unwrap_or_default())
.unwrap();
RAMPS
.set(config.get_table("ramps").unwrap_or_default())
.unwrap();
BGS.set(config.get_table("bgs").unwrap_or_default())
.unwrap();
CONSTS
.set(config.get_table("consts").unwrap_or_default())
.unwrap();
IMAGES
.set(config.get_table("images").unwrap_or_default())
.unwrap();
let mut bars_table = config
.get_table("bars")
.context("`bars` doesn't exist or isn't a table")?;
log::trace!("got bars table from config");
let mut bar_table = bars_table
.remove(bar_name)
.with_context(|| format!("`{bar_name}` doesn't exist"))?
.into_table()
.with_context(|| format!("`{bar_name}` isn't a table"))?;
log::trace!("got bar table {bar_name} from config");
let mut bar = BarConfigBuilder::default()
.name(bar_name.to_owned())
.position({
let val = match bar_table
.remove("position")
.unwrap_or_default()
.into_string()
.unwrap_or_default()
.as_str()
{
"top" => Position::Top,
"bottom" => Position::Bottom,
_ => Position::Top,
};
log::trace!("got bar position: {val:?}");
val
})
.height({
let val = bar_table
.remove("height")
.unwrap_or_default()
.into_uint()
.unwrap_or(24) as u16;
log::trace!("got bar height: {val}");
val
})
.transparent({
let val = bar_table
.remove("transparent")
.unwrap_or_default()
.into_bool()
.unwrap_or_default();
log::trace!("got bar transparency: {val}");
val
})
.bg({
let val = bar_table
.remove("bg")
.unwrap_or_default()
.into_string()
.unwrap_or_default()
.parse()
.unwrap_or_default();
log::trace!("got bar background: {val}");
val
})
.margins({
let val = Margins::new(
bar_table
.remove("margin_left")
.unwrap_or_default()
.into_float()
.unwrap_or_default(),
bar_table
.remove("margin_internal")
.unwrap_or_default()
.into_float()
.unwrap_or_default(),
bar_table
.remove("margin_right")
.unwrap_or_default()
.into_float()
.unwrap_or_default(),
);
log::trace!("got bar margins: {val:?}");
val
})
.reverse_scroll({
let val = bar_table
.remove("reverse_scroll")
.unwrap_or_default()
.into_bool()
.unwrap_or_default();
log::trace!("got bar reverse scroll: {val}");
val
})
.ipc({
let val = bar_table
.remove("ipc")
.unwrap_or_default()
.into_bool()
.unwrap_or_default();
log::trace!("got bar ipc: {val}");
val
})
.attrs({
let val =
remove_string_from_config("default_attrs", &mut bar_table)
.map_or_else(Attrs::default, Attrs::parse_global);
log::trace!("got bar attrs: {val:?}");
val
})
.monitor({
let val = remove_string_from_config("monitor", &mut bar_table);
log::trace!("got bar monitor: {val:?}");
val
})
.left(Vec::new())
.center(Vec::new())
.right(Vec::new())
.build()?;
let mut left_final = Vec::new();
let mut center_final = Vec::new();
let mut right_final = Vec::new();
let panels_left = bar_table.remove("panels_left");
if let Some(pl) = panels_left {
let panel_list =
pl.into_array().context("`panels_left` isn't an array")?;
for p in panel_list {
if let Ok(name) = p.clone().into_string() {
log::debug!("Adding left panel {name}");
left_final.push(name);
} else {
log::warn!("Ignoring non-string value {p:?} in `panels_left`");
}
}
}
let panels_center = bar_table.remove("panels_center");
if let Some(pc) = panels_center {
let panel_list =
pc.into_array().context("`panels_center` isn't an array")?;
for p in panel_list {
if let Ok(name) = p.clone().into_string() {
log::debug!("Adding center panel {name}");
center_final.push(name);
} else {
log::warn!(
"Ignoring non-string value {p:?} in `panels_center`"
);
}
}
}
let panels_right = bar_table.remove("panels_right");
if let Some(pr) = panels_right {
let panel_list =
pr.into_array().context("`panels_right` isn't an array")?;
for p in panel_list {
if let Ok(name) = p.clone().into_string() {
log::debug!("Adding right panel {name}");
right_final.push(name);
} else {
log::warn!("Ignoring non-string value {p:?} in `panels_right`");
}
}
}
let panels_table = config
.get_table("panels")
.context("`panels` doesn't exist or isn't a table")?;
log::trace!("got panels table");
left_final
.into_iter()
.filter_map(|p| parse_panel(p.leak(), &panels_table, &config))
.for_each(|p| bar.add_panel(p, Alignment::Left));
log::debug!("left panels added");
center_final
.into_iter()
.filter_map(|p| parse_panel(p.leak(), &panels_table, &config))
.for_each(|p| bar.add_panel(p, Alignment::Center));
log::debug!("center panels added");
right_final
.into_iter()
.filter_map(|p| parse_panel(p.leak(), &panels_table, &config))
.for_each(|p| bar.add_panel(p, Alignment::Right));
log::debug!("right panels added");
Ok(bar)
}
fn parse_panel(
p: &'static str,
panels_table: &HashMap<String, Value>,
config: &Config,
) -> Option<Box<dyn PanelConfig>> {
if let Some(mut table) = get_table_from_config(p, panels_table) {
if let Some(s) = remove_string_from_config("type", &mut table) {
log::debug!("parsing {s} panel");
return match s.as_str() {
#[cfg(feature = "battery")]
"battery" => {
Battery::parse(p, &mut table, config)
.map::<Box<dyn PanelConfig>, _>(|p| Box::new(p))
}
#[cfg(feature = "clock")]
"clock" => {
if let Some(precision) = &mut table.remove("precision") {
if let Ok(precision) = precision.clone().into_string() {
match precision.as_str() {
"days" => {
Clock::<Days>::parse(p, &mut table, config)
.map::<Box<dyn PanelConfig>, _>(|p| {
Box::new(p)
})
}
"hours" => {
Clock::<Hours>::parse(p, &mut table, config)
.map::<Box<dyn PanelConfig>, _>(|p| {
Box::new(p)
})
}
"minutes" => Clock::<Minutes>::parse(
p, &mut table, config,
)
.map::<Box<dyn PanelConfig>, _>(|p| {
Box::new(p)
}),
"seconds" | _ => Clock::<Seconds>::parse(
p, &mut table, config,
)
.map::<Box<dyn PanelConfig>, _>(|p| {
Box::new(p)
}),
}
} else {
log::warn!(
"Ignoring non-string value {precision:?} \
(location attempt: {:?})",
precision.origin()
);
Clock::<Seconds>::parse(p, &mut table, config)
.map::<Box<dyn PanelConfig>, _>(|p| Box::new(p))
}
} else {
Clock::<Seconds>::parse(p, &mut table, config)
.map::<Box<dyn PanelConfig>, _>(|p| Box::new(p))
}
}
#[cfg(feature = "cpu")]
"cpu" => Cpu::parse(p, &mut table, config)
.map::<Box<dyn PanelConfig>, _>(|p| Box::new(p)),
#[cfg(feature = "custom")]
"custom" => {
Custom::parse(p, &mut table, config)
.map::<Box<dyn PanelConfig>, _>(|p| Box::new(p))
}
#[cfg(feature = "fanotify")]
"fanotify" => {
Fanotify::parse(p, &mut table, config)
.map::<Box<dyn PanelConfig>, _>(|p| Box::new(p))
}
#[cfg(feature = "github")]
"github" => {
Github::parse(p, &mut table, config)
.map::<Box<dyn PanelConfig>, _>(|p| Box::new(p))
}
#[cfg(feature = "inotify")]
"inotify" => {
Inotify::parse(p, &mut table, config)
.map::<Box<dyn PanelConfig>, _>(|p| Box::new(p))
}
#[cfg(feature = "memory")]
"memory" => {
Memory::parse(p, &mut table, config)
.map::<Box<dyn PanelConfig>, _>(|p| Box::new(p))
}
#[cfg(feature = "mpd")]
"mpd" => Mpd::parse(p, &mut table, config)
.map::<Box<dyn PanelConfig>, _>(|p| Box::new(p)),
#[cfg(feature = "network")]
"network" => {
Network::parse(p, &mut table, config)
.map::<Box<dyn PanelConfig>, _>(|p| Box::new(p))
}
#[cfg(feature = "ping")]
"ping" => {
Ping::parse(p, &mut table, config)
.map::<Box<dyn PanelConfig>, _>(|p| Box::new(p))
}
#[cfg(feature = "pulseaudio")]
"pulseaudio" => Pulseaudio::parse(p, &mut table, config)
.map::<Box<dyn PanelConfig>, _>(|p| Box::new(p)),
#[cfg(feature = "separator")]
"separator" => Separator::parse(p, &mut table, config)
.map::<Box<dyn PanelConfig>, _>(|p| Box::new(p)),
#[cfg(feature = "temp")]
"temp" => {
Temp::parse(p, &mut table, config)
.map::<Box<dyn PanelConfig>, _>(|p| Box::new(p))
}
#[cfg(feature = "xwindow")]
"xwindow" => {
XWindow::parse(p, &mut table, config)
.map::<Box<dyn PanelConfig>, _>(|p| Box::new(p))
}
#[cfg(feature = "xworkspaces")]
"xworkspaces" => XWorkspaces::parse(p, &mut table, config)
.map::<Box<dyn PanelConfig>, _>(|p| Box::new(p)),
s => Err(anyhow!("Unknown panel type {s}")),
}
.map_err(|e| {
log::error!("{e}");
e
})
.ok();
}
}
None
}