From 4c7c5d88faa503e547169dc2c84bfbf3e40fd573 Mon Sep 17 00:00:00 2001 From: JMARyA Date: Thu, 6 Jun 2024 08:57:13 +0200 Subject: [PATCH] refactor --- src/main.rs | 184 ++-------------------------------------------- src/ui/mod.rs | 56 ++++++++++++++ src/ui/project.rs | 43 +++++++++++ src/ui/task.rs | 89 ++++++++++++++++++++++ 4 files changed, 196 insertions(+), 176 deletions(-) create mode 100644 src/ui/mod.rs create mode 100644 src/ui/project.rs create mode 100644 src/ui/task.rs diff --git a/src/main.rs b/src/main.rs index 9e1e5fd..8775b66 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,179 +1,9 @@ mod api; mod args; mod config; +mod ui; -use std::{collections::HashMap, io::stdout}; - -use api::{Project, Task, VikunjaAPI}; -use chrono::{DateTime, Utc}; -use crossterm::{ - style::{Color, SetBackgroundColor, SetForegroundColor}, - ExecutableCommand, -}; - -pub fn print_color(color: Color, txt: &str) { - stdout().execute(SetForegroundColor(color)).unwrap(); - print!("{txt}"); - stdout().execute(SetForegroundColor(Color::Reset)).unwrap(); -} - -fn print_task_oneline(task: &Task, api: &VikunjaAPI) { - let done_indicator = if task.done { "✓" } else { " " }; - - println!( - "[{}] ({}) '{}' [{}]", - done_indicator, - task.id, - task.title, - api.get_project_name_from_id(task.project_id), - ); -} - -fn print_current_tasks(api: &VikunjaAPI, done: bool, fav: bool) { - let current_tasks = api.get_all_tasks(); - - let selection: Vec<_> = if done { - current_tasks - } else { - current_tasks.into_iter().filter(|x| !x.done).collect() - }; - - let selection = if fav { - selection.into_iter().filter(|x| x.is_favorite).collect() - } else { - selection - }; - - for task in selection { - print_task_oneline(&task, api); - } -} - -fn parse_datetime(datetime_str: &str) -> Option> { - if datetime_str == "0001-01-01T00:00:00Z" { - return None; - } - - match DateTime::parse_from_rfc3339(datetime_str) { - Ok(dt) => Some(dt.with_timezone(&Utc)), - Err(_) => None, // Return None if parsing fails - } -} - -pub fn time_since(event: DateTime) -> String { - let now = Utc::now(); - let duration = now.signed_duration_since(event); - - if duration.num_days() > 0 { - return format!("{}d ago", duration.num_days()); - } else if duration.num_hours() > 0 { - return format!("{}h ago", duration.num_hours()); - } else if duration.num_minutes() > 0 { - return format!("{}m ago", duration.num_minutes()); - } else { - return "Just now".to_string(); - } -} - -fn print_task_info(task_id: isize, api: &VikunjaAPI) { - let task = api.get_task(task_id); - let done_indicator = if task.done { - format!("{} ✓ ", parse_datetime(&task.done_at).unwrap()) - } else { - String::new() - }; - let fav_indicator = if task.is_favorite { " ★ " } else { "" }; - - println!( - "{}{}'{}' [{}] [{}]", - done_indicator, - fav_indicator, - task.title, - task.id, - api.get_project_name_from_id(task.project_id) - ); - println!("Created by {}", task.created_by.username); - - if let Some(due_date) = parse_datetime(&task.due_date) { - println!("Due at {due_date}"); - } - - if task.priority != 0 { - println!("Priority: {}", task.priority); - } - - if let (Some(start_date), Some(end_date)) = ( - parse_datetime(&task.start_date), - parse_datetime(&task.end_date), - ) { - println!("{start_date} -> {end_date}"); - } - - println!("Labels: {}", task.labels.unwrap().first().unwrap().title); - - println!( - "Created: {} | Updated: {}", - time_since(parse_datetime(&task.created).unwrap()), - time_since(parse_datetime(&task.updated).unwrap()) - ); - - if task.description != "

" { - println!("---\n{}", task.description); - } - - //pub assignees: Option>, - //pub labels: Option>, - // pub percent_done: f64, -} - -fn hex_to_color(hex: &str) -> Result { - let hex = hex.trim_start_matches('#'); - - if hex.len() != 6 { - return Err("Invalid hex color length".to_string()); - } - - let r = u8::from_str_radix(&hex[0..2], 16).map_err(|_| "Invalid red component")?; - let g = u8::from_str_radix(&hex[2..4], 16).map_err(|_| "Invalid green component")?; - let b = u8::from_str_radix(&hex[4..6], 16).map_err(|_| "Invalid blue component")?; - - Ok(Color::Rgb { r, g, b }) -} - -fn list_projects(api: &VikunjaAPI) { - let projects = api.get_all_projects(); - - let mut project_map: HashMap> = HashMap::new(); - - for prj in projects { - project_map - .entry(prj.parent_project_id) - .or_insert_with(Vec::new) - .push(prj); - } - - for prj in project_map.get(&0).unwrap() { - let color = if prj.hex_color.is_empty() { - Color::Reset - } else { - hex_to_color(&prj.hex_color).unwrap() - }; - print_color(color, &prj.title); - print!(" [{}]\n", prj.id); - - if let Some(sub_projects) = project_map.get(&(prj.id as usize)) { - for sub_prj in sub_projects { - let color = if sub_prj.hex_color.is_empty() { - Color::Reset - } else { - hex_to_color(&sub_prj.hex_color).unwrap() - }; - print_color(color, &format!(" - {}", sub_prj.title)); - print!(" [{}]\n", sub_prj.id); - } - } - } -} +use api::VikunjaAPI; fn main() { let config: config::Config = @@ -184,18 +14,20 @@ fn main() { match arg.subcommand() { Some(("info", task_info_arg)) => { let task_id: &String = task_info_arg.get_one("task_id").unwrap(); - print_task_info(task_id.parse().unwrap(), &api); + ui::task::print_task_info(task_id.parse().unwrap(), &api); } Some(("prj", prj_arg)) => match prj_arg.subcommand() { Some(("ls", _)) => { - list_projects(&api); + ui::project::list_projects(&api); + } + _ => { + ui::project::list_projects(&api); } - _ => {} }, _ => { let done = arg.get_flag("done"); let fav = arg.get_flag("favorite"); - print_current_tasks(&api, done, fav); + ui::task::print_current_tasks(&api, done, fav); } } } diff --git a/src/ui/mod.rs b/src/ui/mod.rs new file mode 100644 index 0000000..b8f474d --- /dev/null +++ b/src/ui/mod.rs @@ -0,0 +1,56 @@ +use std::io::stdout; + +use chrono::{DateTime, Utc}; +use crossterm::{ + style::{Color, SetForegroundColor}, + ExecutableCommand, +}; + +pub mod project; +pub mod task; + +pub fn print_color(color: Color, txt: &str) { + stdout().execute(SetForegroundColor(color)).unwrap(); + print!("{txt}"); + stdout().execute(SetForegroundColor(Color::Reset)).unwrap(); +} + +fn hex_to_color(hex: &str) -> Result { + let hex = hex.trim_start_matches('#'); + + if hex.len() != 6 { + return Err("Invalid hex color length".to_string()); + } + + let r = u8::from_str_radix(&hex[0..2], 16).map_err(|_| "Invalid red component")?; + let g = u8::from_str_radix(&hex[2..4], 16).map_err(|_| "Invalid green component")?; + let b = u8::from_str_radix(&hex[4..6], 16).map_err(|_| "Invalid blue component")?; + + Ok(Color::Rgb { r, g, b }) +} + +fn parse_datetime(datetime_str: &str) -> Option> { + if datetime_str == "0001-01-01T00:00:00Z" { + return None; + } + + match DateTime::parse_from_rfc3339(datetime_str) { + Ok(dt) => Some(dt.with_timezone(&Utc)), + Err(_) => None, // Return None if parsing fails + } +} + +pub fn time_since(event: DateTime) -> String { + let now = Utc::now(); + let duration = now.signed_duration_since(event); + + if duration.num_days() > 0 { + return format!("{}d ago", duration.num_days()); + } else if duration.num_hours() > 0 { + return format!("{}h ago", duration.num_hours()); + } else if duration.num_minutes() > 0 { + return format!("{}m ago", duration.num_minutes()); + } else { + return "Just now".to_string(); + } +} diff --git a/src/ui/project.rs b/src/ui/project.rs new file mode 100644 index 0000000..4f38e63 --- /dev/null +++ b/src/ui/project.rs @@ -0,0 +1,43 @@ +use std::collections::HashMap; + +use crossterm::style::Color; + +use crate::{ + api::{Project, VikunjaAPI}, + ui::{hex_to_color, print_color}, +}; + +pub fn list_projects(api: &VikunjaAPI) { + let projects = api.get_all_projects(); + + let mut project_map: HashMap> = HashMap::new(); + + for prj in projects { + project_map + .entry(prj.parent_project_id) + .or_insert_with(Vec::new) + .push(prj); + } + + for prj in project_map.get(&0).unwrap() { + let color = if prj.hex_color.is_empty() { + Color::Reset + } else { + hex_to_color(&prj.hex_color).unwrap() + }; + print_color(color, &prj.title); + print!(" [{}]\n", prj.id); + + if let Some(sub_projects) = project_map.get(&(prj.id as usize)) { + for sub_prj in sub_projects { + let color = if sub_prj.hex_color.is_empty() { + Color::Reset + } else { + hex_to_color(&sub_prj.hex_color).unwrap() + }; + print_color(color, &format!(" - {}", sub_prj.title)); + print!(" [{}]\n", sub_prj.id); + } + } + } +} diff --git a/src/ui/task.rs b/src/ui/task.rs new file mode 100644 index 0000000..b060304 --- /dev/null +++ b/src/ui/task.rs @@ -0,0 +1,89 @@ +use crate::{ + api::{Task, VikunjaAPI}, + ui::{parse_datetime, time_since}, +}; + +fn print_task_oneline(task: &Task, api: &VikunjaAPI) { + let done_indicator = if task.done { "✓" } else { " " }; + + println!( + "[{}] ({}) '{}' [{}]", + done_indicator, + task.id, + task.title, + api.get_project_name_from_id(task.project_id), + ); +} + +pub fn print_current_tasks(api: &VikunjaAPI, done: bool, fav: bool) { + let current_tasks = api.get_all_tasks(); + + let selection: Vec<_> = if done { + current_tasks + } else { + current_tasks.into_iter().filter(|x| !x.done).collect() + }; + + let selection = if fav { + selection.into_iter().filter(|x| x.is_favorite).collect() + } else { + selection + }; + + for task in selection { + print_task_oneline(&task, api); + } +} + +pub fn print_task_info(task_id: isize, api: &VikunjaAPI) { + let task = api.get_task(task_id); + let done_indicator = if task.done { + format!("{} ✓ ", parse_datetime(&task.done_at).unwrap()) + } else { + String::new() + }; + let fav_indicator = if task.is_favorite { " ★ " } else { "" }; + + println!( + "{}{}'{}' [{}] [{}]", + done_indicator, + fav_indicator, + task.title, + task.id, + api.get_project_name_from_id(task.project_id) + ); + println!("Created by {}", task.created_by.username); + + if let Some(due_date) = parse_datetime(&task.due_date) { + println!("Due at {due_date}"); + } + + if task.priority != 0 { + println!("Priority: {}", task.priority); + } + + if let (Some(start_date), Some(end_date)) = ( + parse_datetime(&task.start_date), + parse_datetime(&task.end_date), + ) { + println!("{start_date} -> {end_date}"); + } + + if let Some(labels) = task.labels { + println!("Labels: {}", labels.first().unwrap().title); + } + + println!( + "Created: {} | Updated: {}", + time_since(parse_datetime(&task.created).unwrap()), + time_since(parse_datetime(&task.updated).unwrap()) + ); + + if task.description != "

" { + println!("---\n{}", task.description); + } + + //pub assignees: Option>, + //pub labels: Option>, + // pub percent_done: f64, +}