use directories::*; use serde_derive::*; use std::ffi::OsStr; use std::net::{SocketAddr, ToSocketAddrs}; use std::path::{Path, PathBuf}; pub fn load_default_config() -> Result { let default_config = r###"--- address: "localhost:5959" autoconnect: true autoreconnect: true logging: level: "info" terminal: enabled: false file: enabled: true directory: '%LOGGING_FILE_DIRECTORY%' append: true interface: node_log: scrollback: 2048 command_line: history_size: 2048 theme: shadow: false borders: "simple" colors: background : "black" shadow : "black" view : "black" primary : "light cyan" secondary : "cyan" tertiary : "green" title_primary : "light magenta" title_secondary : "magenta" highlight : "light white" highlight_inactive : "white" highlight_text : "black" log_colors: trace : "light blue" debug : "light green" info : "white" warn : "light yellow" error : "light red" "### .replace( "%LOGGING_FILE_DIRECTORY%", &Settings::get_default_log_directory().to_string_lossy(), ); config::Config::builder() .add_source(config::File::from_str( &default_config, config::FileFormat::Yaml, )) .build() } pub fn load_config( cfg: config::Config, config_file: &Path, ) -> Result { if let Some(config_file_str) = config_file.to_str() { config::Config::builder() .add_source(cfg) .add_source(config::File::new(config_file_str, config::FileFormat::Yaml)) .build() } else { Err(config::ConfigError::Message( "config file path is not valid UTF-8".to_owned(), )) } } #[derive(Copy, Clone, Debug)] pub enum LogLevel { Error, Warn, Info, Debug, Trace, } impl<'de> serde::Deserialize<'de> for LogLevel { fn deserialize(deserializer: D) -> Result where D: serde::Deserializer<'de>, { let s = String::deserialize(deserializer)?; match s.to_ascii_lowercase().as_str() { "error" => Ok(LogLevel::Error), "warn" => Ok(LogLevel::Warn), "info" => Ok(LogLevel::Info), "debug" => Ok(LogLevel::Debug), "trace" => Ok(LogLevel::Trace), _ => Err(serde::de::Error::custom(format!( "Invalid log level: {}", s ))), } } } pub fn convert_loglevel(log_level: LogLevel) -> log::LevelFilter { match log_level { LogLevel::Error => log::LevelFilter::Error, LogLevel::Warn => log::LevelFilter::Warn, LogLevel::Info => log::LevelFilter::Info, LogLevel::Debug => log::LevelFilter::Debug, LogLevel::Trace => log::LevelFilter::Trace, } } #[derive(Debug)] pub struct NamedSocketAddrs { pub name: String, pub addrs: Vec, } impl<'de> serde::Deserialize<'de> for NamedSocketAddrs { fn deserialize(deserializer: D) -> Result where D: serde::Deserializer<'de>, { let s = String::deserialize(deserializer)?; let addr_iter = s.to_socket_addrs().map_err(serde::de::Error::custom)?; Ok(NamedSocketAddrs { name: s, addrs: addr_iter.collect(), }) } } #[derive(Debug, Deserialize)] pub struct Terminal { pub enabled: bool, } #[derive(Debug, Deserialize)] pub struct File { pub enabled: bool, pub directory: String, pub append: bool, } #[derive(Debug, Deserialize)] pub struct Logging { pub terminal: Terminal, pub file: File, pub level: LogLevel, } #[derive(Debug, Deserialize)] pub struct Colors { pub background: String, pub shadow: String, pub view: String, pub primary: String, pub secondary: String, pub tertiary: String, pub title_primary: String, pub title_secondary: String, pub highlight: String, pub highlight_inactive: String, pub highlight_text: String, } #[derive(Debug, Deserialize)] pub struct LogColors { pub trace: String, pub debug: String, pub info: String, pub warn: String, pub error: String, } #[derive(Debug, Deserialize)] pub struct Theme { pub shadow: bool, pub borders: String, pub colors: Colors, pub log_colors: LogColors, } #[derive(Debug, Deserialize)] pub struct NodeLog { pub scrollback: usize, } #[derive(Debug, Deserialize)] pub struct CommandLine { pub history_size: usize, } #[derive(Debug, Deserialize)] pub struct Interface { pub theme: Theme, pub node_log: NodeLog, pub command_line: CommandLine, } #[derive(Debug, Deserialize)] pub struct Settings { pub address: NamedSocketAddrs, pub autoconnect: bool, pub autoreconnect: bool, pub logging: Logging, pub interface: Interface, } impl Settings { pub fn get_default_config_path() -> PathBuf { // Get default configuration file location let mut default_config_path = if let Some(my_proj_dirs) = ProjectDirs::from("org", "Veilid", "Veilid") { PathBuf::from(my_proj_dirs.config_dir()) } else { PathBuf::from("./") }; default_config_path.push("veilid-client.conf"); default_config_path } pub fn get_default_log_directory() -> PathBuf { // Get default configuration file location let mut default_log_directory = if let Some(my_proj_dirs) = ProjectDirs::from("org", "Veilid", "Veilid") { PathBuf::from(my_proj_dirs.config_dir()) } else { PathBuf::from("./") }; default_log_directory.push("logs/"); default_log_directory } pub fn new(config_file: Option<&OsStr>) -> Result { // Load the default config let mut cfg = load_default_config()?; // Merge in the config file if we have one if let Some(config_file) = config_file { let config_file_path = Path::new(config_file); // If the user specifies a config file on the command line then it must exist cfg = load_config(cfg, config_file_path)?; } // Generate config cfg.try_deserialize() } } #[test] fn test_default_config() { let cfg = load_default_config().unwrap(); let settings = cfg.try_deserialize::().unwrap(); println!("default settings: {:?}", settings); }