view.rs 7.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218
  1. use std::{cmp::Ordering, fmt::Write, str::FromStr};
  2. use prettytable::{
  3. format::{consts::FORMAT_NO_COLSEP, FormatBuilder, LinePosition, LineSeparator},
  4. row, table, Cell, Row, Table,
  5. };
  6. use textwrap::fill;
  7. use darkfi::{
  8. util::time::{timestamp_to_date, DateFormat},
  9. Result,
  10. };
  11. use crate::{
  12. primitives::{Comment, State, TaskInfo},
  13. TaskEvent,
  14. };
  15. pub fn print_task_list(tasks: Vec<TaskInfo>, ws: String) -> Result<()> {
  16. let mut tasks = tasks;
  17. let mut table = Table::new();
  18. table.set_format(
  19. FormatBuilder::new()
  20. .padding(1, 1)
  21. .separators(&[LinePosition::Title], LineSeparator::new('-', ' ', ' ', ' '))
  22. .build(),
  23. );
  24. table.set_titles(row!["ID", "Title", "Tags", "Project", "Assigned", "Due", "Rank"]);
  25. // group tasks by state.
  26. tasks.sort_by_key(|task| task.state.clone());
  27. // sort tasks by there rank only if they are not stopped.
  28. tasks.sort_by(|a, b| {
  29. if a.state != "stop" && b.state != "stop" {
  30. b.rank.partial_cmp(&a.rank).unwrap()
  31. } else {
  32. // because sort_by does not reorder equal elements
  33. Ordering::Equal
  34. }
  35. });
  36. let mut min_rank = None;
  37. let mut max_rank = None;
  38. if let Some(first) = tasks.first() {
  39. max_rank = first.rank;
  40. }
  41. if let Some(last) = tasks.last() {
  42. min_rank = last.rank;
  43. }
  44. for task in tasks {
  45. let state = State::from_str(&task.state.clone())?;
  46. let (max_style, min_style, mid_style, gen_style) = if state.is_start() {
  47. ("bFg", "Fc", "Fg", "Fg")
  48. } else if state.is_pause() {
  49. ("iFYBd", "iFYBd", "iFYBd", "iFYBd")
  50. } else if state.is_stop() {
  51. ("Fr", "Fr", "Fr", "Fr")
  52. } else {
  53. ("", "", "", "")
  54. };
  55. let rank = if let Some(r) = task.rank { r.to_string() } else { "".to_string() };
  56. let mut print_tags = vec![];
  57. for tag in &task.tags {
  58. let t = tag.replace('+', "");
  59. print_tags.push(t)
  60. }
  61. table.add_row(Row::new(vec![
  62. Cell::new(&task.id.to_string()).style_spec(gen_style),
  63. Cell::new(&task.title).style_spec(gen_style),
  64. Cell::new(&print_tags.join(", ")).style_spec(gen_style),
  65. Cell::new(&task.project.join(", ")).style_spec(gen_style),
  66. Cell::new(&task.assign.join(", ")).style_spec(gen_style),
  67. Cell::new(&timestamp_to_date(task.due.unwrap_or(0), DateFormat::Date))
  68. .style_spec(gen_style),
  69. if task.rank == max_rank {
  70. Cell::new(&rank).style_spec(max_style)
  71. } else if task.rank == min_rank {
  72. Cell::new(&rank).style_spec(min_style)
  73. } else {
  74. Cell::new(&rank).style_spec(mid_style)
  75. },
  76. ]));
  77. }
  78. let workspace = format!("Workspace: {}", ws);
  79. let mut ws_table = table!([workspace]);
  80. ws_table.set_format(
  81. FormatBuilder::new()
  82. .padding(1, 1)
  83. .separators(&[LinePosition::Bottom], LineSeparator::new('-', ' ', ' ', ' '))
  84. .build(),
  85. );
  86. ws_table.printstd();
  87. table.printstd();
  88. Ok(())
  89. }
  90. pub fn print_task_info(taskinfo: TaskInfo) -> Result<()> {
  91. let due = timestamp_to_date(taskinfo.due.unwrap_or(0), DateFormat::Date);
  92. let created_at = timestamp_to_date(taskinfo.created_at, DateFormat::DateTime);
  93. let rank = if let Some(r) = taskinfo.rank { r.to_string() } else { "".to_string() };
  94. let mut table = table!(
  95. [Bd => "ref_id", &taskinfo.ref_id],
  96. ["workspace", &taskinfo.workspace],
  97. [Bd =>"id", &taskinfo.id.to_string()],
  98. ["owner", &taskinfo.owner],
  99. [Bd =>"title", &taskinfo.title],
  100. ["tags", &taskinfo.tags.join(", ")],
  101. [Bd =>"desc", &taskinfo.desc.to_string()],
  102. ["assign", taskinfo.assign.join(", ")],
  103. [Bd =>"project", taskinfo.project.join(", ")],
  104. ["due", due],
  105. [Bd =>"rank", rank],
  106. ["created_at", created_at],
  107. [Bd =>"current_state", &taskinfo.state]);
  108. table.set_format(
  109. FormatBuilder::new()
  110. .padding(1, 1)
  111. .separators(&[LinePosition::Title], LineSeparator::new('-', ' ', ' ', ' '))
  112. .build(),
  113. );
  114. table.set_titles(row!["Name", "Value"]);
  115. table.printstd();
  116. let (events, timestamps) = &events_as_string(taskinfo.events);
  117. let mut event_table = table!([events, timestamps]);
  118. event_table.set_format(*FORMAT_NO_COLSEP);
  119. event_table.printstd();
  120. Ok(())
  121. }
  122. pub fn comments_as_string(comments: Vec<Comment>) -> String {
  123. let mut comments_str = String::new();
  124. for comment in comments {
  125. writeln!(comments_str, "{}", comment).unwrap();
  126. }
  127. comments_str.pop();
  128. comments_str
  129. }
  130. pub fn events_as_string(events: Vec<TaskEvent>) -> (String, String) {
  131. let mut events_str = String::new();
  132. let mut timestamps_str = String::new();
  133. let width = 50;
  134. for event in events {
  135. writeln!(timestamps_str, "{}", event.timestamp).unwrap();
  136. match event.action.as_str() {
  137. "title" => {
  138. writeln!(events_str, "- {} changed title to {}", event.author, event.content)
  139. .unwrap();
  140. }
  141. "rank" => {
  142. writeln!(events_str, "- {} changed rank to {}", event.author, event.content)
  143. .unwrap();
  144. }
  145. "state" => {
  146. writeln!(events_str, "- {} changed state to {}", event.author, event.content)
  147. .unwrap();
  148. }
  149. "assign" => {
  150. writeln!(events_str, "- {} assigned {}", event.author, event.content).unwrap();
  151. }
  152. "project" => {
  153. writeln!(events_str, "- {} changed project to {}", event.author, event.content)
  154. .unwrap();
  155. }
  156. "tags" => {
  157. writeln!(events_str, "- {} changed tags to {}", event.author, event.content)
  158. .unwrap();
  159. }
  160. "due" => {
  161. writeln!(
  162. events_str,
  163. "- {} changed due date to {}",
  164. event.author,
  165. timestamp_to_date(event.content.parse::<i64>().unwrap_or(0), DateFormat::Date)
  166. )
  167. .unwrap();
  168. }
  169. "comment" => {
  170. // wrap long comments
  171. let ev_content =
  172. fill(&event.content, textwrap::Options::new(width).subsequent_indent(" "));
  173. // skip wrapped lines to align timestamp with the first line
  174. for _ in 1..ev_content.lines().count() {
  175. writeln!(timestamps_str, " ").unwrap();
  176. }
  177. writeln!(events_str, "- {} made a comment: {}", event.author, ev_content).unwrap();
  178. }
  179. "desc" => {
  180. // wrap long description
  181. let ev_content =
  182. fill(&event.content, textwrap::Options::new(width).subsequent_indent(" "));
  183. // skip wrapped lines to align timestamp with the first line
  184. for _ in 1..ev_content.lines().count() {
  185. writeln!(timestamps_str, " ").unwrap();
  186. }
  187. writeln!(events_str, "- {} changed description to: {}", event.author, ev_content)
  188. .unwrap();
  189. }
  190. _ => {}
  191. }
  192. }
  193. (events_str, timestamps_str)
  194. }