main.rs 8.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215
  1. use clap::{CommandFactory, Parser};
  2. use log::error;
  3. use prettytable::{cell, format, row, table, Cell, Row, Table};
  4. use serde_json::{json, Value};
  5. use simplelog::{ColorChoice, TermLogger, TerminalMode};
  6. use darkfi::{
  7. util::{
  8. cli::{log_config, spawn_config, Config},
  9. path::get_config_path,
  10. },
  11. Result,
  12. };
  13. mod jsonrpc;
  14. mod util;
  15. use crate::{
  16. jsonrpc::{add, get_by_id, get_state, list, set_comment, set_state, update},
  17. util::{
  18. desc_in_editor, due_as_timestamp, get_comments, get_events, get_from_task, set_title,
  19. timestamp_to_date, CliTau, CliTauSubCommands, TaskInfo, TauConfig, CONFIG_FILE_CONTENTS,
  20. },
  21. };
  22. async fn start(options: CliTau, config: TauConfig) -> Result<()> {
  23. let rpc_addr = &format!("tcp://{}", &config.rpc_listener_url.url.clone());
  24. match options.command {
  25. Some(CliTauSubCommands::Add { title, desc, assign, project, due, rank }) => {
  26. let title = match title {
  27. Some(t) => t,
  28. None => set_title()?,
  29. };
  30. let desc = match desc {
  31. Some(d) => Some(d),
  32. None => desc_in_editor()?,
  33. };
  34. let assign: Vec<String> = match assign {
  35. Some(a) => a.split(',').map(|s| s.into()).collect(),
  36. None => vec![],
  37. };
  38. let project: Vec<String> = match project {
  39. Some(p) => p.split(',').map(|s| s.into()).collect(),
  40. None => vec![],
  41. };
  42. let due = match due {
  43. Some(d) => due_as_timestamp(&d),
  44. None => None,
  45. };
  46. let rank = rank.unwrap_or(0.0);
  47. add(
  48. rpc_addr,
  49. json!([{"title": title, "desc": desc, "assign": assign, "project": project, "due": due, "rank": rank}]),
  50. )
  51. .await?;
  52. }
  53. Some(CliTauSubCommands::Update { id, key, value }) => {
  54. let value = value.as_str().trim();
  55. let updated_value: Value = match key.as_str() {
  56. "due" => {
  57. json!(due_as_timestamp(value))
  58. }
  59. "rank" => {
  60. json!(value.parse::<f32>()?)
  61. }
  62. "project" | "assign" => {
  63. json!(value.split(',').collect::<Vec<&str>>())
  64. }
  65. _ => {
  66. json!(value)
  67. }
  68. };
  69. update(rpc_addr, id, json!({ key: updated_value })).await?;
  70. }
  71. Some(CliTauSubCommands::SetState { id, state }) => {
  72. if state.as_str() == "open" {
  73. set_state(rpc_addr, id, state.trim()).await?;
  74. } else if state.as_str() == "pause" {
  75. set_state(rpc_addr, id, state.trim()).await?;
  76. } else if state.as_str() == "stop" {
  77. set_state(rpc_addr, id, state.trim()).await?;
  78. } else {
  79. error!("Task state could only be one of three states: open, pause or stop");
  80. }
  81. }
  82. Some(CliTauSubCommands::GetState { id }) => {
  83. let state = get_state(rpc_addr, id).await?;
  84. println!("Task with id {} is: {}", id, state);
  85. }
  86. Some(CliTauSubCommands::SetComment { id, author, content }) => {
  87. set_comment(rpc_addr, id, author.trim(), content.trim()).await?;
  88. }
  89. Some(CliTauSubCommands::GetComment { id }) => {
  90. let rep = get_by_id(rpc_addr, id).await?;
  91. let comments = get_comments(rep)?;
  92. println!("Comments on Task with id {}:\n{}", id, comments);
  93. }
  94. Some(CliTauSubCommands::Get { id }) => {
  95. let task = get_by_id(rpc_addr, id).await?;
  96. let taskinfo: TaskInfo = serde_json::from_value(task.clone())?;
  97. let current_state: String = serde_json::from_value(get_state(rpc_addr, id).await?)?;
  98. let mut table = table!([Bd => "ref_id", &taskinfo.ref_id],
  99. ["id", &taskinfo.id.to_string()],
  100. [Bd =>"title", &taskinfo.title],
  101. ["desc", &taskinfo.desc],
  102. [Bd =>"assign", get_from_task(task.clone(), "assign")?],
  103. ["project", get_from_task(task.clone(), "project")?],
  104. [Bd =>"due", timestamp_to_date(task["due"].clone(),"date")],
  105. ["rank", &taskinfo.rank.to_string()],
  106. [Bd =>"created_at", timestamp_to_date(task["created_at"].clone(), "datetime")],
  107. ["current_state", &current_state],
  108. [Bd => "comments", get_comments(task.clone())?]);
  109. table.set_format(*format::consts::FORMAT_NO_BORDER_LINE_SEPARATOR);
  110. table.set_titles(row!["Name", "Value"]);
  111. table.printstd();
  112. let mut event_table = table!(["events", get_events(task.clone())?]);
  113. event_table.set_format(*format::consts::FORMAT_NO_COLSEP);
  114. event_table.printstd();
  115. }
  116. Some(CliTauSubCommands::List {}) | None => {
  117. let rep = list(rpc_addr, json!([])).await?;
  118. let mut table = Table::new();
  119. table.set_format(*format::consts::FORMAT_NO_BORDER_LINE_SEPARATOR);
  120. table.set_titles(row!["ID", "Title", "Project", "Assigned", "Due", "Rank"]);
  121. let mut tasks: Vec<Value> = serde_json::from_value(rep)?;
  122. tasks.sort_by(|a, b| b["rank"].as_f64().partial_cmp(&a["rank"].as_f64()).unwrap());
  123. let (max_rank, min_rank) = if !tasks.is_empty() {
  124. (
  125. serde_json::from_value(tasks[0]["rank"].clone())?,
  126. serde_json::from_value(tasks[tasks.len() - 1]["rank"].clone())?,
  127. )
  128. } else {
  129. (0.0, 0.0)
  130. };
  131. for task in tasks {
  132. let events: Vec<Value> = serde_json::from_value(task["events"].clone())?;
  133. let state = match events.last() {
  134. Some(s) => s["action"].as_str().unwrap(),
  135. None => "open",
  136. };
  137. let rank = task["rank"].as_f64().unwrap_or(0.0) as f32;
  138. let (max_style, min_style, mid_style, gen_style) = if state == "open" {
  139. ("bFC", "Fb", "Fc", "")
  140. } else {
  141. ("iFYBd", "iFYBd", "iFYBd", "iFYBd")
  142. };
  143. table.add_row(Row::new(vec![
  144. Cell::new(&task["id"].to_string()).style_spec(gen_style),
  145. Cell::new(task["title"].as_str().unwrap()).style_spec(gen_style),
  146. Cell::new(&get_from_task(task.clone(), "project")?).style_spec(gen_style),
  147. Cell::new(&get_from_task(task.clone(), "assign")?).style_spec(gen_style),
  148. Cell::new(&timestamp_to_date(task["due"].clone(), "date"))
  149. .style_spec(gen_style),
  150. if rank == max_rank {
  151. Cell::new(&rank.to_string()).style_spec(max_style)
  152. } else if rank == min_rank {
  153. Cell::new(&rank.to_string()).style_spec(min_style)
  154. } else {
  155. Cell::new(&rank.to_string()).style_spec(mid_style)
  156. },
  157. ]));
  158. }
  159. table.printstd();
  160. }
  161. }
  162. Ok(())
  163. }
  164. #[async_std::main]
  165. async fn main() -> Result<()> {
  166. let args = CliTau::parse();
  167. let matches = CliTau::command().get_matches();
  168. let verbosity_level = matches.occurrences_of("verbose");
  169. let (lvl, conf) = log_config(verbosity_level)?;
  170. TermLogger::init(lvl, conf, TerminalMode::Mixed, ColorChoice::Auto)?;
  171. let config_path = get_config_path(args.config.clone(), "taud_config.toml")?;
  172. spawn_config(&config_path, CONFIG_FILE_CONTENTS)?;
  173. let config: TauConfig = Config::<TauConfig>::load(config_path)?;
  174. start(args, config).await
  175. }