diff --git a/src/config.rs b/src/config.rs index 3384e0b..3387549 100644 --- a/src/config.rs +++ b/src/config.rs @@ -1,3 +1,4 @@ +use crate::log::LogLevel; use crate::Key; use serde::Deserialize; use std::fmt; @@ -17,6 +18,8 @@ pub struct Config { pub conn: Vec, #[serde(default)] pub key_config: KeyConfig, + #[serde(default)] + pub log_level: LogLevel, } #[derive(Debug, Deserialize, Clone)] @@ -51,6 +54,7 @@ impl Default for Config { database: None, }], key_config: KeyConfig::default(), + log_level: LogLevel::default(), } } } @@ -159,15 +163,15 @@ impl Connection { let user = self .user .as_ref() - .ok_or_else(|| anyhow::anyhow!("user is not set"))?; + .ok_or_else(|| anyhow::anyhow!("type mysql needs the user field"))?; let host = self .host .as_ref() - .ok_or_else(|| anyhow::anyhow!("host is not set"))?; + .ok_or_else(|| anyhow::anyhow!("type mysql needs the host field"))?; let port = self .port .as_ref() - .ok_or_else(|| anyhow::anyhow!("port is not set"))?; + .ok_or_else(|| anyhow::anyhow!("type mysql needs the port field"))?; match self.database.as_ref() { Some(database) => Ok(format!( @@ -189,15 +193,15 @@ impl Connection { let user = self .user .as_ref() - .ok_or_else(|| anyhow::anyhow!("user is not set"))?; + .ok_or_else(|| anyhow::anyhow!("type postgres needs the user field"))?; let host = self .host .as_ref() - .ok_or_else(|| anyhow::anyhow!("host is not set"))?; + .ok_or_else(|| anyhow::anyhow!("type postgres needs the host field"))?; let port = self .port .as_ref() - .ok_or_else(|| anyhow::anyhow!("port is not set"))?; + .ok_or_else(|| anyhow::anyhow!("type postgres needs the port field"))?; match self.database.as_ref() { Some(database) => Ok(format!( @@ -216,12 +220,10 @@ impl Connection { } } DatabaseType::Sqlite => { - let path = self - .path - .as_ref() - .map_or(Err(anyhow::anyhow!("path is not set")), |path| { - Ok(path.to_str().unwrap()) - })?; + let path = self.path.as_ref().map_or( + Err(anyhow::anyhow!("type sqlite needs the path field")), + |path| Ok(path.to_str().unwrap()), + )?; Ok(format!("sqlite://{path}", path = path)) } diff --git a/src/log.rs b/src/log.rs index eecf27c..0a2da10 100644 --- a/src/log.rs +++ b/src/log.rs @@ -1,14 +1,63 @@ +use serde::Deserialize; + +#[derive(Debug, PartialEq, PartialOrd, Eq, Ord, Clone, Deserialize)] +pub enum LogLevel { + Quiet, + Error, + Info, +} + +impl Default for LogLevel { + fn default() -> Self { + Self::Info + } +} + +impl LogLevel { + pub fn is_writable(&self, level: &Self) -> bool { + use std::cmp::Ordering; + matches!(self.cmp(level), Ordering::Greater | Ordering::Equal) + } + + pub fn write(&self, level: &Self) -> Box { + if self.is_writable(level) { + match level { + Self::Error => Box::from(std::io::stderr()), + _ => Box::from(std::io::stdout()), + } + } else { + Box::from(std::io::sink()) + } + } +} + +impl From for &'static str { + fn from(log_level: LogLevel) -> &'static str { + match log_level { + LogLevel::Quiet => "quiet", + LogLevel::Info => "info", + LogLevel::Error => "error", + } + } +} + +impl std::str::FromStr for LogLevel { + type Err = String; + + fn from_str(s: &str) -> Result { + match s { + "quiet" => Ok(Self::Quiet), + "info" | "all" => Ok(Self::Info), + "error" => Ok(Self::Error), + level => Err(format!("I don't know the log level of {:?}", level)), + } + } +} + #[macro_export] macro_rules! outln { - ($($expr:expr),+) => {{ - use std::io::{Write}; - use std::fs::OpenOptions; - let mut file = OpenOptions::new() - .write(true) - .create(true) - .append(true) - .open("gobang.log") - .unwrap(); - writeln!(file, $($expr),+).expect("Can't write output"); + ($config:ident#$level:path, $($expr:expr),+) => {{ + use $crate::log::LogLevel::*; + writeln!($config.log_level.write(&$level), $($expr),+).expect("Can't write output"); }} } diff --git a/src/main.rs b/src/main.rs index 241f73d..8a1e718 100644 --- a/src/main.rs +++ b/src/main.rs @@ -22,8 +22,6 @@ use tui::{backend::CrosstermBackend, Terminal}; #[tokio::main] async fn main() -> anyhow::Result<()> { - outln!("gobang logger"); - let value = crate::cli::parse(); let config = config::Config::new(&value.config)?; @@ -32,12 +30,17 @@ async fn main() -> anyhow::Result<()> { let backend = CrosstermBackend::new(io::stdout()); let mut terminal = Terminal::new(backend)?; let events = event::Events::new(250); - let mut app = App::new(config); + let mut app = App::new(config.clone()); terminal.clear()?; loop { - terminal.draw(|f| app.draw(f).unwrap())?; + terminal.draw(|f| { + if let Err(err) = app.draw(f) { + outln!(config#Error, "error: {}", err.to_string()); + std::process::exit(1); + } + })?; match events.next()? { Event::Input(key) => match app.event(key).await { Ok(state) => {