Просмотр исходного кода

bin/tau: adding filter to listed tasksin tau-cli

Dastan-glitch 4 лет назад
Родитель
Сommit
2016d363d7
2 измененных файлов с 265 добавлено и 152 удалено
  1. 108 149
      bin/tau/tau-cli/src/main.rs
  2. 157 3
      bin/tau/tau-cli/src/util.rs

+ 108 - 149
bin/tau/tau-cli/src/main.rs

@@ -1,6 +1,6 @@
 use clap::{CommandFactory, Parser};
 use clap::{CommandFactory, Parser};
 use log::error;
 use log::error;
-use prettytable::{cell, format, row, table, Cell, Row, Table};
+use prettytable::{cell, format, row, table};
 use serde_json::{json, Value};
 use serde_json::{json, Value};
 use simplelog::{ColorChoice, TermLogger, TerminalMode};
 use simplelog::{ColorChoice, TermLogger, TerminalMode};
 
 
@@ -18,181 +18,140 @@ mod util;
 use crate::{
 use crate::{
     jsonrpc::{add, get_by_id, get_state, list, set_comment, set_state, update},
     jsonrpc::{add, get_by_id, get_state, list, set_comment, set_state, update},
     util::{
     util::{
-        desc_in_editor, due_as_timestamp, get_comments, get_events, get_from_task, set_title,
-        timestamp_to_date, CliTau, CliTauSubCommands, TaskInfo, TauConfig, CONFIG_FILE_CONTENTS,
+        desc_in_editor, due_as_timestamp, get_comments, get_events, get_from_task, list_tasks,
+        set_title, timestamp_to_date, CliTau, CliTauSubCommands, TaskInfo, TauConfig,
+        CONFIG_FILE_CONTENTS,
     },
     },
 };
 };
 
 
 async fn start(options: CliTau, config: TauConfig) -> Result<()> {
 async fn start(options: CliTau, config: TauConfig) -> Result<()> {
-    let rpc_addr = &format!("tcp://{}", &config.rpc_listen.url.clone());
-
-    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?;
-        }
+    let rpc_addr = &format!("tcp://{}", &config.rpc_listen.clone());
+
+    if !options.filter.is_empty() {
+        let rep = list(rpc_addr, json!([])).await?;
+        list_tasks(rep, options.filter)?;
+    } else {
+        match options.command {
+            Some(CliTauSubCommands::Add { title, desc, assign, project, due, rank }) => {
+                let title = match title {
+                    Some(t) => t,
+                    None => set_title()?,
+                };
 
 
-        Some(CliTauSubCommands::Update { id, key, value }) => {
-            let value = value.as_str().trim();
+                let desc = match desc {
+                    Some(d) => Some(d),
+                    None => desc_in_editor()?,
+                };
 
 
-            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)
-                }
-            };
+                let assign: Vec<String> = match assign {
+                    Some(a) => a.split(',').map(|s| s.into()).collect(),
+                    None => vec![],
+                };
 
 
-            update(rpc_addr, id, json!({ key: updated_value })).await?;
-        }
+                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,
+                };
 
 
-        Some(CliTauSubCommands::SetState { id, state }) => {
-            if state.as_str() == "open" {
-                set_state(rpc_addr, id, state.trim()).await?;
-            } else if state.as_str() == "pause" {
-                set_state(rpc_addr, id, state.trim()).await?;
-            } else if state.as_str() == "stop" {
-                set_state(rpc_addr, id, state.trim()).await?;
-            } else {
-                error!("Task state could only be one of three states: open, pause or stop");
+                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::GetState { id }) => {
-            let state = get_state(rpc_addr, id).await?;
-            println!("Task with id {} is: {}", id, state);
-        }
+            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)
+                    }
+                };
 
 
-        Some(CliTauSubCommands::SetComment { id, author, content }) => {
-            set_comment(rpc_addr, id, author.trim(), content.trim()).await?;
-        }
+                update(rpc_addr, id, json!({ key: updated_value })).await?;
+            }
 
 
-        Some(CliTauSubCommands::GetComment { id }) => {
-            let rep = get_by_id(rpc_addr, id).await?;
-            let comments = get_comments(rep)?;
+            Some(CliTauSubCommands::SetState { id, state }) => {
+                if state.as_str() == "open" {
+                    set_state(rpc_addr, id, state.trim()).await?;
+                } else if state.as_str() == "pause" {
+                    set_state(rpc_addr, id, state.trim()).await?;
+                } else if state.as_str() == "stop" {
+                    set_state(rpc_addr, id, state.trim()).await?;
+                } else {
+                    error!("Task state could only be one of three states: open, pause or stop");
+                }
+            }
 
 
-            println!("Comments on Task with id {}:\n{}", id, comments);
-        }
+            Some(CliTauSubCommands::GetState { id }) => {
+                let state = get_state(rpc_addr, id).await?;
+                println!("Task with id {} is: {}", id, state);
+            }
 
 
-        Some(CliTauSubCommands::Get { id }) => {
-            let task = get_by_id(rpc_addr, id).await?;
+            Some(CliTauSubCommands::SetComment { id, author, content }) => {
+                set_comment(rpc_addr, id, author.trim(), content.trim()).await?;
+            }
 
 
-            let taskinfo: TaskInfo = serde_json::from_value(task.clone())?;
-            let current_state: String = serde_json::from_value(get_state(rpc_addr, id).await?)?;
+            Some(CliTauSubCommands::GetComment { id }) => {
+                let rep = get_by_id(rpc_addr, id).await?;
+                let comments = get_comments(rep)?;
 
 
-            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())?]);
+                println!("Comments on Task with id {}:\n{}", id, comments);
+            }
 
 
-            table.set_format(*format::consts::FORMAT_NO_BORDER_LINE_SEPARATOR);
-            table.set_titles(row!["Name", "Value"]);
+            Some(CliTauSubCommands::Get { id }) => {
+                let task = get_by_id(rpc_addr, id).await?;
 
 
-            table.printstd();
+                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 event_table = table!(["events", get_events(task.clone())?]);
-            event_table.set_format(*format::consts::FORMAT_NO_COLSEP);
+                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())?]);
 
 
-            event_table.printstd();
-        }
+                table.set_format(*format::consts::FORMAT_NO_BORDER_LINE_SEPARATOR);
+                table.set_titles(row!["Name", "Value"]);
 
 
-        Some(CliTauSubCommands::List {}) | None => {
-            let rep = list(rpc_addr, json!([])).await?;
-
-            let mut table = Table::new();
-            table.set_format(*format::consts::FORMAT_NO_BORDER_LINE_SEPARATOR);
-            table.set_titles(row!["ID", "Title", "Project", "Assigned", "Due", "Rank"]);
-
-            let mut tasks: Vec<Value> = serde_json::from_value(rep)?;
-            tasks.sort_by(|a, b| b["rank"].as_f64().partial_cmp(&a["rank"].as_f64()).unwrap());
-
-            let (max_rank, min_rank) = if !tasks.is_empty() {
-                (
-                    serde_json::from_value(tasks[0]["rank"].clone())?,
-                    serde_json::from_value(tasks[tasks.len() - 1]["rank"].clone())?,
-                )
-            } else {
-                (0.0, 0.0)
-            };
-
-            for task in tasks {
-                let events: Vec<Value> = serde_json::from_value(task["events"].clone())?;
-                let state = match events.last() {
-                    Some(s) => s["action"].as_str().unwrap(),
-                    None => "open",
-                };
+                table.printstd();
 
 
-                let rank = task["rank"].as_f64().unwrap_or(0.0) as f32;
+                let mut event_table = table!(["events", get_events(task.clone())?]);
+                event_table.set_format(*format::consts::FORMAT_NO_COLSEP);
 
 
-                let (max_style, min_style, mid_style, gen_style) = if state == "open" {
-                    ("bFC", "Fb", "Fc", "")
-                } else {
-                    ("iFYBd", "iFYBd", "iFYBd", "iFYBd")
-                };
+                event_table.printstd();
+            }
 
 
-                table.add_row(Row::new(vec![
-                    Cell::new(&task["id"].to_string()).style_spec(gen_style),
-                    Cell::new(task["title"].as_str().unwrap()).style_spec(gen_style),
-                    Cell::new(&get_from_task(task.clone(), "project")?).style_spec(gen_style),
-                    Cell::new(&get_from_task(task.clone(), "assign")?).style_spec(gen_style),
-                    Cell::new(&timestamp_to_date(task["due"].clone(), "date"))
-                        .style_spec(gen_style),
-                    if rank == max_rank {
-                        Cell::new(&rank.to_string()).style_spec(max_style)
-                    } else if rank == min_rank {
-                        Cell::new(&rank.to_string()).style_spec(min_style)
-                    } else {
-                        Cell::new(&rank.to_string()).style_spec(mid_style)
-                    },
-                ]));
+            Some(CliTauSubCommands::List {}) | None => {
+                let rep = list(rpc_addr, json!([])).await?;
+                list_tasks(rep, vec![])?;
             }
             }
-            table.printstd();
         }
         }
     }
     }
+
     Ok(())
     Ok(())
 }
 }
 
 

+ 157 - 3
bin/tau/tau-cli/src/util.rs

@@ -2,6 +2,7 @@ use std::{
     env::{temp_dir, var},
     env::{temp_dir, var},
     fs::{self, File},
     fs::{self, File},
     io::{self, Read, Write},
     io::{self, Read, Write},
+    net::SocketAddr,
     ops::Index,
     ops::Index,
     process::Command,
     process::Command,
 };
 };
@@ -9,17 +10,18 @@ use std::{
 use chrono::{Datelike, Local, NaiveDate, NaiveDateTime};
 use chrono::{Datelike, Local, NaiveDate, NaiveDateTime};
 use clap::{Parser, Subcommand};
 use clap::{Parser, Subcommand};
 use log::error;
 use log::error;
+use prettytable::{cell, format, row, Cell, Row, Table};
 use serde::{Deserialize, Serialize};
 use serde::{Deserialize, Serialize};
 use serde_json::Value;
 use serde_json::Value;
 
 
-use darkfi::{util::cli::UrlConfig, Error, Result};
+use darkfi::{Error, Result};
 
 
 pub const CONFIG_FILE_CONTENTS: &[u8] = include_bytes!("../../taud_config.toml");
 pub const CONFIG_FILE_CONTENTS: &[u8] = include_bytes!("../../taud_config.toml");
 
 
 #[derive(Clone, Debug, Serialize, Deserialize)]
 #[derive(Clone, Debug, Serialize, Deserialize)]
 pub struct TauConfig {
 pub struct TauConfig {
-    /// The address where taud should bind its RPC socket
-    pub rpc_listen: UrlConfig,
+    /// JSON-RPC listen URL
+    pub rpc_listen: SocketAddr,
 }
 }
 
 
 #[derive(Subcommand)]
 #[derive(Subcommand)]
@@ -117,6 +119,9 @@ pub struct CliTau {
     pub config: Option<String>,
     pub config: Option<String>,
     #[clap(subcommand)]
     #[clap(subcommand)]
     pub command: Option<CliTauSubCommands>,
     pub command: Option<CliTauSubCommands>,
+    #[clap(multiple_values = true)]
+    /// Search criteria (zero or more)
+    pub filter: Vec<String>,
 }
 }
 
 
 pub fn due_as_timestamp(due: &str) -> Option<i64> {
 pub fn due_as_timestamp(due: &str) -> Option<i64> {
@@ -265,3 +270,152 @@ pub fn get_from_task(task: Value, value: &str) -> Result<String> {
     }
     }
     Ok(result)
     Ok(result)
 }
 }
+
+fn sort_and_filter(tasks: Vec<Value>, filter: Option<String>) -> Result<Vec<Value>> {
+    let filter = match filter {
+        Some(f) => f,
+        None => "all".to_string(),
+    };
+
+    let mut filtered_tasks: Vec<Value> = match filter.as_str() {
+        "all" => tasks,
+
+        "open" => tasks
+            .into_iter()
+            .filter(|task| {
+                let events = task["events"].as_array().unwrap().to_owned();
+
+                let state = match events.last() {
+                    Some(s) => s["action"].as_str().unwrap(),
+                    None => "open",
+                };
+                state == "open"
+            })
+            .collect(),
+
+        "pause" => tasks
+            .into_iter()
+            .filter(|task| {
+                let events = task["events"].as_array().unwrap().to_owned();
+
+                let state = match events.last() {
+                    Some(s) => s["action"].as_str().unwrap(),
+                    None => "open",
+                };
+                state == "pause"
+            })
+            .collect(),
+
+        _ if filter.contains("assign:") | filter.contains("project:") => {
+            let kv: Vec<&str> = filter.split(':').collect();
+            let key = kv[0];
+            let value = kv[1];
+
+            tasks
+                .into_iter()
+                .filter(|task| {
+                    task[key]
+                        .as_array()
+                        .unwrap()
+                        .iter()
+                        .map(|s| s.as_str().unwrap())
+                        .any(|x| x == value)
+                })
+                .collect()
+        }
+
+        _ if filter.contains("rank>") | filter.contains("rank<") => {
+            let kv: Vec<&str> = if filter.contains('>') {
+                filter.split('>').collect()
+            } else {
+                filter.split('<').collect()
+            };
+            let key = kv[0];
+            let value = kv[1].parse::<f32>()?;
+
+            tasks
+                .into_iter()
+                .filter(|task| {
+                    let rank = task[key].as_f64().unwrap_or(0.0) as f32;
+                    if filter.contains('>') {
+                        rank > value
+                    } else {
+                        rank < value
+                    }
+                })
+                .collect()
+        }
+
+        _ => tasks,
+    };
+
+    filtered_tasks.sort_by(|a, b| b["rank"].as_f64().partial_cmp(&a["rank"].as_f64()).unwrap());
+
+    Ok(filtered_tasks)
+}
+
+pub fn list_tasks(rep: Value, filter: Vec<String>) -> Result<()> {
+    let mut table = Table::new();
+    table.set_format(*format::consts::FORMAT_NO_BORDER_LINE_SEPARATOR);
+    table.set_titles(row!["ID", "Title", "Project", "Assigned", "Due", "Rank"]);
+
+    let tasks: Vec<Value> = serde_json::from_value(rep)?;
+
+    // we match up to 3 filters to keep things simple and avoid using loops
+    let tasks = match filter.len() {
+        1 => sort_and_filter(tasks, Some(filter[0].clone()))?,
+        2 => {
+            let res = sort_and_filter(tasks, Some(filter[0].clone()))?;
+            sort_and_filter(res, Some(filter[1].clone()))?
+        }
+        3 => {
+            let res1 = sort_and_filter(tasks, Some(filter[0].clone()))?;
+            let res2 = sort_and_filter(res1, Some(filter[1].clone()))?;
+            sort_and_filter(res2, Some(filter[2].clone()))?
+        }
+        _ => sort_and_filter(tasks, None)?,
+    };
+
+    let (max_rank, min_rank) = if !tasks.is_empty() {
+        (
+            serde_json::from_value(tasks[0]["rank"].clone())?,
+            serde_json::from_value(tasks[tasks.len() - 1]["rank"].clone())?,
+        )
+    } else {
+        (0.0, 0.0)
+    };
+
+    for task in tasks {
+        let events: Vec<Value> = serde_json::from_value(task["events"].clone())?;
+        let state = match events.last() {
+            Some(s) => s["action"].as_str().unwrap(),
+            None => "open",
+        };
+
+        let rank = task["rank"].as_f64().unwrap_or(0.0) as f32;
+
+        let (max_style, min_style, mid_style, gen_style) = if state == "open" {
+            ("bFC", "Fb", "Fc", "")
+        } else {
+            ("iFYBd", "iFYBd", "iFYBd", "iFYBd")
+        };
+
+        table.add_row(Row::new(vec![
+            Cell::new(&task["id"].to_string()).style_spec(gen_style),
+            Cell::new(task["title"].as_str().unwrap()).style_spec(gen_style),
+            Cell::new(&get_from_task(task.clone(), "project")?).style_spec(gen_style),
+            Cell::new(&get_from_task(task.clone(), "assign")?).style_spec(gen_style),
+            Cell::new(&timestamp_to_date(task["due"].clone(), "date")).style_spec(gen_style),
+            if rank == max_rank {
+                Cell::new(&rank.to_string()).style_spec(max_style)
+            } else if rank == min_rank {
+                Cell::new(&rank.to_string()).style_spec(min_style)
+            } else {
+                Cell::new(&rank.to_string()).style_spec(mid_style)
+            },
+        ]));
+    }
+    table.printstd();
+
+    Ok(())
+}