Ver Fonte

bin/tau: making get task by id positional arg and general clean-up

Dastan-glitch há 4 anos atrás
pai
commit
0a0d04951a
2 ficheiros alterados com 150 adições e 148 exclusões
  1. 82 101
      bin/tau/tau-cli/src/main.rs
  2. 68 47
      bin/tau/tau-cli/src/util.rs

+ 82 - 101
bin/tau/tau-cli/src/main.rs

@@ -1,5 +1,4 @@
 use log::error;
-use prettytable::{cell, format, row, table};
 use serde_json::{json, Value};
 use simplelog::{ColorChoice, TermLogger, TerminalMode};
 
@@ -18,75 +17,84 @@ mod util;
 use crate::{
     jsonrpc::{add, get_by_id, get_state, list, set_comment, set_state, update},
     util::{
-        desc_in_editor, due_as_timestamp, get_comments, get_events, get_from_task, list_tasks,
-        set_title, timestamp_to_date, CliTau, CliTauSubCommands, TaskInfo, CONFIG_FILE,
-        CONFIG_FILE_CONTENTS,
+        desc_in_editor, due_as_timestamp, get_comments, list_tasks, set_title, show_task, CliTau,
+        CliTauSubCommands, TaskInfo, CONFIG_FILE, CONFIG_FILE_CONTENTS,
     },
 };
 
-async fn start(options: CliTau) -> Result<()> {
+async fn start(mut options: CliTau) -> Result<()> {
     let rpc_addr = &format!("tcp://{}", &options.rpc_listen.clone());
 
-    if !options.filters.is_empty() {
-        let rep = list(rpc_addr, json!([])).await?;
-        list_tasks(rep, options.filters)?;
-    } else {
-        match options.command {
-            Some(CliTauSubCommands::Add { title, desc, assign, project, due, rank }) => {
-                let title = match title {
-                    Some(t) => t,
-                    None => set_title()?,
-                };
-
-                let desc = match desc {
-                    Some(d) => Some(d),
-                    None => desc_in_editor()?,
-                };
-
-                let assign: Vec<String> = match assign {
-                    Some(a) => a.split(',').map(|s| s.into()).collect(),
-                    None => vec![],
-                };
-
-                let project: Vec<String> = match project {
-                    Some(p) => p.split(',').map(|s| s.into()).collect(),
-                    None => vec![],
-                };
-
-                let due = match due {
-                    Some(d) => due_as_timestamp(&d),
-                    None => None,
-                };
-
-                let rank = rank.unwrap_or(0.0);
-
-                add(rpc_addr,
-                    json!([{"title": title, "desc": desc, "assign": assign, "project": project, "due": due, "rank": rank}]),
-                    ).await?;
-            }
+    match options.id {
+        Some(id) if id.len() < 4 && id.parse::<u64>().is_ok() => {
+            let task = get_by_id(rpc_addr, id.parse::<u64>().unwrap()).await?;
+            let taskinfo: TaskInfo = serde_json::from_value(task.clone())?;
+            let current_state: String =
+                serde_json::from_value(get_state(rpc_addr, id.parse::<u64>().unwrap()).await?)?;
+            show_task(task, taskinfo, current_state)?;
+            return Ok(())
+        }
+        Some(id) => options.filters.push(id),
+        None => {}
+    }
 
-            Some(CliTauSubCommands::Update { id, key, value }) => {
-                let value = value.as_str().trim();
-
-                let updated_value: Value = match key.as_str() {
-                    "due" => {
-                        json!(due_as_timestamp(value))
-                    }
-                    "rank" => {
-                        json!(value.parse::<f32>()?)
-                    }
-                    "project" | "assign" => {
-                        json!(value.split(',').collect::<Vec<&str>>())
-                    }
-                    _ => {
-                        json!(value)
-                    }
-                };
-
-                update(rpc_addr, id, json!({ key: updated_value })).await?;
-            }
+    match options.command {
+        Some(CliTauSubCommands::Add { title, desc, assign, project, due, rank }) => {
+            let title = match title {
+                Some(t) => t,
+                None => set_title()?,
+            };
+
+            let desc = match desc {
+                Some(d) => Some(d),
+                None => desc_in_editor()?,
+            };
+
+            let assign: Vec<String> = match assign {
+                Some(a) => a.split(',').map(|s| s.into()).collect(),
+                None => vec![],
+            };
+
+            let project: Vec<String> = match project {
+                Some(p) => p.split(',').map(|s| s.into()).collect(),
+                None => vec![],
+            };
+
+            let due = match due {
+                Some(d) => due_as_timestamp(&d),
+                None => None,
+            };
+
+            let rank = rank.unwrap_or(0.0);
+
+            add(rpc_addr,
+                            json!([{"title": title, "desc": desc, "assign": assign, "project": project, "due": due, "rank": rank}]),
+                            ).await?;
+        }
+
+        Some(CliTauSubCommands::Update { id, key, value }) => {
+            let value = value.as_str().trim();
+
+            let updated_value: Value = match key.as_str() {
+                "due" => {
+                    json!(due_as_timestamp(value))
+                }
+                "rank" => {
+                    json!(value.parse::<f32>()?)
+                }
+                "project" | "assign" => {
+                    json!(value.split(',').collect::<Vec<&str>>())
+                }
+                _ => {
+                    json!(value)
+                }
+            };
+
+            update(rpc_addr, id, json!({ key: updated_value })).await?;
+        }
 
-            Some(CliTauSubCommands::SetState { id, state }) => {
+        Some(CliTauSubCommands::State { id, state }) => match state {
+            Some(state) => {
                 if state.as_str() == "open" {
                     set_state(rpc_addr, id, state.trim()).await?;
                 } else if state.as_str() == "pause" {
@@ -97,56 +105,29 @@ async fn start(options: CliTau) -> Result<()> {
                     error!("Task state could only be one of three states: open, pause or stop");
                 }
             }
-
-            Some(CliTauSubCommands::GetState { id }) => {
+            None => {
                 let state = get_state(rpc_addr, id).await?;
                 println!("Task with id {} is: {}", id, state);
             }
+        },
 
-            Some(CliTauSubCommands::SetComment { id, author, content }) => {
+        Some(CliTauSubCommands::Comment { id, author, content }) => match (author, content) {
+            (Some(author), Some(content)) => {
                 set_comment(rpc_addr, id, author.trim(), content.trim()).await?;
             }
-
-            Some(CliTauSubCommands::GetComment { id }) => {
+            (None, None) => {
                 let rep = get_by_id(rpc_addr, id).await?;
                 let comments = get_comments(rep)?;
 
                 println!("Comments on Task with id {}:\n{}", id, comments);
             }
+            (None, Some(_)) => error!("Please provide the author name"),
+            (Some(_), None) => error!("Please provide some content"),
+        },
 
-            Some(CliTauSubCommands::Get { id }) => {
-                let task = get_by_id(rpc_addr, id).await?;
-
-                let taskinfo: TaskInfo = serde_json::from_value(task.clone())?;
-                let current_state: String = serde_json::from_value(get_state(rpc_addr, id).await?)?;
-
-                let mut table = table!([Bd => "ref_id", &taskinfo.ref_id],
-                                            ["id", &taskinfo.id.to_string()],
-                                            [Bd =>"title", &taskinfo.title],
-                                            ["desc", &taskinfo.desc],
-                                            [Bd =>"assign", get_from_task(task.clone(), "assign")?],
-                                            ["project", get_from_task(task.clone(), "project")?],
-                                            [Bd =>"due", timestamp_to_date(task["due"].clone(),"date")],
-                                            ["rank", &taskinfo.rank.to_string()],
-                                            [Bd =>"created_at", timestamp_to_date(task["created_at"].clone(), "datetime")],
-                                            ["current_state", &current_state],
-                                            [Bd => "comments", get_comments(task.clone())?]);
-
-                table.set_format(*format::consts::FORMAT_NO_BORDER_LINE_SEPARATOR);
-                table.set_titles(row!["Name", "Value"]);
-
-                table.printstd();
-
-                let mut event_table = table!(["events", get_events(task.clone())?]);
-                event_table.set_format(*format::consts::FORMAT_NO_COLSEP);
-
-                event_table.printstd();
-            }
-
-            Some(CliTauSubCommands::List {}) | None => {
-                let rep = list(rpc_addr, json!([])).await?;
-                list_tasks(rep, vec![])?;
-            }
+        Some(CliTauSubCommands::List {}) | None => {
+            let rep = list(rpc_addr, json!([])).await?;
+            list_tasks(rep, options.filters)?;
         }
     }
 

+ 68 - 47
bin/tau/tau-cli/src/util.rs

@@ -8,9 +8,8 @@ use std::{
 };
 
 use chrono::{Datelike, Local, NaiveDate, NaiveDateTime};
-use clap::Subcommand;
 use log::error;
-use prettytable::{cell, format, row, Cell, Row, Table};
+use prettytable::{cell, format, row, table, Cell, Row, Table};
 use rand::distributions::{Alphanumeric, DistString};
 use serde::{Deserialize, Serialize};
 use serde_json::Value;
@@ -22,27 +21,21 @@ use structopt_toml::StructOptToml;
 pub const CONFIG_FILE: &str = "taud_config.toml";
 pub const CONFIG_FILE_CONTENTS: &str = include_str!("../../taud_config.toml");
 
-#[derive(Subcommand, Deserialize, Debug, StructOpt)]
+#[derive(StructOpt, Deserialize, Debug)]
 pub enum CliTauSubCommands {
     /// Add a new task
     Add {
         /// Specify task title
-        #[clap(short, long)]
         title: Option<String>,
         /// Specify task description
-        #[clap(long)]
         desc: Option<String>,
         /// Assign task to user
-        #[clap(short, long)]
         assign: Option<String>,
         /// Task project (can be hierarchical: crypto.zk)
-        #[clap(short, long)]
         project: Option<String>,
         /// Due date in DDMM format: "2202" for 22 Feb
-        #[clap(short, long)]
         due: Option<String>,
         /// Project rank single precision decimal real value: 4.8761
-        #[clap(short, long)]
         rank: Option<f32>,
     },
     /// Update/Edit an existing task by ID
@@ -54,39 +47,24 @@ pub enum CliTauSubCommands {
         /// New value
         value: String,
     },
-    /// Set task state
-    SetState {
+    /// Set or Get task state
+    State {
         /// Task ID
         id: u64,
         /// Set task state
-        state: String,
+        state: Option<String>,
     },
-    /// Get task state
-    GetState {
-        /// Task ID
-        id: u64,
-    },
-    /// Set comment for a task
-    SetComment {
+    /// Set or Get comment for a task
+    Comment {
         /// Task ID
         id: u64,
         /// Comment author
-        author: String,
+        author: Option<String>,
         /// Comment content
-        content: String,
+        content: Option<String>,
     },
-    /// Get task's comments
-    GetComment {
-        /// Task ID
-        id: u64,
-    },
-    /// List open tasks
+    /// List all tasks
     List {},
-    /// Get task by ID
-    Get {
-        /// Task ID
-        id: u64,
-    },
 }
 
 #[derive(Debug, Clone, Deserialize, Serialize)]
@@ -120,8 +98,10 @@ pub struct CliTau {
     pub config: Option<String>,
     #[structopt(subcommand)]
     pub command: Option<CliTauSubCommands>,
-    #[structopt(multiple = true)]
+    /// Get task by ID
+    pub id: Option<String>,
     /// Search criteria (zero or more)
+    #[structopt(multiple = true)]
     pub filters: Vec<String>,
 }
 
@@ -207,6 +187,32 @@ pub fn desc_in_editor() -> Result<Option<String>> {
     Ok(Some(description))
 }
 
+pub fn show_task(task: Value, taskinfo: TaskInfo, current_state: String) -> Result<()> {
+    let mut table = table!([Bd => "ref_id", &taskinfo.ref_id],
+                                            ["id", &taskinfo.id.to_string()],
+                                            [Bd =>"title", &taskinfo.title],
+                                            ["desc", &taskinfo.desc],
+                                            [Bd =>"assign", get_from_task(task.clone(), "assign")?],
+                                            ["project", get_from_task(task.clone(), "project")?],
+                                            [Bd =>"due", timestamp_to_date(task["due"].clone(),"date")],
+                                            ["rank", &taskinfo.rank.to_string()],
+                                            [Bd =>"created_at", timestamp_to_date(task["created_at"].clone(), "datetime")],
+                                            ["current_state", &current_state],
+                                            [Bd => "comments", get_comments(task.clone())?]);
+
+    table.set_format(*format::consts::FORMAT_NO_BORDER_LINE_SEPARATOR);
+    table.set_titles(row!["Name", "Value"]);
+
+    table.printstd();
+
+    let mut event_table = table!(["events", get_events(task)?]);
+    event_table.set_format(*format::consts::FORMAT_NO_COLSEP);
+
+    event_table.printstd();
+
+    Ok(())
+}
+
 pub fn get_comments(rep: Value) -> Result<String> {
     let task: Value = serde_json::from_value(rep)?;
 
@@ -274,12 +280,18 @@ pub fn get_from_task(task: Value, value: &str) -> Result<String> {
 
 // Helper function to check task's state
 fn check_task_state(task: &Value, state: &str) -> bool {
-    let mut default_events = serde_json::Map::new();
-    default_events.insert("action".into(), "open".into());
-    let default_events: Vec<Value> = vec![Value::from(default_events)];
+    let events = match task["events"].as_array() {
+        Some(t) => t.to_owned(),
+        None => {
+            error!("Value is not an array!");
+            vec![]
+        }
+    };
 
-    let last_event = task["events"].as_array().unwrap_or(&default_events).last().unwrap();
-    let last_state = last_event["action"].as_str().unwrap();
+    let last_state = match events.last() {
+        Some(s) => s["action"].as_str().unwrap(),
+        None => "open",
+    };
     state == last_state
 }
 
@@ -288,15 +300,24 @@ fn apply_filter(tasks: Vec<Value>, filter: String) -> Result<Vec<Value>> {
         "open" => tasks.into_iter().filter(|task| check_task_state(task, "open")).collect(),
         "pause" => tasks.into_iter().filter(|task| check_task_state(task, "pause")).collect(),
         "stop" => tasks.into_iter().filter(|task| check_task_state(task, "stop")).collect(),
-        "month" => tasks
-            .into_iter()
-            .filter(|task| {
-                let date = task["created_at"].as_i64().unwrap();
-                let task_month = NaiveDateTime::from_timestamp(date, 0).month();
-                let this_month = Local::today().month();
-                task_month == this_month
-            })
-            .collect(),
+
+        _ if filter.len() == 4 && filter.parse::<u32>().is_ok() => {
+            let (month, year) =
+                (filter[..2].parse::<u32>().unwrap(), filter[2..].parse::<i32>().unwrap());
+
+            let year = year + 2000;
+
+            tasks
+                .into_iter()
+                .filter(|task| {
+                    let date = task["created_at"].as_i64().unwrap();
+                    let task_date = NaiveDateTime::from_timestamp(date, 0).date();
+                    let filter_date = NaiveDate::from_ymd(year, month, 1);
+                    task_date.month() == filter_date.month() &&
+                        task_date.year() == filter_date.year()
+                })
+                .collect()
+        }
 
         _ if filter.contains("assign:") | filter.contains("project:") => {
             let kv: Vec<&str> = filter.split(':').collect();