use crate::error::Result;
use crate::sysctl::r#type::{DisplayType, OutputType};
use crate::sysctl::section::Section;
use ini::Ini;
use owo_colors::colored::Color;
use std::collections::HashMap;
use std::path::PathBuf;
pub const DEFAULT_CONFIG: &str = "systeroid.conf";
pub const CONFIG_ENV: &str = "SYSTEROID_CONFIG";
lazy_static! {
pub static ref DEFAULT_CONFIG_PATHS: Vec<Option<PathBuf>> = vec![
dirs_next::config_dir().map(|p| p.join("systeroid").join(DEFAULT_CONFIG)),
dirs_next::home_dir().map(|p| p.join(".systeroid").join(DEFAULT_CONFIG)),
];
}
macro_rules! map {
($( $key: expr => $val: expr ),*) => {{
let mut map = ::std::collections::HashMap::new();
$( map.insert($key, $val); )*
map
}}
}
macro_rules! parse_ini_flag {
($self: ident, $config: ident, $section: ident, $name: ident) => {
if let Some($name) = $section.get(stringify!($name)) {
$self.$config.$name = $name == "true";
}
};
}
#[derive(Clone, Debug)]
pub struct Config {
pub display_deprecated: bool,
pub kernel_docs: Option<PathBuf>,
pub cli: CliConfig,
pub tui: TuiConfig,
}
#[derive(Clone, Debug)]
pub struct CliConfig {
pub ignore_errors: bool,
pub quiet: bool,
pub no_pager: bool,
pub display_type: DisplayType,
pub output_type: OutputType,
pub color: CliColorConfig,
}
#[derive(Clone, Debug)]
pub struct CliColorConfig {
pub default_color: Color,
pub section_colors: HashMap<Section, Color>,
}
#[derive(Clone, Debug)]
pub struct TuiConfig {
pub tick_rate: u64,
pub no_docs: bool,
pub save_path: Option<PathBuf>,
pub log_file: Option<String>,
pub color: TuiColorConfig,
}
#[derive(Clone, Debug)]
pub struct TuiColorConfig {
pub fg_color: String,
pub bg_color: String,
}
impl Config {
pub fn parse(&mut self, path: Option<PathBuf>) -> Result<()> {
let mut config_paths = DEFAULT_CONFIG_PATHS.clone();
if path.is_some() {
config_paths.insert(0, path);
}
if let Some(path) = config_paths.into_iter().flatten().find(|p| p.exists()) {
log::trace!(target: "config", "Parsing configuration from {:?}", path);
let ini = Ini::load_from_file(path)?;
if let Some(general_section) = ini.section(Some("general")) {
if let Some(display_deprecated) = general_section.get("display_deprecated") {
self.display_deprecated = display_deprecated == "true";
}
if let Some(kernel_docs) = general_section.get("kernel_docs") {
self.kernel_docs = Some(PathBuf::from(kernel_docs));
}
}
if let Some(section) = ini.section(Some("cli")) {
parse_ini_flag!(self, cli, section, ignore_errors);
parse_ini_flag!(self, cli, section, quiet);
parse_ini_flag!(self, cli, section, no_pager);
if let Some(display_type) = section.get("display_type").map(DisplayType::from) {
self.cli.display_type = display_type;
}
if let Some(output_type) = section.get("output_type").map(OutputType::from) {
self.cli.output_type = output_type;
}
}
if let Some(section) = ini.section(Some("cli.colors")) {
if let Some(default_color) = section.get("default_color").map(Color::from) {
self.cli.color.default_color = default_color;
}
for (key, value) in section.iter() {
if key.starts_with("section_") {
self.cli.color.section_colors.insert(
Section::from(key.trim_start_matches("section_").to_string()),
Color::from(value),
);
}
}
}
if let Some(section) = ini.section(Some("tui")) {
if let Some(tick_rate) = section.get("tick_rate").and_then(|v| v.parse().ok()) {
self.tui.tick_rate = tick_rate;
}
if let Some(save_path) = section.get("save_path") {
self.tui.save_path = Some(PathBuf::from(save_path));
}
if let Some(log_file) = section.get("log_file") {
self.tui.log_file = Some(log_file.to_string());
}
parse_ini_flag!(self, tui, section, no_docs);
}
if let Some(section) = ini.section(Some("tui.colors")) {
if let Some(fg_color) = section.get("fg_color") {
self.tui.color.fg_color = fg_color.to_string();
}
if let Some(bg_color) = section.get("bg_color") {
self.tui.color.bg_color = bg_color.to_string();
}
}
}
Ok(())
}
}
impl Default for Config {
fn default() -> Self {
Self {
display_deprecated: false,
kernel_docs: None,
cli: CliConfig {
ignore_errors: false,
quiet: false,
no_pager: false,
display_type: DisplayType::Default,
output_type: OutputType::Default,
color: CliColorConfig {
default_color: Color::BrightBlack,
section_colors: map! {
Section::Abi => Color::Red,
Section::Fs => Color::Green,
Section::Kernel => Color::Magenta,
Section::Net => Color::Blue,
Section::Sunrpc => Color::Yellow,
Section::User => Color::Cyan,
Section::Vm => Color::BrightRed,
Section::Unknown => Color::White
},
},
},
tui: TuiConfig {
tick_rate: 250,
no_docs: false,
save_path: None,
log_file: None,
color: TuiColorConfig {
fg_color: String::from("white"),
bg_color: String::from("black"),
},
},
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_config() -> Result<()> {
let mut config = Config {
display_deprecated: true,
..Default::default()
};
config.cli.display_type = DisplayType::Value;
config.cli.color.default_color = Color::Blue;
config.cli.color.section_colors = HashMap::new();
config.tui.tick_rate = 3000;
config.tui.color.fg_color = String::new();
let path = PathBuf::from(env!("CARGO_MANIFEST_DIR"))
.parent()
.expect("parent directory not found")
.join("config")
.join(DEFAULT_CONFIG);
config.parse(Some(path))?;
assert_eq!(
Config::default().display_deprecated,
config.display_deprecated
);
assert_eq!(
Some(PathBuf::from("/usr/share/doc/linux")),
config.kernel_docs
);
assert_eq!(Config::default().cli.display_type, config.cli.display_type);
assert_eq!(
Config::default().cli.color.default_color,
config.cli.color.default_color
);
assert_eq!(
Config::default().cli.color.section_colors,
config.cli.color.section_colors
);
assert_eq!(Config::default().tui.tick_rate, config.tui.tick_rate);
assert_eq!(
Config::default().tui.color.fg_color,
config.tui.color.fg_color
);
Ok(())
}
}