So much premature optimization
This commit is contained in:
parent
77cabd9e3e
commit
ab9b3647f9
8 changed files with 91 additions and 79 deletions
20
src/cli.rs
20
src/cli.rs
|
@ -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
|
||||||
|
|
|
@ -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(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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)]
|
||||||
|
|
20
src/main.rs
20
src/main.rs
|
@ -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
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -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);
|
||||||
|
|
|
@ -1,3 +1 @@
|
||||||
use anyhow::Error;
|
pub type Result<T> = color_eyre::Result<T>;
|
||||||
|
|
||||||
pub type Result<T> = std::result::Result<T, Error>;
|
|
||||||
|
|
|
@ -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()));
|
||||||
|
|
60
src/tasks.rs
60
src/tasks.rs
|
@ -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?
|
||||||
|
|
Loading…
Reference in a new issue