From ab9b3647f97139bd89131a907cb31d8456475dc5 Mon Sep 17 00:00:00 2001 From: Daniel Flanagan Date: Thu, 18 Apr 2024 12:14:21 -0500 Subject: [PATCH] So much premature optimization --- src/cli.rs | 20 ++++++++++++++++ src/cli/config.rs | 13 +++------- src/config.rs | 35 ++++++++------------------- src/main.rs | 20 ++++------------ src/observe.rs | 10 +++++--- src/result.rs | 4 +--- src/task.rs | 8 +++---- src/tasks.rs | 60 +++++++++++++++++++++++++++++++++-------------- 8 files changed, 91 insertions(+), 79 deletions(-) diff --git a/src/cli.rs b/src/cli.rs index abc11cf..df6497b 100644 --- a/src/cli.rs +++ b/src/cli.rs @@ -33,6 +33,26 @@ pub struct Cli { pub config_file_path: Option, } +impl Cli { + pub async fn exec(self) -> Result<()> { + let tasks = Arc::new(crate::tasks::Tasks::try_new( + self.config_file_path, + self.data_directory, + )?); + + match self.command { + Commands::Ui(args) => args.run(tasks).await, + Commands::Purge(args) => args.run(tasks).await, + Commands::Cleanup(args) => args.run(tasks).await, + Commands::Sync(args) => args.sync(tasks).await, + Commands::List(args) => args.list(tasks).await, + Commands::Jira(jira) => jira.exec(tasks).await, + Commands::Gitlab(gitlab) => gitlab.exec(tasks).await, + Commands::Config(config) => config.exec(tasks).await, + } + } +} + #[derive(Subcommand)] pub enum Commands { /// List local tasks with options for syncing diff --git a/src/cli/config.rs b/src/cli/config.rs index bb8f078..97b7156 100644 --- a/src/cli/config.rs +++ b/src/cli/config.rs @@ -8,19 +8,12 @@ pub enum Command { impl Command { pub async fn exec(&self, tasks: SharedTasks) -> Result<()> { match self { - Command::Show => self.show().await, + Command::Show => self.show(tasks).await, } } - pub async fn show(&self) -> Result<()> {} -} - -#[derive(Parser)] -pub struct MeArgs {} - -impl MeArgs { - pub async fn me(&self, tasks: SharedTasks) -> Result<()> { - println!("{:#?}", tasks.gitlab()?.me().await?); + pub async fn show(&self, tasks: SharedTasks) -> Result<()> { + println!("{:#?}", tasks.config()); Ok(()) } } diff --git a/src/config.rs b/src/config.rs index 225bc3c..19d37bf 100644 --- a/src/config.rs +++ b/src/config.rs @@ -1,7 +1,8 @@ use crate::prelude::*; -use std::{collections::HashMap, fmt::Debug, path::Path}; +use std::{fmt::Debug, path::Path}; +use color_eyre::{eyre::Context, Section}; use config::{builder::DefaultState, Config as CConfig, ConfigBuilder}; use serde::{Deserialize, Serialize}; @@ -25,38 +26,22 @@ pub struct Config { } impl Config { - pub fn load

(config_file_path: Option

) -> Result - where - P: AsRef, - { + pub fn load(config_file_path: &Path) -> Result { let c = Self::builder(config_file_path)?.build()?; - Ok(c.try_deserialize()?) + Ok(c.try_deserialize() + .suggestion("run `taskr config setup` to ensure valid configuration") + .with_context(|| "failed to deserialize configuration")?) } - pub fn builder

(config_file_path: Option

) -> Result> - where - P: AsRef, - { - let config_file_path: Box> = config_file_path - .as_ref() - .map(|s| Ok::>, anyhow::Error>(Box::new(s.as_ref()))) - .unwrap_or_else(|| { - Ok(Box::new( - xdg::BaseDirectories::new()?.get_config_file("taskr/config.toml"), - )) - })?; - - Ok(Self::default_builder() - .map_err(anyhow::Error::from)? + pub fn builder(config_file_path: &Path) -> Result> +where { + Ok(Self::default_builder()? .add_source(config::File::from((*config_file_path).as_ref()).required(false)) .add_source(config::Environment::with_prefix("taskr").separator("__"))) } pub fn default_builder() -> Result> { - Ok(CConfig::builder() - .set_default("version", CURRENT_VERSION)? - .set_default("jira", Option::>::None)? - .set_default("gitlab", Option::>::None)?) + Ok(CConfig::builder().set_default("version", CURRENT_VERSION)?) } #[allow(dead_code)] diff --git a/src/main.rs b/src/main.rs index 08c2c8f..30e54cd 100644 --- a/src/main.rs +++ b/src/main.rs @@ -12,31 +12,19 @@ mod task; mod tasks; mod tui; -use config::Config; - use crate::cli::{Cli, Commands}; use crate::prelude::*; #[tokio::main] async fn main() -> Result<()> { + observe::setup_error_handling()?; + let cli = Cli::new(); - let conf = Config::load(cli.config_file_path)?; // this guard causes logs to be flushed when dropped (which is at the end of // this function which would be the end of our program) // https://docs.rs/tracing-appender/latest/tracing_appender/non_blocking/struct.WorkerGuard.html - let _log_guard = observe::setup_logging(cli.logs_directory, cli.tracing_env_filter)?; + let _log_guard = observe::setup_logging(&cli.logs_directory, &cli.tracing_env_filter)?; - let tasks = Arc::new(crate::tasks::Tasks::try_new(conf, cli.data_directory)?); - - let result = match cli.command { - Commands::Ui(args) => args.run(tasks).await, - Commands::Purge(args) => args.run(tasks).await, - Commands::Cleanup(args) => args.run(tasks).await, - Commands::Sync(args) => args.sync(tasks).await, - Commands::List(args) => args.list(tasks).await, - Commands::Jira(jira) => jira.exec(tasks).await, - Commands::Gitlab(gitlab) => gitlab.exec(tasks).await, - }; - result + cli.exec().await } diff --git a/src/observe.rs b/src/observe.rs index f5178ee..f432713 100644 --- a/src/observe.rs +++ b/src/observe.rs @@ -4,9 +4,14 @@ use std::path::Path; use tracing_appender::non_blocking::WorkerGuard; use tracing_subscriber::{filter::LevelFilter, EnvFilter}; +pub fn setup_error_handling() -> Result<()> { + color_eyre::install()?; + Ok(()) +} + pub fn setup_logging( - logs_directory: Option, - env_filter_directive: Option, + logs_directory: &Option, + env_filter_directive: &Option, ) -> Result where T: AsRef, @@ -38,7 +43,6 @@ where .map(S::as_ref) .unwrap_or("taskr=trace,info"); - color_eyre::install().expect("Failed to install color_eyre"); let filter = EnvFilter::builder() .with_default_directive(LevelFilter::TRACE.into()) .parse_lossy(env_filter); diff --git a/src/result.rs b/src/result.rs index 0d6435f..a4c2dbd 100644 --- a/src/result.rs +++ b/src/result.rs @@ -1,3 +1 @@ -use anyhow::Error; - -pub type Result = std::result::Result; +pub type Result = color_eyre::Result; diff --git a/src/task.rs b/src/task.rs index d274a59..c1de9b2 100644 --- a/src/task.rs +++ b/src/task.rs @@ -85,15 +85,15 @@ impl Display for Task { } impl TryFrom for Task { - type Error = anyhow::Error; + type Error = serde_json::Error; fn try_from(value: IVec) -> std::prelude::v1::Result { - serde_json::from_slice(&value).map_err(|e| e.into()) + serde_json::from_slice(&value) } } impl TryInto for &Task { - type Error = anyhow::Error; + type Error = serde_json::Error; fn try_into(self) -> std::prelude::v1::Result { Ok(IVec::from(serde_json::to_vec(self)?)) @@ -101,7 +101,7 @@ impl TryInto for &Task { } impl TryFrom<&Issue> for Task { - type Error = anyhow::Error; + type Error = std::num::ParseIntError; fn try_from(value: &Issue) -> std::prelude::v1::Result { let mut tags = HashSet::from_iter(value.fields.labels.iter().map(|s| s.to_owned())); diff --git a/src/tasks.rs b/src/tasks.rs index c306201..62cd134 100644 --- a/src/tasks.rs +++ b/src/tasks.rs @@ -19,9 +19,10 @@ use crate::{gitlab::GitLab, jira::Jira, result::Result}; pub type Db = sled::Db; pub struct Tasks { - config: Config, data_dir: PathBuf, + config_file_path: PathBuf, + config: OnceLock, gitlab: OnceLock, jira: OnceLock, @@ -31,30 +32,42 @@ pub struct Tasks { const MY_DONE_STATUS_CATEGORY_KEY: &str = "done"; impl Tasks { - pub fn try_new(config: Config, data_dir: Option) -> Result + pub fn try_new(config_file_path: Option, data_dir: Option) -> Result where D: AsRef, { + let config = OnceLock::new(); let gitlab = OnceLock::new(); let jira = OnceLock::new(); - let mut default_data_dir = Default::default(); - let data_dir = data_dir - .as_ref() - .map(D::as_ref) - .map(Result::Ok) - .unwrap_or_else(|| { - // really want to avoid calling this so it's put in here to force it to be lazy - // also have to shove it in a function-scoped variable so the - // reference we create in this closure has the same lifetime as the function - default_data_dir = - xdg::BaseDirectories::new()?.create_data_directory("taskr/data")?; - Ok(default_data_dir.as_ref()) - })?; + let xdgonce = OnceLock::new(); + let xdg = || -> Result<&xdg::BaseDirectories> { + match xdgonce.get() { + Some(x) => Ok(x), + None => { + let result = xdg::BaseDirectories::new()?; + let _ = xdgonce.set(result); + Ok(xdgonce.get().unwrap()) + } + } + }; + + let config_file_path: PathBuf = if let Some(p) = config_file_path { + p.as_ref().to_owned() + } else { + xdg()?.get_config_file("taskr/config.toml") + }; + + let data_dir: PathBuf = if let Some(p) = data_dir { + p.as_ref().to_owned() + } else { + xdg()?.create_data_directory("taskr/data")? + }; + let db = OnceLock::new(); - let data_dir: PathBuf = data_dir.to_owned(); Ok(Self { + config_file_path, config, gitlab, jira, @@ -63,6 +76,17 @@ impl Tasks { }) } + pub fn config(&self) -> Result<&Config> { + match self.config.get() { + Some(c) => Ok(c), + None => { + let result = Config::load(self.config_file_path.as_path())?; + let _ = self.config.set(result); + Ok(self.config.get().unwrap()) + } + } + } + pub fn db(&self) -> Result<&Db> { match self.db.get() { Some(d) => Ok(d), @@ -109,7 +133,7 @@ impl Tasks { } }; let result = - GitLab::try_new(&format!("{}/api/v4", self.config.gitlab.url), &gl_token)?; + GitLab::try_new(&format!("{}/api/v4", self.config()?.gitlab.url), &gl_token)?; let _ = self.gitlab.set(result); Ok(self.gitlab.get().unwrap()) // TODO: ensure the token works? @@ -152,7 +176,7 @@ impl Tasks { } }; - let result = Jira::try_new(&format!("{}", self.config.jira.url), &jira_token)?; + let result = Jira::try_new(&format!("{}", self.config()?.jira.url), &jira_token)?; let _ = self.jira.set(result); Ok(self.jira.get().unwrap()) // TODO: ensure the token works?