Improve config options

This commit is contained in:
Daniel Flanagan 2024-04-17 10:26:15 -05:00
parent 1b70e7c9bd
commit c08929c765
8 changed files with 109 additions and 29 deletions

1
Cargo.lock generated
View file

@ -1681,6 +1681,7 @@ dependencies = [
"clap_derive",
"color-eyre",
"crossterm",
"futures",
"ratatui",
"redact",
"regex",

View file

@ -11,6 +11,7 @@ clap = { version = "4.5.4", features = ["derive"] }
clap_derive = "4.5.4"
color-eyre = "0.6.3"
crossterm = "0.27.0"
futures = "0.3.30"
ratatui = "0.26.2"
redact = "0.1.9"
regex = "1.10.3"

View file

@ -10,9 +10,15 @@ pub struct Cli {
#[command(subcommand)]
pub command: Commands,
/// Tell taskr which directory to write log files to
/// The directory to write log files
#[arg(long, default_value = None)]
pub logs_directory: Option<String>,
/// Define which tracing values are logged
/// See https://docs.rs/tracing-subscriber/latest/tracing_subscriber/filter/struct.EnvFilter.html
/// for details
#[arg(long, default_value = None)]
pub tracing_env_filter: Option<String>,
}
#[derive(Subcommand)]
@ -22,6 +28,12 @@ pub enum Commands {
/// Lists tasks with options for syncing
List(ListArgs),
/// Sync local tasks with GitLab and Jira
Sync,
/// Interact with the associated tasks' Jira issues
Jira(JiraArgs),
}
#[derive(Args)]
@ -33,14 +45,27 @@ pub struct ListArgs {
pub sync: bool,
}
#[derive(Args, Debug)]
pub struct JiraArgs {
#[arg(short, long)]
pub key: String,
}
impl Cli {
pub fn new() -> Self {
Self::parse()
}
}
pub async fn sync() -> Result<()> {
eprintln!("Syncing...");
Arc::new(crate::tasks::Tasks::try_new()?).sync().await?;
eprintln!("Done!");
Ok(())
}
pub async fn list_tasks(args: &ListArgs) -> Result<()> {
let tasks = crate::tasks::Tasks::try_new()?;
let tasks = Arc::new(crate::tasks::Tasks::try_new()?);
if args.sync {
eprintln!("Syncing...");
tasks.sync().await?;
@ -58,6 +83,13 @@ pub async fn list_tasks(args: &ListArgs) -> Result<()> {
Ok(())
}
pub async fn jira(args: &JiraArgs) -> Result<()> {
let tasks = crate::tasks::Tasks::try_new()?;
let issue = tasks.jira.issue(&args.key).await?;
println!("{issue:?}");
Ok(())
}
#[test]
fn verify_cli() {
use clap::CommandFactory;

View file

@ -18,10 +18,19 @@ use cli::{Cli, Commands};
#[tokio::main]
async fn main() -> Result<()> {
let cli = Cli::new();
observe::setup_logging(cli.logs_directory)?;
// this guard causes logs to be flushed when dropped
// 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)?;
println!("Starting...");
trace!("Starting...");
match cli.command {
info!("Starting...");
let result = match cli.command {
Commands::Sync => cli::sync().await,
Commands::Ui(_args) => tui::run().await,
Commands::List(args) => cli::list_tasks(&args).await,
}
Commands::Jira(args) => cli::jira(&args).await,
};
result
}

View file

@ -1,32 +1,54 @@
use crate::prelude::*;
use std::path::PathBuf;
use std::path::Path;
use tracing_appender::non_blocking::WorkerGuard;
use tracing_subscriber::{filter::LevelFilter, EnvFilter};
pub fn setup_logging<T: Into<PathBuf>>(logs_directory: Option<T>) -> Result<()> {
let default_logs_directory = || -> Result<PathBuf> {
Ok(xdg::BaseDirectories::new()?.create_cache_directory("taskr/logs")?)
};
let logs_directory: PathBuf = match logs_directory {
Some(p) => p.into(),
None => default_logs_directory()?,
};
if !logs_directory.exists() {
std::fs::create_dir_all(&logs_directory)?;
pub fn setup_logging<T, S>(
logs_directory: Option<T>,
env_filter_directive: Option<S>,
) -> Result<WorkerGuard>
where
T: AsRef<Path>,
S: AsRef<str>,
{
let mut default_logs_dir = Default::default();
let logs_dir = logs_directory
.as_ref()
.map(T::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_logs_dir = xdg::BaseDirectories::new()?.create_cache_directory("taskr/logs")?;
Ok(default_logs_dir.as_ref())
})?;
eprintln!("Logs Directory: {}", logs_dir.display());
if !logs_dir.exists() {
std::fs::create_dir_all(&logs_dir)?;
}
let file_appender = tracing_appender::rolling::hourly(logs_directory, "log");
let (non_blocking, _guard) = tracing_appender::non_blocking(file_appender);
let file_appender = tracing_appender::rolling::hourly(logs_dir, "log");
let (non_blocking, guard) = tracing_appender::non_blocking(file_appender);
let env_filter = env_filter_directive
.as_ref()
.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("info,taskr=trace");
.parse_lossy(env_filter);
eprintln!("EnvFilter Directive: {}", filter);
tracing_subscriber::fmt()
.with_writer(non_blocking)
.with_env_filter(filter)
.init();
Ok(())
Ok(guard)
}

View file

@ -1,4 +1,7 @@
#![allow(unused_imports)]
pub use crate::result::Result;
pub use regex::Regex;
pub use std::sync::Arc;
pub use tokio::sync::Mutex;
pub use tracing::{debug, error, info, trace, warn};

View file

@ -5,13 +5,13 @@ use sled::IVec;
use crate::jira::Issue;
#[derive(Serialize, Deserialize, Debug, PartialEq, Eq)]
#[derive(Serialize, Deserialize, Debug, PartialEq, Eq, Clone)]
pub struct MergeRequestRef {
pub url: String,
pub state: String,
}
#[derive(Serialize, Deserialize, Debug, PartialEq, Eq)]
#[derive(Serialize, Deserialize, Debug, PartialEq, Eq, Clone)]
pub struct Task {
pub jira_key: String,
pub description: String,

View file

@ -1,4 +1,5 @@
use regex::Regex;
use crate::prelude::*;
use std::{
collections::{HashMap, HashSet},
env,
@ -21,11 +22,7 @@ pub struct Tasks {
db: sled::Db,
}
#[derive(Debug)]
pub struct Desyncs {
issues: Vec<crate::jira::Issue>,
tasks: Vec<Task>,
}
const MY_DONE_STATUS_CATEGORY_KEY: &str = "done";
impl Tasks {
pub fn try_new() -> Result<Self> {
@ -135,6 +132,14 @@ impl Tasks {
async fn fix_dangling_task(&self, task: &Task) -> Result<()> {
// check if closed, if it is, delete the task
let issue = self.jira.issue(&task.jira_key).await?;
if issue.fields.status.status_category.key == MY_DONE_STATUS_CATEGORY_KEY {
info!(
"Deleting task {} due to being marked {} in Jira",
task.jira_key, MY_DONE_STATUS_CATEGORY_KEY
);
// remote issue is done, so delete the task
self.delete(task)?;
}
Ok(())
}
@ -149,7 +154,7 @@ impl Tasks {
HashSet::from_iter(issues.keys().map(|s| s.to_owned()));
// keys which have a task but not an issue
let _dangling_keys: HashSet<String, RandomState> = HashSet::from_iter(
let dangling_keys: HashSet<String, RandomState> = HashSet::from_iter(
task_keys
.difference(&issue_keys)
.into_iter()
@ -166,6 +171,13 @@ impl Tasks {
tasks.insert(task.jira_key.clone(), task);
}
futures::future::try_join_all(
dangling_keys
.iter()
.map(|t| self.fix_dangling_task(tasks.get(t).unwrap())),
)
.await?;
// TODO: blocking? maybe should be async?
// while let Ok(_res) = rx.recv() {
// awaiting all the tasks in the joinset