Merge remote-tracking branch 'origin/main'
This commit is contained in:
commit
424520b2c1
9 changed files with 241 additions and 114 deletions
48
src/cli.rs
48
src/cli.rs
|
@ -30,15 +30,22 @@ pub struct Cli {
|
||||||
|
|
||||||
#[derive(Subcommand)]
|
#[derive(Subcommand)]
|
||||||
pub enum Commands {
|
pub enum Commands {
|
||||||
/// Lists tasks with options for syncing
|
/// List local tasks with options for syncing
|
||||||
|
#[clap(visible_alias = "ls")]
|
||||||
List(list::Args),
|
List(list::Args),
|
||||||
|
|
||||||
/// Sync local tasks with GitLab and Jira
|
/// Sync local tasks with GitLab and Jira
|
||||||
Sync(sync::Args),
|
Sync(sync::Args),
|
||||||
|
|
||||||
|
/// Purge all local data
|
||||||
|
Purge(PurgeArgs),
|
||||||
|
|
||||||
|
/// Performs various cleanup operations on local data
|
||||||
|
Cleanup(CleanupArgs),
|
||||||
|
|
||||||
/// Run the interactive terminal user interface (TUI)
|
/// Run the interactive terminal user interface (TUI)
|
||||||
#[command(subcommand)]
|
#[clap(visible_alias = "tui")]
|
||||||
Ui(ui::Command),
|
Ui(ui::Args),
|
||||||
|
|
||||||
/// Interact with the configured Jira endpoint
|
/// Interact with the configured Jira endpoint
|
||||||
#[command(subcommand)]
|
#[command(subcommand)]
|
||||||
|
@ -49,11 +56,38 @@ pub enum Commands {
|
||||||
Gitlab(gitlab::Command),
|
Gitlab(gitlab::Command),
|
||||||
}
|
}
|
||||||
|
|
||||||
mod ui {
|
#[derive(Args)]
|
||||||
use clap::Subcommand;
|
pub struct PurgeArgs {}
|
||||||
|
impl PurgeArgs {
|
||||||
|
pub async fn run(&self, tasks: SharedTasks) -> Result<()> {
|
||||||
|
tasks.purge_all()?;
|
||||||
|
println!("Local data purged!");
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Subcommand)]
|
#[derive(Args)]
|
||||||
pub enum Command {}
|
pub struct CleanupArgs {}
|
||||||
|
impl CleanupArgs {
|
||||||
|
pub async fn run(&self, tasks: SharedTasks) -> Result<()> {
|
||||||
|
tasks.cleanup_all()?;
|
||||||
|
println!("Local data cleaned up!");
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
mod ui {
|
||||||
|
use crate::cli::prelude::*;
|
||||||
|
|
||||||
|
#[derive(Parser)]
|
||||||
|
pub struct Args {}
|
||||||
|
|
||||||
|
impl Args {
|
||||||
|
pub async fn run(&self, tasks: SharedTasks) -> Result<()> {
|
||||||
|
let tui = crate::tui::Tui::new(tasks);
|
||||||
|
tui.run().await
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
mod list {
|
mod list {
|
||||||
|
|
|
@ -18,7 +18,7 @@ pub struct MeArgs {}
|
||||||
|
|
||||||
impl MeArgs {
|
impl MeArgs {
|
||||||
pub async fn me(&self, tasks: SharedTasks) -> Result<()> {
|
pub async fn me(&self, tasks: SharedTasks) -> Result<()> {
|
||||||
println!("{:?}", tasks.gitlab.me().await?);
|
println!("{:?}", tasks.gitlab()?.me().await?);
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -21,7 +21,7 @@ pub struct IssueArgs {
|
||||||
|
|
||||||
impl IssueArgs {
|
impl IssueArgs {
|
||||||
pub async fn issue(&self, tasks: SharedTasks) -> Result<()> {
|
pub async fn issue(&self, tasks: SharedTasks) -> Result<()> {
|
||||||
let issue = tasks.jira.issue(&self.key).await?;
|
let issue = tasks.jira()?.issue(&self.key).await?;
|
||||||
println!("{issue:?}");
|
println!("{issue:?}");
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
|
@ -19,16 +19,20 @@ use cli::{Cli, Commands};
|
||||||
async fn main() -> Result<()> {
|
async fn main() -> Result<()> {
|
||||||
let cli = Cli::new();
|
let cli = Cli::new();
|
||||||
|
|
||||||
// this guard causes logs to be flushed when dropped
|
// 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
|
// 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)?;
|
||||||
|
|
||||||
|
info!("Initializing taskr...");
|
||||||
println!("Initializing taskr...");
|
println!("Initializing taskr...");
|
||||||
let tasks = Arc::new(crate::tasks::Tasks::try_new(cli.data_directory)?);
|
let tasks = Arc::new(crate::tasks::Tasks::try_new(cli.data_directory)?);
|
||||||
|
|
||||||
let result = match cli.command {
|
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::Sync(args) => args.sync(tasks).await,
|
||||||
Commands::Ui(_args) => tui::run(tasks).await,
|
|
||||||
Commands::List(args) => args.list(tasks).await,
|
Commands::List(args) => args.list(tasks).await,
|
||||||
Commands::Jira(jira) => jira.exec(tasks).await,
|
Commands::Jira(jira) => jira.exec(tasks).await,
|
||||||
Commands::Gitlab(gitlab) => gitlab.exec(tasks).await,
|
Commands::Gitlab(gitlab) => gitlab.exec(tasks).await,
|
||||||
|
|
|
@ -30,7 +30,7 @@ where
|
||||||
std::fs::create_dir_all(&logs_dir)?;
|
std::fs::create_dir_all(&logs_dir)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
let file_appender = tracing_appender::rolling::hourly(logs_dir, "log");
|
let file_appender = tracing_appender::rolling::daily(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
|
let env_filter = env_filter_directive
|
||||||
|
|
12
src/task.rs
12
src/task.rs
|
@ -68,8 +68,18 @@ impl Display for Task {
|
||||||
status,
|
status,
|
||||||
tags,
|
tags,
|
||||||
} = self;
|
} = self;
|
||||||
|
let tags_text = tags
|
||||||
|
.iter()
|
||||||
|
.map(String::as_str)
|
||||||
|
.collect::<Vec<&str>>()
|
||||||
|
.join(", ");
|
||||||
|
let mr_text = if merge_requests.len() > 0 {
|
||||||
|
format!(" {} MRs", merge_requests.len())
|
||||||
|
} else {
|
||||||
|
"".into()
|
||||||
|
};
|
||||||
f.write_fmt(format_args!(
|
f.write_fmt(format_args!(
|
||||||
"{jira_key} {status:>10} {jira_priority} {description}",
|
"{jira_key} {status:>10} {jira_priority} {description} [{tags_text}]{mr_text}",
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
151
src/tasks.rs
151
src/tasks.rs
|
@ -4,7 +4,7 @@ use std::{
|
||||||
collections::{HashMap, HashSet},
|
collections::{HashMap, HashSet},
|
||||||
env,
|
env,
|
||||||
hash::RandomState,
|
hash::RandomState,
|
||||||
path::Path,
|
path::{Path, PathBuf},
|
||||||
process::Command,
|
process::Command,
|
||||||
sync::OnceLock,
|
sync::OnceLock,
|
||||||
};
|
};
|
||||||
|
@ -16,11 +16,14 @@ use sled::IVec;
|
||||||
|
|
||||||
use crate::{gitlab::GitLab, jira::Jira, result::Result};
|
use crate::{gitlab::GitLab, jira::Jira, result::Result};
|
||||||
|
|
||||||
pub struct Tasks {
|
pub type Db = sled::Db;
|
||||||
pub gitlab: GitLab,
|
|
||||||
pub jira: Jira,
|
|
||||||
|
|
||||||
db: sled::Db,
|
pub struct Tasks {
|
||||||
|
data_dir: PathBuf,
|
||||||
|
gitlab: OnceLock<GitLab>,
|
||||||
|
jira: OnceLock<Jira>,
|
||||||
|
|
||||||
|
db: OnceLock<Db>,
|
||||||
}
|
}
|
||||||
|
|
||||||
const MY_DONE_STATUS_CATEGORY_KEY: &str = "done";
|
const MY_DONE_STATUS_CATEGORY_KEY: &str = "done";
|
||||||
|
@ -30,6 +33,48 @@ impl Tasks {
|
||||||
where
|
where
|
||||||
D: AsRef<Path>,
|
D: AsRef<Path>,
|
||||||
{
|
{
|
||||||
|
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 db = OnceLock::new();
|
||||||
|
let data_dir: PathBuf = data_dir.to_owned();
|
||||||
|
|
||||||
|
Ok(Self {
|
||||||
|
gitlab,
|
||||||
|
jira,
|
||||||
|
data_dir,
|
||||||
|
db,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn db(&self) -> Result<&Db> {
|
||||||
|
match self.db.get() {
|
||||||
|
Some(d) => Ok(d),
|
||||||
|
None => {
|
||||||
|
let result = sled::open(&self.data_dir)?;
|
||||||
|
let _ = self.db.set(result);
|
||||||
|
Ok(self.db.get().unwrap())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn gitlab(&self) -> Result<&GitLab> {
|
||||||
|
match self.gitlab.get() {
|
||||||
|
Some(g) => Ok(g),
|
||||||
|
None => {
|
||||||
// TODO: find a way to more-lazily load the token?
|
// TODO: find a way to more-lazily load the token?
|
||||||
let gitlab_token_entry = keyring::Entry::new("taskr", "gitlab_token")?;
|
let gitlab_token_entry = keyring::Entry::new("taskr", "gitlab_token")?;
|
||||||
|
|
||||||
|
@ -49,7 +94,8 @@ impl Tasks {
|
||||||
.arg("client/divvy/gitlab-glpat")
|
.arg("client/divvy/gitlab-glpat")
|
||||||
.output()?;
|
.output()?;
|
||||||
|
|
||||||
let result = String::from_utf8_lossy(&output.stdout).trim().to_string();
|
let result =
|
||||||
|
String::from_utf8_lossy(&output.stdout).trim().to_string();
|
||||||
info!("GitLab token loaded from password-store entry client/divvy/gitlab-glpat");
|
info!("GitLab token loaded from password-store entry client/divvy/gitlab-glpat");
|
||||||
result
|
result
|
||||||
}
|
}
|
||||||
|
@ -59,41 +105,60 @@ impl Tasks {
|
||||||
token
|
token
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
let gitlab = GitLab::try_new("https://git.hq.bill.com/api/v4", &gl_token)?;
|
let result = GitLab::try_new("https://git.hq.bill.com/api/v4", &gl_token)?;
|
||||||
|
let _ = self.gitlab.set(result);
|
||||||
|
Ok(self.gitlab.get().unwrap())
|
||||||
// TODO: ensure the token works?
|
// TODO: ensure the token works?
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// TODO: cache, use keyring, talk to a daemon, or otherwise cache this safely
|
pub fn jira(&self) -> Result<&Jira> {
|
||||||
// TODO: or find a way to more-lazily load the token?
|
match self.jira.get() {
|
||||||
let jira_token = env::var("JIRA_TOKEN").or_else(|_| -> Result<String> {
|
Some(j) => Ok(j),
|
||||||
|
None => {
|
||||||
|
// TODO: find a way to more-lazily load the token?
|
||||||
|
let jira_token_entry = keyring::Entry::new("taskr", "jira_token")?;
|
||||||
|
|
||||||
|
let jira_token = match jira_token_entry.get_password() {
|
||||||
|
Ok(token) => {
|
||||||
|
info!("Jira token loaded from keyring");
|
||||||
|
token
|
||||||
|
}
|
||||||
|
Err(_) => {
|
||||||
|
let token = match env::var("JIRA_TOKEN") {
|
||||||
|
Ok(token) => {
|
||||||
|
info!("Jira token loaded from environment variable JIRA_TOKEN");
|
||||||
|
token
|
||||||
|
}
|
||||||
|
Err(_) => {
|
||||||
let output = Command::new("pass")
|
let output = Command::new("pass")
|
||||||
.arg("client/divvy/jira-api-token-with-email")
|
.arg("client/divvy/jira-api-token-with-email")
|
||||||
.output()?;
|
.output()?;
|
||||||
|
|
||||||
Ok(String::from_utf8_lossy(&output.stdout).trim().to_string())
|
let result =
|
||||||
})?;
|
String::from_utf8_lossy(&output.stdout).trim().to_string();
|
||||||
let jira = Jira::try_new("https://billcom.atlassian.net", &jira_token)?;
|
info!("Jira token loaded from password-store entry client/divvy/jira-api-token-with-email");
|
||||||
|
result
|
||||||
|
}
|
||||||
|
};
|
||||||
|
info!("GitLab token stored in keyring");
|
||||||
|
jira_token_entry.set_password(&token)?;
|
||||||
|
token
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
let mut default_data_dir = Default::default();
|
let result = Jira::try_new("https://billcom.atlassian.net", &jira_token)?;
|
||||||
let data_dir = data_dir
|
let _ = self.jira.set(result);
|
||||||
.as_ref()
|
Ok(self.jira.get().unwrap())
|
||||||
.map(D::as_ref)
|
// TODO: ensure the token works?
|
||||||
.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 db = sled::open(data_dir)?;
|
|
||||||
|
|
||||||
Ok(Self { gitlab, jira, db })
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn all(&self) -> Result<HashMap<String, Task>> {
|
pub fn all(&self) -> Result<HashMap<String, Task>> {
|
||||||
let mut result = HashMap::new();
|
let mut result = HashMap::new();
|
||||||
let scan = self.db.open_tree("tasks")?.scan_prefix(&[]);
|
let scan = self.db()?.open_tree("tasks")?.scan_prefix(&[]);
|
||||||
for entry in scan {
|
for entry in scan {
|
||||||
match entry {
|
match entry {
|
||||||
Ok((_key, val)) => {
|
Ok((_key, val)) => {
|
||||||
|
@ -106,8 +171,9 @@ impl Tasks {
|
||||||
Ok(result)
|
Ok(result)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[allow(dead_code)]
|
||||||
pub fn get(&self, key: &str) -> Result<Option<Task>> {
|
pub fn get(&self, key: &str) -> Result<Option<Task>> {
|
||||||
let ivec = self.db.open_tree("tasks")?.get(key)?;
|
let ivec = self.db()?.open_tree("tasks")?.get(key)?;
|
||||||
match ivec {
|
match ivec {
|
||||||
Some(v) => Ok(Some(Task::try_from(v)?)),
|
Some(v) => Ok(Some(Task::try_from(v)?)),
|
||||||
None => Ok(None),
|
None => Ok(None),
|
||||||
|
@ -116,14 +182,17 @@ impl Tasks {
|
||||||
|
|
||||||
pub fn save(&self, task: &Task) -> Result<()> {
|
pub fn save(&self, task: &Task) -> Result<()> {
|
||||||
let ivec: IVec = task.try_into()?;
|
let ivec: IVec = task.try_into()?;
|
||||||
let _previous_value = self.db.open_tree("tasks")?.insert(&task.jira_key, ivec)?;
|
let _previous_value = self
|
||||||
self.db.flush()?;
|
.db()?
|
||||||
|
.open_tree("tasks")?
|
||||||
|
.insert(&task.jira_key, ivec)?;
|
||||||
|
self.db()?.flush()?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn delete(&self, task: &Task) -> Result<()> {
|
pub fn delete(&self, task: &Task) -> Result<()> {
|
||||||
let _previous_value = self.db.open_tree("tasks")?.remove(&task.jira_key)?;
|
let _previous_value = self.db()?.open_tree("tasks")?.remove(&task.jira_key)?;
|
||||||
self.db.flush()?;
|
self.db()?.flush()?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -132,7 +201,7 @@ impl Tasks {
|
||||||
for (_, t) in self.all()? {
|
for (_, t) in self.all()? {
|
||||||
self.delete(&t)?;
|
self.delete(&t)?;
|
||||||
}
|
}
|
||||||
self.db.flush()?;
|
self.db()?.flush()?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -162,14 +231,14 @@ impl Tasks {
|
||||||
for (_, mut t) in self.all()? {
|
for (_, mut t) in self.all()? {
|
||||||
self.cleanup(&mut t)?;
|
self.cleanup(&mut t)?;
|
||||||
}
|
}
|
||||||
self.db.flush()?;
|
self.db()?.flush()?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// for a task that has no associated open and appropriately assigned jira issue
|
/// for a task that has no associated open and appropriately assigned jira issue
|
||||||
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 {
|
if issue.fields.status.status_category.key == MY_DONE_STATUS_CATEGORY_KEY {
|
||||||
info!(
|
info!(
|
||||||
"Deleting task {} due to being marked {} in Jira",
|
"Deleting task {} due to being marked {} in Jira",
|
||||||
|
@ -185,7 +254,7 @@ impl Tasks {
|
||||||
/// for use when sync'ing local tasks to remote state (jira, gitlab)
|
/// for use when sync'ing local tasks to remote state (jira, gitlab)
|
||||||
pub async fn sync(&self) -> Result<()> {
|
pub async fn sync(&self) -> Result<()> {
|
||||||
let mut tasks = self.all()?;
|
let mut tasks = self.all()?;
|
||||||
let issues = crate::jira::Jira::by_key(self.jira.assigned_open_issues().await?);
|
let issues = crate::jira::Jira::by_key(self.jira()?.assigned_open_issues().await?);
|
||||||
let task_keys: HashSet<String, RandomState> =
|
let task_keys: HashSet<String, RandomState> =
|
||||||
HashSet::from_iter(tasks.keys().map(|s| s.to_owned()));
|
HashSet::from_iter(tasks.keys().map(|s| s.to_owned()));
|
||||||
let issue_keys: HashSet<String, RandomState> =
|
let issue_keys: HashSet<String, RandomState> =
|
||||||
|
@ -242,9 +311,9 @@ impl Tasks {
|
||||||
// save last sync date
|
// save last sync date
|
||||||
let now = chrono::Local::now();
|
let now = chrono::Local::now();
|
||||||
|
|
||||||
self.db
|
self.db()?
|
||||||
.open_tree("task:meta")?
|
.open_tree("task:meta")?
|
||||||
.insert(b"last_sync_date", serde_json::to_vec(&now)?);
|
.insert(b"last_sync_date", serde_json::to_vec(&now)?)?;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
20
src/tui.rs
20
src/tui.rs
|
@ -12,16 +12,25 @@ use ratatui::{
|
||||||
};
|
};
|
||||||
use std::io::stdout;
|
use std::io::stdout;
|
||||||
|
|
||||||
pub async fn run(t: SharedTasks) -> Result<()> {
|
pub struct Tui {
|
||||||
|
tasks: SharedTasks,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Tui {
|
||||||
|
pub fn new(tasks: SharedTasks) -> Self {
|
||||||
|
Self { tasks }
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn run(&self) -> Result<()> {
|
||||||
// print!("{ANSI_CLEAR}");
|
// print!("{ANSI_CLEAR}");
|
||||||
// let gitlab_user = tasks.gitlab.me().await?;
|
// let gitlab_user = tasks.gitlab.me().await?;
|
||||||
// info!("{gitlab_user:#?}");
|
// info!("{gitlab_user:#?}");
|
||||||
// let jira_user = tasks.jira.me().await?;
|
// let jira_user = tasks.jira.me().await?;
|
||||||
// tasks.purge_all()?;
|
// tasks.purge_all()?;
|
||||||
let tasks = t.all()?;
|
let tasks = self.tasks.all()?;
|
||||||
|
|
||||||
if tasks.len() < 1 {
|
if tasks.len() < 1 {
|
||||||
info!("{:?}", t.sync().await?);
|
info!("{:?}", self.tasks.sync().await?);
|
||||||
}
|
}
|
||||||
let mut vtasks: Vec<&task::Task> = tasks.values().collect();
|
let mut vtasks: Vec<&task::Task> = tasks.values().collect();
|
||||||
vtasks.sort_unstable();
|
vtasks.sort_unstable();
|
||||||
|
@ -29,10 +38,10 @@ pub async fn run(t: SharedTasks) -> Result<()> {
|
||||||
info!("{}", t);
|
info!("{}", t);
|
||||||
}
|
}
|
||||||
info!("Number of tasks: {}", vtasks.len());
|
info!("Number of tasks: {}", vtasks.len());
|
||||||
tui().await
|
self.tui().await
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn tui() -> Result<()> {
|
async fn tui(&self) -> Result<()> {
|
||||||
stdout().execute(EnterAlternateScreen)?;
|
stdout().execute(EnterAlternateScreen)?;
|
||||||
enable_raw_mode()?;
|
enable_raw_mode()?;
|
||||||
let mut terminal = Terminal::new(CrosstermBackend::new(stdout()))?;
|
let mut terminal = Terminal::new(CrosstermBackend::new(stdout()))?;
|
||||||
|
@ -60,3 +69,4 @@ async fn tui() -> Result<()> {
|
||||||
disable_raw_mode()?;
|
disable_raw_mode()?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in a new issue