Improve config options
This commit is contained in:
parent
1b70e7c9bd
commit
c08929c765
8 changed files with 109 additions and 29 deletions
1
Cargo.lock
generated
1
Cargo.lock
generated
|
@ -1681,6 +1681,7 @@ dependencies = [
|
||||||
"clap_derive",
|
"clap_derive",
|
||||||
"color-eyre",
|
"color-eyre",
|
||||||
"crossterm",
|
"crossterm",
|
||||||
|
"futures",
|
||||||
"ratatui",
|
"ratatui",
|
||||||
"redact",
|
"redact",
|
||||||
"regex",
|
"regex",
|
||||||
|
|
|
@ -11,6 +11,7 @@ clap = { version = "4.5.4", features = ["derive"] }
|
||||||
clap_derive = "4.5.4"
|
clap_derive = "4.5.4"
|
||||||
color-eyre = "0.6.3"
|
color-eyre = "0.6.3"
|
||||||
crossterm = "0.27.0"
|
crossterm = "0.27.0"
|
||||||
|
futures = "0.3.30"
|
||||||
ratatui = "0.26.2"
|
ratatui = "0.26.2"
|
||||||
redact = "0.1.9"
|
redact = "0.1.9"
|
||||||
regex = "1.10.3"
|
regex = "1.10.3"
|
||||||
|
|
36
src/cli.rs
36
src/cli.rs
|
@ -10,9 +10,15 @@ pub struct Cli {
|
||||||
#[command(subcommand)]
|
#[command(subcommand)]
|
||||||
pub command: Commands,
|
pub command: Commands,
|
||||||
|
|
||||||
/// Tell taskr which directory to write log files to
|
/// The directory to write log files
|
||||||
#[arg(long, default_value = None)]
|
#[arg(long, default_value = None)]
|
||||||
pub logs_directory: Option<String>,
|
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)]
|
#[derive(Subcommand)]
|
||||||
|
@ -22,6 +28,12 @@ pub enum Commands {
|
||||||
|
|
||||||
/// Lists tasks with options for syncing
|
/// Lists tasks with options for syncing
|
||||||
List(ListArgs),
|
List(ListArgs),
|
||||||
|
|
||||||
|
/// Sync local tasks with GitLab and Jira
|
||||||
|
Sync,
|
||||||
|
|
||||||
|
/// Interact with the associated tasks' Jira issues
|
||||||
|
Jira(JiraArgs),
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Args)]
|
#[derive(Args)]
|
||||||
|
@ -33,14 +45,27 @@ pub struct ListArgs {
|
||||||
pub sync: bool,
|
pub sync: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Args, Debug)]
|
||||||
|
pub struct JiraArgs {
|
||||||
|
#[arg(short, long)]
|
||||||
|
pub key: String,
|
||||||
|
}
|
||||||
|
|
||||||
impl Cli {
|
impl Cli {
|
||||||
pub fn new() -> Self {
|
pub fn new() -> Self {
|
||||||
Self::parse()
|
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<()> {
|
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 {
|
if args.sync {
|
||||||
eprintln!("Syncing...");
|
eprintln!("Syncing...");
|
||||||
tasks.sync().await?;
|
tasks.sync().await?;
|
||||||
|
@ -58,6 +83,13 @@ pub async fn list_tasks(args: &ListArgs) -> Result<()> {
|
||||||
Ok(())
|
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]
|
#[test]
|
||||||
fn verify_cli() {
|
fn verify_cli() {
|
||||||
use clap::CommandFactory;
|
use clap::CommandFactory;
|
||||||
|
|
15
src/main.rs
15
src/main.rs
|
@ -18,10 +18,19 @@ use cli::{Cli, Commands};
|
||||||
#[tokio::main]
|
#[tokio::main]
|
||||||
async fn main() -> Result<()> {
|
async fn main() -> Result<()> {
|
||||||
let cli = Cli::new();
|
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...");
|
trace!("Starting...");
|
||||||
match cli.command {
|
info!("Starting...");
|
||||||
|
let result = match cli.command {
|
||||||
|
Commands::Sync => cli::sync().await,
|
||||||
Commands::Ui(_args) => tui::run().await,
|
Commands::Ui(_args) => tui::run().await,
|
||||||
Commands::List(args) => cli::list_tasks(&args).await,
|
Commands::List(args) => cli::list_tasks(&args).await,
|
||||||
}
|
Commands::Jira(args) => cli::jira(&args).await,
|
||||||
|
};
|
||||||
|
result
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,32 +1,54 @@
|
||||||
use crate::prelude::*;
|
use crate::prelude::*;
|
||||||
|
|
||||||
use std::path::PathBuf;
|
use std::path::Path;
|
||||||
|
use tracing_appender::non_blocking::WorkerGuard;
|
||||||
use tracing_subscriber::{filter::LevelFilter, EnvFilter};
|
use tracing_subscriber::{filter::LevelFilter, EnvFilter};
|
||||||
|
|
||||||
pub fn setup_logging<T: Into<PathBuf>>(logs_directory: Option<T>) -> Result<()> {
|
pub fn setup_logging<T, S>(
|
||||||
let default_logs_directory = || -> Result<PathBuf> {
|
logs_directory: Option<T>,
|
||||||
Ok(xdg::BaseDirectories::new()?.create_cache_directory("taskr/logs")?)
|
env_filter_directive: Option<S>,
|
||||||
};
|
) -> Result<WorkerGuard>
|
||||||
let logs_directory: PathBuf = match logs_directory {
|
where
|
||||||
Some(p) => p.into(),
|
T: AsRef<Path>,
|
||||||
None => default_logs_directory()?,
|
S: AsRef<str>,
|
||||||
};
|
{
|
||||||
if !logs_directory.exists() {
|
let mut default_logs_dir = Default::default();
|
||||||
std::fs::create_dir_all(&logs_directory)?;
|
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 file_appender = tracing_appender::rolling::hourly(logs_dir, "log");
|
||||||
let (non_blocking, _guard) = tracing_appender::non_blocking(file_appender);
|
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");
|
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("info,taskr=trace");
|
.parse_lossy(env_filter);
|
||||||
|
|
||||||
|
eprintln!("EnvFilter Directive: {}", filter);
|
||||||
|
|
||||||
tracing_subscriber::fmt()
|
tracing_subscriber::fmt()
|
||||||
.with_writer(non_blocking)
|
.with_writer(non_blocking)
|
||||||
.with_env_filter(filter)
|
.with_env_filter(filter)
|
||||||
.init();
|
.init();
|
||||||
|
|
||||||
Ok(())
|
Ok(guard)
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1,7 @@
|
||||||
#![allow(unused_imports)]
|
#![allow(unused_imports)]
|
||||||
|
|
||||||
pub use crate::result::Result;
|
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};
|
pub use tracing::{debug, error, info, trace, warn};
|
||||||
|
|
|
@ -5,13 +5,13 @@ use sled::IVec;
|
||||||
|
|
||||||
use crate::jira::Issue;
|
use crate::jira::Issue;
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Debug, PartialEq, Eq)]
|
#[derive(Serialize, Deserialize, Debug, PartialEq, Eq, Clone)]
|
||||||
pub struct MergeRequestRef {
|
pub struct MergeRequestRef {
|
||||||
pub url: String,
|
pub url: String,
|
||||||
pub state: String,
|
pub state: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Debug, PartialEq, Eq)]
|
#[derive(Serialize, Deserialize, Debug, PartialEq, Eq, Clone)]
|
||||||
pub struct Task {
|
pub struct Task {
|
||||||
pub jira_key: String,
|
pub jira_key: String,
|
||||||
pub description: String,
|
pub description: String,
|
||||||
|
|
26
src/tasks.rs
26
src/tasks.rs
|
@ -1,4 +1,5 @@
|
||||||
use regex::Regex;
|
use crate::prelude::*;
|
||||||
|
|
||||||
use std::{
|
use std::{
|
||||||
collections::{HashMap, HashSet},
|
collections::{HashMap, HashSet},
|
||||||
env,
|
env,
|
||||||
|
@ -21,11 +22,7 @@ pub struct Tasks {
|
||||||
db: sled::Db,
|
db: sled::Db,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
const MY_DONE_STATUS_CATEGORY_KEY: &str = "done";
|
||||||
pub struct Desyncs {
|
|
||||||
issues: Vec<crate::jira::Issue>,
|
|
||||||
tasks: Vec<Task>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Tasks {
|
impl Tasks {
|
||||||
pub fn try_new() -> Result<Self> {
|
pub fn try_new() -> Result<Self> {
|
||||||
|
@ -135,6 +132,14 @@ impl Tasks {
|
||||||
async fn fix_dangling_task(&self, task: &Task) -> Result<()> {
|
async fn fix_dangling_task(&self, task: &Task) -> Result<()> {
|
||||||
// check if closed, if it is, delete the task
|
// check if closed, if it is, delete the task
|
||||||
let issue = self.jira.issue(&task.jira_key).await?;
|
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(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -149,7 +154,7 @@ impl Tasks {
|
||||||
HashSet::from_iter(issues.keys().map(|s| s.to_owned()));
|
HashSet::from_iter(issues.keys().map(|s| s.to_owned()));
|
||||||
|
|
||||||
// keys which have a task but not an issue
|
// 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
|
task_keys
|
||||||
.difference(&issue_keys)
|
.difference(&issue_keys)
|
||||||
.into_iter()
|
.into_iter()
|
||||||
|
@ -166,6 +171,13 @@ impl Tasks {
|
||||||
tasks.insert(task.jira_key.clone(), task);
|
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?
|
// TODO: blocking? maybe should be async?
|
||||||
// while let Ok(_res) = rx.recv() {
|
// while let Ok(_res) = rx.recv() {
|
||||||
// awaiting all the tasks in the joinset
|
// awaiting all the tasks in the joinset
|
||||||
|
|
Loading…
Reference in a new issue