So much premature optimization

This commit is contained in:
Daniel Flanagan 2024-04-18 12:14:21 -05:00
parent 77cabd9e3e
commit ab9b3647f9
8 changed files with 91 additions and 79 deletions

View file

@ -33,6 +33,26 @@ pub struct Cli {
pub config_file_path: Option<String>, pub config_file_path: Option<String>,
} }
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)] #[derive(Subcommand)]
pub enum Commands { pub enum Commands {
/// List local tasks with options for syncing /// List local tasks with options for syncing

View file

@ -8,19 +8,12 @@ pub enum Command {
impl Command { impl Command {
pub async fn exec(&self, tasks: SharedTasks) -> Result<()> { pub async fn exec(&self, tasks: SharedTasks) -> Result<()> {
match self { match self {
Command::Show => self.show().await, Command::Show => self.show(tasks).await,
} }
} }
pub async fn show(&self) -> Result<()> {} pub async fn show(&self, tasks: SharedTasks) -> Result<()> {
} println!("{:#?}", tasks.config());
#[derive(Parser)]
pub struct MeArgs {}
impl MeArgs {
pub async fn me(&self, tasks: SharedTasks) -> Result<()> {
println!("{:#?}", tasks.gitlab()?.me().await?);
Ok(()) Ok(())
} }
} }

View file

@ -1,7 +1,8 @@
use crate::prelude::*; 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 config::{builder::DefaultState, Config as CConfig, ConfigBuilder};
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
@ -25,38 +26,22 @@ pub struct Config {
} }
impl Config { impl Config {
pub fn load<P>(config_file_path: Option<P>) -> Result<Self> pub fn load(config_file_path: &Path) -> Result<Self> {
where
P: AsRef<Path>,
{
let c = Self::builder(config_file_path)?.build()?; 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<P>(config_file_path: Option<P>) -> Result<ConfigBuilder<DefaultState>> pub fn builder(config_file_path: &Path) -> Result<ConfigBuilder<DefaultState>>
where where {
P: AsRef<Path>, Ok(Self::default_builder()?
{
let config_file_path: Box<dyn AsRef<Path>> = config_file_path
.as_ref()
.map(|s| Ok::<Box<dyn AsRef<Path>>, 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)?
.add_source(config::File::from((*config_file_path).as_ref()).required(false)) .add_source(config::File::from((*config_file_path).as_ref()).required(false))
.add_source(config::Environment::with_prefix("taskr").separator("__"))) .add_source(config::Environment::with_prefix("taskr").separator("__")))
} }
pub fn default_builder() -> Result<ConfigBuilder<DefaultState>> { pub fn default_builder() -> Result<ConfigBuilder<DefaultState>> {
Ok(CConfig::builder() Ok(CConfig::builder().set_default("version", CURRENT_VERSION)?)
.set_default("version", CURRENT_VERSION)?
.set_default("jira", Option::<HashMap<String, config::Value>>::None)?
.set_default("gitlab", Option::<HashMap<String, config::Value>>::None)?)
} }
#[allow(dead_code)] #[allow(dead_code)]

View file

@ -12,31 +12,19 @@ mod task;
mod tasks; mod tasks;
mod tui; mod tui;
use config::Config;
use crate::cli::{Cli, Commands}; use crate::cli::{Cli, Commands};
use crate::prelude::*; use crate::prelude::*;
#[tokio::main] #[tokio::main]
async fn main() -> Result<()> { async fn main() -> Result<()> {
observe::setup_error_handling()?;
let cli = Cli::new(); 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 guard causes logs to be flushed when dropped (which is at the end of
// this function which would be the end of our program) // this function which would be the end of our program)
// https://docs.rs/tracing-appender/latest/tracing_appender/non_blocking/struct.WorkerGuard.html // 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)?); cli.exec().await
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
} }

View file

@ -4,9 +4,14 @@ use std::path::Path;
use tracing_appender::non_blocking::WorkerGuard; use tracing_appender::non_blocking::WorkerGuard;
use tracing_subscriber::{filter::LevelFilter, EnvFilter}; use tracing_subscriber::{filter::LevelFilter, EnvFilter};
pub fn setup_error_handling() -> Result<()> {
color_eyre::install()?;
Ok(())
}
pub fn setup_logging<T, S>( pub fn setup_logging<T, S>(
logs_directory: Option<T>, logs_directory: &Option<T>,
env_filter_directive: Option<S>, env_filter_directive: &Option<S>,
) -> Result<WorkerGuard> ) -> Result<WorkerGuard>
where where
T: AsRef<Path>, T: AsRef<Path>,
@ -38,7 +43,6 @@ where
.map(S::as_ref) .map(S::as_ref)
.unwrap_or("taskr=trace,info"); .unwrap_or("taskr=trace,info");
color_eyre::install().expect("Failed to install color_eyre");
let filter = EnvFilter::builder() let filter = EnvFilter::builder()
.with_default_directive(LevelFilter::TRACE.into()) .with_default_directive(LevelFilter::TRACE.into())
.parse_lossy(env_filter); .parse_lossy(env_filter);

View file

@ -1,3 +1 @@
use anyhow::Error; pub type Result<T> = color_eyre::Result<T>;
pub type Result<T> = std::result::Result<T, Error>;

View file

@ -85,15 +85,15 @@ impl Display for Task {
} }
impl TryFrom<IVec> for Task { impl TryFrom<IVec> for Task {
type Error = anyhow::Error; type Error = serde_json::Error;
fn try_from(value: IVec) -> std::prelude::v1::Result<Self, Self::Error> { fn try_from(value: IVec) -> std::prelude::v1::Result<Self, Self::Error> {
serde_json::from_slice(&value).map_err(|e| e.into()) serde_json::from_slice(&value)
} }
} }
impl TryInto<IVec> for &Task { impl TryInto<IVec> for &Task {
type Error = anyhow::Error; type Error = serde_json::Error;
fn try_into(self) -> std::prelude::v1::Result<IVec, Self::Error> { fn try_into(self) -> std::prelude::v1::Result<IVec, Self::Error> {
Ok(IVec::from(serde_json::to_vec(self)?)) Ok(IVec::from(serde_json::to_vec(self)?))
@ -101,7 +101,7 @@ impl TryInto<IVec> for &Task {
} }
impl TryFrom<&Issue> 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<Self, Self::Error> { fn try_from(value: &Issue) -> std::prelude::v1::Result<Self, Self::Error> {
let mut tags = HashSet::from_iter(value.fields.labels.iter().map(|s| s.to_owned())); let mut tags = HashSet::from_iter(value.fields.labels.iter().map(|s| s.to_owned()));

View file

@ -19,9 +19,10 @@ use crate::{gitlab::GitLab, jira::Jira, result::Result};
pub type Db = sled::Db; pub type Db = sled::Db;
pub struct Tasks { pub struct Tasks {
config: Config,
data_dir: PathBuf, data_dir: PathBuf,
config_file_path: PathBuf,
config: OnceLock<Config>,
gitlab: OnceLock<GitLab>, gitlab: OnceLock<GitLab>,
jira: OnceLock<Jira>, jira: OnceLock<Jira>,
@ -31,30 +32,42 @@ pub struct Tasks {
const MY_DONE_STATUS_CATEGORY_KEY: &str = "done"; const MY_DONE_STATUS_CATEGORY_KEY: &str = "done";
impl Tasks { impl Tasks {
pub fn try_new<D>(config: Config, data_dir: Option<D>) -> Result<Self> pub fn try_new<D>(config_file_path: Option<D>, data_dir: Option<D>) -> Result<Self>
where where
D: AsRef<Path>, D: AsRef<Path>,
{ {
let config = OnceLock::new();
let gitlab = OnceLock::new(); let gitlab = OnceLock::new();
let jira = OnceLock::new(); let jira = OnceLock::new();
let mut default_data_dir = Default::default(); let xdgonce = OnceLock::new();
let data_dir = data_dir let xdg = || -> Result<&xdg::BaseDirectories> {
.as_ref() match xdgonce.get() {
.map(D::as_ref) Some(x) => Ok(x),
.map(Result::Ok) None => {
.unwrap_or_else(|| { let result = xdg::BaseDirectories::new()?;
// really want to avoid calling this so it's put in here to force it to be lazy let _ = xdgonce.set(result);
// also have to shove it in a function-scoped variable so the Ok(xdgonce.get().unwrap())
// 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 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 db = OnceLock::new();
let data_dir: PathBuf = data_dir.to_owned();
Ok(Self { Ok(Self {
config_file_path,
config, config,
gitlab, gitlab,
jira, 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> { pub fn db(&self) -> Result<&Db> {
match self.db.get() { match self.db.get() {
Some(d) => Ok(d), Some(d) => Ok(d),
@ -109,7 +133,7 @@ impl Tasks {
} }
}; };
let result = 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); let _ = self.gitlab.set(result);
Ok(self.gitlab.get().unwrap()) Ok(self.gitlab.get().unwrap())
// TODO: ensure the token works? // 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); let _ = self.jira.set(result);
Ok(self.jira.get().unwrap()) Ok(self.jira.get().unwrap())
// TODO: ensure the token works? // TODO: ensure the token works?