view.rs 6.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207
  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", "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. table.add_row(Row::new(vec![
  57. Cell::new(&task.id.to_string()).style_spec(gen_style),
  58. Cell::new(&task.title).style_spec(gen_style),
  59. Cell::new(&task.project.join(", ")).style_spec(gen_style),
  60. Cell::new(&task.assign.join(", ")).style_spec(gen_style),
  61. Cell::new(&timestamp_to_date(task.due.unwrap_or(0), DateFormat::Date))
  62. .style_spec(gen_style),
  63. if task.rank == max_rank {
  64. Cell::new(&rank).style_spec(max_style)
  65. } else if task.rank == min_rank {
  66. Cell::new(&rank).style_spec(min_style)
  67. } else {
  68. Cell::new(&rank).style_spec(mid_style)
  69. },
  70. ]));
  71. }
  72. let workspace = format!("Workspace: {}", ws);
  73. let mut ws_table = table!([workspace]);
  74. ws_table.set_format(
  75. FormatBuilder::new()
  76. .padding(1, 1)
  77. .separators(&[LinePosition::Bottom], LineSeparator::new('-', ' ', ' ', ' '))
  78. .build(),
  79. );
  80. ws_table.printstd();
  81. table.printstd();
  82. Ok(())
  83. }
  84. pub fn print_task_info(taskinfo: TaskInfo) -> Result<()> {
  85. let due = timestamp_to_date(taskinfo.due.unwrap_or(0), DateFormat::Date);
  86. let created_at = timestamp_to_date(taskinfo.created_at, DateFormat::DateTime);
  87. let rank = if let Some(r) = taskinfo.rank { r.to_string() } else { "".to_string() };
  88. let mut table = table!(
  89. [Bd => "ref_id", &taskinfo.ref_id],
  90. ["workspace", &taskinfo.workspace],
  91. [Bd =>"id", &taskinfo.id.to_string()],
  92. ["owner", &taskinfo.owner],
  93. [Bd =>"title", &taskinfo.title],
  94. ["desc", &taskinfo.desc.to_string()],
  95. [Bd =>"assign", taskinfo.assign.join(", ")],
  96. ["project", taskinfo.project.join(", ")],
  97. [Bd =>"due", due],
  98. ["rank", rank],
  99. [Bd =>"created_at", created_at],
  100. ["current_state", &taskinfo.state]);
  101. table.set_format(
  102. FormatBuilder::new()
  103. .padding(1, 1)
  104. .separators(&[LinePosition::Title], LineSeparator::new('-', ' ', ' ', ' '))
  105. .build(),
  106. );
  107. table.set_titles(row!["Name", "Value"]);
  108. table.printstd();
  109. let (events, timestamps) = &events_as_string(taskinfo.events);
  110. let mut event_table = table!([events, timestamps]);
  111. event_table.set_format(*FORMAT_NO_COLSEP);
  112. event_table.printstd();
  113. Ok(())
  114. }
  115. pub fn comments_as_string(comments: Vec<Comment>) -> String {
  116. let mut comments_str = String::new();
  117. for comment in comments {
  118. writeln!(comments_str, "{}", comment).unwrap();
  119. }
  120. comments_str.pop();
  121. comments_str
  122. }
  123. pub fn events_as_string(events: Vec<TaskEvent>) -> (String, String) {
  124. let mut events_str = String::new();
  125. let mut timestamps_str = String::new();
  126. let width = 50;
  127. for event in events {
  128. writeln!(timestamps_str, "{}", event.timestamp).unwrap();
  129. match event.action.as_str() {
  130. "title" => {
  131. writeln!(events_str, "- {} changed title to {}", event.author, event.content)
  132. .unwrap();
  133. }
  134. "rank" => {
  135. writeln!(events_str, "- {} changed rank to {}", event.author, event.content)
  136. .unwrap();
  137. }
  138. "state" => {
  139. writeln!(events_str, "- {} changed state to {}", event.author, event.content)
  140. .unwrap();
  141. }
  142. "assign" => {
  143. writeln!(events_str, "- {} assigned {}", event.author, event.content).unwrap();
  144. }
  145. "project" => {
  146. writeln!(events_str, "- {} changed project to {}", event.author, event.content)
  147. .unwrap();
  148. }
  149. "due" => {
  150. writeln!(
  151. events_str,
  152. "- {} changed due date to {}",
  153. event.author,
  154. timestamp_to_date(event.content.parse::<i64>().unwrap_or(0), DateFormat::Date)
  155. )
  156. .unwrap();
  157. }
  158. "comment" => {
  159. // wrap long comments
  160. let ev_content =
  161. fill(&event.content, textwrap::Options::new(width).subsequent_indent(" "));
  162. // skip wrapped lines to align timestamp with the first line
  163. for _ in 1..ev_content.lines().count() {
  164. writeln!(timestamps_str, " ").unwrap();
  165. }
  166. writeln!(events_str, "- {} made a comment: {}", event.author, ev_content).unwrap();
  167. }
  168. "desc" => {
  169. // wrap long description
  170. let ev_content =
  171. fill(&event.content, textwrap::Options::new(width).subsequent_indent(" "));
  172. // skip wrapped lines to align timestamp with the first line
  173. for _ in 1..ev_content.lines().count() {
  174. writeln!(timestamps_str, " ").unwrap();
  175. }
  176. writeln!(events_str, "- {} changed description to: {}", event.author, ev_content)
  177. .unwrap();
  178. }
  179. _ => {}
  180. }
  181. }
  182. (events_str, timestamps_str)
  183. }