view.rs 8.1 KB

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