use std::{cmp::Ordering, collections::HashMap, fmt::Write, str::FromStr};
use prettytable::{
format::{consts::FORMAT_NO_COLSEP, FormatBuilder, LinePosition, LineSeparator},
row, table, Cell, Row, Table,
};
use textwrap::fill;
use darkfi::{
util::time::{timestamp_to_date, DateFormat},
Result,
};
use crate::{
primitives::{State, TaskInfo},
TaskEvent,
};
pub fn print_task_list(tasks_map: HashMap<usize, TaskInfo>, ws: String) -> Result<()> {
let mut tasks = tasks_map.clone().into_values().collect::<Vec<TaskInfo>>();
let mut table = Table::new();
table.set_format(
FormatBuilder::new()
.padding(1, 1)
.separators(&[LinePosition::Title], LineSeparator::new('-', ' ', ' ', ' '))
.build(),
);
table.set_titles(row!["ID", "Title", "Tags", "Project", "Assigned", "Due", "Rank"]);
tasks.sort_by_key(|task| task.state.clone());
tasks.sort_by(|a, b| {
if a.state != "stop" && b.state != "stop" {
b.rank.partial_cmp(&a.rank).unwrap()
} else {
Ordering::Equal
}
});
let mut min_rank = None;
let mut max_rank = None;
if let Some(first) = tasks.first() {
max_rank = first.rank;
}
if let Some(last) = tasks.last() {
min_rank = last.rank;
}
for (task_id, task) in tasks_map {
let state = State::from_str(&task.state.clone())?;
let (max_style, min_style, mid_style, gen_style) = if state.is_start() {
("bFg", "Fc", "Fg", "Fg")
} else if state.is_pause() {
("iFYBd", "iFYBd", "iFYBd", "iFYBd")
} else if state.is_stop() {
("Fr", "Fr", "Fr", "Fr")
} else {
("", "", "", "")
};
let rank = if let Some(r) = task.rank { r.to_string() } else { "".to_string() };
let mut print_tags = vec![];
for tag in &task.tags {
let t = tag.replace('+', "");
print_tags.push(t)
}
let due_ = match task.due {
Some(ts) => ts.0,
None => 0,
};
table.add_row(Row::new(vec![
Cell::new(&task_id.to_string()).style_spec(gen_style),
Cell::new(&task.title).style_spec(gen_style),
Cell::new(&print_tags.join(", ")).style_spec(gen_style),
Cell::new(&task.project.join(", ")).style_spec(gen_style),
Cell::new(&task.assign.join(", ")).style_spec(gen_style),
Cell::new(×tamp_to_date(due_, DateFormat::Date)).style_spec(gen_style),
if task.rank == max_rank {
Cell::new(&rank).style_spec(max_style)
} else if task.rank == min_rank {
Cell::new(&rank).style_spec(min_style)
} else {
Cell::new(&rank).style_spec(mid_style)
},
]));
}
let workspace = format!("Workspace: {}", ws);
let mut ws_table = table!([workspace]);
ws_table.set_format(
FormatBuilder::new()
.padding(1, 1)
.separators(&[LinePosition::Bottom], LineSeparator::new('-', ' ', ' ', ' '))
.build(),
);
if unsafe { libc::isatty(libc::STDOUT_FILENO) } == 1 {
ws_table.printstd();
table.printstd();
} else {
for row in table.row_iter() {
for cell in row.iter() {
print!("{}\t", cell.get_content());
}
println!();
}
}
Ok(())
}
pub fn taskinfo_table(id: usize, taskinfo: TaskInfo) -> Result<Table> {
let due_ = match taskinfo.due {
Some(ts) => ts.0,
None => 0,
};
let due = timestamp_to_date(due_, DateFormat::Date);
let created_at = timestamp_to_date(taskinfo.created_at.0, DateFormat::DateTime);
let rank = if let Some(r) = taskinfo.rank { r.to_string() } else { "".to_string() };
let mut table = table!(
[Bd => "ref_id", &taskinfo.ref_id],
["workspace", &taskinfo.workspace],
[Bd =>"id", &id.to_string()],
["owner", &taskinfo.owner],
[Bd =>"title", &taskinfo.title],
["tags", &taskinfo.tags.join(", ")],
[Bd =>"desc", &taskinfo.desc.to_string()],
["assign", taskinfo.assign.join(", ")],
[Bd =>"project", taskinfo.project.join(", ")],
["due", due],
[Bd =>"rank", rank],
["created_at", created_at],
[Bd =>"current_state", &taskinfo.state]);
table.set_format(
FormatBuilder::new()
.padding(1, 1)
.separators(&[LinePosition::Title], LineSeparator::new('-', ' ', ' ', ' '))
.build(),
);
table.set_titles(row!["Name", "Value"]);
Ok(table)
}
pub fn events_table(taskinfo: TaskInfo) -> Result<Table> {
let (events, timestamps) = &events_as_string(taskinfo.events);
let mut events_table = table!([events, timestamps]);
events_table.set_format(*FORMAT_NO_COLSEP);
events_table.set_titles(row!["Events"]);
Ok(events_table)
}
pub fn comments_table(taskinfo: TaskInfo) -> Result<Table> {
let (events, timestamps) = &comments_as_string(taskinfo.events);
let mut comments_table = table!([events, timestamps]);
comments_table.set_format(*FORMAT_NO_COLSEP);
comments_table.set_titles(row!["Comments"]);
Ok(comments_table)
}
pub fn print_task_info(id: usize, taskinfo: TaskInfo) -> Result<()> {
let table = taskinfo_table(id, taskinfo.clone())?;
table.printstd();
let events_table = events_table(taskinfo.clone())?;
events_table.printstd();
let comments_table = comments_table(taskinfo)?;
comments_table.printstd();
println!();
Ok(())
}
pub fn events_as_string(events: Vec<TaskEvent>) -> (String, String) {
let mut events_str = String::new();
let mut timestamps_str = String::new();
let width = 50;
for event in events {
if event.action.as_str() == "comment" {
continue
}
writeln!(timestamps_str, "{}", event.timestamp).unwrap();
match event.action.as_str() {
"title" => {
writeln!(events_str, "- {} changed title to {}", event.author, event.content)
.unwrap();
}
"rank" => {
writeln!(events_str, "- {} changed rank to {}", event.author, event.content)
.unwrap();
}
"state" => {
writeln!(events_str, "- {} changed state to {}", event.author, event.content)
.unwrap();
}
"assign" => {
writeln!(events_str, "- {} assigned {}", event.author, event.content).unwrap();
}
"project" => {
writeln!(events_str, "- {} changed project to {}", event.author, event.content)
.unwrap();
}
"tags" => {
writeln!(events_str, "- {} changed tags to {}", event.author, event.content)
.unwrap();
}
"due" => {
writeln!(
events_str,
"- {} changed due date to {}",
event.author,
timestamp_to_date(event.content.parse::<u64>().unwrap_or(0), DateFormat::Date)
)
.unwrap();
}
"desc" => {
let ev_content =
fill(&event.content, textwrap::Options::new(width).subsequent_indent(" "));
for _ in 1..ev_content.lines().count() {
writeln!(timestamps_str, " ").unwrap();
}
writeln!(events_str, "- {} changed description to: {}", event.author, ev_content)
.unwrap();
}
_ => {}
}
}
(events_str, timestamps_str)
}
pub fn comments_as_string(events: Vec<TaskEvent>) -> (String, String) {
let mut events_str = String::new();
let mut timestamps_str = String::new();
let width = 50;
for event in events {
if event.action.as_str() != "comment" {
continue
}
writeln!(timestamps_str, "{}", event.timestamp).unwrap();
if event.action.as_str() == "comment" {
let ev_content =
fill(&event.content, textwrap::Options::new(width).subsequent_indent(" "));
for _ in 1..ev_content.lines().count() {
writeln!(timestamps_str, " ").unwrap();
}
writeln!(events_str, "{}> {}", event.author, ev_content).unwrap();
}
}
(events_str, timestamps_str)
}
pub fn find_free_id(task_ids: &[u32]) -> u32 {
for i in 1.. {
if !task_ids.contains(&i) {
return i
}
}
1
}