main.rs 5.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195
  1. use chrono::{Datelike, Local, NaiveDate};
  2. use clap::{AppSettings, IntoApp, Parser, Subcommand};
  3. use log::{debug, error};
  4. use std::{
  5. env::{temp_dir, var},
  6. fs::{self, File},
  7. io::{Read, Write},
  8. };
  9. use darkfi::{
  10. rpc::jsonrpc::{self, JsonResult},
  11. util::cli::log_config,
  12. Error, Result,
  13. };
  14. use serde_json::{json, Value};
  15. use simplelog::{ColorChoice, TermLogger, TerminalMode};
  16. use std::{io, process::Command};
  17. use url::Url;
  18. #[derive(Subcommand)]
  19. pub enum CliTauSubCommands {
  20. /// Add a new task
  21. Add {
  22. /// Specify task title
  23. #[clap(short, long)]
  24. title: Option<String>,
  25. /// Specify task description
  26. #[clap(long)]
  27. desc: Option<String>,
  28. /// Assign task to user
  29. #[clap(short, long)]
  30. assign: Option<Vec<String>>,
  31. /// Task project (can be hierarchical: crypto.zk)
  32. #[clap(short, long)]
  33. project: Option<Vec<String>>,
  34. /// Due date in DDMM format: "2202" for 22 Feb
  35. #[clap(short, long)]
  36. due: Option<u64>,
  37. /// Project rank
  38. #[clap(short, long)]
  39. rank: Option<u32>,
  40. },
  41. }
  42. /// Tau cli
  43. #[derive(Parser)]
  44. #[clap(name = "tau")]
  45. #[clap(author, version, about)]
  46. #[clap(global_setting(AppSettings::PropagateVersion))]
  47. #[clap(global_setting(AppSettings::UseLongFormatForHelpSubcommand))]
  48. #[clap(setting(AppSettings::SubcommandRequiredElseHelp))]
  49. pub struct CliTau {
  50. /// Increase verbosity
  51. #[clap(short, parse(from_occurrences))]
  52. pub verbose: u8,
  53. #[clap(subcommand)]
  54. pub command: Option<CliTauSubCommands>,
  55. }
  56. async fn request(r: jsonrpc::JsonRequest, url: String) -> Result<Value> {
  57. let reply: JsonResult = match jsonrpc::send_request(&Url::parse(&url)?, json!(r), None).await {
  58. Ok(v) => v,
  59. Err(e) => return Err(e),
  60. };
  61. match reply {
  62. JsonResult::Resp(r) => {
  63. debug!(target: "RPC", "<-- {}", serde_json::to_string(&r)?);
  64. Ok(r.result)
  65. }
  66. JsonResult::Err(e) => {
  67. debug!(target: "RPC", "<-- {}", serde_json::to_string(&e)?);
  68. Err(Error::JsonRpcError(e.error.message.to_string()))
  69. }
  70. JsonResult::Notif(n) => {
  71. debug!(target: "RPC", "<-- {}", serde_json::to_string(&n)?);
  72. Err(Error::JsonRpcError("Unexpected reply".to_string()))
  73. }
  74. }
  75. }
  76. // Add new task and returns `true` upon success.
  77. // --> {"jsonrpc": "2.0", "method": "add", "params": ["title", "desc", ["assign"], ["project"], "due", "rank"], "id": 1}
  78. // <-- {"jsonrpc": "2.0", "result": true, "id": 1}
  79. async fn add(
  80. url: String,
  81. title: Option<String>,
  82. desc: Option<String>,
  83. assign: Option<Vec<String>>,
  84. project: Option<Vec<String>>,
  85. due: Option<u64>,
  86. rank: Option<u32>,
  87. ) -> Result<Value> {
  88. let req = jsonrpc::request(json!("add"), json!([title, desc, assign, project, due, rank]));
  89. Ok(request(req, url).await?)
  90. }
  91. async fn start(options: CliTau) -> Result<()> {
  92. let rpc_addr = "tcp://127.0.0.1:8875";
  93. if let Some(CliTauSubCommands::Add { title, desc, assign, project, due, rank }) =
  94. options.command
  95. {
  96. let t = if title.is_none() {
  97. print!("Title: ");
  98. io::stdout().flush()?;
  99. let mut t = String::new();
  100. io::stdin().read_line(&mut t)?;
  101. if &t[(t.len() - 1)..] == "\n" {
  102. t.pop();
  103. }
  104. Some(t)
  105. } else {
  106. title
  107. };
  108. let des = if desc.is_none() {
  109. let editor = var("EDITOR").unwrap();
  110. let mut file_path = temp_dir();
  111. file_path.push("temp_file");
  112. File::create(&file_path)?;
  113. fs::write(
  114. &file_path,
  115. "\n# Write task description above this line\n# These lines will be removed\n",
  116. )?;
  117. Command::new(editor).arg(&file_path).status()?;
  118. let mut lines = String::new();
  119. File::open(file_path)?.read_to_string(&mut lines)?;
  120. let mut description = String::new();
  121. for line in lines.split('\n') {
  122. if !line.starts_with('#') {
  123. description.push_str(line)
  124. }
  125. }
  126. Some(description)
  127. } else {
  128. desc
  129. };
  130. let d = if due.is_some() {
  131. let du = due.unwrap().to_string();
  132. assert!(du.len() == 4);
  133. let (day, month) = (du[..2].parse::<u32>()?, du[2..].parse::<u32>()?);
  134. let mut year = Local::today().year();
  135. if month < Local::today().month() {
  136. year += 1;
  137. }
  138. if month == Local::today().month() && day < Local::today().day() {
  139. year += 1;
  140. }
  141. let dt = NaiveDate::from_ymd(year, month, day).and_hms(12, 0, 0);
  142. // let dt_string = dt.format("%A %-d %B").to_string(); // Format: Weekday Day Month
  143. let timestamp = dt.timestamp().try_into().unwrap();
  144. Some(timestamp)
  145. } else {
  146. None
  147. };
  148. let r = if rank.is_none() { Some(0) } else { rank };
  149. add(rpc_addr.to_string(), t, des, assign, project, d, r).await?;
  150. //println!("Added task: {:#?}", t);
  151. return Ok(())
  152. }
  153. error!("Please run 'tau help' to see usage.");
  154. Err(Error::MissingParams)
  155. }
  156. #[async_std::main]
  157. async fn main() -> Result<()> {
  158. let args = CliTau::parse();
  159. let matches = CliTau::into_app().get_matches();
  160. let verbosity_level = matches.occurrences_of("verbose");
  161. //let config_path = if args.config.is_some() {
  162. // expand_path(&args.config.clone().unwrap())?
  163. //} else {
  164. // join_config_path(&PathBuf::from("tau.toml"))?
  165. //};
  166. // Spawn config file if it's not in place already.
  167. //spawn_config(&config_path, CONFIG_FILE_CONTENTS)?;
  168. let (lvl, conf) = log_config(verbosity_level)?;
  169. TermLogger::init(lvl, conf, TerminalMode::Mixed, ColorChoice::Auto)?;
  170. start(args).await
  171. }