This commit is contained in:
JMARyA 2024-06-06 11:32:39 +02:00
parent a11fe29048
commit 09f6dd0625
Signed by: jmarya
GPG key ID: 901B2ADDF27C2263
6 changed files with 53 additions and 70 deletions

View file

@ -1,5 +1,3 @@
use std::collections::HashSet;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
mod project; mod project;
@ -47,24 +45,11 @@ where
ret ret
} }
fn unique_tasks(tasks: Vec<Task>) -> Vec<Task> {
let mut seen_ids = HashSet::new();
let mut unique_tasks = Vec::with_capacity(tasks.len());
for task in tasks {
if seen_ids.insert(task.id) {
unique_tasks.push(task);
}
}
unique_tasks
}
pub struct ProjectID(pub isize); pub struct ProjectID(pub isize);
impl ProjectID { impl ProjectID {
pub fn parse(api: &VikunjaAPI, project: &str) -> Option<Self> { pub fn parse(api: &VikunjaAPI, project: &str) -> Option<Self> {
let project = project.trim_start_matches("#"); let project = project.trim_start_matches('#');
if let Ok(num) = project.parse() { if let Ok(num) = project.parse() {
Some(Self(num)) Some(Self(num))
@ -95,29 +80,30 @@ impl VikunjaAPI {
} }
fn get_request(&self, path: &str) -> String { fn get_request(&self, path: &str) -> String {
if let Some(cached) = self.cache.get(path) { self.cache.get(path).map_or_else(
cached || {
} else { let client = reqwest::blocking::Client::new();
let client = reqwest::blocking::Client::new();
let ret = client let ret = client
.get(&format!("{}/api/v1{}", self.host, path)) .get(format!("{}/api/v1{}", self.host, path))
.header("Authorization", format!("Bearer {}", self.token)) .header("Authorization", format!("Bearer {}", self.token))
.send() .send()
.unwrap() .unwrap()
.text() .text()
.unwrap(); .unwrap();
self.cache.insert(path.to_string(), ret.clone()); self.cache.insert(path.to_string(), ret.clone());
ret ret
} },
|cached| cached,
)
} }
fn put_request(&self, path: &str, data: serde_json::Value) -> String { fn put_request(&self, path: &str, data: &serde_json::Value) -> String {
let client = reqwest::blocking::Client::new(); let client = reqwest::blocking::Client::new();
client client
.put(&format!("{}/api/v1{}", self.host, path)) .put(format!("{}/api/v1{}", self.host, path))
.header("Authorization", format!("Bearer {}", self.token)) .header("Authorization", format!("Bearer {}", self.token))
.json(&data) .json(&data)
.send() .send()
@ -126,11 +112,11 @@ impl VikunjaAPI {
.unwrap() .unwrap()
} }
fn post_request(&self, path: &str, data: serde_json::Value) -> String { fn post_request(&self, path: &str, data: &serde_json::Value) -> String {
let client = reqwest::blocking::Client::new(); let client = reqwest::blocking::Client::new();
client client
.post(&format!("{}/api/v1{}", self.host, path)) .post(format!("{}/api/v1{}", self.host, path))
.header("Authorization", format!("Bearer {}", self.token)) .header("Authorization", format!("Bearer {}", self.token))
.json(&data) .json(&data)
.send() .send()
@ -180,7 +166,7 @@ impl VikunjaAPI {
serde_json::from_str(&resp).unwrap() serde_json::from_str(&resp).unwrap()
} }
pub fn new_task(&self, title: &str, project: ProjectID) -> Task { pub fn new_task(&self, title: &str, project: &ProjectID) -> Task {
let id = project.0; let id = project.0;
let data = serde_json::json!({ let data = serde_json::json!({
@ -194,14 +180,14 @@ impl VikunjaAPI {
// labels // labels
// priority // priority
let resp = self.put_request(&format!("/projects/{id}/tasks"), data); let resp = self.put_request(&format!("/projects/{id}/tasks"), &data);
serde_json::from_str(&resp).unwrap() serde_json::from_str(&resp).unwrap()
} }
pub fn done_task(&self, task_id: isize) -> Task { pub fn done_task(&self, task_id: isize) -> Task {
let resp = self.post_request( let resp = self.post_request(
&format!("/tasks/{task_id}"), &format!("/tasks/{task_id}"),
serde_json::json!({ &serde_json::json!({
"done": true "done": true
}), }),
); );

View file

@ -18,6 +18,8 @@ pub fn get_args() -> clap::ArgMatches {
.name("prj") .name("prj")
.about("Commands about projects") .about("Commands about projects")
.subcommand(command!().name("ls").about("List projects")), .subcommand(command!().name("ls").about("List projects")),
// todo : prj add <project>
// todo : prj rm <project>
) )
.subcommand( .subcommand(
command!() command!()
@ -35,6 +37,9 @@ pub fn get_args() -> clap::ArgMatches {
.name("label") .name("label")
.about("Manage labels") .about("Manage labels")
.subcommand(command!().name("ls").about("List all labels")), .subcommand(command!().name("ls").about("List all labels")),
// todo : label new
// todo : label rm
// todo : label <label> <task>
) )
.subcommand( .subcommand(
command!() command!()

View file

@ -20,6 +20,7 @@ fn main() {
Some(("ls", _)) => { Some(("ls", _)) => {
ui::project::list_projects(&api); ui::project::list_projects(&api);
} }
Some(("add", add_prj_arg)) => {}
_ => { _ => {
ui::project::list_projects(&api); ui::project::list_projects(&api);
} }
@ -28,15 +29,16 @@ fn main() {
Some(("ls", _)) => { Some(("ls", _)) => {
ui::print_all_labels(&api); ui::print_all_labels(&api);
} }
Some(("new", new_label_arg)) => {}
_ => { _ => {
ui::print_all_labels(&api); // todo : label tasks
} }
}, },
Some(("new", new_task_arg)) => { Some(("new", new_task_arg)) => {
let title: &String = new_task_arg.get_one("title").unwrap(); let title: &String = new_task_arg.get_one("title").unwrap();
let project: &String = new_task_arg.get_one("project").unwrap(); let project: &String = new_task_arg.get_one("project").unwrap();
let project = ProjectID::parse(&api, project).unwrap(); let project = ProjectID::parse(&api, project).unwrap();
let task = api.new_task(title.as_str(), project); let task = api.new_task(title.as_str(), &project);
ui::task::print_task_info(task.id, &api); ui::task::print_task_info(task.id, &api);
} }
Some(("done", done_args)) => { Some(("done", done_args)) => {

View file

@ -46,10 +46,7 @@ fn parse_datetime(datetime_str: &str) -> Option<DateTime<Utc>> {
return None; return None;
} }
match DateTime::parse_from_rfc3339(datetime_str) { DateTime::parse_from_rfc3339(datetime_str).map_or(None, |dt| Some(dt.with_timezone(&Utc)))
Ok(dt) => Some(dt.with_timezone(&Utc)),
Err(_) => None, // Return None if parsing fails
}
} }
/// Return a formatted time duration /// Return a formatted time duration
@ -58,19 +55,19 @@ pub fn time_since(event: DateTime<Utc>) -> String {
let duration = now.signed_duration_since(event); let duration = now.signed_duration_since(event);
if duration.num_days() > 0 { if duration.num_days() > 0 {
return format!("{}d ago", duration.num_days()); format!("{}d ago", duration.num_days())
} else if duration.num_hours() > 0 { } else if duration.num_hours() > 0 {
return format!("{}h ago", duration.num_hours()); format!("{}h ago", duration.num_hours())
} else if duration.num_minutes() > 0 { } else if duration.num_minutes() > 0 {
return format!("{}m ago", duration.num_minutes()); format!("{}m ago", duration.num_minutes())
} else { } else {
return "Just now".to_string(); "Just now".to_string()
} }
} }
fn print_label(label: &Label) { fn print_label(label: &Label) {
let color = hex_to_color(&label.hex_color).unwrap(); let color = hex_to_color(&label.hex_color).unwrap();
print_color_bg(color, &label.title.trim()); print_color_bg(color, label.title.trim());
} }
pub fn print_all_labels(api: &VikunjaAPI) { pub fn print_all_labels(api: &VikunjaAPI) {

View file

@ -15,7 +15,7 @@ pub fn list_projects(api: &VikunjaAPI) {
for prj in projects { for prj in projects {
project_map project_map
.entry(prj.parent_project_id) .entry(prj.parent_project_id)
.or_insert_with(Vec::new) .or_default()
.push(prj); .push(prj);
} }
@ -26,7 +26,7 @@ pub fn list_projects(api: &VikunjaAPI) {
hex_to_color(&prj.hex_color).unwrap() hex_to_color(&prj.hex_color).unwrap()
}; };
print_color(color, &prj.title); print_color(color, &prj.title);
print!(" [{}]\n", prj.id); println!(" [{}]", prj.id);
if let Some(sub_projects) = project_map.get(&(prj.id as usize)) { if let Some(sub_projects) = project_map.get(&(prj.id as usize)) {
for sub_prj in sub_projects { for sub_prj in sub_projects {
@ -36,7 +36,7 @@ pub fn list_projects(api: &VikunjaAPI) {
hex_to_color(&sub_prj.hex_color).unwrap() hex_to_color(&sub_prj.hex_color).unwrap()
}; };
print_color(color, &format!(" - {}", sub_prj.title)); print_color(color, &format!(" - {}", sub_prj.title));
print!(" [{}]\n", sub_prj.id); println!(" [{}]", sub_prj.id);
} }
} }
} }

View file

@ -1,6 +1,6 @@
use crate::{ use crate::{
api::{Label, ProjectID, Task, VikunjaAPI}, api::{ProjectID, Task, VikunjaAPI},
ui::{hex_to_color, parse_datetime, print_color, print_color_bg, print_label, time_since}, ui::{parse_datetime, print_color, print_label, time_since},
}; };
fn print_task_oneline(task: &Task, api: &VikunjaAPI) { fn print_task_oneline(task: &Task, api: &VikunjaAPI) {
@ -30,7 +30,7 @@ fn print_task_oneline(task: &Task, api: &VikunjaAPI) {
} }
} }
print!("\n"); println!();
} }
pub fn print_current_tasks( pub fn print_current_tasks(
@ -57,27 +57,20 @@ pub fn print_current_tasks(
if let Some(project) = project { if let Some(project) = project {
let p_id = ProjectID::parse(api, project).unwrap(); let p_id = ProjectID::parse(api, project).unwrap();
selection.retain(|x| x.project_id == p_id.0);
selection = selection
.into_iter()
.filter(|x| x.project_id == p_id.0)
.collect();
} }
if let Some(label_match) = label { if let Some(label_match) = label {
selection = selection selection.retain(|x| {
.into_iter() if let Some(labels) = &x.labels {
.filter(|x| { for label in labels {
if let Some(labels) = &x.labels { if label.title.trim() == *label_match {
for label in labels { return true;
if label.title.trim() == *label_match {
return true;
}
} }
} }
false }
}) false
.collect(); });
} }
for task in selection { for task in selection {